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

相关推荐
拉里小猪的迷弟4 小时前
设计模式-行为型-常用-2:职责链模式、状态模式、迭代器模式
java·设计模式·迭代器模式·状态模式·责任链模式
桦说编程5 小时前
把 CompletableFuture 当做 monad 使用的潜在问题与改进
后端·设计模式·函数式编程
Chengweili12029 小时前
《设计模式》创建型模式总结
设计模式
ducking__9 小时前
设计模式练习(二) 简单工厂模式
c++·设计模式·简单工厂模式
捕鲸叉9 小时前
C++创建型设计模式综合示例
开发语言·c++·设计模式
G皮T9 小时前
【设计模式】行为型模式(三):责任链模式、状态模式
java·设计模式·状态模式·编程·责任链模式·state
捕鲸叉9 小时前
C++创建型设计模式体现出的面向对象设计原则
开发语言·c++·设计模式
不会叫的狼9 小时前
设计模式-策略模式
设计模式·策略模式
吾与谁归in10 小时前
【C#设计模式(7)——桥接模式(Bridge Pattern)】
设计模式·c#·桥接模式