一种游戏设计解决方案,用于映射具有许多可应用的突变的大型属性数据集
本文关键字:可应用 许多 突变 数据集 属性 大型 映射 用于 一种 游戏 解决方案 | 更新日期: 2023-09-27 18:36:47
一个对我来说相当棘手的模块的声音设计时遇到了一些麻烦。实际的业务逻辑让我陷入了可怕的混乱,所以我把它改写成更容易理解的形式。我用 C# 编码,尽管我想实际设计与语言无关。
想象一下,您正在编写一个简单的幻想角色扮演游戏。你有一个球员,他有一些基本的属性。玩家可以装备一些物品,这可能会以某种方式影响他的属性。项具有一个或多个属性,这些属性定义要修改的属性。
public Character
{
public string charName;
public int charStrength;
public int charHitPoints;
...
public List<Item> equippedItems;
}
public Item
{
public string itemName;
public List<ItemProperty> itemProperties;
}
public ItemProperty
{
public int propertyValue;
public virtual void applyPropertyModification(Character charStatsToBeModified){}
}
字符数据采用 XML 格式:
<Character>
<Name>John Doe</Name>
<BaseStrength>10</BaseStrength>
<BaseHitPoints>100</BaseHitPoints>
<Items>
<Item>
<Name>Short Sword</Name>
<Properties>
<Strength>10</Strength>
<HitPoints>200</HitPoints>
</Properties>
</Item>
</Items>
<Character>
我正在寻找一种从 XML 加载字符数据的方法,它允许我轻松添加 X 个通用新字符属性(想象一下 50 个左右)和/或 Y 个通用项目属性(想象一下 100-150 个左右)。
我的设计思路:
每个可能的字符属性都将作为新属性添加到 Character 类中。随着属性数量的增加,可能会变得笨拙。每个可能的 Item 属性都添加为其自己的 ItemProperty 子类,并具有适当的 applyPropertyModification
方法。
public Strength : ItemProperty
{
public override void applyPropertyModification(Character charStatsToBeModified)
{
charStatsToBeModified.charStrength += this.propertyValue
}
}
这看起来确实是大量过多的代码,特别是因为所有被覆盖的方法所做的都是确定应该修改哪个字符属性。
ItemProperty将使用反射创建,元素 XML 标记对应于相应的 ItemProperty 子类(我认为替代方案是 100 条件开关?
我的总体设计是合理的,还是我接近这是错误的方向?我没有足够的知识来自己解决这个问题,但我相信有一种更整洁的方法可以做到这一点。另外,如果这个问题根本不清楚,或者漫无边际,请告诉我,我会尝试澄清/改写。谢谢。
这里的一些人走在正确的道路上......但是没有人一针见血,所以它就在这里。 看起来你开始走在正确的道路上,但需要更进一步。 我在代码中用注释写了这个,让它为我做一些解释。
这里是:
//So you have a character...
public class Character
{
//You know you ALWAYS need Raw Attribs in a game
private Dictionary<string, Attrib> _rawAttributes;
//You know you will always need a record of modified attribs
private Dictionary<string, Attrib> _modifiedAttributes;
//And while your at it, take a light optimization to not have to recalculate everytime
private bool _areModifiedAttribsCurrent { get; set; }
//A character has gear! This is what makes the world go around
public List<Equipment> Equipment { get; private set; }
//You don't want to give public access to setting gear, this should be controlled.
//You'll want to do more as far as remove / change... but this'll get you started
public void AddEquipment(Equipment e)
{
Equipment.Add(e);
_areModifiedAttribsCurrent = false;
}
//And a way to add attribs and set base values..
//once again, you will want more but this will get you started
public void AddAttribute(Attrib x)
{
_rawAttributes.Add(x.Name, x);
}
//Finally you want a way to fetch the modified attribs
//Keep in mind you need to do the copy dance in the apply to not upset your
//base stats.
public Dictionary<string, Attrib> FetchModifiedAttributes()
{
if (!_areModifiedAttribsCurrent)
{
var traceAttribs = _rawAttributes;
foreach (var e in Equipment.OrderBy(x => x.ApplyOrder))
{
traceAttribs = e.ApplyModifiers(traceAttribs);
}
_modifiedAttributes = traceAttribs;
}
return _modifiedAttributes;
}
}
//Attrib, pretty simple.. Could go away but we all know attribs have effects so don't even start down that path
//You WILL need this class later on... but right now it looks pretty meaningless.
public class Attrib
{
public string Name { get; set; }
public decimal Value { get; set; }
}
//GEAR... yes, this is what all RPG lovers love...
//Grind for that awesome gear!
public class Equipment
{
//Ok so I put in some stuff unrelated to your problem but you need a name right?!
public string Name { get; set; }
//What order does gear effect stats... this is important if you do more than flat modifiers.
public int ApplyOrder { get; set; }
//What modifiers does this gear have?!
public List<Modifier> ItemModifiers { get; set; }
//Aha.... let the gear apply its modifiers to an attrib dictionary... I knew I loved OO for some reason
public Dictionary<string, Attrib> ApplyModifiers(Dictionary<string, Attrib> inParams)
{
//Copy / Clone... Whatever you want to call it this is important as to not
//unintentionally screw up yoru base collection.
var response = new Dictionary<string, Attrib>();
foreach (var m in ItemModifiers)
{
//If we have this attrib, keep going
if (inParams.ContainsKey(m.TargetName))
{
//If this is the first time the response ran into it, add it
if (!response.ContainsKey(m.TargetName))
{
response.Add(m.TargetName, inParams[m.TargetName]);
}
//And wait what's this... let the Modifier apply it!?
//yes... pass it down again... you'll see why in a second.
m.Apply(response[m.TargetName]);
}
}
return response;
}
}
//A modifier... what the!?
//Yep... abstraction is key to maintainable and expandable code
public class Modifier
{
public string TargetName { get; set; }
public decimal ModifierValue { get; set; }
//The other stuff is kind of pointless... but this is where the magic happens... All in a modifier type.
public ModifierType ModifierType { get; set; }
//Let the modifier apply it's own values... off the type... yea
//I did that on purpose ;-)
public void Apply(Attrib a)
{
a.Value = ModifierType.ApplyModifier(this, a.Value);
}
}
//Decoration... Wonderful
//This base class gives you a interface to work with... Hell, it could be an interface but I decided
//to type abstract today.
public abstract class ModifierType
{
public abstract string ModifierName { get; }
public abstract decimal ApplyModifier(Modifier m, decimal InitialValue);
}
//A concrete type of ModifierType... This is what determines how the modifier value is applied.
//This gives you more flexibility than hard coding modifier types. If you really wanted to you could
//serialize these and store lambda expressions in the DB so you not only have type driven logic, you could have
//data driven behavior.
public class FlatModifier : ModifierType
{
//The names can be really handy if you want to expose calculations to players.
public override string ModifierName { get { return "Flat Effect"; } }
//And finally... let the calculation happen! Time to bubble back up!
public override decimal ApplyModifier(Modifier m, decimal InitialValue)
{
return InitialValue + m.ModifierValue;
}
}
让您的对象为您完成工作。 我知道它来自 XML,但让它们反序列化到您的对象中并调用一些选择方法来处理它。 这阻止了一些令人讨厌的问题,例如 C# 是参考和所有烦人的东西。 此外,它还可以防止冗余处理传递提升您的对象图。
如果您有任何问题,请告诉我...今晚我很无聊。
所以你Character
类型有数百个属性/字段,你想简单地根据XML元素名称设置这些字段的值?
最简单的解决方案是简单地读取 XML 元素并构造一个包含值的Dictionary<string, string>
。然后,对于字典的每个元素,如果Character
类型公开字典键中的匹配属性/字段,请尝试通过使用该值的反射来设置属性的值。即:
Character someCharacter = // Create your character
Dictionary<string, string> myDictionary = // Load XML and transform
var properties = someCharacter.GetType().GetProperties();
foreach(KeyValuePair<string, string> kvp in myDictionary)
{
var matchingProperty = properties.FirstOrDefault(x => x.PropertyName.ToLower() == kvp.Key.ToLower());
if(matchingProperty != null)
{
matchingProperty.SetValue(character, kvp.Value, null); // make sure to type convert if necesary here, since kvp.Value is a string, etc
}
}
您只需编写一次代码,因此您可以向 XML/对象添加属性,并能够根据需要覆盖。
而不是使用反射。更改 xml 以匹配如下所示的内容
<Items>
<Item name="Short Sword">
<Properties>
<Property name="Strength" type="int">10<Property>
<Propery name="HitPoints" type="int">200</HitPoints>
</Properties>
</Item>
</Items>
根据属性解析成Dictionary<String,Dynamic>
,实现索引器
例如
private Dictionary<String, Dynamic> _properties = new Dictionary<String,Dynamic>();
public dynamic this[Strng argIndex]
{
get {return _properties[argIndex];}
}
把它带到角色等中,你可以做一些有趣的事情,比如
characters["Fred"].Hitpoints = characters["Fred"].Items["Short Sword"].Hitpoints * characters["Fred"].Experience["Blades"]
一旦你定义了属性包类(Dictionary<String,Dynamic>
的东西),属性就可以是一个属性包。
注意它会慢一点,因为基本上这是使用 .net 版本的 Ducktyping,它使用后期绑定,但它非常灵活,如果你想付出代价,在运行时为 sirry ellors。