Unity有限状态机:如何仅访问子类独有变量?
基于继承的敌人AI有限状态机:访问子类状态变量的解决方案
问题描述
开发游戏时采用基于继承的有限状态系统实现敌人与BOSS AI,遇到以下问题:
- 基类
EnemyState被多个状态子类继承(如AirPatrol) AirPatrol拥有EnemyState没有的speed、patrolPositions等公共变量- 状态机类(
EnemyController)中的currentState/nextState为EnemyState类型,直接引用currentState.speed会报“未包含定义”错误 - 希望找到无需将子类特有变量添加到基类的解决方案
相关代码
SecurityDroneAI 类(问题出现位置)
public class SecurityDroneAI : EnemyController { [Header("Movement")] public float patrolSpeed = 3.0f; public float aggroSpeed = 5.0f; [SerializeField] float aggroDistance = 20f; public float acceleration = 2f; [Header("Pathfinding")] public float pathUpdateDelay = 1.0f; public List<Vector2> patrolPositions = new List<Vector2>(); public float nextWaypointDistance = 2; Animator anim; Rigidbody2D rb; private void Start() { rb = GetComponent<Rigidbody2D>(); anim = GetComponent<Animator>(); SetNextState(new AirPatrol()); nextState.speed = 1; // 此处报错:EnemyState未定义speed } private void Update() { anim.SetBool("seeking", currentState.GetType() == typeof(AirPatrol)); float rot = -rb.velocity.x * 3; } }
EnemyState 基类
public class EnemyState { public Rigidbody2D rb; public Animator anim; public Transform transform; public EnemyController controller; public virtual void OnEnter(EnemyController parentController) { controller = parentController; rb = controller.GetComponent<Rigidbody2D>(); anim = controller.GetComponentInChildren<Animator>(); transform = controller.transform; } public virtual void OnUpdate() { } public virtual void OnFixedUpdate() { } public virtual void OnLateUpdate() { } public virtual void OnExit() { } }
AirPatrol 子类
public class AirPatrol : EnemyState { public List<Vector2> patrolPositions = new List<Vector2>(); // 巡逻点列表 public float speed = 3.0f; // 移动速度 public float acceleration = 2f; // 加速度 public float pathUpdateDelay = 1.0f; // 路径更新间隔 public float nextWaypointDistance = 1.5f; // 到达下一个路径点的判定距离 int positionIndex; // 当前目标巡逻点索引 int currentWaypoint = 0; // 当前路径点索引 float timeSinceUpdatedPath; // 距离上次更新路径的时间 Path path; // 当前路径 public Seeker seeker; // A*寻路组件 public override void OnEnter(EnemyController parentController) { base.OnEnter(parentController); positionIndex = 0; timeSinceUpdatedPath = 0; } public override void OnUpdate() { base.OnUpdate(); if (timeSinceUpdatedPath <= 0) // 到达路径更新间隔时更新路径 { UpdatePath(); timeSinceUpdatedPath = pathUpdateDelay; } timeSinceUpdatedPath += Time.deltaTime; if (path == null) { return; } if (currentWaypoint >= path.vectorPath.Count) // 到达路径终点 { positionIndex = positionIndex + 1 >= patrolPositions.Count ? 0 : positionIndex + 1; // 切换到下一个巡逻点 UpdatePath(); return; } MoveAlongPath(); } private void MoveAlongPath() { Vector2 direction = (path.vectorPath[currentWaypoint] - transform.position).normalized; rb.velocity = Vector3.Lerp(rb.velocity, direction * speed, acceleration * Time.deltaTime); float distance = Vector2.Distance(rb.position, path.vectorPath[currentWaypoint]); if (distance < nextWaypointDistance) { currentWaypoint++; } } private void UpdatePath() { if (!seeker.IsDone()) { return; } seeker.StartPath(transform.position, patrolPositions[positionIndex], SetPath); } private void SetPath(Path p) { if (p.error) { return; } path = p; currentWaypoint = 0; } }
EnemyController 状态机类
public class EnemyController : MonoBehaviour { public EnemyState mainStateType; public EnemyState currentState; public EnemyState nextState; private void Update() { if (nextState != null) { SetState(nextState); } if (currentState != null) { currentState.OnUpdate(); } } private void LateUpdate() { if (currentState != null) { currentState.OnLateUpdate(); } } private void FixedUpdate() { if (currentState != null) { currentState.OnFixedUpdate(); } } private void SetState(EnemyState newState) { nextState = null; if (currentState != null) { currentState.OnExit(); } currentState = newState; currentState.OnEnter(this); } public void SetNextState(EnemyState newState) { if (newState != null) { nextState = newState; } } }
解决方案
1. 显式类型转换(直接转换或as/is关键字)
这是最直接的方案,在访问子类变量前,将EnemyState类型对象转换为对应子类类型,确保类型安全。
示例代码:
private void Start() { rb = GetComponent<Rigidbody2D>(); anim = GetComponent<Animator>(); SetNextState(new AirPatrol()); // 方法1:使用as关键字(安全,转换失败返回null) var patrolState = nextState as AirPatrol; if (patrolState != null) { patrolState.speed = 1; } // 方法2:强制转换(适合确定类型的场景,转换失败会抛出异常) // var patrolState = (AirPatrol)nextState; // patrolState.speed = 1; }
在需要访问currentState的子类变量时,同样处理:
private void Update() { var patrolState = currentState as AirPatrol; if (patrolState != null) { // 访问patrolState的speed、patrolPositions等变量 Debug.Log("当前巡逻速度:" + patrolState.speed); } }
2. 为特定状态定义接口
如果多个状态子类有相似的变量或行为,可以定义接口规范,让子类实现接口,外部通过接口访问变量,提升代码扩展性。
步骤:
- 定义巡逻状态接口:
public interface IPatrolState { float speed { get; set; } List<Vector2> patrolPositions { get; set; } }
- 让
AirPatrol实现该接口:
public class AirPatrol : EnemyState, IPatrolState { // 原有变量和方法保持不变 }
- 外部通过接口访问:
private void Start() { rb = GetComponent<Rigidbody2D>(); anim = GetComponent<Animator>(); SetNextState(new AirPatrol()); var patrolState = nextState as IPatrolState; if (patrolState != null) { patrolState.speed = 1; } }
3. 状态初始化时传递参数
不在外部直接修改状态变量,而是在创建状态对象时,通过构造函数或初始化方法将控制器中的配置参数传递给状态,避免类型转换。
方法A:构造函数传参
修改AirPatrol添加构造函数:
public class AirPatrol : EnemyState { public AirPatrol(float speed, List<Vector2> patrolPositions) { this.speed = speed; this.patrolPositions = patrolPositions; } // 原有变量和方法保持不变 }
在SecurityDroneAI中调用:
private void Start() { rb = GetComponent<Rigidbody2D>(); anim = GetComponent<Animator>(); // 直接传递配置参数 SetNextState(new AirPatrol(1f, patrolPositions)); }
方法B:初始化方法传参
如果需要无参构造函数,可添加初始化方法:
public class AirPatrol : EnemyState { public void Initialize(float speed, List<Vector2> patrolPositions) { this.speed = speed; this.patrolPositions = patrolPositions; } // 原有变量和方法保持不变 }
调用时:
private void Start() { rb = GetComponent<Rigidbody2D>(); anim = GetComponent<Animator>(); var patrolState = new AirPatrol(); patrolState.Initialize(1f, patrolPositions); SetNextState(patrolState); }
4. 状态从控制器获取配置
将状态需要的配置变量放在控制器(SecurityDroneAI)中,状态在OnEnter方法里自动从控制器获取这些值,无需外部手动设置变量,让状态与控制器解耦。
修改AirPatrol的OnEnter方法:
public override void OnEnter(EnemyController parentController) { base.OnEnter(parentController); // 将父控制器转换为SecurityDroneAI类型 var droneController = parentController as SecurityDroneAI; if (droneController != null) { speed = droneController.patrolSpeed; patrolPositions = droneController.patrolPositions; acceleration = droneController.acceleration; pathUpdateDelay = droneController.pathUpdateDelay; nextWaypointDistance = droneController.nextWaypointDistance; } positionIndex = 0; timeSinceUpdatedPath = 0; }
此时SecurityDroneAI的Start方法只需创建状态:
private void Start() { rb = GetComponent<Rigidbody2D>(); anim = GetComponent<Animator>(); SetNextState(new AirPatrol()); }
内容的提问来源于stack exchange,提问作者Glass Shard Games
相关产品推荐
相关产品推荐

