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

时间:2018-04-05 06:25:10

标签: c# dictionary unity3d data-structures lookup

在我的游戏中,我有许多不同类型的单位,这些单位经常被实例化/销毁。它们具有某些值,如MaxHP或Level或Defense,需要在实例化时引用,并且可能会根据某些因素而有所不同。例如,如果等级增加,则MaxHP可能更高,但不是基于严格的公式,该公式随单位而变化。所以,例如:

  • 士兵等级1最高HP:5
  • 士兵等级2最高HP:6
  • 士兵等级3最高HP:8

  • Mage Level 1 Max HP:3

  • Mage Level 2 Max HP:4
  • Mage Level 3 Max HP:5

班级名称很容易被引用,单位的级别会存储在其他地方,我需要知道我一直想要查找哪个特征。因此,对我来说有意义的是将它们存储为键/值对。我可以以编程方式检查类似(className + lvlString + traitName)的内容作为键,因此键最终将成为Mage2MaxHP,值将为4.

通常我在这方面的直觉非常糟糕,因为我从未学过很多关于数据结构的知识。所以,我想知道是否有更好的方法来实现这一目标并组织这些数据。例如,对于字典而言,这似乎非常大。也许它可以更容易管理分成几个词典(一个用于MaxHP,一个用于防御等),但我仍然觉得我忽略了一个更适合这个目的的数据结构。有什么建议吗?

6 个答案:

答案 0 :(得分:3)

&#34;数百&#34;在现代CPU方面真的不是很多,字典增加了很多复杂性并且将你与一种访问模式联系在一起。通常最好从简单开始:使用-180 431.845 -180 434.337 -180 436.819 -180 439.289 -180 469.936 -180 472.152 -180 474.343 -180 476.509 -180 478.649 -180 480.761 -180 482.846 -180 484.902 -180 486.929 -180 488.926 -175 387.566 -175 384.891 -175 382.216 -175 379.541 -175 376.868 -175 374.197 -175 371.53 -175 368.867 -175 366.209 -175 363.557 -175 360.912 -175 358.275 -175 355.648 -175 353.03 -175 350.422 -175 347.826 -175 345.243 -175 342.673 -175 340.117 -175 337.576 -175 335.05 -175 332.541 -175 330.05 -175 327.576 并找出您的访问模式。您可以使用LINQ来查询类名,hp,级别或其任何组合。一旦你有了它的工作,如果你发现你需要提高一些查询的性能,那么,那时候继续并重构它可能使用List<T>作为索引。

但永远不要低估现代编译器和现代CPU迭代连续内存块的能力,快速击败许多其他数据结构,而不是为非常大的N值提供更好的理论性能。

答案 1 :(得分: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

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

我喜欢这种特殊的方法是它基于OOP-Principles并且它减少了冗余的结构元素(例如避免了特征类型的枚举等)。您可以访问单元的属性 - 单元属性。

这里使用的字典相当小。 但是,在尺寸限制方面,我同意评论/答案中的人。无论您选择何种方法,您都可能不会使您的词典接近其极限。

答案 2 :(得分: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; }
    }

答案 3 :(得分: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方法。

有关详细信息,请参阅示例Why is it important to override GetHashCode when Equals method is overridden?MSDN

答案 4 :(得分:2)

没有特别的理由说明为什么这比任何其他方法都要好。但是,您可以使用内容为Indexer并带有Dictionary密钥的Tuple的类

  

注意:理想情况下,您希望从db或文件中存储和加载这些内容,或者只是动态生成它   不是太多但是,根据游戏的性质,这可能   一种新颖的方法。

Demo here

主要

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]);
}

<强>枚举

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");
   }
}

答案 5 :(得分: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
    }
}