使用具有不同 return 类型的协程
Using a Coroutine with a different return type
在这个函数中,我想获得一个介于 0 和列表的最大大小之间的随机索引。
然后我使用那个随机索引,这样我就可以在列表中随机选择一个 Node。
我通过 if 语句检查其他 对象 是否没有使用我选择的随机 节点。
如果没有其他对象正在使用那个随机节点,我return那个节点 所以调用此方法的 Object 可以使用它。
但是,如果那个 Node 当前正被另一个 Object 使用,那么我不想再次执行该函数,直到它得到一个可以使用的节点,所以我return函数本身。
结果是溢出错误,因为它被无限调用(游戏仍然有效)。我的第一个想法是使用延迟(协程),这样该函数就不会被如此频繁地调用;但问题是我需要 return 类型 DodgeNode.
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
}
}
}
我集思广益,如果我使用协程会是什么样子,我想到了这个。
public class DodgeLocations : MonoBehaviour {
public List<DodgeNode> nodes;
private DodgeNode randomNode;
private int randomIndex;
IEnumerator SelectRandomNode(){
yield return new WaitForSeconds(2f);
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);
yield return randomNode;
} else {
yield return SelectRandomNode (); // If the node is taken, ask again if there's another node that's free to attack
}
}
}
当然这是错误的,因为我不能 return 类型 DodgeNode 具有 return 类型 [=55] 的函数=]IEnumerator。但是,我仍然想使用 WaitForSeconds,所以我确实需要 IEnumerator return 类型。
这里的问题是我想return一个DodgeNode,但同时我需要函数是return类型IEnumerator 用于 WaitForSeconds 工作。
回应Serlite,这里是我使用这个函数的地方(删除了很多不相关的代码):
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) {
nodeToTarget = dodgeLocations.SelectRandomNode();
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
}
}
}
找到我的"Solution"
免责声明:我是初学者programmer/student
我拿了一张纸,试图写下我所有的思考过程,最后得到了一个替代 "solution"。我没有尝试在 SelectRandomNode() 中尝试调用 WaitForSeconds,而是决定 SelectRandomNode() return null 如果所有 Nodes 都被占用。在 IEnumerator AttackPlayerNode() 中,我有以下代码:
// If the bat doesn't have a node to target
while(nodeToTarget == null){
yield return new WaitForSeconds(0.5f);
nodeToTarget = dodgeLocations.SelectRandomNode();
}
因为我 returning null,这个 while 循环将一直运行直到 Node 打开。这仍然会产生溢出错误(我认为应该如此),但我现在使用 WaitForSeconds 这将使检查打开节点的频率降低,从而防止溢出错误(对我来说理解)。
这可能是一个非常ugly/temporary的解决方案,但我以后可以随时返回进行优化!这困扰了我一整天,很高兴我现在可以专注于游戏的其他元素。
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 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;
}
当然,如果可能没有任何符合条件的节点,您需要适当地处理这种情况。从你的问题中不清楚 "appropriate" 在这种情况下会是什么。
从您提供的简单示例中不清楚为什么将 randomIndex
存储为实例字段而不是局部变量。如果你真的需要它是相对于原始 collection 的索引,你需要做更多的工作来跟踪原始索引(参见 Select()
重载,它与索引一起传递枚举项)。但基本思路是一样的。
如果您不想在每次需要选取节点时都重新创建 eligible
数组,那么您应该维护两个 collection:"not taken"collection,以及"taken"collection。然后,您只需根据需要将节点从一个移动到另一个。如果这些 collection 相对较小(数百个,或者可能只有数千个项目),则这些可以只是常规的 List<T>
objects。较大的 collection 删除元素的成本可能很高(由于移动剩余元素),在这种情况下您可能更喜欢使用 LinkedList<T>
.
旁白:您似乎已经用两个重载声明了 IsTaken()
,一个是 return 当前值,一个是设置它。这是糟糕的设计。理想情况下,它应该只是一个 属性,因此您可以省略方法调用所需的 ()
。如果 属性 由于某种原因对你不起作用,那么设置方法应该有一个不同的名称,比如 SetIsTaken()
我找到了替代方案 "solution" 并在上面编辑了我的 post。
CTRL+F 以下粗体文本: 找到我的 "Solution"
在这个函数中,我想获得一个介于 0 和列表的最大大小之间的随机索引。
然后我使用那个随机索引,这样我就可以在列表中随机选择一个 Node。
我通过 if 语句检查其他 对象 是否没有使用我选择的随机 节点。
如果没有其他对象正在使用那个随机节点,我return那个节点 所以调用此方法的 Object 可以使用它。
但是,如果那个 Node 当前正被另一个 Object 使用,那么我不想再次执行该函数,直到它得到一个可以使用的节点,所以我return函数本身。
结果是溢出错误,因为它被无限调用(游戏仍然有效)。我的第一个想法是使用延迟(协程),这样该函数就不会被如此频繁地调用;但问题是我需要 return 类型 DodgeNode.
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
}
}
}
我集思广益,如果我使用协程会是什么样子,我想到了这个。
public class DodgeLocations : MonoBehaviour {
public List<DodgeNode> nodes;
private DodgeNode randomNode;
private int randomIndex;
IEnumerator SelectRandomNode(){
yield return new WaitForSeconds(2f);
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);
yield return randomNode;
} else {
yield return SelectRandomNode (); // If the node is taken, ask again if there's another node that's free to attack
}
}
}
当然这是错误的,因为我不能 return 类型 DodgeNode 具有 return 类型 [=55] 的函数=]IEnumerator。但是,我仍然想使用 WaitForSeconds,所以我确实需要 IEnumerator return 类型。
这里的问题是我想return一个DodgeNode,但同时我需要函数是return类型IEnumerator 用于 WaitForSeconds 工作。
回应Serlite,这里是我使用这个函数的地方(删除了很多不相关的代码):
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) {
nodeToTarget = dodgeLocations.SelectRandomNode();
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
}
}
}
找到我的"Solution"
免责声明:我是初学者programmer/student
我拿了一张纸,试图写下我所有的思考过程,最后得到了一个替代 "solution"。我没有尝试在 SelectRandomNode() 中尝试调用 WaitForSeconds,而是决定 SelectRandomNode() return null 如果所有 Nodes 都被占用。在 IEnumerator AttackPlayerNode() 中,我有以下代码:
// If the bat doesn't have a node to target
while(nodeToTarget == null){
yield return new WaitForSeconds(0.5f);
nodeToTarget = dodgeLocations.SelectRandomNode();
}
因为我 returning null,这个 while 循环将一直运行直到 Node 打开。这仍然会产生溢出错误(我认为应该如此),但我现在使用 WaitForSeconds 这将使检查打开节点的频率降低,从而防止溢出错误(对我来说理解)。
这可能是一个非常ugly/temporary的解决方案,但我以后可以随时返回进行优化!这困扰了我一整天,很高兴我现在可以专注于游戏的其他元素。
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 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;
}
当然,如果可能没有任何符合条件的节点,您需要适当地处理这种情况。从你的问题中不清楚 "appropriate" 在这种情况下会是什么。
从您提供的简单示例中不清楚为什么将 randomIndex
存储为实例字段而不是局部变量。如果你真的需要它是相对于原始 collection 的索引,你需要做更多的工作来跟踪原始索引(参见 Select()
重载,它与索引一起传递枚举项)。但基本思路是一样的。
如果您不想在每次需要选取节点时都重新创建 eligible
数组,那么您应该维护两个 collection:"not taken"collection,以及"taken"collection。然后,您只需根据需要将节点从一个移动到另一个。如果这些 collection 相对较小(数百个,或者可能只有数千个项目),则这些可以只是常规的 List<T>
objects。较大的 collection 删除元素的成本可能很高(由于移动剩余元素),在这种情况下您可能更喜欢使用 LinkedList<T>
.
旁白:您似乎已经用两个重载声明了 IsTaken()
,一个是 return 当前值,一个是设置它。这是糟糕的设计。理想情况下,它应该只是一个 属性,因此您可以省略方法调用所需的 ()
。如果 属性 由于某种原因对你不起作用,那么设置方法应该有一个不同的名称,比如 SetIsTaken()
我找到了替代方案 "solution" 并在上面编辑了我的 post。
CTRL+F 以下粗体文本: 找到我的 "Solution"