使用具有不同返回类型的Coroutine

时间:2017-01-12 00:32:55

标签: c# unity3d coroutine

在这个函数中,我想得到一个介于0和我列表最大大小之间的随机索引。

然后我使用该随机索引,以便我可以在列表中选择一个随机的节点

我查看if语句并检查其他对象是否未使用我选择的随机节点

如果没有其他对象正在使用该随机节点,我会返回节点,以便调用对象这种方法可以使用它。

但是,如果其他对象当前正在使用节点,那么我不会再次检查该功能,直到它获得节点它可以使用,所以我返回函数本身。

结果是溢出错误,因为它被无限调用(游戏仍然有效)。我的第一个想法是使用延迟(协程),因此函数不会被频繁调用;但问题是我需要返回类型 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
    }
}

}

当然这是错误的,因为我无法使用返回类型为 IEnumerator 的函数返回 DodgeNode 类型的内容。但是,我仍然想使用 WaitForSeconds ,所以我确实需要 IEnumerator 返回类型。

这里的问题是我想要返回 DodgeNode ,但同时我需要函数为 WaitForSeconds <返回类型 IEnumerator / strong>工作。

在回应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
    }
}

}

找到我的“解决方案”

免责声明:我是初学程序员/学生

我拿到了一张纸,试图写出我的所有思考过程,最后得到了一个替代的“解决方案”。我没有尝试在 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循环将继续运行,直到 Node 打开。这仍然会产生溢出错误(我认为应该如此),但我现在正在使用 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 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;
    }
}

2 个答案:

答案 0 :(得分:1)

正如您可能已经怀疑的那样,您只是做错了。

使用递归进行简单循环是错误的。在最坏的情况下,您的方法应该看起来像这样:

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

当然,如果没有任何合格的节点可能,那么您需要适当地处理该情况。从您的问题中不清楚在这种情况下&#34;适当的&#34; 会是什么。

从最基本的示例中可以看出,您提供了将randomIndex存储为实例字段而非本地变量的原因。如果您真的需要将其作为相对于原始集合的索引,那么您需要做更多的工作来跟踪原始索引(请参阅传递索引的Select()重载以及枚举项目)。但基本思路是一样的。

如果您不想在每次需要选择节点时重新创建eligible数组,那么您应该维护两个集合:&#34; not take&#34; 集合,以及&#34;拍摄&#34; 集合。然后根据需要将节点从一个移动到另一个。如果这些集合相对较小(数百个,或者可能只有数千个项目),则这些集合可以只是常规的List<T>个对象。删除元素可能会花费较大的集合(由于移动其余元素),在这种情况下,您可能更喜欢使用LinkedList<T>


除此之外:您似乎已声明IsTaken()有两个重载,一个用于返回当前值,另一个用于设置它。这是糟糕的设计。理想情况下,它应该只是一个属性,因此您可以忽略方法调用所需的()。如果某个属性由于某种原因不适合您,则设置方法应具有不同的名称,例如SetIsTaken()

答案 1 :(得分:0)

我找到了另一种“解决方案”并编辑了上面的帖子。

CTRL + F 以下粗体文字:找到我的“解决方案”