我正在制作一个游戏的物品系统,我们正在制作类似于古老的古典居民邪恶游戏。目前,我正在实施项目组合,您可以将不同的项目相互组合以获得新的内容。并发症来自这样的事实:存在具有多个转换级别的项目,并且每个级别具有多个配合。请允许我澄清,让我们假设我们有绿色,红色和蓝色草药。您无法将红色+蓝色组合在一起,但是您可以将G + B与 GreenBlueHerb 或G + R结合使用以获得 GreenRedHerb ,现在如果你把这些结果中的任何一个结合到一个蓝色药草上,你就会得到一个 GreyHerb 。正如你从这个例子中看到的那样,对于绿色草本有两个转换级别,因为它达到第一级,有两个可能的配偶,(红色|蓝色),从那一点到第二级,那里'只有一个配偶(蓝色)。
所以我提出了一个有趣的树,覆盖了所有可能性 nLevels ,而不仅仅是2,越多的级别,树越复杂,看看这个3级的例子,您在中间看到的三角形形状代表一个项目,其周围的其他颜色形状代表了它可以达到下一个级别的配偶:
有许多不同的组合,我可以先将我的项目与蓝色组合,然后是红色,然后是绿色,或绿色,然后是红色,然后是蓝色等。达到我的最终水平。 我想出了代表所有可能组合的树:
(右边的数字是#levels,左边是每个级别的#nodes)但是正如你所看到的,它是多余的。如果你看一下端节点,它们都应该是一个,因为它们都会导致相同的最终结果,即G + R + B.对于这种情况,实际上总共有7种可能的状态,这是正确的树:
这很有道理,注意节点数量的巨大差异。
现在我的问题是,这是什么样的数据结构? - 我很确定没有内置的,所以我必须制作我自己的定制,我实际上做了,并设法让它工作,但有一个问题。 (有一点值得一提的是,我从XML文件中获取节点信息,通过信息我的意思是 itemRequired 来达到节点/级别以及它将成为什么名称我的项目在该节点,例如:绿色药草到达 RedGreenHerb 状态,它需要&#39; RedHerb ,当这种组合发生时,名称&#34; GreenHerb &#34;将更改为&#34; RedGreenHerb &#34;如果您想知道< strong> RedHerb ,它只是消失了,我不再需要它了,这是我的数据结构:
public struct TransData
{
public TransData(string transItemName, string itemRequired)
{
this.transItemName = transItemName;
this.itemRequired = itemRequired;
}
public string transItemName;
public string itemRequired;
}
public class TransNode
{
public List<TransNode> nodes = new List<TransNode>();
public TransData data;
public TransNode(TransNode node): this(node.data.transItemName, node.data.itemRequired) { }
public TransNode(string itemName, string itemRequired)
{
data = new TransData(itemName, itemRequired);
}
}
public class TransLevel
{
public List<TransNode> nodes = new List<TransNode>();
public TransNode NextNode { get { return nodes[cnt++ % nodes.Count]; } }
int cnt;
}
public class TransTree
{
public TransTree(string itemName)
{
this.itemName = itemName;
}
public string itemName;
public TransNode[] nodes;
public List <TransLevel> levels = new List<TransLevel>();
// other stuff...
}
让我解释一下:TransTree
实际上是基本节点,其开头有项目的名称( GreenHerb 为ex),树有多个级别(你在图片中看到的黑色线条),每个级别都有许多节点,每个节点都带有一个新的项目数据和一些指向的节点(子节点)。现在您可能会问在TransTree
类中放置节点列表需要什么? - 在我向您展示如何从我的XML文件中获取数据后,我将回答这个问题:
public TransTree GetTransItemData(string itemName)
{
var doc = new XmlDocument();
var tree = new TransTree(itemName);
doc.LoadXml(databasePath.text);
var itemNode = doc.DocumentElement.ChildNodes[GetIndex(itemName)];
int nLevels = itemNode.ChildNodes.Count;
for (int i = 0; i < nLevels; i++) {
var levelNode = itemNode.ChildNodes[i];
tree.levels.Add(new TransLevel());
int nPaths = levelNode.ChildNodes.Count;
for (int j = 0; j < nPaths; j++) {
var pathNode = levelNode.ChildNodes[j];
string newName = pathNode.SelectSingleNode("NewName").InnerText;
string itemRequired = pathNode.SelectSingleNode("ItemRequired").InnerText;
tree.levels[i].nodes.Add(new TransNode(newName, itemRequired));
}
}
tree.ConnectNodes(); // pretend these two
tree.RemoveLevels(); // lines don't exist for now
return tree;
}
这是一个让一切都清晰的XML示例: ItemName-&gt; Level-&gt; Path(它只是一个节点) - &gt;路径数据
<IOUTransformableItemsDatabaseManager>
<GreenHerb>
<Level_0>
<Path_0>
<NewName>RedGreenHerb</NewName>
<ItemRequired>RedHerb</ItemRequired>
</Path_0>
<Path_1>
<NewName>BlueGreenHerb</NewName>
<ItemRequired>BlueHerb</ItemRequired>
</Path_1>
</Level_0>
<Level_1>
<Path_0>
<NewName>GreyHerb</NewName>
<ItemRequired>BlueHerb</ItemRequired>
</Path_0>
</Level_1>
</GreenHerb>
</IOUTransformableItemsDatabaseManager>
现在这样做的问题是节点之间没有相互连接,那么这意味着什么呢?好吧,如果一个项目走了一定的路径到某个级别,那么我们当然不需要继续存储它没有采取的其他路径,那么为什么要将它们保存在内存中呢? (没有转变,一旦你走了一条路,那就是你不得不走这条路,永不回头)我想要的是,当我拿出去的时候一个节点,它下面的所有其余节点也会下降,这是有道理的,但目前我这样做的方式是这样的:
正如您所看到的那样,节点没有连接,持有它们的是什么级别!这意味着,目前我没有办法取出一个节点,以一种方式取出它的所有子节点。 (在没有连接节点的情况下这样做非常困难,而且会降低性能)这将我们带到:
tree.ConnectNodes();
tree.RemoveLevels();
我首先连接节点,然后删除级别?为什么,因为如果我不这样做,那么每个节点都有两个引用,一个来自其父节点,另一个来自当前级别。
现在ConnectNode
实际上是我展示的长树,而不是具有7种状态的优化树:
// this is an overloaded version I use inside a for loop in ConnectNodes()
private void ConnectNodes(int level1, int level2)
{
int level1_nNodes = levels[level1].nodes.Count;
int level2_nNodes = levels[level2].nodes.Count;
// the result of the division gives us the number of nodes in level2,
// that should connect to each node in level1. 12/4 = 3, means that each
// node from level1 will connect to 3 nodes from level2;
int nNdsToAtch = level2_nNodes / level1_nNodes;
for (int i = 0, j = 0; i < level2_nNodes; j++)
{
var level1_nextNode = levels[level1].nodes[j];
for (int cnt = 0; cnt < nNdsToAtch; cnt++, i++)
{
var level2_nextNode = levels[level2].nodes[i];
level1_nextNode.nodes.Add(new TransNode(level2_nextNode));
}
}
}
这最终是我想要在我的另一棵树上,但我不知道如何。我想连接节点并形成我展示的第二棵树,这比前面的4级相对简单,我甚至无法连接油漆中的节点! (当我尝试4级时)
如果你盯着它,你会发现一些二进制数字的相似之处,这里是二进制形式的树:
001
010
100
011
101
110
111
每个&#39; 1&#39;表示实际项目,&#39; 0&#39; 0意思是空的。在我的树上,&#39; 001&#39; =蓝色,&#39; 010&#39; =绿色,&#39; 100&#39; =红色,&#39; 011&#39;表示绿色+蓝色,......&#39; 111&#39; =灰色(最终级别)
所以现在我解释了一切,首先:我的方法是否正确?如果不是那么是什么? 如果是这样,那么我可以使用/ make来实现这个目标的数据结构是什么?如果我提出的数据结构在他们的位置,我怎样才能将XML文件中的数据存储到我的数据结构中,将节点连接在一起,这样每当我取出一个节点时它就会取出它的子节点用它?
提前感谢您的帮助和耐心:)
编辑:有趣的是,整个系统适用于整个游戏中只出现一次的项目(获得一次)。这就是为什么每当我走一条路径时,我都会从记忆中删除它,而且每当我拿起一个项目时,我都会从数据库中删除它的条目,因为我再也不会遇到它了。
编辑:请注意,我不仅仅通过字符串代表我的商品,他们还有很多其他属性。但在这种情况下,我只关心他们的名字,这就是我处理字符串的原因。
答案 0 :(得分:2)
我不喜欢这个解决方案:
Reflection
来处理小问题(我说很小,因为如果你做这样的游戏,你会遇到更难的问题;))。这意味着不必要的复杂性。我喜欢这个解决方案:
我会做什么(juste恕我直言,你的解决方案也很好):在仅节点的观点中使用OOP。因此,如果要将其附加到数据结构,那么您的树将成为状态机(正如您所说的路径;))。
public class InventoryObject
{
protected Dictionnary<Type, InventoryObject> _combinations = new Dictionnary<Type, InventoryObject>();
public InventoryObject() {}
public InventoryObject Combine(InventoryObject o)
{
foreach (var c in _combinations)
if (typeof(o) == c.Key)
return c.Value
throw new Exception("These objects aren't combinable");
}
}
public class BlueHerb : InventoryObject
{
public Herb()
{
_combinations.Add(RedHerb, new BlueRedHerb());
_combinations.Add(GreenHerb, new BlueGreenHerb());
}
}
public class BlueRedHerb: InventoryObject
{
public BlueRedHerb()
{
_combinations.Add(GreenHerb, new GreyHerb());
}
}
然后只需致电BlueHerb.Combine(myRedHerb);
即可获得结果。您也可以执行BlueHerb.Combine(myStone);
并轻松调试。
我尽量保持我的榜样尽可能简单。可以进行大量修改以点亮代码(类Herb
,类CombinedHerb
,使用LINQ查询等...)
答案 1 :(得分:2)
好的,最后在摆弄并盯着整个系统几个小时之后,我昨天提出了一个想法,我实现了它,它工作得很好:)我只是在3级转换上测试它它像魔术一样工作。 (图片只显示了一个组合,但我保证,所有组合都有效)
请允许我分享我的解决方案。在我以前的系统上我做了一些调整,我先说一下调整是什么,然后我为什么要制作每个调整。
以下是结构的外观:
public struct TransData
{
public TransData(string itemName, List <string> itemsRequired)
{
this.itemName = itemName;
this.itemsRequired = itemsRequired;
}
public string itemName;
public List <string> itemsRequired;
}
节点构造函数:
public TransNode(string itemName, List <string> itemsRequired)
{
data = new TransData(itemName, itemsRequired);
}
现在如何处理需求的示例:
Necklace L.1_A: Requires BlueGem
Necklace L.1_B: Requires GreenGem
Necklace L.1_C: Requires RedGem
Necklace L.2_A: Requires BlueGem AND GreenGem
Necklace L.2_B: Requires GreenGem AND RedGem
Necklace L.2_C: Requires RedGem AND BlueGem
Necklace L.3_A: Requires RedGem AND BlueGem AND GreenGem
现在是XML:
<IOUTransformableItemsDatabaseManager>
<Necklace>
<Level_0>
<Path_0>
<NewName>RedNecklace</NewName>
<ItemsRequired>
<Item_0>Red</Item_0>
</ItemsRequired>
</Path_0>
<Path_1>
.......
</Level_0>
<Level_1>
<Path_0>
<NewName>RedGreenNecklace</NewName>
<ItemsRequired>
<Item_0>Red</Item_0>
<Item_1>Green</Item_1>
</ItemsRequired>
</Path_0>
<Path_1>
.....
</Level_1>
<Level_2>
<Path_0>
<NewName>RedGreenBlueNecklace</NewName>
<ItemsRequired>
<Item_0>Red</Item_0>
<Item_1>Green</Item_1>
<Item_2>Blue</Item_2>
</ItemsRequired>
</Path_0>
</Level_2>
</Necklace>
</IOUTransformableItemsDatabaseManager>
root
节点,并删除了nodes
列表
有,这更有意义。之前的nodes
列表现在是相同的
到root.nodes
以下是树的样子:
public class TransTree
{
//public string itemName;
//public List <TransNode> nodes;
public List <TransLevel> levels = new List<TransLevel>();
public TransNode root { private set; get; }
public TransTree(string itemName)
{
// this.itemName = itemName;
root = new TransNode(itemName, null);
}
}
每当两个节点之间由一个级别分隔时,就会出现问题 通常,上述级别的节点应指向上面的节点
所以我所做的只是遍历一个级别的所有节点,并将每个级别节点与下一级别中的每个节点进行比较,如果第一级节点有一个要求,即下一级节点具有,这是一个连接:
public void ConnectNodes()
{
for (int i = 0; i < levels.Count - 1; i++)
ConnectNodes(i, i + 1);
ConnectRoot();
}
private void ConnectNodes(int level1, int level2)
{
int level1_nNodes = levels[level1].nodes.Count;
int level2_nNodes = levels[level2].nodes.Count;
for (int i = 0; i < level1_nNodes; i++)
{
var node1 = levels[level1].nodes[i];
for (int j = 0; j < level2_nNodes; j++)
{
var node2 = levels[level2].nodes[j];
foreach (var itemReq in node1.data.itemsRequired)
{
if (node2.data.itemsRequired.Contains(itemReq))
{
node1.nodes.Add(node2);
break;
}
}
}
}
}
请注意非常重要的一句话:
ConnectRoot();
public void ConnectRoot()
{
foreach (var node in levels[0].nodes)
root.nodes.Add(node);
}
很明白吧? - 我只是将根节点连接到第一级节点,然后我将level1的节点连接到2,2到3等。 当然,在清除关卡之前我必须这样做。这并没有改变:
// fetching the data from the xml file
tree.ConnectNodes();
tree.RemoveLevels();
为什么我要进行第二次更改? (在树中添加了一个根节点)。 好吧,你可以清楚地看到拥有根节点的好处吧 更有意义。但是我实现这个根本想法的主要原因, 是为了轻松地核实我不会接受的节点。我提到了我的 问:无论何时我采用路径/节点,同一级别的节点 应该走了,因为它,我走了一条路,没有去 背部。有了根,现在我可以很容易地说:
tree.SetRoot(nodeTakenToTransform);
无需查看我没有采取的节点并将其置空,只需将树的根更改为我所接受的节点,这样可以减轻我对待树的负担的额外好处一些排序链表,(要访问树下的节点,我必须通过根,以及我想访问的根和节点之间的内容,最后到达我的目的地) - 每次转换后root下降到一个级别,我现在需要访问的是根节点。
还有一个问题。这是我自定义字典中用于处理项目的方法,当项目转换时,它会通知&#39;字典采取必要的行动(更新其键,值等)
public void Notify_ItemTransformed(TransTree itemTree, TransNode nodeTaken)
{
var key = new Tuple<string, string>(itemTree.root.data.itemName, nodeTaken.data.itemsRequired[0]);
itemTree.SetRoot(nodeTaken);
itemTree.UpdateNodesRequirements(itemTree.root); // take note here
RemoveKey(key);
RegisterItem(itemTree);
}
itemTree.UpdateNodesRequirements(itemTree.root)
做什么?
我的第一个改变引入了一个问题例如:当我到达L.1_A时,我已经拥有 BlueGem 了吗?否则我不会达到这个水平。但问题是所有节点,在此级别之后还需要 BlueGem ,仍然现在需要它,即使我拥有它!他们不应该要求它,即。 BlueGreenNecklace 现在应该只需要一个 GreenGem - 这就是为什么现在我必须更新那些节点要求,从新的root开始递归:
tree.SetRoot(nodeTaken);
tree.UpdateNodesRequirements(tree.root);
以下是方法:
public void UpdateNodesRequirements(TransNode node)
{
foreach (var n in node.nodes)
{
if (n.data.itemsRequired.Contains(root.data.itemsRequired[0]))
n.data.itemsRequired.Remove(root.data.itemsRequired[0]);
if (n.nodes != null && n.nodes.Count > 0)
UpdateNodesRequirements(n);
}
}
我只是说,&#34;亲爱的下一级节点,如果您需要我已经拥有的东西,请删除该要求&#34; - 那就是它:)希望你喜欢我的文章XD - 我将把我的下一个视频演示放在问题的更新中。
性能方面?嗯,有几个地方我可以做一些优化,但是现在,用3个级别测试它真的很好,根本没有降级。我怀疑我的游戏设计师会提出需要超过4个级别的东西,但即使他这样做,我的代码应该足够快,如果没有,我可以在适当的地方使用yield return null
来划分工作不同的框架(我使用Unity3D)
我真正喜欢这个解决方案的是它适用于所有类型的转换树,即使它不是对称的nPaths和nLevels:)
感谢任何试图提供帮助的人,并花时间阅读本文。 - Vexe