本文部分内容出自游戏编程模式一书,游戏编程模式,有兴趣的小伙伴可以去看看,虽然不是unity x c#写的 但是思路挺好的
目录
目录
0.先说结论
发现问题
命令模式如何解耦
打个断点更利于分析
怎么实现延迟命令?
如何撤销命令?
脚本整体一览
不足分析(AI)
0.先说结论
命令模式的好处:
解耦 扩展 延迟 撤销
命令模式是将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化; 对请求排队或记录请求日志,以及支持可撤销的操作
说白了,所谓命令模式就是将操作脚本分为一大堆单个命令 然后用一个类统一管理,所以 命令是具现化的方法调用
而其一般包含如下几个部分也可以有其他的部分,我只取其中一种
- 命令接口:定义命令的执行方法
- 具体命令类:实现命令接口,封装具体操作
- 命令管理类: 管理所有的具体命令类
- 实现命令类:实现上述所有的行为
发现问题
首先 不使用命令模式来实现角色的几个行为代码如下,这是非常非常常用的方法
public class CommandPart : MonoBehaviour
{private void Update() {if(Input.GetKeyDown(KeyCode.Q)){jump();}if (Input.GetKeyDown(KeyCode.W)) {move();}if (Input.GetKeyDown(KeyCode.E)) {hit();}}public void jump() {Debug.Log("跳跃");}public void move() {Debug.Log("移动");}public void hit() {Debug.Log("击打");}
}
但是,似乎有几个问题
1.当我角色的行为一旦多起来,岂不是要在update写一大堆东西?
2.输入检测和行为方法强耦合,改键容易影响到其他代码
3.试想一下如果我有10种动作 ,CommandPart 个类说不定会扩展到一万多行,当我需要修改其中一个动作的时候
就需要在有一万多行代码的类中找一个函数中的几行关键代码,感觉很不好
命令模式如何解耦
为了演示方便 我都将其写到一个脚本中了,通常来说是写到很多个不同脚本之中的,请注意到这一点!!!
我需要一个命令基类
// 命令基类 提供一个可重写的执行方法
public abstract class Command {public abstract void Execute();
}
我给将三个行为分成三个具体命令类,并继承基类,对其方法进行封装
// 角色行为的几个方法 需要去继承
public class JumpCommand : Command {public override void Execute() {Jump();}private void Jump() {Debug.Log("跳跃");}
}public class MoveCommand : Command {public override void Execute() {Move();}private void Move() {Debug.Log("移动");}
}public class HitCommand : Command {public override void Execute() {Hit();}private void Hit() {Debug.Log("击打");}
}
之后 写一个管理脚本,传入需要执行的参数
// 命令管理器类
public class CommandCtrl{private Command command;public void ExecuteCommand(Command cmd) {command = cmd;cmd.Execute();
}
}
最后因为里氏替换原则,通过命令管理类去实现命令
// 玩家的 实现命令类
public class CommandPart : MonoBehaviour {private CommandCtrl cCtrl;private void Awake() {cCtrl =new CommandCtrl();}private void Update() {if (Input.GetKeyDown(KeyCode.Q)) {cCtrl.ExecuteCommand(new JumpCommand());}if (Input.GetKeyDown(KeyCode.W)) {}if (Input.GetKeyDown(KeyCode.E)) {}}
}
打个断点更利于分析
按下Q之后
可以清楚的看到JumpCommand的实例被传进来 执行了重写后的Execute方法
当我添加了HitCommand之后,按下对应按键
扩展的话就只需要多写几个具体命令类并继承命令类就行了
怎么实现延迟命令?
修改一下命令管理类 将命令存入一个List表里面就行了,别忘了清空命令表
如果还有其他需求 可以用栈或队列去存储
en造数据结构与算法C# 用数组实现个栈还不简单???看我一秒破之!!!(unity演示)-CSDN博客
en造数据结构与算法 c#语言 数组实现队列很难???看我一击破之!!!-CSDN博客
// 命令管理器类
public class CommandCtrl{private List<Command> cmdList = new List<Command>();//执行命令public void ExecuteCommand(Command cmd) {this.cmdList.Add(cmd);cmd.Execute();}//延迟执行的函数public void DelayCommand(Command cmd){this.cmdList.Add(cmd);//满足一定条件后 执行逻辑 单纯延时用定时器 协程 Time类等 cmd.Execute();}//清空命令表public void ClearComman(){cmdList.Clear();}
}
if (Input.GetKeyDown(KeyCode.Q)) {cCtrl.DelayCommand(new JumpCommand());}
如何撤销命令?
实现思路如图
代码我就抛砖引玉了
//移除最后一个命令public void UndoCommand(){ if(cmdList.Count>0){cmdList.RemoveAt(cmdList.Count - 1);//这里执行游戏回溯 比如位置回溯 金币回溯等等}}
脚本整体一览
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;// 命令基类 提供一个可重写的执行方法
public abstract class Command {public abstract void Execute();
}// 角色行为的几个方法 需要去继承
public class JumpCommand : Command {public override void Execute() {Jump();}private void Jump() {Debug.Log("跳跃");}
}public class MoveCommand : Command {public override void Execute() {Move();}private void Move() {Debug.Log("移动");}
}public class HitCommand : Command {public override void Execute() {Hit();}private void Hit() {Debug.Log("击打");}
}// 命令管理器类
public class CommandCtrl{private List<Command> cmdList = new List<Command>();//执行命令public void ExecuteCommand(Command cmd) {this.cmdList.Add(cmd);cmd.Execute();}//延迟执行的函数public void DelayCommand(Command cmd){this.cmdList.Add(cmd);//满足一定条件后 执行逻辑 单纯延时用定时器 协程 Time类等 cmd.Execute();}//移除最后一个命令public void UndoCommand(){ if(cmdList.Count>0){cmdList.RemoveAt(cmdList.Count - 1);//这里执行游戏回溯 比如位置回溯 金币回溯等等}}//清空命令表public void ClearComman(){cmdList.Clear();}
}
// 玩家的 实现命令类
public class CommandPart : MonoBehaviour {private CommandCtrl cCtrl;private void Awake() {cCtrl =new CommandCtrl();}private void Update() {if (Input.GetKeyDown(KeyCode.Q)) {cCtrl.DelayCommand(new JumpCommand());}if (Input.GetKeyDown(KeyCode.W)) {cCtrl.ExecuteCommand(new HitCommand());}if (Input.GetKeyDown(KeyCode.E)) {cCtrl.ExecuteCommand(new MoveCommand());}}
}
不足分析(AI)
命令模式的基础框架比较简单的,但是要完善起来还是需要具体问题具体分析
以下是这段代码存在的一些不足之处:
### 1. 延迟执行逻辑不完善 在 `CommandCtrl` 类的 `DelayCommand` 方法中,虽然有添加命令到列表以及执行命令的操作,但目前对于延迟执行的关键部分,只是简单提及可以用定时器、协程等方式,却没有真正合理地实现延迟逻辑。像这样直接执行命令,并没有达到真正延迟执行的效果,和普通执行命令没有实质性区别,无法满足实际需要延迟一段时间后再执行命令的场景需求
### 2. 撤销功能不完整 `CommandCtrl` 类里的 `UndoCommand` 方法目前只是从命令列表中移除了最后一个命令,但对于一些具有实际行为影响的命令(比如移动了角色位置、改变了游戏场景状态等),仅仅移除命令本身并没有回退这些因命令执行而产生的影响。例如角色执行了移动命令后,撤销操作不仅要移除该命令,还应该将角色位置恢复到移动前的状态,需要额外的逻辑来记录和还原执行命令前的相关状态信息,才能实现完整有效的撤销功能。
### 3. 代码复用性与灵活性受限 - **输入绑定方面**:在 `CommandPart` 类的 `Update` 方法中,按键与具体命令的关联是硬编码的方式(通过 `Input.GetKeyDown` 判断具体按键来执行对应的命令)。如果后续想要更改按键绑定,或者增加新的输入方式(如手柄操作等),就必须修改 `Update` 方法中的代码,代码复用性较差,不够灵活。 - **命令执行方面**:目前的代码结构对于命令的使用相对固定,如果想在其他地方复用这些命令执行相关逻辑(比如在不同的游戏角色控制或者不同的游戏模式下复用命令的执行、延迟、撤销等操作),不太方便,缺乏更灵活的配置和调用机制来适应多种场景需求
### 4. 缺乏错误处理机制 整个代码中基本没有对可能出现的异常情况进行处理。例如在执行命令过程中,如果某个具体命令类内部的逻辑出现错误(比如空引用、越界访问等情况),或者在操作命令列表(添加、移除等操作)时出现异常,程序可能会直接崩溃或者出现不符合预期的行为,影响程序的稳定性和健壮性
### 5. 命令类功能扩展性不足 以现有的具体命令类(如 `JumpCommand`、`MoveCommand`、`HitCommand` )来看,它们内部的 `Execute` 方法直接调用对应的私有方法来实现行为,这种方式在需要对命令的执行添加更多参数或者条件时扩展性不佳。比如想要实现不同力度的击打命令,或者不同高度、距离的跳跃命令等,较难在现有结构上方便地扩展功能,需要对代码结构做一定调整
### 6. 类职责不够清晰明确 `CommandCtrl` 类承担了较多功能,包括命令执行、延迟执行(虽然目前没完善好)、撤销以及清空命令列表等,随着功能进一步扩展和复杂,这个类可能会变得越来越臃肿,不利于代码的维护和阅读,各功能模块的职责没有更加细分和独立出来,不符合单一职责原则。 针对这些不足,可以通过进一步优化延迟执行逻辑、完善撤销功能的状态记录与还原、采用配置化的输入绑定方式、添加异常处理代码、改进命令类的设计以增强扩展性以及对 `CommandCtrl` 类进行职责细分等措施来提升代码的质量和功能完整性