首页 文章

使用具有不同返回类型的协同程序

提问于
浏览
2

在这个函数中,我希望获得一个介于0和列表最大大小之间的随机索引 .

然后我使用那个随机索引,这样我就可以在列表中选择一个随机的 Node .

我通过if语句检查是否有其他 Objects 没有使用我选择的随机 Node .

如果没有其他 Objects 正在使用该随机 Node ,我返回 Node ,因此调用此方法的 Object 可以使用它 .

但是,如果 Node 当前正被另一个 Object 使用,那么我不想再次通过该函数,直到它得到它可以使用的 Node ,所以我返回函数本身 .

结果是溢出错误,因为它经常被调用;但问题是我需要 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 ,但同时我需要函数返回类型 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
    }
}

}


Found my "Solution"

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

我得到了一张纸,并试图写出我的所有思考过程,结果是另一个"solution" . 而不是试图在 SelectRandomNode() 中尝试调用 WaitForSeconds ,如果所有 Nodes 都被占用,我决定让 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 回答

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

    当然,如果它需要适当地处理这种情况 . 从你的问题中不清楚"appropriate"在这种情况下会是什么 .

    它's not clear from the bare example you' ve提供了为什么将 randomIndex 存储为实例字段而不是局部变量 . 如果你真的需要将它作为相对于原始集合的索引,那么你需要做更多的工作来跟踪原始索引(参见传递索引和枚举项的 Select() 重载) . 但基本思路是一样的 .

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

    除此之外:您似乎已声明 IsTaken() 有两个重载,一个用于返回当前值,另一个用于设置它 . 这是糟糕的设计 . 理想情况下,它应该只是一个属性,所以你可以省略 () 由于某种原因为你工作,然后设置方法应该有一个不同的名称,如 SetIsTaken()

  • 0

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

    CTRL+F 以下粗体文字: Found my "Solution"

相关问题