C# x Unity 从玩家控制类去分析命令模式该如何使用

server/2024/11/17 5:12:57/

        本文部分内容出自游戏编程模式一书,游戏编程模式,有兴趣的小伙伴可以去看看,虽然不是unity x c#写的 但是思路挺好的

目录

目录

0.先说结论

发现问题

命令模式如何解耦

打个断点更利于分析

怎么实现延迟命令?

如何撤销命令?

脚本整体一览

不足分析(AI)


8e821a2e270840b585418281a366de48.png

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之后

0f0b58de208e4dac941dcff663eb4395.png 可以清楚的看到JumpCommand的实例被传进来 执行了重写后的Execute方法

7c8bc9d8568b4c9facb0dd02aa372f2b.png

当我添加了HitCommand之后,按下对应按键

b9b5847f4f6e42b794deb951f84a9c6d.png

扩展的话就只需要多写几个具体命令类并继承命令类就行了 

怎么实现延迟命令?

修改一下命令管理类 将命令存入一个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());}

如何撤销命令?

实现思路如图

bc8b9a9bfcc0405abf3cbd857fbcaf10.png

代码我就抛砖引玉了 

  //移除最后一个命令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` 类进行职责细分等措施来提升代码的质量和功能完整性


http://www.ppmy.cn/server/142566.html

相关文章

IDEA2024:右下角显示内存

使用场景&#xff1a; 实时知晓idea内存使用情况 解决方案: 开启内存显示 View -> Apperance -> Status Bar Widgets -> Memory Indicator 效果如下&#xff1a;

IntelliJ+SpringBoot项目实战(七)--在SpringBoot中整合Redis

Redis是项目开发中必不可少的缓存工具。所以在SpringBoot项目中必须整合Redis。下面是Redis整合的步骤&#xff1a; &#xff08;1&#xff09;因为目前使用openjweb-sys作为SpringBoot的启动应用&#xff0c;所以在openjweb-sys模块的application-dev.yml中增加配置参数&…

Transformer中的算子:其中Q,K,V就是算子

目录 Transformer中的算子 其中Q,K,V就是算子 一、数学中的算子 二、计算机科学中的算子 三、深度学习中的算子 四、称呼的由来 Transformer中的算子 其中Q,K,V就是算子 “算子”这一称呼源于其在数学、计算机科学以及深度学习等多个领域中的广泛应用和特定功能。以下是…

uniapp vuex的使用

实现组件全局&#xff08;数据&#xff09;管理的一种机制&#xff0c;可以方便的实现组件之间共享数据&#xff0c;不同于上述三种传递值的方式。 可以把vuex当成一个store仓库&#xff0c;可以集中管理共享的数据&#xff0c;并且存储在vuex中的数据都是响应式的&#xff0c…

git修改当前分支名称并推送到远程仓库

git修改当前分支名称并推送到远程仓库 在Git中修改当前分支的名称可以通过两种主要方式进行&#xff1a;直接在本地修改分支名称&#xff0c;或者如果你已经推送了分支到远程仓库&#xff0c;你也需要更新远程分支的名称。以下是详细步骤&#xff1a; 修改本地分支名称 查看当…

【SQL】mysql常用命令

为方便查询&#xff0c;特整理MySQL常用命令。 约定&#xff1a;$后为Shell环境命令&#xff0c;>后为MySQL命令。 1 常用命令 第一步&#xff0c;连接数据库。 $ mysql -u root -p # 进入MySQL bin目录后执行&#xff0c;回车后输入密码连接。# 常用参数&…

Spring Boot编程训练系统:性能优化实践

摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了编程训练系统的开发全过程。通过分析编程训练系统管理的不足&#xff0c;创建了一个计算机管理编程训练系统的方案。文章介绍了编程训练系统的系统分析部分&…

通过物流分拣系统来理解RabbitMQ的消息机制

RabbitMQ作为一个消息中间件&#xff0c;通过队列和路由机制&#xff0c;帮助应用程序高效传递消息。而它的消息流转过程&#xff0c;其实可以用物流分拣系统来直观理解。 在一个典型的物流分拣系统中&#xff0c;包裹会经过多个节点&#xff08;比如分拣中心、配送站&#xf…