【行为型之命令模式】游戏开发实战——Unity可撤销系统与高级输入管理的架构秘钥

发布于:2025-05-16 ⋅ 阅读:(15) ⋅ 点赞:(0)

⌨️ 命令模式(Command Pattern)深度解析

——以Unity实现可撤销操作智能输入系统为核心案例


一、模式本质与核心价值

核心目标
封装操作为对象,支持撤销/重做功能
解耦请求发送者与执行者,提升系统扩展性
✅ 支持请求队列日志记录,实现复杂操作管理

关键术语

  • Command(命令接口):定义执行操作的统一接口
  • ConcreteCommand(具体命令):实现具体业务逻辑
  • Invoker(调用者):触发命令执行(如输入处理器)
  • Receiver(接收者):实际执行操作的对象

数学表达
操作历史H可表示为命令序列:
H = [C₁, C₂, …, Cₙ]
撤销操作为:H.pop() → ExecuteInverse(Cₙ)


二、经典UML结构
«interface»
ICommand
+Execute()
+Undo()
MoveCommand
-_unit: Unit
-_from: Vector3
-_to: Vector3
+Execute()
+Undo()
InputHandler
-_commandHistory: Stack<ICommand>
+HandleInput()
Unit
+Move(Vector3)

三、Unity实战代码(可撤销的建造系统)
1. 定义命令接口与接收者
public interface ICommand {
    void Execute();
    void Undo();
}

public class Builder : MonoBehaviour {
    public void BuildWall(Vector3 position) {
        Instantiate(wallPrefab, position, Quaternion.identity);
    }
    
    public void DestroyWall(Vector3 position) {
        var wall = Physics.OverlapSphere(position, 0.5f)
                         .FirstOrDefault(c => c.CompareTag("Wall"));
        if(wall != null) Destroy(wall.gameObject);
    }
}
2. 实现具体命令
public class BuildCommand : ICommand {
    private Builder _builder;
    private Vector3 _position;
    private GameObject _builtWall;
    
    public BuildCommand(Builder builder, Vector3 pos) {
        _builder = builder;
        _position = pos;
    }
    
    public void Execute() {
        _builder.BuildWall(_position);
        _builtWall = GameObject.FindWithTag("Wall");
    }
    
    public void Undo() {
        if(_builtWall != null) {
            _builder.DestroyWall(_builtWall.transform.position);
        }
    }
}
3. 命令管理器(Invoker)
public class CommandManager : MonoBehaviour {
    private Stack<ICommand> _commandHistory = new();
    private Stack<ICommand> _redoStack = new();
    
    public void ExecuteCommand(ICommand command) {
        command.Execute();
        _commandHistory.Push(command);
        _redoStack.Clear();
    }
    
    public void Undo() {
        if(_commandHistory.Count == 0) return;
        
        var cmd = _commandHistory.Pop();
        cmd.Undo();
        _redoStack.Push(cmd);
    }
    
    public void Redo() {
        if(_redoStack.Count == 0) return;
        
        var cmd = _redoStack.Pop();
        cmd.Execute();
        _commandHistory.Push(cmd);
    }
}
4. 客户端使用
public class BuildController : MonoBehaviour {
    [SerializeField] private Builder builder;
    [SerializeField] private CommandManager cmdManager;
    
    void Update() {
        if(Input.GetMouseButtonDown(0)) {
            var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            if(Physics.Raycast(ray, out var hit)) {
                var cmd = new BuildCommand(builder, hit.point);
                cmdManager.ExecuteCommand(cmd);
            }
        }
        
        if(Input.GetKeyDown(KeyCode.Z)) cmdManager.Undo();
        if(Input.GetKeyDown(KeyCode.Y)) cmdManager.Redo();
    }
}

四、模式进阶技巧
1. 宏命令(组合命令)
public class MacroCommand : ICommand {
    private List<ICommand> _commands = new();
    
    public void Add(ICommand cmd) => _commands.Add(cmd);
    
    public void Execute() {
        foreach(var cmd in _commands) cmd.Execute();
    }
    
    public void Undo() {
        foreach(var cmd in _commands.AsEnumerable().Reverse()) {
            cmd.Undo();
        }
    }
}
2. 异步命令执行
public class AsyncMoveCommand : ICommand {
    public async Task ExecuteAsync() {
        await MoveCoroutine();
    }
    
    private IEnumerator MoveCoroutine() {
        // 移动动画协程
    }
}
3. 命令序列化
[System.Serializable]
public class SaveCommand : ICommand {
    public string SaveData;
    
    public void Execute() {
        PlayerPrefs.SetString("Save", SaveData);
    }
    
    public void Undo() {
        PlayerPrefs.DeleteKey("Save");
    }
}

五、游戏开发典型应用场景
  1. 输入映射系统

    public class InputSystem {
        private Dictionary<KeyCode, ICommand> _keyBindings = new();
        
        public void Update() {
            foreach(var binding in _keyBindings) {
                if(Input.GetKeyDown(binding.Key)) {
                    binding.Value.Execute();
                }
            }
        }
    }
    
  2. AI行为队列

    public class AICommander {
        private Queue<ICommand> _actionQueue = new();
        
        public void ScheduleAction(ICommand cmd) {
            _actionQueue.Enqueue(cmd);
        }
        
        void Update() {
            if(_actionQueue.Count > 0) {
                _actionQueue.Dequeue().Execute();
            }
        }
    }
    
  3. 网络命令同步

    public class NetworkCommand : ICommand {
        public void Execute() {
            if(PhotonNetwork.IsMasterClient) {
                photonView.RPC("RpcExecute", RpcTarget.All);
            }
        }
        
        [PunRPC]
        private void RpcExecute() {
            // 实际执行逻辑
        }
    }
    
  4. 回放系统

    public class ReplaySystem {
        private List<TimestampedCommand> _commandLog = new();
        
        public void Record(ICommand cmd) {
            _commandLog.Add(new TimestampedCommand(Time.time, cmd));
        }
        
        public void PlayReplay() {
            StartCoroutine(ReplayCommands());
        }
    }
    

六、性能优化策略
策略 实现方式 适用场景
命令池 重用命令对象 高频命令创建
批量处理 合并相似命令 大量小操作
延迟执行 分帧处理命令队列 性能敏感场景
二进制序列化 优化存储空间 回放/存档系统

七、模式对比与选择
维度 命令模式 策略模式
目的 封装操作 替换算法
状态管理 支持撤销 无状态
执行时机 可延迟执行 立即执行
典型应用 输入处理 AI决策

八、最佳实践原则
  1. 接口最小化:保持命令接口简洁
  2. 不可变状态:命令参数应在构造时确定
  3. 原子操作:每个命令代表一个完整操作
  4. 安全撤销:确保Undo操作的幂等性
    public void Undo() {
        if(_isUndone) return;
        // 撤销逻辑
        _isUndone = true;
    }
    

九、常见问题解决方案

Q1:如何处理命令依赖?
→ 实现命令版本控制

public class VersionedCommand : ICommand {
    public int Version;
    public bool IsCompatibleWith(int currentVersion) {
        return Version <= currentVersion;
    }
}

Q2:如何优化大量命令存储?
→ 使用增量压缩

public class DeltaCommand : ICommand {
    private byte[] _deltaData;
    
    public void Compress(CommandState fullState) {
        // 计算差异并压缩
    }
}

Q3:如何处理网络延迟?
→ 实现预测回滚

public class PredictiveMoveCommand : ICommand {
    public void Execute() {
        _predictedPosition = CalculatePrediction();
        _serverPosition = NetworkSync();
        
        if(_predictedPosition != _serverPosition) {
            RollbackAndResync();
        }
    }
}

上一篇 【行为型之责任链模式】游戏开发实战——Unity灵活事件处理系统的架构核心
下一篇 【行为型之解释器模式】游戏开发实战——Unity动态公式解析与脚本系统的架构奥秘


网站公告

今日签到

点亮在社区的每一天
去签到