You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,但内存中该对象实际存在,所以方法能正常执行。
  • 修正方案:
    1. 移除MonoBehaviour继承:将EmptyState改为普通C#类,状态逻辑不需要Unity组件特性,这样直接new实例化完全合法,Inspector也能正常显示状态对象(如果需要序列化,可添加[Serializable]特性)
    2. 保留MonoBehaviour的处理方式:将每个状态类作为组件挂载到敌人GameObject上,初始化字典时通过GetComponent<RoamingState>()获取实例,而非new创建
  • 辅助验证步骤:
    • 在currentState = amalgamStates.Values.First();后添加Debug.Log(currentState.GetType());,确认内存中对象的实际类型
    • 检查GrabAllStates是否在AmalgamFSM.Update执行前完成调用,避免amalgamStates未初始化导致的问题
  • Inspector赋值问题:如果坚持用MonoBehaviour状态,需先在GameObject上挂载对应状态组件,才能在Inspector中给currentState赋值

内容的提问来源于stack exchange,提问作者unitedinc

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 22:08:11