【发布时间】:2021-07-29 17:26:36
【问题描述】:
在我的程序刚启动后调用 GetComponent() 时,我发现该方法有时不能足够快地返回组件,以防止稍后代码尝试访问组件成员变量时出现空引用异常。我的问题是 - 有没有办法等待 GetComponent 完成找到它需要的东西?我知道可以使用协程等待,但是还有其他方法可以通过某种 lambda 回调或事件来做到这一点吗?
public class GameManager : MonoBehaviour
{
public bool AutosaveEnabled = true;
public static GameManager Instance;
[HideInInspector] public InputManager InputManager;
[HideInInspector] public UIManager UIManager;
...
private void Awake()
{
Setup();
}
public void Setup()
{
if (Instance == null)
{
Instance = this;
}
else
{
throw new Exception();
}
UIManager = GetComponent<UIManager>();
...
UIManager.Setup();
...
}
public class UIManager : StateMachine, IUIManager
{
public static UIManager Instance;
public ITitleMenu TitleMenu;
public void Setup()
{
if (Instance == null)
{
Instance = this;
}
else
{
throw new Exception();
}
TitleMenu = GetComponentInChildren<ITitleMenu>();
}
private void SetupScene()
{
UIManager.Instance.ChangeState<TitleMenuState>();
}
...
}
public interface ITitleMenu : IMenu
{
void ExitGame();
void LoadTitleMenuScene();
void OnNewGameClick();
}
public interface IMenu
{
public void Setup(IUIManager uiManager);
public void SetActive(bool toggle);
int selectedIndex { get; set; }
int previouslySelectedIndex { get; set; }
TextMeshProUGUI[] Options { get; set; }
void OnControllerMoveDown();
void OnControllerMoveUp();
void OnControllerConfirm();
}
public class TitleMenu : MenuBase, ITitleMenu
{
private enum MenuElements { Continue, NewGame, Controls, VideoSettings, AudioSettings, ExitGame };
public void Setup(IUIManager uiManager)
{
this.uiManager = uiManager;
DataManager.Instance.SaveFileMetadata = GameManager.Instance.SaveFileManager.GetSaveFileMetadata();
if (DataManager.Instance.SaveFileMetadata.Count > 0)
{
Options[(int)MenuElements.Continue].transform.parent.gameObject.SetActive(true);
selectedIndex = (int)MenuElements.Continue;
}
else
{
Options[(int)MenuElements.Continue].transform.parent.gameObject.SetActive(false);
selectedIndex = (int)MenuElements.NewGame;
}
previouslySelectedIndex = selectedIndex;
}
...
}
public class StateMachine : MonoBehaviour, IStateMachine
{
public virtual State CurrentState
{
get
{
return _currentState;
}
set
{
if (_currentState == value)
{
return;
}
if (_currentState != null)
{
_currentState.Exit();
}
_currentState = value;
if (_currentState != null)
{
_currentState.Enter();
}
}
}
protected State _currentState;
public virtual T GetState<T>() where T : State
{
T target = GetComponent<T>();
if (target == null)
{
target = gameObject.AddComponent<T>();
target.Initialize();
}
return target;
}
}
...
public class TitleMenuState : UIState
{
protected override void OnMove(object sender, InfoEventArgs<Vector2> e)
{
if (e.info.y == 1)
{
owner.TitleMenu.OnControllerMoveUp();
}
else if (e.info.y == -1)
{
owner.TitleMenu.OnControllerMoveDown();
}
}
protected override void OnInteract(object sender, EventArgs e)
{
owner.TitleMenu.OnControllerConfirm();
}
public override void Enter()
{
owner.TitleMenu.SetActive(true);
owner.TitleMenu.Setup(owner);
EventManager.UIMoveEvent += OnMove;
EventManager.UISubmitEvent += OnInteract;
}
public override void Exit()
{
UIManager.Instance.TitleMenu.SetActive(false);
EventManager.UIMoveEvent -= OnMove;
EventManager.UISubmitEvent -= OnInteract;
}
}
public abstract class State : MonoBehaviour
{
public virtual void Enter()
{
}
public virtual void Exit()
{
}
public virtual void Initialize()
{
}
}
public abstract class UIState : State
{
protected UIManager owner;
public override void Initialize()
{
owner = UIManager.Instance;
}
protected virtual void OnInteract(object sender, EventArgs e)
{
}
protected virtual void OnCancel(object sender, EventArgs args)
{
}
public override void Enter()
{
}
public override void Exit()
{
}
protected virtual void OnMove(object sender, InfoEventArgs<Vector2> e)
{
}
public virtual bool IsStateOfType(UIStates state)
{
return false;
}
}
现在游戏在我调用 owner.TitleMenu.SetActive() 的 TitleMenuState.Enter() 中崩溃,因为 TitleMenu 为空。
层次结构:
【问题讨论】:
-
GetComponent()不是异步的。它是一个阻塞调用,如果组件存在,它将被返回... -
这个问题对我来说没有任何意义。
GetComponent返回一个组件。在它能够返回一个之前,它不会返回。它当然永远不会返回 null ,然后再返回一些东西......我不知道它在 c# 中是如何工作的。我错过了什么? -
对于初学者 - 你在哪里运行
GetComponent? “其他代码”在哪里运行?如果“其他代码”在主线程中运行(这意味着它只是在 beforeGetComponent甚至启动之前被调用),那么 Omi 的答案可能就足够了。但是从问题如何表明事情同时发生......如果“其他代码”在非主线程中运行......那么其他同步方法可能是必要的。需要更多细节 -
Unity 不允许其 API 的一部分被主线程以外的线程使用。 GetComponent 是如果在主线程以外的任何东西上使用它将彻底崩溃的方法之一。它也是同步的,立即返回组件(或 null,如果这样的组件不在游戏对象上)。
-
以上代码中没有任何内容调用
UIManager.Setup,因此如果代码按原样运行,则可能是 owner.TitleMenu 将始终为 null