初学者,自用笔记
把“要执行的动作/请求”封装成一个对象(Command),从而让你能把请求的发起者(Invoker)和实际执行者(Receiver)解耦,并且支持:排队、延迟执行、撤销/重做、录制回放、网络同步、宏命令等
- Command(命令接口/抽象类):定义统一执行入口(如 Execute(),可选 Undo())。
- ConcreteCommand(具体命令):持有执行者 Receiver 及参数,实现具体执行/撤销逻辑。
- Receiver(接收者):真正干活的对象(角色控制器、背包系统、战斗系统等)。
- Invoker(调用者/触发者):保存命令并在合适时机触发(输入系统、UI按钮、回放系统、网络消息处理等)。
- (可选)CommandQueue/History:命令队列与历史栈,用于排队与 Undo/Redo。
基本命令模式
指令接口
public interface ICommand
{
// 执行命令
void Execute();
// 撤销命令(可选,用于需要撤销功能的场景)
void Undo();
}
接收者
public class CommandReceiver : MonoBehaviour
{
// 示例:移动接收者
public void Move(Vector3 direction, float distance)
{
transform.Translate(direction.normalized * distance);
Debug.Log($"移动: {direction} 距离: {distance}");
}
// 示例:旋转接收者
public void Rotate(Vector3 axis, float angle)
{
transform.Rotate(axis, angle);
Debug.Log($"旋转: 轴 {axis} 角度 {angle}");
}
// 示例:缩放接收者
public void Scale(Vector3 scaleFactor)
{
transform.localScale *= scaleFactor;
Debug.Log($"缩放: 因子 {scaleFactor}");
}
}
移动指令
public class MoveCommand : ICommand
{
private CommandReceiver receiver;
private Vector3 direction;
private float distance;
// 构造函数注入接收者和命令参数
public MoveCommand(CommandReceiver receiver, Vector3 direction, float distance)
{
this.receiver = receiver;
this.direction = direction;
this.distance = distance;
}
public void Execute()
{
receiver.Move(direction, distance);
}
public void Undo()
{
// 撤销移动:向相反方向移动相同距离
receiver.Move(-direction, distance);
}
}
命令管理
public class CommandManager : MonoBehaviour
{
// 存储已执行的命令(用于撤销)
private Stack<ICommand> commandHistory = new Stack<ICommand>();
// 存储已撤销的命令(用于重做)
private Stack<ICommand> undoneCommands = new Stack<ICommand>();
/// 执行命令并记录到历史
public void ExecuteCommand(ICommand command)
{
command.Execute();
commandHistory.Push(command);
// 执行新命令后,清空重做栈(避免混淆)
undoneCommands.Clear();
}
/// 撤销上一个命令
public void Undo()
{
if (commandHistory.Count == 0)
{
Debug.Log("没有可撤销的命令");
return;
}
ICommand command = commandHistory.Pop();
command.Undo();
undoneCommands.Push(command);
}
/// 重做上一个被撤销的命令
public void Redo()
{
if (undoneCommands.Count == 0)
{
Debug.Log("没有可重做的命令");
return;
}
ICommand command = undoneCommands.Pop();
command.Execute();
commandHistory.Push(command);
}
/// 清空命令历史(用于重置场景等)
public void ClearHistory()
{
commandHistory.Clear();
undoneCommands.Clear();
Debug.Log("命令历史已清空");
}
}
命令存储
public class CommandInvoker : MonoBehaviour
{
public CommandManager commandManager;
public CommandReceiver receiver;
[Header("控制参数")]
public float moveDistance = 1f;
public float rotateAngle = 90f;
public Vector3 scaleFactor = new Vector3(1.1f, 1.1f, 1.1f);
private void Update()
{
// 移动命令(WASD)
if (Input.GetKeyDown(KeyCode.W))
{
ICommand moveCommand = new MoveCommand(receiver, Vector3.forward, moveDistance);
commandManager.ExecuteCommand(moveCommand);
}
if (Input.GetKeyDown(KeyCode.S))
{
ICommand moveCommand = new MoveCommand(receiver, Vector3.back, moveDistance);
commandManager.ExecuteCommand(moveCommand);
}
if (Input.GetKeyDown(KeyCode.A))
{
ICommand moveCommand = new MoveCommand(receiver, Vector3.left, moveDistance);
commandManager.ExecuteCommand(moveCommand);
}
if (Input.GetKeyDown(KeyCode.D))
{
ICommand moveCommand = new MoveCommand(receiver, Vector3.right, moveDistance);
commandManager.ExecuteCommand(moveCommand);
}
}
命令模式实例(角色动作回放,记录状态)
指令接口
// 时间回溯命令接口:定义了"记录状态"和"执行回溯"的规范
// 所有需要支持时间回溯的命令(如移动、旋转等)都需实现此接口
public interface ITimeBackCommand
{
// 记录源对象的当前状态(用于后续回溯)
// source:需要记录状态的对象(如玩家、物体)
void Record(GameObject source);
// 在目标对象上执行回溯操作(恢复到记录时的状态)
// target:需要被回溯的对象
void Execute(GameObject target);
}
public void Record(GameObject source)
{
// ActionCommand在创建时就已经确定了动作类型,所以Record方法是空的
}
}
public class MoveCommand : ITimeBackCommand
{
// 记录的状态数据:速度、角速度、时间间隔
private Vector3 _velocity; // 线性速度
private Vector3 _angularVelocity; // 角速度
private float _deltaTime; // 记录状态时的物理帧时间间隔
public StateType curType;
private Quaternion _rotation;
// 执行回溯:将目标对象的刚体状态恢复到记录时的状态
public void Execute(GameObject target)
{
// 获取目标对象的刚体组件
Rigidbody rb = target.GetComponent<Rigidbody>();
if (rb != null)
{
// 恢复记录的速度和角速度
rb.velocity = _velocity;
rb.angularVelocity = _angularVelocity;
}
target.transform.rotation = _rotation;
CloneC clone;
if(clone=target.GetComponent<CloneC>())//切换动画状态
{
if(clone.curType!=curType)
{
clone.animator.SetBool(clone.curType.ToString(), false);
if (curType.ToString() == "Find"|| curType.ToString() == "Die")
{
clone.GetComponent<CloneC>().animator.SetBool("Idle", true);
}
else
{
clone.animator.SetBool(curType.ToString(), true);
}
}
clone.curType=curType;
}
}
// 记录状态:保存源对象刚体的当前运动状态
public void Record(GameObject source)
{
Rigidbody rb = source.GetComponent<Rigidbody>();
if (rb != null)
{
// 记录当前的线性速度、角速度和物理帧时间
_velocity = rb.velocity;
_angularVelocity = rb.angularVelocity;
_deltaTime = Time.fixedDeltaTime; // 物理更新的时间间隔(用于精确回溯)
}
_rotation = source.transform.rotation;
if (source.GetComponent<Player>())
{
curType = source.GetComponent<Player>().PlayerFSM.curType;
}
}
}
执行逻辑
public class TimeBackManager : TheManager<TimeBackManager>
{
public class TimeBackManager : TheManager<TimeBackManager>
{
public GameObject player; // 玩家对象(要记录状态的主体)
public GameObject clon; // 克隆体对象(回溯时执行状态的目标)
public float recordTime = 5f; // 最大记录时长(最多记录5秒内的玩家状态)
public float invincibilityDuration = 2f; // 无敌时间
private List<ITimeBackCommand> commands = new List<ITimeBackCommand>(); // 存储所有记录的状态命令(如移动状态)
private float recordTimer = 0f; // 记录时长计时器
private bool isRecording = false; // 是否正在记录状态的标记
private Vector3 playerPosition; // 记录开始时玩家的初始位置
private Quaternion playerRotation; // 记录开始时玩家的初始旋转
public bool isDie;
private void Start()
{
EventCenter.Instance.AddEventList<bool>("Die",SetDieBool);
}
private void Update()
{
if (isDie) return;
// 按Tab键:切换记录状态(未记录则开始,正在记录则停止)
if (Input.GetKeyDown(KeyCode.Tab)&&!clon.activeSelf)
{
if (!isRecording)
{
StartRecording(); // 开始记录玩家状态
}
else
{
StopRecording(); // 停止记录玩家状态
}
}
// 按E键:触发时间回溯(需满足:未在记录、已有记录的状态)
if (Input.GetKeyDown(KeyCode.E) && !isRecording && commands.Count > 0)
{
if (!clon.activeSelf)
{
StartTimeBack(); // 启动回溯逻辑
MusicManager.Instance.PlaySFX("e",1);
}
}
}
// FixedUpdate:物理帧更新,记录玩家每帧的运动状态(确保物理逻辑同步)
private void FixedUpdate()
{
if (isRecording) // 仅在记录状态时执行
{
if (isDie)
{
commands.Clear();
isRecording = false;
return;
}
RecordFrame(); // 记录当前帧的玩家状态
recordTimer += Time.fixedDeltaTime; // 累加记录时间
// 记录时长达到上限,自动停止记录
if (recordTimer >= recordTime)
{
StopRecording();
}
}
}
// 开始记录:初始化记录参数,保存玩家初始位置和旋转
public void StartRecording()
{
if (player == null) return; // 玩家为空则不执行
isRecording = true; // 标记为正在记录
commands.Clear(); // 清空之前的记录(避免残留旧状态)
recordTimer = 0f; // 重置记录计时器
MusicManager.Instance.PlaySFX("Music/ping");
EventCenter.Instance.TriggerEvent<bool>("SetTimeBackUI",true);
EventCenter.Instance.TriggerEvent("Setaura_flat",player.transform.position);
// 保存记录开始时玩家的位置和旋转(用于回溯后克隆体的初始位置)
playerPosition = player.transform.position;
playerRotation = player.transform.rotation;
}
// 停止记录:结束记录状态,玩家回到记录开始时的位置和旋转
public void StopRecording()
{
isRecording = false; // 标记为停止记录
// 使用DOTween实现平滑的“高速倒带”效果
player.GetComponent<Collider>().enabled = false;
EventCenter.Instance.TriggerEvent<Player>("DeletePlayer",player.GetComponent<Player>());
var playerComponent = player.GetComponent<Player>();
var playerCollider = player.GetComponent<Collider>();
var collidersList = playerComponent.colliders;
for (int i = 0; i < collidersList.Count; i++)
{
var a = collidersList[i];
var parent = a.transform.parent;
if (parent != null)
{
for (int j = 0; j < parent.childCount; j++)
{
var child = parent.GetChild(j);
var ppInteract = child.GetComponent<PressurePlateInteract>();
if (ppInteract != null)
{
ppInteract.colliders.Remove(playerCollider);
playerComponent.colliders.Remove(a);
if (ppInteract.colliders.Count == 0)
{
ppInteract.isTrigger = false;
ppInteract.AniSetBool(false);
}
break; // 找到后就退出循环
}
}
}
}
player.transform.DOMove(playerPosition, 0.3f).SetEase(Ease.InOutQuad);//回归动画
player.transform.DORotateQuaternion(playerRotation, 0.3f)
.OnComplete(() => player.GetComponent<Collider>().enabled = true); // 动画结束后恢复碰撞体
MusicManager.Instance.PlaySFX("Music/tap回归");
EventCenter.Instance.TriggerEvent<bool>("SetTimeBackUI", false);
EventCenter.Instance.TriggerEvent("Clearaura_flat");
}
// 记录当前帧状态:创建移动命令,记录玩家当前的刚体运动状态(速度等)
private void RecordFrame()
{
MoveCommand cmd = new MoveCommand(); // 实例化移动命令(记录刚体状态)
cmd.Record(player); // 记录玩家当前的运动状态(速度、角速度)
commands.Add(cmd); // 将命令添加到列表,保存帧状态
}
public void RecordAction(ActionCommand cmd)
{
if (isRecording)
{
commands.Add(cmd);
}
}
public void StartTimeBack()
{
if (clon == null || player == null) return;
clon.GetComponent<CloneC>().onCloneCheck(); // 克隆体激活相关逻辑(自定义脚本方法)
clon.transform.position = playerPosition; // 克隆体移动到记录开始时的玩家位置
clon.transform.rotation = playerRotation; // 克隆体旋转到记录开始时的玩家旋转
// 重置克隆体刚体状态(避免初始速度影响回溯效果)
Rigidbody rb = clon.GetComponent<Rigidbody>();
rb.velocity = Vector3.zero; // 线性速度归零
rb.angularVelocity = Vector3.zero; // 角速度归零
StartCoroutine(TimeBack()); // 启动协程,执行逐帧回溯
}
// 回溯协程:逐帧执行记录的状态命令,让克隆体复现玩家的运动轨迹
private IEnumerator TimeBack()
{
// 遍历所有记录的帧命令,逐帧执行
foreach (var cmd in commands)
{
cmd.Execute(clon);
if (cmd is MoveCommand)
{
yield return new WaitForFixedUpdate();
}
}
// 回溯结束后,重置克隆体刚体状态(停止运动)
Rigidbody rb = clon.GetComponent<Rigidbody>();
rb.velocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
clon.GetComponent<CloneC>().animator.SetBool(clon.GetComponent<CloneC>().curType.ToString(), false);
clon.GetComponent<CloneC>().animator.SetBool("Idle",true);
//commands.Clear();
yield return new WaitForSeconds(1f); // 等待1秒后,关闭克隆体相关状态
clon.GetComponent<CloneC>().outCloneCheck(); // 克隆体取消激活相关逻辑(自定义脚本方法)
}
public void SetDieBool(bool _bool)
{
isDie = _bool;
}
}
}

京公网安备 11010502036488号