Unity中子类状态对象无法赋值给父类变量的问题排查
敌人AI有限状态机(FSM)异常排查问题
我正在为Unity游戏中的敌人实现有限状态机(FSM),所有状态都继承自EmptyState.cs,该类定义了stateStart、stateUpdate和stateExit三个虚方法。
FSM核心用字典存储所有状态,定义了当前状态变量:
private Dictionary<Type, EmptyState> amalgamStates; public EmptyState currentState;
字典初始化代码如下:
Dictionary<Type, EmptyState> states = new Dictionary<Type, EmptyState> { {typeof(RoamingState), new RoamingState() }, {typeof(IdleState), new IdleState() }, {typeof(StalkingState), new StalkingState() }, {typeof(HuntingState), new HuntingState() }, {typeof(HauntingState), new HauntingState() }, {typeof(LeavingState), new LeavingState() } };
异常现象
- 给
currentState赋值后,该变量始终显示为null,Debug.Log(currentState);输出null,且无法在Unity Inspector中手动赋值 - 即便显示null,
currentState.stateStart(amalgamBrain);仍能正常执行第一个状态(RoamingState)的stateStart方法
完整相关脚本
EmptyState.cs
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class EmptyState : MonoBehaviour { public AmalgamCentralAI amalgamBrain; public virtual void stateStart(AmalgamCentralAI amalgamBrain) { } public virtual Type stateUpdate(AmalgamCentralAI amalgamBrain) { return null; } public virtual void stateExit(AmalgamCentralAI amalgamBrain) { } }
AmalgamCentralAI.cs
using System.Collections; using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; public class AmalgamCentralAI : MonoBehaviour { private float stunHealth; public bool playerInSight; public Transform interuptedEvent; public GameObject playerTracker; public Transform[] leavingAreas; private int tensionMeter; public AmalgamFSM FSMLogic; private void Start() { interuptedEvent = null; FSMLogic = GetComponent<AmalgamFSM>(); Dictionary<Type, EmptyState> states = new Dictionary<Type, EmptyState> { {typeof(RoamingState), new RoamingState() }, {typeof(IdleState), new IdleState() }, {typeof(StalkingState), new StalkingState() }, {typeof(HuntingState), new HuntingState() }, {typeof(HauntingState), new HauntingState() }, {typeof(LeavingState), new LeavingState() } }; Debug.Log(states); FSMLogic.GrabAllStates(states); } private void Update() { trackPlayerVisability(); } private void trackPlayerVisability() { RaycastHit hit; if (Physics.Raycast(transform.position, playerTracker.transform.position, out hit, Mathf.Infinity)) { if(hit.transform.tag == "Player") { playerInSight = true; } else { playerInSight = false; } } } }
AmalgamFSM.cs
using System; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; public class AmalgamFSM : MonoBehaviour { public AmalgamCentralAI amalgamBrain; public Dictionary<Type, EmptyState> amalgamStates; public EmptyState currentState; private void Start() { //Fetch all Amalgam Commands amalgamBrain = GetComponent<AmalgamCentralAI>(); //Start in Roaming State //Debug.Log("state start"); } private void Update() { if (currentState == null) { currentState = amalgamStates.Values.First(); Debug.Log(currentState); currentState.stateStart(amalgamBrain); } //Run state's update and store output (output will be next state) var temp = currentState.stateUpdate(amalgamBrain); //If the stored state is different -> switch states to new state if (temp != currentState.GetType()) { switchStates(temp); } } public void GrabAllStates(Dictionary<Type, EmptyState> states) { //Fetch dictionary of all states amalgamStates = states; } public void switchStates(Type state) { //Run current state's exit, create reference to new state, run new state's start currentState.stateExit(amalgamBrain); currentState = amalgamStates[state]; currentState.stateStart(amalgamBrain); } }
RoamingState.cs
using System.Collections; using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; public class RoamingState : EmptyState { private float SearchRange; private Transform centerPoint; private bool pointFound; private NavMeshAgent agent; public override void stateStart(AmalgamCentralAI amalgamBrain) { //Define search range SearchRange = 100f; //Amalgam serves as the center of the search area //Debug.Log("fetching center point"); centerPoint = amalgamBrain.gameObject.transform; agent = amalgamBrain.gameObject.GetComponent<NavMeshAgent>(); pointFound = false; } public override Type stateUpdate(AmalgamCentralAI amalgamBrain) { if(!pointFound) { //Find suitable Roam Position Vector3 point; if(RandomPointOnNav(centerPoint.position, SearchRange, out point)) { //Set roam position in navmeshagent agent.SetDestination(point); pointFound = true; } } // if the agent is close enough to the random point else if(agent.remainingDistance <= agent.stoppingDistance && pointFound) { //idle return typeof(IdleState); } //if agent is still looking for a point or is travelling to a point, remain in roaming state return typeof(RoamingState); } public override void stateExit(AmalgamCentralAI amalgamBrain) { } public bool RandomPointOnNav(Vector3 centerPoint, float range, out Vector3 resultPoint) { //Find a random point in a sphere of searchrange radius around the amalgam Vector3 RandomSearchPoint = centerPoint + UnityEngine.Random.insideUnitSphere * range; NavMeshHit hit; // Can the amalgam travel to this point on the navmesh? if (NavMesh.SamplePosition(RandomSearchPoint, out hit, 1.0f, NavMesh.AllAreas)) { //return that the amalgam has found a suitable position resultPoint = hit.position; return true; } // if not remain still and continue searching on next update resultPoint = Vector3.zero; return false; } }
排查思路
- 核心原因:MonoBehaviour实例化方式错误:
EmptyState继承自MonoBehaviour,但你通过new RoamingState()直接实例化,这违反Unity规则——MonoBehaviour对象必须挂载到GameObject上才能成为有效实例,直接new出来的对象不会被Unity的对象管理系统识别,因此Inspector显示为null,但内存中该对象实际存在,所以方法能正常执行。 - 修正方案:
- 移除MonoBehaviour继承:将
EmptyState改为普通C#类,状态逻辑不需要Unity组件特性,这样直接new实例化完全合法,Inspector也能正常显示状态对象(如果需要序列化,可添加[Serializable]特性) - 保留MonoBehaviour的处理方式:将每个状态类作为组件挂载到敌人GameObject上,初始化字典时通过
GetComponent<RoamingState>()获取实例,而非new创建
- 移除MonoBehaviour继承:将
- 辅助验证步骤:
- 在
currentState = amalgamStates.Values.First();后添加Debug.Log(currentState.GetType());,确认内存中对象的实际类型 - 检查
GrabAllStates是否在AmalgamFSM.Update执行前完成调用,避免amalgamStates未初始化导致的问题
- 在
- Inspector赋值问题:如果坚持用MonoBehaviour状态,需先在GameObject上挂载对应状态组件,才能在Inspector中给
currentState赋值
内容的提问来源于stack exchange,提问作者unitedinc
相关产品推荐
相关产品推荐

