命令模式在游戏中的应用

设计模式:命令模式(Command)

命令模式在游戏中的应用

最近在做的一个游戏需要撤销重做功能,而这正是命令模式使用的场合,于是尝试使用命令模式实现这个功能

并且该游戏的操作可能会引发一系列结果,到时候撤销需要将一系列结果撤销=。=

这里就不讲命令模式是什么了直接上链接命令模式 · Design Patterns Revisited · 游戏设计模式 (tkchu.me)

之前的实现

之前忘记了命令模式这个设计模式,实现了一个丑陋的东西

之前对于对应的操作,调用操作并记录该操作,在操作前后记录该物体的状态,然后撤销时调用该操作对应的撤销操作,十分不方便且会记录一些不需要的状态,并且每个操作都需对应的撤销操作写在case中

并且一个操作导致一系列的后续结果时,此时撤销将十分麻烦

命令模式实现

对于操作,实现一个抽象Command类,里面有执行撤销两个方法,还有两个命令类的字段,记录前后指令,last指令指向什么指令导致该指令执行,next用于记录该一个命令引发了什么指令发生,可能某个指令会导致一系列指令发生,于是使用List记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public abstract class Command
{
public abstract void Execute();
public abstract void Undo();

protected Command last;
protected List<Command> next;
public Command Last { get => last; }
public List<Command> Next
{
set
{
next = value;
}
get
{
if (next == null)
next = new List<Command>();
return next;
}
}
}

对于具体的command,继承该类并实现执行、撤销方法

现在的结构是:

PlayerCommand实现了player的command父类,其下一堆子类实现具体command

以玩家移动为例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public abstract class PlayerCommand : Command
{
protected Player player;
public PlayerCommand(Player player)
{
this.player = player;
}
}
public class PlayerMove : PlayerCommand
{
Vector2 dir;
public PlayerMove(Player player,Vector2 dir):base(player) {
this.dir = dir;
}

public override void Execute()
{
player.Move(dir);
}

public override void Undo()
{
player.Move(-dir);
}
}

对于指令的执行实际调用对应的操作,并将其添加到RedoManager已执行操作中,对指令的撤销重做执行交给redomanager管理执行,到此重做撤销就方便的实现啦

1
2
3
4
5
6
private void Move(Vector2 dir)
{
Command cmd = new PlayerMove(player, dir);
cmd.Execute();
RedoManager.Instance.AddCommand(cmd);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class RedoManager : MonoBehaviour
{
private RedoCommandList redo;

private static RedoManager instance;
public static RedoManager Instance { get { return instance; } }
private void Awake()
{
instance = this;
redo = new RedoCommandList();
}

public void AddCommand(Command command)
{
redo.AppendRedoCommand(command, true);
}

/// <summary>
/// 重做
/// </summary>
public void Redo()
{
Command command = redo.GetUndoCommand(out bool flag);
if (!flag) return;

command.Execute();

redo.RedoHelp(command);
}

/// <summary>
/// 撤销
/// </summary>
public void Undo()
{
Command command = redo.GetRedoCommand(out bool flag);
if (!flag) return;

undo(command);

redo.UndoHelp(command);
}

private void undo(Command cmd)
{
foreach (Command command in cmd.Next)
{
undo(command);
}
cmd.Undo();
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
using MyInputSystem;
using System;
using System.Collections.Generic;

public class RedoCommandList
{
/// <summary>
/// 可重做队列,即撤销后放入其中
/// </summary>
private List<Command> undoList;
/// <summary>
/// 可撤销队列,即已经做了的放入其中
/// </summary>
private List<Command> redoList;

public RedoCommandList()
{
undoList = new List<Command>();
redoList = new List<Command>();
}

/// <summary>
/// 已经执行事件放入其中
/// </summary>
/// <param name="command"></param>
/// <param name="flag">true 新执行事件,而非重做事件</param>
public virtual void AppendRedoCommand(Command command, bool flag)
{
redoList.Add(command);
if (flag)
{
undoList.Clear();
}
}
/// <summary>
/// 撤销的事件放入其中
/// </summary>
/// <param name="command"></param>
public void AppendUndoCommand(Command command)
{
undoList.Add(command);
}
/// <summary>
/// 获得已撤销的事件,并移除
/// </summary>
/// <param name="flag"></param>
/// <returns></returns>
public Command GetUndoCommand(out bool flag)
{
Command t = null;
if (undoList.Count != 0)
{
flag = true;
t = undoList[undoList.Count - 1];
undoList.RemoveAt(undoList.Count - 1);
}
else
flag = false;
return t;
}
/// <summary>
/// 获得已经做了的事件,并移除
/// </summary>
/// <param name="flag"></param>
/// <returns></returns>
public Command GetRedoCommand(out bool flag)
{
Command t = null;
if (redoList.Count != 0)
{
flag = true;
t = redoList[redoList.Count - 1];
redoList.RemoveAt(redoList.Count - 1);
}
else
flag = false;
return t;
}

internal void RedoHelp(Command command)
{
AppendRedoCommand(command, false);
}

internal void UndoHelp(Command command)
{
AppendUndoCommand(command);
}
}