Unity命令模式

在游戏开发中,输入处理往往是最早实现、但最容易被低估的部分。

一开始我们只关心"按下按键角色动了没有",但随着需求增加,问题会逐渐暴露出来:

回放、录像、AI 行为复现、技能连招记录......

如果直接在输入代码里写逻辑,这些需求几乎无法实现。

本文将通过一个 Unity 示例,演示如何使用指令模式将"输入行为"抽象为可记录、可回放的指令对象,从而构建一个简单但可扩展的输入回放系统。

1.定义指令接口

cs 复制代码
public interface ICommand
{
    void Execute();
}

2.定义具体指令

攻击指令

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AttackCommand : ICommand
{
    private Player player;

    public AttackCommand(Player player)
    {
        this.player = player;
    }

    public void Execute()
    {
        player.Attack();
    }
}

移动指令(这里记录的是位置直接瞬移的)

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MoveCommand : ICommand
{
    private Player player;
    private Vector3 direction;

    public MoveCommand(Player player, Vector3 direction)
    {
        this.player = player;
        this.direction = direction;
    }

    public void Execute()
    {
        player.Move(direction);
    }
}

3.创建指令记录器

这里主要是三个方法开始记录,添加记录,获取所有的记录。这里我们只能存一个指令集合,如果需要保存多条可以考虑用字典加集合来实现。

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class CommandRecord
{
    public float time;        // 发生时间
    public ICommand command;  // 执行的命令
}
//指令记录器
public class CommandRecorder
{
    public List<CommandRecord> records = new List<CommandRecord>();
    private float startTime;
    //开始记录
    public void StartRecord()
    {
        records.Clear();
        startTime = Time.time;
    }
    //添加指令
    public void Record(ICommand command)
    {
        records.Add(new CommandRecord
        {
            time = Time.time - startTime,
            command = command
        });
    }
    //获取记录的指令集
    public List<CommandRecord> GetRecords()
    {
        return records;
    }
}

4.创建回放指令播放器

主要提供一个携程来实现依次读取指令的逻辑。

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//回放指令播放器
public class ReplayPlayer  
{
    public IEnumerator Play(List<CommandRecord> records)
    {
        Debug.Log("开始回放");
        float lastTime = 0f;

        foreach (var record in records)
        {
            //record.time表示第一条指令发生的时间,后续的等待时间就是上一条指令时间和下一条指令的差值
            yield return new WaitForSeconds(record.time - lastTime);
            record.command.Execute();
            lastTime = record.time;
        }
        Debug.Log("回放结束");
    }
}

5.指令输入

玩家脚本负责处理具体的指令逻辑。

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{
    public void Attack()
    {
        Debug.Log("玩家攻击");
    }
    public void Move(Vector3 direction)
    {
        gameObject.transform.position = direction;
        Debug.Log("玩家移动"+ direction);
    }
}

输入处理器负责创建指令。

cs 复制代码
using JetBrains.Annotations;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class InputHandler : MonoBehaviour
{
    public Player player;
    //回放记录仪
    CommandRecorder recorder;

    void Start()
    {
        recorder = new CommandRecorder();
        recorder.StartRecord();
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.J))
        {
            var cmd = new AttackCommand(player);
            cmd.Execute();
            recorder.Record(cmd);
        }

        if (Input.GetKeyDown(KeyCode.W))
        {
            Debug.Log("位置记录");
            var cmd = new MoveCommand(player,player.transform.position);
            cmd.Execute();
            recorder.Record(cmd);
        }
        if (Input.GetKeyDown(KeyCode.S))
        {
            ReplayPlayer myPlayer = new ReplayPlayer();
            StartCoroutine(myPlayer.Play(recorder.GetRecords()));
        }
    }
}

6.运行结果

将玩家和输入采集脚本挂载到场景中的一个方块上,并拖动方块的位置,每拖动一次点击游戏面板按下W键记录一次,多记录几次后按下S键就可以看到方块沿着我们之间记录位置的顺序移动了,并且控制台打印出了对应的结果。

7.总结

通过这个示例可以看到,指令模式的价值并不只是"把操作包成一个类",

而是让行为本身成为一种可以被保存、传递和重放的数据

当输入被抽象为指令后,我们可以非常自然地实现回放、撤销、宏操作,甚至让 AI 直接复用玩家的行为逻辑。

在实际项目中,指令模式常常与输入系统、状态机、网络同步等模块结合使用,是构建复杂交互系统的重要基础。

如果你发现自己的输入逻辑越来越难维护,那么也许问题不在功能实现上,而在于行为没有被抽象出来

相关推荐
心疼你的一切9 小时前
Unity异步编程神器:Unitask库深度解析(功能+实战案例+API全指南)
深度学习·unity·c#·游戏引擎·unitask
呆呆敲代码的小Y11 小时前
【Unity 实用工具篇】 | Book Page Curl 快速实现翻书效果
游戏·unity·游戏引擎·u3d·免费游戏·翻书插件
不绝1911 天前
UGUI相关——基础篇
命令模式
AC梦1 天前
unity中如何将UI上的字高清显示
ui·unity
小贺儿开发1 天前
Unity3D 智慧城市管理平台
数据库·人工智能·unity·智慧城市·数据可视化
June bug2 天前
【领域知识】休闲游戏一次发版全流程:Google Play + Apple App Store
unity
星夜泊客2 天前
C# 基础:为什么类可以在静态方法中创建自己的实例?
开发语言·经验分享·笔记·unity·c#·游戏引擎
dzj20212 天前
PointerEnter、PointerExit、PointerDown、PointerUp——鼠标点击物体,则开始旋转,鼠标离开或者松开物体,则停止旋转
unity·pointerdown·pointerup
心前阳光2 天前
Unity 模拟父子关系
android·unity·游戏引擎
在路上看风景2 天前
26. Mipmap
unity