C# 使用具有不同返回类型的协同程序
在这个函数中,我想得到一个介于0和列表最大大小之间的随机索引 然后我使用该随机索引,以便在列表中选择一个随机节点 我遍历if语句,检查其他对象是否未使用我选择的随机节点 如果没有其他对象使用该随机节点,我将返回该节点,以便调用此方法的对象可以使用它 但是,如果该节点当前正被另一个对象使用,则我不想再次执行该函数,直到它得到一个可以使用的节点,所以我返回该函数本身 结果是一个溢出错误,因为它被无限调用(游戏仍然有效)。我的第一个想法是使用延迟(协程),这样函数就不会被频繁调用;但问题是我需要返回类型DodgeNodeC# 使用具有不同返回类型的协同程序,c#,unity3d,coroutine,C#,Unity3d,Coroutine,在这个函数中,我想得到一个介于0和列表最大大小之间的随机索引 然后我使用该随机索引,以便在列表中选择一个随机节点 我遍历if语句,检查其他对象是否未使用我选择的随机节点 如果没有其他对象使用该随机节点,我将返回该节点,以便调用此方法的对象可以使用它 但是,如果该节点当前正被另一个对象使用,则我不想再次执行该函数,直到它得到一个可以使用的节点,所以我返回该函数本身 结果是一个溢出错误,因为它被无限调用(游戏仍然有效)。我的第一个想法是使用延迟(协程),这样函数就不会被频繁调用;但问题是我需要返回类
public class DodgeLocations : MonoBehaviour {
public List<DodgeNode> nodes;
private DodgeNode randomNode;
private int randomIndex;
public DodgeNode SelectRandomNode(){
randomIndex = Random.Range(0, nodes.Count);
randomNode = nodes[randomIndex];
// If the random node is not currently taken (which means if an enemy isn't currently attacking it)
if (!randomNode.IsTaken ()) {
// Then the random node is now taken; and other enemies can't touch that node, until the current enemy finishes attacking it
randomNode.IsTaken (true);
return randomNode;
} else {
return SelectRandomNode (); // If the node is taken, ask again if there's another node that's free to attack
}
}
}
找到了我的“解决方案” 免责声明:我是一名初级程序员/学生 我拿了一张纸,试着写下我所有的思考过程,最后得到了另一个“解决方案”。我没有尝试在SelectRandomNode()中调用WaitForSeconds,而是决定在所有节点都被占用时,使SelectRandomNode()返回null。在IEnumerator AttackPlayerNode()中,我有以下代码:
// If the bat doesn't have a node to target
while(nodeToTarget == null){
yield return new WaitForSeconds(0.5f);
nodeToTarget = dodgeLocations.SelectRandomNode();
}
由于我返回null,这个while循环将一直运行,直到节点打开为止。这仍然会产生溢出错误(我应该这样认为),但我现在使用的是WaitForSeconds,这将减少对打开节点的检查频率,从而防止溢出错误(据我所知)
这可能是一个非常难看/临时的解决方案,但我可以在将来随时回去进行优化!这困扰了我整整一天,我很高兴我现在可以专注于我比赛的其他元素
public class DodgeLocations : MonoBehaviour {
public List<DodgeNode> nodes;
private DodgeNode randomNode;
// Returns a randomly chosen node
public DodgeNode SelectRandomNode(){
int randomIndex = Random.Range(0, nodes.Count);
randomNode = nodes[randomIndex];
if (!randomNode.isTaken) {
randomNode.IsTaken (true);
return randomNode;
} else {
return null; // <--- What was changed
}
}
正如你可能已经怀疑的那样,你只是做错了 使用递归进行简单循环是错误的。在最坏的情况下,您的方法应该如下所示:
public DodgeNode SelectRandomNode(){
while (true)
{
randomIndex = Random.Range(0, nodes.Count);
randomNode = nodes[randomIndex];
// If the random node is not currently taken (which means if an enemy isn't currently attacking it)
if (!randomNode.IsTaken ()) {
// Then the random node is now taken; and other enemies can't touch that node, until the current enemy finishes attacking it
randomNode.IsTaken (true);
return randomNode;
}
}
}
最好在开始随机拾取合格节点之前确定它们:
public DodgeNode SelectRandomNode(){
DodgeNode[] eligible = nodes.Where(n => !n.IsTaken()).ToArray();
randomIndex = Random.Range(0, eligible.Length);
randomNode = nodes[randomIndex];
randomNode.IsTaken(true);
return randomNode;
}
当然,如果可能没有任何符合条件的节点,您需要适当地处理这种情况。你的问题不清楚在这种情况下什么是“合适的”
从您提供的简单示例中不清楚为什么要将randomIndex
存储为实例字段而不是局部变量。如果确实需要将其作为相对于原始集合的索引,则需要做更多的工作来跟踪原始索引(请参阅将索引与枚举项一起传递的Select()
重载)。但基本思想是一样的
如果您不希望每次需要选择节点时都重新创建qualified
数组,那么您应该维护两个集合:“not take”集合和“take”集合。然后根据需要将节点从一个移动到另一个。如果这些集合相对较小(数百项,或者可能只有数千项),则它们可以是常规的列表对象。较大的集合删除元素的成本可能较高(由于移动了剩余的元素),在这种情况下,您可能更喜欢使用LinkedList
旁白:您似乎用两个重载声明了IsTaken()
,一个用于返回当前值,另一个用于设置当前值。这是拙劣的设计。理想情况下,它应该只是一个属性,因此可以省略方法调用所需的()
。如果某个属性由于某种原因不适用于您,那么设置方法应该有不同的名称,如SetIsTaken()
我找到了另一个“解决方案”,并编辑了上面的文章
CTRL+F以下粗体文本:找到了我的“解决方案”您计划在哪里使用返回的DodgeNode
?我在上面添加了使用DodgeNode的位置。希望我没有漏掉任何东西(必须删除很多不相关的代码,这样它就不会被聚集)。嗯,在现实生活中,袋子里的东西是通过从袋子里拿出来的来取旗的。因此,我觉得这个列表听起来更自然:o)作为一个新手程序员,我非常感谢您对最佳实践的建议,并将记住所有这些(并对我难看的代码进行了必要的更改)!当我开始深入思考时,循环的递归非常愚蠢!你应该把你的答案编辑成你的答案——不要把它放在你的问题正文里,因为人们不会在那里寻找它。
public class Bat : Enemy {
private DodgeNode nodeToTarget; // Node that bat want's to attack
private Vector3 startPoint; // Bat's original position
private Vector3 endPoint; // Bat's end position
void Start(){
startCoroutine(AttackPlayerNode());
}
IEnumerator AttackPlayerNode(){
while (true) {
// If the bat doesn't have a node to target
while(nodeToTarget == null){
yield return new WaitForSeconds(0.5f); // Prevent overflow error
nodeToTarget = dodgeLocations.SelectRandomNode1();
}
endPoint = nodeToTarget.transform.position;
yield return new WaitForSeconds (2f);
yield return StartCoroutine(MoveToPoint(startPoint, endPoint));
nodeToTarget.IsFree(); // This makes the Node free for other object to use it
nodeToTarget = null;
}
}
public DodgeNode SelectRandomNode(){
while (true)
{
randomIndex = Random.Range(0, nodes.Count);
randomNode = nodes[randomIndex];
// If the random node is not currently taken (which means if an enemy isn't currently attacking it)
if (!randomNode.IsTaken ()) {
// Then the random node is now taken; and other enemies can't touch that node, until the current enemy finishes attacking it
randomNode.IsTaken (true);
return randomNode;
}
}
}
public DodgeNode SelectRandomNode(){
DodgeNode[] eligible = nodes.Where(n => !n.IsTaken()).ToArray();
randomIndex = Random.Range(0, eligible.Length);
randomNode = nodes[randomIndex];
randomNode.IsTaken(true);
return randomNode;
}