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

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

目录

目录

0.先说结论

发现问题

命令模式如何解耦

打个断点更利于分析

怎么实现延迟命令?

如何撤销命令?

脚本整体一览

不足分析(AI)


0.先说结论

命令模式的好处:

解耦 扩展 延迟 撤销

命令模式是将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化; 对请求排队或记录请求日志,以及支持可撤销的操作

说白了,所谓命令模式就是将操作脚本分为一大堆单个命令 然后用一个类统一管理,所以 命令是具现化的方法调用

而其一般包含如下几个部分也可以有其他的部分,我只取其中一种

  • 命令接口:定义命令的执行方法
  • 具体命令类:实现命令接口,封装具体操作
  • 命令管理类: 管理所有的具体命令类
  • 实现命令类:实现上述所有的行为

发现问题

首先 不使用命令模式来实现角色的几个行为代码如下,这是非常非常常用的方法

cs 复制代码
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 个类说不定会扩展到一万多行,当我需要修改其中一个动作的时候

就需要在有一万多行代码的类中找一个函数中的几行关键代码,感觉很不好

命令模式如何解耦

为了演示方便 我都将其写到一个脚本中了,通常来说是写到很多个不同脚本之中的,请注意到这一点!!!

我需要一个命令基类

cs 复制代码
// 命令基类 提供一个可重写的执行方法
public abstract class Command {
    public abstract void Execute();
}

我给将三个行为分成三个具体命令类,并继承基类,对其方法进行封装

cs 复制代码
// 角色行为的几个方法 需要去继承
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("击打");
    }
}

之后 写一个管理脚本,传入需要执行的参数

cs 复制代码
// 命令管理器类
public class CommandCtrl{
    private Command command;
    public void ExecuteCommand(Command cmd) {
        command = cmd;
        cmd.Execute();
}   
}

最后因为里氏替换原则,通过命令管理类去实现命令

cs 复制代码
// 玩家的 实现命令类
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博客

cs 复制代码
// 命令管理器类
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();
    }
}
cs 复制代码
        if (Input.GetKeyDown(KeyCode.Q)) {
            cCtrl.DelayCommand(new JumpCommand());
        }

如何撤销命令?

实现思路如图

代码我就抛砖引玉了

cs 复制代码
  //移除最后一个命令
  public void UndoCommand(){       
      if(cmdList.Count>0){
          cmdList.RemoveAt(cmdList.Count - 1);
          //这里执行游戏回溯 比如位置回溯 金币回溯等等
      }
  }

脚本整体一览

cs 复制代码
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` 类进行职责细分等措施来提升代码的质量和功能完整性

相关推荐
会员果汁25 分钟前
23.设计模式-解释器模式
设计模式·解释器模式
「QT(C++)开发工程师」8 小时前
C++设计模式
开发语言·c++·设计模式
茶本无香8 小时前
设计模式之七—装饰模式(Decorator Pattern)
java·设计模式·装饰器模式
漂洋过海的鱼儿20 小时前
设计模式——EIT构型(三)
java·网络·设计模式
老蒋每日coding1 天前
AI Agent 设计模式系列(十八)—— 安全模式
人工智能·安全·设计模式
老蒋每日coding1 天前
AI Agent 设计模式系列(十六)—— 资源感知优化设计模式
人工智能·设计模式·langchain
老蒋每日coding1 天前
AI Agent 设计模式系列(十七)—— 推理设计模式
人工智能·设计模式
冷崖1 天前
桥模式-结构型
c++·设计模式
连山齐名1 天前
设计模式之一——堵塞队列
设计模式
会员果汁1 天前
19.设计模式-命令模式
设计模式·命令模式