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

文章目录

⌨️ 命令模式(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. 定义命令接口与接收者
csharp 复制代码
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. 实现具体命令
csharp 复制代码
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)
csharp 复制代码
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. 客户端使用
csharp 复制代码
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. 宏命令(组合命令)
csharp 复制代码
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. 异步命令执行
csharp 复制代码
public class AsyncMoveCommand : ICommand {
    public async Task ExecuteAsync() {
        await MoveCoroutine();
    }
    
    private IEnumerator MoveCoroutine() {
        // 移动动画协程
    }
}
3. 命令序列化
csharp 复制代码
[System.Serializable]
public class SaveCommand : ICommand {
    public string SaveData;
    
    public void Execute() {
        PlayerPrefs.SetString("Save", SaveData);
    }
    
    public void Undo() {
        PlayerPrefs.DeleteKey("Save");
    }
}

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

    csharp 复制代码
    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行为队列

    csharp 复制代码
    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. 网络命令同步

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

    csharp 复制代码
    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操作的幂等性

    csharp 复制代码
    public void Undo() {
        if(_isUndone) return;
        // 撤销逻辑
        _isUndone = true;
    }

九、常见问题解决方案

Q1:如何处理命令依赖?

→ 实现命令版本控制

csharp 复制代码
public class VersionedCommand : ICommand {
    public int Version;
    public bool IsCompatibleWith(int currentVersion) {
        return Version <= currentVersion;
    }
}

Q2:如何优化大量命令存储?

→ 使用增量压缩

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

Q3:如何处理网络延迟?

→ 实现预测回滚

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

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

相关推荐
EndingCoder38 分钟前
React从基础入门到高级实战:React 实战项目 - 项目三:实时聊天应用
前端·react.js·架构·前端框架
WarPigs3 小时前
Unity性能优化笔记
笔记·unity·游戏引擎
季鸢4 小时前
Java设计模式之状态模式详解
java·设计模式·状态模式
后海 0_o6 小时前
2025前端微服务 - 无界 的实战应用
前端·微服务·架构
喵叔哟6 小时前
24.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--单体转微服务--认证微服务
微服务·架构·.net
java干货6 小时前
虚拟线程与消息队列:Spring Boot 3.5 中异步架构的演进与选择
spring boot·后端·架构
SoFlu软件机器人6 小时前
智能生成完整 Java 后端架构,告别手动编写 ControllerServiceDao
java·开发语言·架构
西陵7 小时前
前端框架渲染DOM的的方式你知道多少?
前端·javascript·架构
smallluan9 小时前
JS设计模式(4):观察者模式
javascript·观察者模式·设计模式
T.D.C10 小时前
【业务框架】3C-相机-Cinemachine
unity