Unity协程yield return null可以防止出现错误,

问题描述 投票:0回答:1

我正在制作一款TurnBased游戏,我想使用协程来控制游戏流程。

为什么我添加了“yield return null”,游戏正常,当我去掉“yield return null”后,游戏就抛出异常

这是BattleRole中的错误代码

    public IEnumerator ActionRoutine(){
        yield return new WaitForSeconds(1f);
        Debug.Log(gameObject + " Action Done!");
        IamDead();
        
        //yield return null; // Have thie one line will no error, else cause error
    }
    public void IamDead(){
        OnDead?.Invoke(this, EventArgs.Empty);
        Destroy(gameObject);
    }

错误信息

MissingReferenceException: The object of type 'BattleRole' has been destroyed but you are still trying to access it.

Your script should either check if it is null or you should not destroy the object.

BattleRole+\<ActionRoutine\>d__19.MoveNext () (at Assets/Scripts/Player/BattleRole.cs:58)

UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) (at \<f712b1dc50b4468388b9c5f95d0d0eaf\>:0)

这是我的 TurnBasedManager:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(LevelManager))]
public class TurnBasedManager : Singleton<TurnBasedManager>
{
    public event EventHandler OnTurnStart;
    // turn Gaming is a flag that point out if the turn based cycle is done
    private bool turnGaming = true;
    private bool isTurnEnd = false;

    private void Start() {
        StartCoroutine(MainCoroutine());

        RoleManager.Instance.OnNoRoleAlive += (_, _) => {
            turnGaming = false;
        };
        RoleManager.Instance.OnActionsDone += (_, _) => {
            isTurnEnd = true;
        };
    }

    // The Main Corotine that turn based have
    private IEnumerator MainCoroutine(){
        // Init the game
        yield return InitGame();

        // Start run the Turn based logic
        yield return TurnBasedCycle();
    }

    // I think there is no the key that cause problem
    private IEnumerator InitGame(){
        // Init the scene
        LevelManager.Instance.LoadSceneInfo();
        // Sign up all the BattleRole in Scene
        yield return RoleManager.Instance.InitRoles();
    }

    private IEnumerator TurnBasedCycle(){
        // One while cycle is one Turn in game
        while(turnGaming){
            isTurnEnd = false;

            OnTurnStart?.Invoke(this, EventArgs.Empty);
            BattleRole nextRole;
            while((nextRole = RoleManager.Instance.GetNextActionRole()) != null){
                // Wait until the role action done
                yield return nextRole.ActionRoutine();
            }
            if (isTurnEnd){
                // Tell the RoleManager to update the action order of roles
                RoleManager.Instance.UpdateActionOrder();
            }
        }
        yield return null;
    }
}

这是我的角色管理器

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;


public class RoleManager : Singleton<RoleManager>
{
    // OnActionsDone is Invoke when all role's action done
    public event EventHandler OnActionsDone;
    public event EventHandler OnNoRoleAlive;
    
    // Use Linked list to define action queue, because roles may die 
    private LinkedList<BattleRole> actionQueue = new LinkedList<BattleRole>();
    // The "head" and "end" is two sentinels
    private LinkedListNode<BattleRole> currentActionNode, head = new(null), end = new(null);

    private List<BattleRole> allRoles = new List<BattleRole>();

    public IEnumerator InitRoles(){
        // Wait one frame till all the role Inited
        yield return null;
    }

    private void Start() {
        TurnBasedManager.Instance.OnTurnStart += (_, _) => {
            UpdateActionOrder();
        };
    }

    public void AddOneRole(BattleRole role){
        if (!allRoles.Contains(role)){
            allRoles.Add(role);

            role.OnDead += (_, _) => {
                allRoles.Remove(role);
                // Can not delete role node in actionQueue, because "role" may be the current actino role
            };
        }
    }

    public List<BattleRole> GetEnemies(){
        return allRoles.Where(entry => entry.IsFriend == false).ToList();
    }
    public List<BattleRole> GetFriends(){
        return allRoles.Where(entry => entry.IsFriend == true).ToList();
    }

    public void UpdateActionOrder(){
        actionQueue.Clear();

        var orderedRoles = allRoles.OrderByDescending(entry => entry.Param.agi);
        foreach (var role in orderedRoles)
        {
            actionQueue.AddLast(role);
        }
        actionQueue.AddFirst(head);
        actionQueue.AddLast(end);
        currentActionNode = head;  
    }
    // Get the next Action Role
    public BattleRole GetNextActionRole(){
        // Onle sentinels in queue, so there is no alives, invoke the event 
        if (actionQueue.Count <= 2){
            OnNoRoleAlive?.Invoke(this, EventArgs.Empty);
            return null;
        }

        while(currentActionNode == head || 
            (currentActionNode != end && currentActionNode.Value.IsDead)){
            currentActionNode = currentActionNode.Next;
        }
        if(currentActionNode == end){
            OnActionsDone?.Invoke(this, EventArgs.Empty);
        }
        return currentActionNode.Value;
    }
}

这是我的战斗角色

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// Something like HP, agi,
[Serializable]
public class RoleParameter{
...
}

// 
public class BattleRole : MonoBehaviour
{
    public event EventHandler OnDead;

    [SerializeField]
    private RoleParameter param;
/*  
    Something does not matter I think
    ... 
*/

    private void Start() {
        // Sign up self to RoleManager
        RoleManager.Instance.AddOneRole(this);
    }

    // The routine that action
    public IEnumerator ActionRoutine(){
        yield return new WaitForSeconds(1f);
        Debug.Log(gameObject + " Action Done!");
        IamDead();
        
        // yield return null // If have no this line code, will cause error
    }
    public void IamDead(){
        OnDead?.Invoke(this, EventArgs.Empty);
        Destroy(gameObject);
    }
}

我期望回合制游戏运行,每个角色在其动作完成后都会摧毁自己,直到所有角色都被摧毁。

我尝试添加和删除该行“yield return null”,添加“yield return null”将正常运行,但是如果我删除该行,它将破坏第一个角色并且不再进行处理,任何人都可以告诉我为什么?

c# unity-game-engine coroutine
1个回答
0
投票

这部分:

while((nextRole = RoleManager.Instance.GetNextActionRole()) != null){
    // Wait until the role action done
    yield return nextRole.ActionRoutine();
}

如果没有这一行,你会在销毁当前角色后立即检索下一个角色,尽管我不知道

IsDead
是如何实现的。如果只用
if(!currentRole)
if(currentRole == null)
之类的东西来判断,结果是假的,需要等到下一帧才能得到正确的结果。为了解决这个问题,使用一个bool字段来指示角色是否被销毁。

© www.soinside.com 2019 - 2024. All rights reserved.