【行为型之命令模式】游戏开发实战——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动态公式解析与脚本系统的架构奥秘

相关推荐
Natsume171014 分钟前
嵌入式开发:GPIO、UART、SPI、I2C 驱动开发详解与实战案例
c语言·驱动开发·stm32·嵌入式硬件·mcu·架构·github
DemonAvenger1 小时前
深入理解Go的网络I/O模型:优势、实践与踩坑经验
网络协议·架构·go
Zlzxzw2 小时前
使用unity创建项目,进行动画制作
unity·游戏引擎
N_NAN_N3 小时前
类图+案例+代码详解:软件设计模式----原型模式
java·设计模式·原型模式
鹏程十八少3 小时前
7.Android 设计模式 享元模式 在商业项目中的落地
架构
缘来是庄3 小时前
设计模式之组合模式
java·设计模式·组合模式
DKPT3 小时前
Java组合模式实现方式与测试方法
java·笔记·学习·设计模式·组合模式
鼠鼠我呀23 小时前
【设计模式09】组合模式
设计模式·组合模式
老周聊大模型4 小时前
《ChatGLM/Llama调优实战:从指令微调到RLHF的工业级对齐方案》
人工智能·程序员·架构
weixin_437398214 小时前
转Go学习笔记
linux·服务器·开发语言·后端·架构·golang