3

我在想出一个声音设计时遇到了一些麻烦,因为对我来说这是一个相当棘手的模块。实际的业务逻辑把我弄得一团糟,所以我把它改写成更容易理解的形式。我正在用 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 Property 都作为其自己的 ItemProperty 子类添加,并带有适当的applyPropertyModification方法。

public Strength : ItemProperty
{
    public override void applyPropertyModification(Character charStatsToBeModified)
    {
        charStatsToBeModified.charStrength += this.propertyValue
    }
}

这看起来确实是大量的过多代码,特别是因为所有被覆盖的方法正在确定应该修改哪个字符属性。

ItemProperty 将使用反射创建,元素 XML 标记对应于适当的 ItemProperty 子类(我认为替代方案是 100 条件开关?)。

我的总体设计是合理的,还是我接近这个方向是错误的?我没有足够的知识来自己解决这个问题,但我相信有一种更简洁的方法可以做到这一点。此外,如果这个问题根本不清楚或漫无边际,请告诉我,我会尝试澄清/改述。谢谢。

4

3 回答 3

2

好的,这里有几个人走在了正确的道路上……但是没有人一针见血,所以就这样吧。看起来你开始在正确的道路上,但需要更进一步。我在代码中写了这个注释,让它为我做一些解释。

开始:

//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# 是通过引用和所有烦人的东西。它还可以防止多余的处理通道抬高您的对象图。

如果您有任何问题,请告诉我...今晚我很无聊。

于 2012-06-11T23:35:11.407 回答
1

那么您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/Object 并能够随意覆盖。

于 2012-06-11T21:53:20.563 回答
1

而不是使用反射。更改 xml 以匹配这样的内容

<Items>
  <Item name="Short Sword">
    <Properties>
      <Property name="Strength" type="int">10<Property>               
      <Propery name="HitPoints" type="int">200</HitPoints>
    </Properties>
  </Item>
</Items>

根据属性将其解析为 a 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"]

一旦你定义了propertybag 类(Dictionary<String,Dynamic>thingy),一个属性就可以是一个propertybag。

注意它会慢一点,因为基本上这是使用 .net 的 Ducktyping 版本,它使用后期绑定但它非常灵活,如果你想在运行时为 sirry ellors 付出代价。

于 2012-06-11T22:17:03.433 回答