Unity中C#状态模式详解
csharp// 选择目标状态机return;Repaint();// 绘制状态节点// 绘制状态转换// 如果是当前状态,高亮显示// 绘制箭头和标签// 通过反射获取所有状态// 获取状态转换信息#endif使用场景推荐实现优点简单状态(<5个)枚举 + switch简单直接,性能好中等复杂度经典状态模式结构清晰,易维护复杂AI/角色分层状态机。
状态模式是一种行为设计模式,允许对象在内部状态改变时改变其行为,使对象看起来像是修改了其类。
1. 状态模式基本概念
1.1 状态模式的核心思想
-
将状态封装成独立的类
-
将状态相关的行为委托给当前状态对象
-
允许状态对象在运行时切换
1.2 状态模式的三个主要组件
-
Context(上下文):维护当前状态,并将与状态相关的操作委托给当前状态对象
-
State(状态接口):定义所有具体状态的通用接口
-
ConcreteState(具体状态):实现特定状态的行为
2. 基础状态模式实现
2.1 基本接口和类结构
csharp
// 状态接口
public interface IState
{
void Enter();
void Update();
void Exit();
}
// 上下文基类
public abstract class StateContext : MonoBehaviour
{
protected IState currentState;
public void ChangeState(IState newState)
{
if (currentState != null)
currentState.Exit();
currentState = newState;
currentState.Enter();
}
protected virtual void Update()
{
if (currentState != null)
currentState.Update();
}
}
3. Unity中的具体实现示例:玩家状态机
3.1 玩家状态接口和具体状态
csharp
// 玩家状态接口
public interface IPlayerState : IState
{
void HandleInput(PlayerInput input);
}
// 玩家输入结构
public struct PlayerInput
{
public float Horizontal;
public float Vertical;
public bool JumpPressed;
public bool AttackPressed;
}
// 空闲状态
public class IdleState : IPlayerState
{
private PlayerController player;
private Animator animator;
public IdleState(PlayerController player)
{
this.player = player;
this.animator = player.GetComponent<Animator>();
}
public void Enter()
{
Debug.Log("进入空闲状态");
animator.SetBool("IsWalking", false);
animator.SetBool("IsRunning", false);
}
public void Update()
{
// 空闲状态下的更新逻辑
}
public void HandleInput(PlayerInput input)
{
if (Mathf.Abs(input.Horizontal) > 0.1f || Mathf.Abs(input.Vertical) > 0.1f)
{
if (input.Horizontal > 0.5f || input.Vertical > 0.5f)
player.ChangeState(new RunState(player));
else
player.ChangeState(new WalkState(player));
}
if (input.JumpPressed)
{
player.ChangeState(new JumpState(player));
}
}
public void Exit()
{
Debug.Log("退出空闲状态");
}
}
// 行走状态
public class WalkState : IPlayerState
{
private PlayerController player;
private Animator animator;
private float walkSpeed = 3f;
public WalkState(PlayerController player)
{
this.player = player;
this.animator = player.GetComponent<Animator>();
}
public void Enter()
{
Debug.Log("进入行走状态");
animator.SetBool("IsWalking", true);
animator.SetBool("IsRunning", false);
}
public void Update()
{
player.Move(walkSpeed);
}
public void HandleInput(PlayerInput input)
{
if (Mathf.Abs(input.Horizontal) < 0.1f && Mathf.Abs(input.Vertical) < 0.1f)
{
player.ChangeState(new IdleState(player));
}
else if (input.Horizontal > 0.5f || input.Vertical > 0.5f)
{
player.ChangeState(new RunState(player));
}
if (input.JumpPressed)
{
player.ChangeState(new JumpState(player));
}
}
public void Exit()
{
Debug.Log("退出行走状态");
}
}
// 奔跑状态
public class RunState : IPlayerState
{
private PlayerController player;
private Animator animator;
private float runSpeed = 6f;
public RunState(PlayerController player)
{
this.player = player;
this.animator = player.GetComponent<Animator>();
}
public void Enter()
{
Debug.Log("进入奔跑状态");
animator.SetBool("IsWalking", false);
animator.SetBool("IsRunning", true);
}
public void Update()
{
player.Move(runSpeed);
}
public void HandleInput(PlayerInput input)
{
if (Mathf.Abs(input.Horizontal) < 0.1f && Mathf.Abs(input.Vertical) < 0.1f)
{
player.ChangeState(new IdleState(player));
}
else if (input.Horizontal < 0.5f && input.Vertical < 0.5f)
{
player.ChangeState(new WalkState(player));
}
if (input.JumpPressed)
{
player.ChangeState(new JumpState(player));
}
}
public void Exit()
{
Debug.Log("退出奔跑状态");
}
}
// 跳跃状态
public class JumpState : IPlayerState
{
private PlayerController player;
private Animator animator;
private float jumpForce = 8f;
private bool isGrounded;
public JumpState(PlayerController player)
{
this.player = player;
this.animator = player.GetComponent<Animator>();
}
public void Enter()
{
Debug.Log("进入跳跃状态");
animator.SetTrigger("Jump");
player.Jump(jumpForce);
isGrounded = false;
}
public void Update()
{
isGrounded = player.IsGrounded();
if (isGrounded)
{
player.ChangeState(new IdleState(player));
}
player.Move(4f); // 跳跃中的移动速度
}
public void HandleInput(PlayerInput input)
{
// 跳跃中可能处理一些输入,如攻击
if (input.AttackPressed)
{
// 跳跃攻击
}
}
public void Exit()
{
Debug.Log("退出跳跃状态");
}
}
3.2 玩家控制器(上下文)
csharp
public class PlayerController : StateContext
{
[SerializeField] private float moveSpeed = 5f;
[SerializeField] private float gravity = -9.81f;
[SerializeField] private LayerMask groundLayer;
private CharacterController characterController;
private Vector3 velocity;
private PlayerInput currentInput;
private void Start()
{
characterController = GetComponent<CharacterController>();
// 初始状态
ChangeState(new IdleState(this));
}
protected override void Update()
{
// 收集输入
currentInput = new PlayerInput
{
Horizontal = Input.GetAxis("Horizontal"),
Vertical = Input.GetAxis("Vertical"),
JumpPressed = Input.GetButtonDown("Jump"),
AttackPressed = Input.GetButtonDown("Fire1")
};
// 处理重力
if (characterController.isGrounded && velocity.y < 0)
{
velocity.y = -2f;
}
velocity.y += gravity * Time.deltaTime;
characterController.Move(velocity * Time.deltaTime);
// 更新当前状态
if (currentState is IPlayerState playerState)
{
playerState.HandleInput(currentInput);
base.Update();
}
}
public void Move(float speed)
{
Vector3 moveDirection = new Vector3(currentInput.Horizontal, 0, currentInput.Vertical);
if (moveDirection.magnitude > 0.1f)
{
// 旋转朝向移动方向
float targetAngle = Mathf.Atan2(moveDirection.x, moveDirection.z) * Mathf.Rad2Deg;
transform.rotation = Quaternion.Euler(0, targetAngle, 0);
// 移动
characterController.Move(moveDirection.normalized * speed * Time.deltaTime);
}
}
public void Jump(float force)
{
velocity.y = Mathf.Sqrt(force * -2f * gravity);
}
public bool IsGrounded()
{
return characterController.isGrounded;
}
}
4. 高级状态模式实现
4.1 带参数的状态机
csharp
// 带参数的状态接口
public interface IParamState<T> : IState
{
void SetContext(StateContext context);
void SetParameter(T parameter);
}
// 泛型状态机
public class StateMachine<T> : MonoBehaviour where T : System.Enum
{
private Dictionary<T, IState> states = new Dictionary<T, IState>();
private IState currentState;
private T currentStateType;
public void RegisterState(T stateType, IState state)
{
states[stateType] = state;
}
public void ChangeState(T newStateType)
{
if (states.TryGetValue(newStateType, out IState newState))
{
if (currentState != null)
currentState.Exit();
currentState = newState;
currentStateType = newStateType;
currentState.Enter();
}
}
public T GetCurrentStateType()
{
return currentStateType;
}
protected virtual void Update()
{
if (currentState != null)
currentState.Update();
}
}
4.2 动画集成状态机
csharp
// 动画状态机
public class AnimatorStateMachine : MonoBehaviour
{
[System.Serializable]
public class StateAnimationPair
{
public string StateName;
public AnimationClip Animation;
public float TransitionDuration = 0.1f;
}
[SerializeField] private Animator animator;
[SerializeField] private List<StateAnimationPair> stateAnimations;
private Dictionary<string, int> animationHashes = new Dictionary<string, int>();
private string currentState;
private void Start()
{
// 预计算动画哈希
foreach (var pair in stateAnimations)
{
animationHashes[pair.StateName] = Animator.StringToHash(pair.Animation.name);
}
}
public void ChangeState(string stateName)
{
if (currentState == stateName) return;
if (animationHashes.ContainsKey(stateName))
{
animator.CrossFade(animationHashes[stateName],
GetTransitionDuration(stateName));
currentState = stateName;
}
}
private float GetTransitionDuration(string stateName)
{
var pair = stateAnimations.Find(p => p.StateName == stateName);
return pair != null ? pair.TransitionDuration : 0.1f;
}
}
5. 状态模式的最佳实践
5.1 状态工厂模式
csharp
// 状态工厂
public class StateFactory
{
private Dictionary<Type, IState> stateCache = new Dictionary<Type, IState>();
public T GetState<T>(object context) where T : IState, new()
{
Type stateType = typeof(T);
if (!stateCache.ContainsKey(stateType))
{
T state = new T();
// 如果状态需要上下文,设置上下文
if (state is IContextState contextState)
{
contextState.SetContext(context);
}
stateCache[stateType] = state;
}
return (T)stateCache[stateType];
}
}
// 需要上下文的接口
public interface IContextState : IState
{
void SetContext(object context);
}
5.2 状态模式在AI中的应用
csharp
// AI敌人状态机
public class AIEnemy : MonoBehaviour
{
public enum AIState { Patrol, Chase, Attack, Flee }
private StateMachine<AIState> stateMachine;
private Transform player;
private void Start()
{
player = GameObject.FindGameObjectWithTag("Player").transform;
stateMachine = gameObject.AddComponent<StateMachine<AIState>>();
// 注册状态
stateMachine.RegisterState(AIState.Patrol, new PatrolState(this));
stateMachine.RegisterState(AIState.Chase, new ChaseState(this, player));
stateMachine.RegisterState(AIState.Attack, new AttackState(this, player));
stateMachine.RegisterState(AIState.Flee, new FleeState(this, player));
// 初始状态
stateMachine.ChangeState(AIState.Patrol);
}
}
// 巡逻状态
public class PatrolState : IState
{
private AIEnemy enemy;
private Vector3[] patrolPoints;
private int currentPointIndex = 0;
public PatrolState(AIEnemy enemy)
{
this.enemy = enemy;
// 初始化巡逻点
}
public void Enter()
{
Debug.Log("开始巡逻");
}
public void Update()
{
// 巡逻逻辑
if (Vector3.Distance(enemy.transform.position, patrolPoints[currentPointIndex]) < 0.5f)
{
currentPointIndex = (currentPointIndex + 1) % patrolPoints.Length;
}
// 检测玩家
// 如果检测到玩家,切换到追逐状态
}
public void Exit()
{
Debug.Log("结束巡逻");
}
}
6. 状态模式的优缺点
优点:
-
单一职责原则:每个状态都有自己的类,代码更清晰
-
开闭原则:易于添加新状态而不修改现有代码
-
消除条件语句:减少了大量的if-else或switch语句
-
状态转换逻辑明确:状态转换集中管理
缺点:
-
类数量增加:每个状态都需要一个类
-
过度设计:对于简单状态机可能过度复杂
-
状态间依赖:状态之间可能有复杂的依赖关系
7. 实际应用建议
-
何时使用状态模式:
-
对象有多个状态,且状态间频繁转换
-
状态相关行为复杂,且有大量条件语句
-
需要清晰的状态管理结构
-
-
Unity特定优化:
-
结合Unity的Animator Controller
-
使用ScriptableObject创建可配置的状态
-
利用Unity的协程处理状态中的时序逻辑
-
-
性能考虑:
-
避免频繁创建状态对象(使用对象池或缓存)
-
对于简单状态机,考虑使用枚举+switch
-
使用状态模式时注意内存占用
-
状态模式在Unity中特别适合用于角色控制、AI行为、游戏流程管理等场景,能够使代码更加模块化和可维护。
补充与优化
1. 状态机的性能优化策略
1.1 状态缓存与对象池
csharp
// 状态工厂与缓存
public class StateFactory
{
private static readonly Dictionary<Type, IState> stateCache = new Dictionary<Type, IState>();
private static readonly Dictionary<Type, object> stateContexts = new Dictionary<Type, object>();
public static T GetState<T>() where T : IState, new()
{
Type type = typeof(T);
if (!stateCache.ContainsKey(type))
{
stateCache[type] = new T();
}
return (T)stateCache[type];
}
public static T GetState<T>(object context) where T : IState, new()
{
Type type = typeof(T);
if (!stateCache.ContainsKey(type))
{
T state = new T();
// 如果状态需要初始化上下文
if (state is IContextableState contextable)
{
contextable.SetContext(context);
}
stateCache[type] = state;
stateContexts[type] = context;
}
else if (stateCache[type] is IContextableState cachedContextable)
{
// 更新上下文
cachedContextable.SetContext(context);
}
return (T)stateCache[type];
}
}
// 支持上下文的状态接口
public interface IContextableState : IState
{
void SetContext(object context);
}
1.2 批处理状态切换
csharp
// 延迟状态切换管理器
public class StateQueueManager : MonoBehaviour
{
private class StateRequest
{
public IState State;
public object Param;
public float Delay;
public System.Action OnComplete;
}
private Queue<StateRequest> stateQueue = new Queue<StateRequest>();
private Coroutine currentTransition;
public void QueueState(IState state, object param = null, float delay = 0f, System.Action onComplete = null)
{
stateQueue.Enqueue(new StateRequest
{
State = state,
Param = param,
Delay = delay,
OnComplete = onComplete
});
if (currentTransition == null)
{
currentTransition = StartCoroutine(ProcessQueue());
}
}
private IEnumerator ProcessQueue()
{
while (stateQueue.Count > 0)
{
var request = stateQueue.Dequeue();
if (request.Delay > 0)
yield return new WaitForSeconds(request.Delay);
// 执行状态切换
request.State.Enter(request.Param);
request.OnComplete?.Invoke();
yield return null;
}
currentTransition = null;
}
public void ClearQueue()
{
stateQueue.Clear();
if (currentTransition != null)
{
StopCoroutine(currentTransition);
currentTransition = null;
}
}
}
2. 复合状态模式(状态嵌套)
2.1 子状态机系统
csharp
// 子状态机支持
public class SubStateMachine : IState
{
private StateMachine innerStateMachine;
private IState parentState;
public SubStateMachine(IState parent)
{
parentState = parent;
innerStateMachine = new StateMachine();
}
public void Enter(object param = null)
{
innerStateMachine.Start();
}
public void Update()
{
innerStateMachine.Update();
// 检查是否应该退出子状态机
if (ShouldExitSubStateMachine())
{
parentState.Exit();
}
}
public void Exit()
{
innerStateMachine.Stop();
}
public void RegisterSubState(IState state, System.Func<bool> exitCondition = null)
{
innerStateMachine.RegisterState(state, exitCondition);
}
private bool ShouldExitSubStateMachine()
{
// 根据游戏逻辑判断是否退出
return false;
}
}
// 支持子状态的状态机
public class AdvancedStateMachine : MonoBehaviour
{
private Stack<IState> stateStack = new Stack<IState>();
private Dictionary<Type, SubStateMachine> subStateMachines = new Dictionary<Type, SubStateMachine>();
public void PushState(IState state)
{
if (stateStack.Count > 0)
stateStack.Peek().Exit();
stateStack.Push(state);
state.Enter();
}
public IState PopState()
{
if (stateStack.Count > 0)
{
IState state = stateStack.Pop();
state.Exit();
if (stateStack.Count > 0)
stateStack.Peek().Enter();
return state;
}
return null;
}
public void RegisterSubStateMachine(Type parentStateType, SubStateMachine subStateMachine)
{
subStateMachines[parentStateType] = subStateMachine;
}
public SubStateMachine GetSubStateMachine(Type parentStateType)
{
subStateMachines.TryGetValue(parentStateType, out SubStateMachine result);
return result;
}
}
3. 可序列化的状态配置
3.1 ScriptableObject状态配置
csharp
// 可序列化的状态配置
[CreateAssetMenu(fileName = "NewStateConfig", menuName = "State Machine/State Config")]
public class StateConfig : ScriptableObject
{
[System.Serializable]
public class StateTransition
{
public string FromState;
public string ToState;
public string Condition;
public float Delay;
}
[System.Serializable]
public class StateData
{
public string StateName;
public AnimationClip Animation;
public float AnimationSpeed = 1f;
public AudioClip EnterSound;
public AudioClip ExitSound;
public float MinDuration = 0f;
public float MaxDuration = float.MaxValue;
public List<StateTransition> Transitions = new List<StateTransition>();
}
public List<StateData> States = new List<StateData>();
public StateData GetStateData(string stateName)
{
return States.Find(s => s.StateName == stateName);
}
}
// 基于配置的状态工厂
public class ConfigurableStateFactory : MonoBehaviour
{
[SerializeField] private StateConfig stateConfig;
private Dictionary<string, IState> createdStates = new Dictionary<string, IState>();
private StateMachine stateMachine;
private void Start()
{
stateMachine = GetComponent<StateMachine>();
InitializeStates();
}
private void InitializeStates()
{
foreach (var stateData in stateConfig.States)
{
var state = CreateStateFromConfig(stateData);
createdStates[stateData.StateName] = state;
stateMachine.RegisterState(state);
}
}
private IState CreateStateFromConfig(StateConfig.StateData data)
{
return new ConfigurableState(data);
}
private class ConfigurableState : IState
{
private readonly StateConfig.StateData data;
private float timeInState;
public ConfigurableState(StateConfig.StateData data)
{
this.data = data;
}
public void Enter()
{
timeInState = 0f;
if (data.EnterSound != null)
{
// 播放音效
}
Debug.Log($"Entering state: {data.StateName}");
}
public void Update()
{
timeInState += Time.deltaTime;
// 检查转换条件
foreach (var transition in data.Transitions)
{
if (CheckTransitionCondition(transition))
{
// 执行状态转换
break;
}
}
// 检查最大持续时间
if (timeInState >= data.MaxDuration)
{
// 强制转换状态
}
}
public void Exit()
{
if (data.ExitSound != null)
{
// 播放音效
}
Debug.Log($"Exiting state: {data.StateName}");
}
private bool CheckTransitionCondition(StateConfig.StateTransition transition)
{
// 实现条件检查逻辑
return false;
}
}
}
4. 可视化状态机编辑器扩展
4.1 自定义编辑器窗口
csharp
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
public class StateMachineEditor : EditorWindow
{
private StateMachine targetMachine;
private Vector2 scrollPosition;
private GUIStyle stateStyle;
private GUIStyle transitionStyle;
[MenuItem("Tools/State Machine Editor")]
public static void ShowWindow()
{
GetWindow<StateMachineEditor>("State Machine Editor");
}
private void OnEnable()
{
stateStyle = new GUIStyle(GUI.skin.box)
{
alignment = TextAnchor.MiddleCenter,
fontStyle = FontStyle.Bold
};
transitionStyle = new GUIStyle(GUI.skin.label)
{
alignment = TextAnchor.MiddleCenter
};
}
private void OnGUI()
{
EditorGUILayout.BeginHorizontal();
// 选择目标状态机
targetMachine = EditorGUILayout.ObjectField("Target State Machine",
targetMachine, typeof(StateMachine), true) as StateMachine;
EditorGUILayout.EndHorizontal();
if (targetMachine == null)
{
EditorGUILayout.HelpBox("Please assign a State Machine component", MessageType.Info);
return;
}
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
DrawStateMachineGraph();
EditorGUILayout.EndScrollView();
if (GUILayout.Button("Refresh"))
{
Repaint();
}
}
private void DrawStateMachineGraph()
{
// 绘制状态节点
foreach (var state in GetStates())
{
DrawStateNode(state);
}
// 绘制状态转换
foreach (var transition in GetTransitions())
{
DrawTransitionArrow(transition);
}
}
private void DrawStateNode(IState state)
{
Rect rect = new Rect(50, 50, 150, 50);
GUI.Box(rect, state.GetType().Name, stateStyle);
// 如果是当前状态,高亮显示
if (IsCurrentState(state))
{
EditorGUI.DrawRect(rect, new Color(0, 1, 0, 0.2f));
}
}
private void DrawTransitionArrow(StateTransition transition)
{
// 绘制箭头和标签
Handles.color = Color.white;
Handles.DrawLine(transition.FromPosition, transition.ToPosition);
Vector2 labelPosition = (transition.FromPosition + transition.ToPosition) / 2;
GUI.Label(new Rect(labelPosition.x, labelPosition.y, 100, 20),
transition.Condition, transitionStyle);
}
private bool IsCurrentState(IState state)
{
return targetMachine.CurrentState == state;
}
private List<IState> GetStates()
{
// 通过反射获取所有状态
return new List<IState>();
}
private List<StateTransition> GetTransitions()
{
// 获取状态转换信息
return new List<StateTransition>();
}
private class StateTransition
{
public Vector2 FromPosition;
public Vector2 ToPosition;
public string Condition;
}
}
#endif
5. 异步状态支持
5.1 协程状态
csharp
// 支持协程的状态接口
public interface ICoroutineState : IState
{
IEnumerator EnterCoroutine();
IEnumerator ExitCoroutine();
bool IsTransitioning { get; }
}
// 协程状态机
public class CoroutineStateMachine : MonoBehaviour
{
private ICoroutineState currentState;
private Coroutine enterCoroutine;
private Coroutine exitCoroutine;
public async void ChangeState(ICoroutineState newState)
{
if (currentState != null && currentState.IsTransitioning)
{
Debug.LogWarning("State is transitioning, cannot change now");
return;
}
// 退出当前状态
if (currentState != null)
{
currentState.Exit();
if (exitCoroutine != null)
StopCoroutine(exitCoroutine);
exitCoroutine = StartCoroutine(currentState.ExitCoroutine());
await WaitForCoroutine(exitCoroutine);
}
// 进入新状态
currentState = newState;
currentState.Enter();
if (enterCoroutine != null)
StopCoroutine(enterCoroutine);
enterCoroutine = StartCoroutine(currentState.EnterCoroutine());
await WaitForCoroutine(enterCoroutine);
}
private async System.Threading.Tasks.Task WaitForCoroutine(Coroutine coroutine)
{
while (coroutine != null)
{
await System.Threading.Tasks.Task.Yield();
}
}
private void Update()
{
if (currentState != null && !currentState.IsTransitioning)
{
currentState.Update();
}
}
}
// 示例:加载场景状态
public class LoadSceneState : ICoroutineState
{
private string sceneName;
private AsyncOperation loadOperation;
public bool IsTransitioning => loadOperation != null && !loadOperation.isDone;
public LoadSceneState(string sceneName)
{
this.sceneName = sceneName;
}
public void Enter()
{
Debug.Log($"Starting to load scene: {sceneName}");
}
public IEnumerator EnterCoroutine()
{
loadOperation = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync(sceneName);
loadOperation.allowSceneActivation = false;
while (!loadOperation.isDone)
{
float progress = Mathf.Clamp01(loadOperation.progress / 0.9f);
Debug.Log($"Loading progress: {progress * 100}%");
if (progress >= 0.9f)
{
// 等待输入或延迟后激活场景
yield return new WaitForSeconds(1f);
loadOperation.allowSceneActivation = true;
}
yield return null;
}
}
public void Update()
{
// 更新加载界面等
}
public void Exit()
{
Debug.Log("Exiting load scene state");
}
public IEnumerator ExitCoroutine()
{
yield return null;
}
}
6. 状态模式与UniRx结合
6.1 响应式状态机
csharp
using UniRx;
using UniRx.Triggers;
public class ReactiveStateMachine : MonoBehaviour
{
private ReactiveProperty<IState> currentState = new ReactiveProperty<IState>();
private CompositeDisposable disposables = new CompositeDisposable();
public IReadOnlyReactiveProperty<IState> CurrentState => currentState;
public void Initialize(IState initialState)
{
ChangeState(initialState);
// 每帧更新当前状态
this.UpdateAsObservable()
.Where(_ => currentState.Value != null)
.Subscribe(_ => currentState.Value.Update())
.AddTo(disposables);
// 状态变化时的回调
currentState
.Skip(1) // 跳过初始值
.Pairwise()
.Subscribe(pair =>
{
Debug.Log($"State changed from {pair.Previous?.GetType().Name} to {pair.Current?.GetType().Name}");
})
.AddTo(disposables);
}
public void ChangeState(IState newState)
{
if (currentState.Value != null)
currentState.Value.Exit();
currentState.Value = newState;
newState.Enter();
}
private void OnDestroy()
{
disposables.Dispose();
currentState.Value?.Exit();
}
}
// 响应式状态示例
public class ReactiveIdleState : IState
{
private Subject<Unit> onEnter = new Subject<Unit>();
private Subject<Unit> onExit = new Subject<Unit>();
private Subject<float> onUpdate = new Subject<float>();
public IObservable<Unit> OnEnter => onEnter;
public IObservable<Unit> OnExit => onExit;
public IObservable<float> OnUpdate => onUpdate;
public void Enter()
{
onEnter.OnNext(Unit.Default);
}
public void Update()
{
onUpdate.OnNext(Time.deltaTime);
}
public void Exit()
{
onExit.OnNext(Unit.Default);
}
}
7. 状态模式的单元测试
7.1 可测试的状态机
csharp
// 可测试的状态接口
public interface ITestableState : IState
{
int EnterCount { get; }
int UpdateCount { get; }
int ExitCount { get; }
object LastEnterParam { get; }
}
// 单元测试辅助类
public class StateMachineTester : MonoBehaviour
{
private StateMachine stateMachine;
private Dictionary<Type, ITestableState> testStates = new Dictionary<Type, ITestableState>();
public void SetupTest()
{
stateMachine = GetComponent<StateMachine>();
// 替换为测试状态
ReplaceStatesWithTestVersions();
}
private void ReplaceStatesWithTestVersions()
{
// 通过反射获取所有状态并替换为测试版本
var fieldInfo = typeof(StateMachine).GetField("states",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (fieldInfo != null)
{
var states = fieldInfo.GetValue(stateMachine) as Dictionary<System.Type, IState>;
foreach (var kvp in states)
{
if (kvp.Value is ITestableState testableState)
{
testStates[kvp.Key] = testableState;
}
}
}
}
public void AssertStateEntered<TState>(int expectedCount = 1) where TState : ITestableState
{
Type type = typeof(TState);
if (testStates.TryGetValue(type, out ITestableState state))
{
Assert.AreEqual(expectedCount, state.EnterCount,
$"State {type.Name} should have been entered {expectedCount} times");
}
}
public void AssertStateExited<TState>(int expectedCount = 1) where TState : ITestableState
{
Type type = typeof(TState);
if (testStates.TryGetValue(type, out ITestableState state))
{
Assert.AreEqual(expectedCount, state.ExitCount,
$"State {type.Name} should have been exited {expectedCount} times");
}
}
public void SimulateStateTransition<TFrom, TTo>() where TFrom : ITestableState where TTo : ITestableState
{
// 模拟状态转换条件
// ...
}
}
// 测试用状态
public class TestIdleState : IdleState, ITestableState
{
public int EnterCount { get; private set; }
public int UpdateCount { get; private set; }
public int ExitCount { get; private set; }
public object LastEnterParam { get; private set; }
public TestIdleState(PlayerController player) : base(player) { }
public override void Enter(object param = null)
{
EnterCount++;
LastEnterParam = param;
base.Enter(param);
}
public override void Update()
{
UpdateCount++;
base.Update();
}
public override void Exit()
{
ExitCount++;
base.Exit();
}
}
8. 性能监控与调试
8.1 状态机性能监控
csharp
public class StateMachineProfiler : MonoBehaviour
{
[System.Serializable]
public class StateProfile
{
public string StateName;
public float TotalTime;
public int TransitionCount;
public float AverageDuration;
}
private Dictionary<string, StateProfile> profiles = new Dictionary<string, StateProfile>();
private string currentStateName;
private float stateStartTime;
public void OnStateChanged(string previousState, string newState)
{
// 记录上一个状态的时间
if (!string.IsNullOrEmpty(previousState))
{
if (profiles.TryGetValue(previousState, out StateProfile profile))
{
profile.TotalTime += Time.time - stateStartTime;
profile.TransitionCount++;
profile.AverageDuration = profile.TotalTime / profile.TransitionCount;
}
}
// 开始记录新状态
currentStateName = newState;
stateStartTime = Time.time;
if (!profiles.ContainsKey(newState))
{
profiles[newState] = new StateProfile { StateName = newState };
}
}
public void LogProfiles()
{
Debug.Log("=== State Machine Profile ===");
foreach (var profile in profiles.Values)
{
Debug.Log($"{profile.StateName}: " +
$"Total={profile.TotalTime:F2}s, " +
$"Transitions={profile.TransitionCount}, " +
$"Avg={profile.AverageDuration:F2}s");
}
}
// 在编辑器窗口中显示
#if UNITY_EDITOR
private void OnDrawGizmos()
{
GUIStyle style = new GUIStyle();
style.normal.textColor = Color.yellow;
Vector3 position = transform.position + Vector3.up * 2f;
UnityEditor.Handles.Label(position, $"Current: {currentStateName}", style);
}
#endif
}
9. 总结与最佳实践
9.1 选择合适的状态模式变体
| 使用场景 | 推荐实现 | 优点 |
|---|---|---|
| 简单状态(<5个) | 枚举 + switch | 简单直接,性能好 |
| 中等复杂度 | 经典状态模式 | 结构清晰,易维护 |
| 复杂AI/角色 | 分层状态机 | 支持状态嵌套,减少重复 |
| 需要配置 | ScriptableObject状态 | 非程序员可配置,数据驱动 |
| 异步操作 | 协程状态机 | 支持长时间运行操作 |
9.2 性能优化建议
-
状态缓存:复用状态对象,避免频繁GC
-
条件检查优化:使用事件驱动代替每帧检查
-
状态预加载:提前初始化可能用到的状态
-
避免深度嵌套:过深的子状态机影响性能
-
使用值类型:状态参数尽量使用值类型
9.3 调试技巧
-
状态历史:记录状态变化历史便于调试
-
可视化工具:开发编辑器扩展监控状态
-
断点条件:在特定状态转换时触发断点
-
日志分级:不同详细程度的调试日志
-
性能统计:监控每个状态的执行时间和频率
通过以上补充和优化,状态模式可以更好地适应Unity项目中的各种复杂需求,提供高性能、易维护且功能强大的状态管理解决方案。
更多推荐


所有评论(0)