存储和引用数百个值的有效方法是什么?

4
在我的游戏中,我有许多不同类型的单位需要经常实例化/销毁。它们具有某些值,如MaxHP、等级或防御力,需要在实例化时引用,并且可能会根据某些因素而变化。例如,MaxHP可能会随着等级的提高而增加,但不是基于严格的公式,这取决于单位的类型。因此,例如:
  • 士兵1级最大HP:5
  • 士兵2级最大HP:6
  • 士兵3级最大HP:8

  • 法师1级最大HP:3

  • 法师2级最大HP:4
  • 法师3级最大HP:5

类名很容易引用,单位等级存储在其他位置,我需要知道要查询哪个特性才能首先进行查找。因此,对我来说有意义的是将它们存储为键/值对。我可以通过编程方式检查(类名+等级字符串+特性名)作为键,因此键最终将成为Mage2MaxHP,而值将为4。

通常情况下,我在这方面的直觉很糟糕,因为我没有学过太多关于数据结构的知识。因此,我想知道是否有更好的方法来实现这个目标并组织这么多数据。例如,这对于字典来说可能会非常大。也许将其分成几个字典(一个用于MaxHP,一个用于防御力等)会更可管理,但我仍然觉得我忽略了一种更适合此目的的数据结构。有什么建议吗?


1
我的第一个猜测会是使用字典(Dictionary)或嵌套字典,但这似乎太简单了吧?还有什么需要考虑的吗? - nozzleman
7
对于一个词典来说,“incredibly large(难以置信的大)”是不适用的。在任何数据结构中,100个值都不算“incredibly large”。 - mjwills
6个回答

3
以下是使用继承和字典来处理您的问题,以避免使用类名字符串等方法。
public abstract class Unit
{
    // This approach would use one Dictionary per Trait
    protected abstract Dictionary<int, int> MaxHpByLevel { get; }

    public int Level { get; set; } = 1;

    public int MaxHp => this.MaxHpByLevel[this.Level];
}

public class Soldier : Unit
{
    protected override Dictionary<int, int> MaxHpByLevel => new Dictionary<int, int>
    {
        [1] = 5,
        [2] = 6,
        [3] = 8
    };
}

public class Mage : Unit
{
    protected override Dictionary<int, int> MaxHpByLevel => new Dictionary<int, int>
    {
        [1] = 3,
        [2] = 4,
        [3] = 5
    };
}

您可以这样使用它:
var soldier = new Soldier { Level = 2 };
Console.WriteLine(soldier.MaxHp); // 6

和你一样,我也认为这个问题可以用不同的方法解决。

我喜欢这个特定方法的原因是它基于面向对象编程原则,并且减少了冗余的结构元素(例如,避免了枚举用于特性类型等)。您可以通过单元的属性访问单元的属性。

这里使用的字典会比较小。但是,当涉及到大小限制时,我同意评论/答案中的人们的看法。无论您选择什么方法,您都不可能将字典限制在其限制范围内。


我非常喜欢这种方法,因为我已经使用继承以类似的方式构建了我的类。我有一个类似于Object(具有名称、描述等)的东西,从中Unit继承(具有诸如HP或防御之类的内容),因此在已经构建的基础上进一步继承类是非常合理的。谢谢。 - Anthony Perez

3
“数百”对于现代CPU来说并不算多,使用字典会增加很多复杂性,并将您绑定到一种访问模式上。通常最好从简单的方式开始:List<T>,并确定您的访问模式是什么。您可以使用LINQ针对它进行查询,按类名、hp、等级或任何组合进行查询。一旦它正常工作,如果您发现需要提高某些查询的性能,那么可以在那时重构它,也许使用Dictionary作为索引。
但是永远不要低估现代编译器和现代CPU迭代连续块内存的能力,它们可以比许多其他数据结构更快地盲目地击败理论上表现更好的数据结构,特别是在N非常大的情况下。

2
注意,List<T> 中的 T 是引用类型时,在内存中不是连续的(除了引用本身)。 - babu646

2
我通常使用以下代码:

我通常使用以下代码:

   public class Unit
    {
        public static Dictionary<string, Unit> units { get; set; } // string is unit name

        public string name { get; set; }
        public int level { get; set; }
        public Dictionary<string, int> attributes { get; set; }
    }

将单位名称存储为整数ID的替代方案似乎会浪费资源,特别是如果有成千上万个单位。尤其是因为单位已经包含了名称。 - babu646
Gigiansen:你不是操作者,也不了解需求。如果应用程序在游戏过程中需要显示名称,那么这并不是浪费。 - jdweng

2
你可能希望将类和属性作为枚举来使用,在很多其他地方也会派上用场。例如:

public enum Class
{
    Soldier,
    Mage,
    /*...*/
}

public enum Attribute
{
    Health,
    Magic,
    /*...*/
}

然后将它们组合在一起,形成一个字典的键,这个键应该比仅仅是连接字符串更加高效和“有用”。类似于:

public struct AttributeKey : IEquatable<AttributeKey>
{
    public AttributeKey(Class @class, Attribute attribute, int level)
    {
        Class = @class;
        Attribute = attribute;
        Level = level;
    }

    public readonly Class Class;
    public readonly Attribute Attribute;
    public readonly int Level;

    public bool Equals(AttributeKey other)
    {
        return Class == other.Class && Attribute == other.Attribute && Level == other.Level;
    }
    public override bool Equals(object obj)
    {
        return obj is AttributeKey && Equals((AttributeKey)obj);
    }
    public override int GetHashCode()
    {
        unchecked
        {
            return (((Class.GetHashCode() * 397) ^ Attribute.GetHashCode()) * 397) ^ Level;
        }
    }
}

作为字典中的关键字,最重要的是要非常谨慎地处理Equals和特别是GetHashCode方法。

例如,有关此问题的进一步信息,请参见为什么在覆盖Equals方法时重写GetHashCode方法很重要?MSDN


2
没有特定的原因说明为什么这种方法比其他方法更好。但是,您可以使用带有内部字典和元组键的 Indexer 类。

注意:理想情况下,您会希望将这些内容存储到数据库、文件中,或者如果内容不太多,可能只需动态生成即可。但是,根据游戏的性质,这可能是一种新颖的方法。

演示点击此处


主要内容

private static readonly CharLookup _lookup = new CharLookup();

private static void Main()
{
   _lookup[CharacterClass.Mage, CharacterTrait.SingingAbility, 2] = 123;
   _lookup[CharacterClass.Mage, CharacterTrait.SingingAbility, 3] = 234;
   _lookup[CharacterClass.Soilder, CharacterTrait.MaxBeers, 3] = 23423;

   Console.WriteLine("Mage,SingingAbility,2 = " + _lookup[CharacterClass.Mage, CharacterTrait.SingingAbility, 2]);
   Console.WriteLine("Soilder,MaxBeers,3 = " + _lookup[CharacterClass.Soilder, CharacterTrait.MaxBeers, 3]);
}

Enums

public enum CharacterClass
{
   Soilder,

   Mage,

   SmellyCoder
}

public enum CharacterTrait
{
   MaxHp,

   MaxBeers,

   SingingAbility
}

CharLookup

public class CharLookup
{
   private Dictionary<Tuple<CharacterClass, CharacterTrait, int>, int> myDict = new Dictionary<Tuple<CharacterClass, CharacterTrait, int>, int>();

   public int this[CharacterClass characterClass, CharacterTrait characterTrait, int level]
   {
      get => Check(characterClass, characterTrait, level);
      set => Add(characterClass, characterTrait, level, value);
   }

   public void Add(CharacterClass characterClass, CharacterTrait characterTrait, int level, int value)
   {
      var key = new Tuple<CharacterClass, CharacterTrait, int>(characterClass, characterTrait, level);

      if (myDict.ContainsKey(key))
         myDict[key] = value;
      else
         myDict.Add(key, value);
   }

   public int Check(CharacterClass characterClass, CharacterTrait characterTrait, int level)
   {
      var key = new Tuple<CharacterClass, CharacterTrait, int>(characterClass, characterTrait, level);

      if (myDict.TryGetValue(key, out var result))
         return result;

      throw new ArgumentOutOfRangeException("blah");
   }
}

2

这仍然存储在字典中,但包括简单的枚举和方法来存储和检索数据。

public enum UnitType
{
    Soldier,
    Mage
}

public enum StatType
{
    MaxHP,
    MaxMP,
    Attack,
    Defense
}

// Where the unit initialisation data is stored
public static class UnitData
{
    private static Dictionary<string, Dictionary<StatType, int>> Data = new Dictionary<UnitType, Dictionary<StatType, int>>();

    private static string GetKey(UnitType unitType, int level)
    {
        return $"{unitType}:{level}";
    }

    public static AddUnit(UnitType unitType, int level, int maxHP, int maxMP, int attack, int defense)
    {
        Data.Add(GetKey(unitType, level), 
            new Dictionary<StatType, int> 
            {
                { StatType.MaxHP, maxHP },
                { StatType.MaxMP, maxMP },
                { StatType.Attack, attack },
                { StatType.Defense, defense }
            });
    }

    public static int GetStat(UnitType unitType, int level, StatType statType)
    {
        return Data[GetKet(unitType, level][statType];
    }
}

// The data is not stored against the unit but referenced from UnitData
public class Unit
{
    public UnitType UnitType { get; private set; }
    public int Level { get; private set; }
    public Unit(UnitType unitType, int level)
    {
        UnitType = unitTypel
        Level = level;
    }
    public int GetStat(StatType statType)
    {
        return UnitData.GetStat(UnitType, Level, statType);
    }
}

// To initialise the data
public class StartClass
{
    public void InitialiseData()
    {
        UnitData.Add(UnitType.Soldier, 1, 5, 0, 1, 1);
        UnitData.Add(UnitType.Soldier, 2, 6, 0, 2, 2);
        UnitData.Add(UnitType.Soldier, 3, 8, 0, 3, 3);

        UnitData.Add(UnitType.Mage, 1, 3, 10, 1, 1);
        UnitData.Add(UnitType.Mage, 2, 4, 15, 2, 2);
        UnitData.Add(UnitType.Mage, 3, 5, 20, 3, 3);
    }
}

// Use of units
public class Level1
{
    public List<Unit> Units = new List<Unit>();
    public void InitialiseUnits()
    {
        Units.Add(new Unit(UnitType.Soldier, 1));
        Units.Add(new Unit(UnitType.Soldier, 1));
        Units.Add(new Unit(UnitType.Mage, 1));
        Units.Add(new Unit(UnitType.Mage, 1));
    }
    public void Something()
    {
        int maxHP = Units.First().GetStat(StatType.MaxHP);
        // etc
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接