学习游戏开发设计模式之一命令模式
开始学习命令模式。
1.命令模式的定义是将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化; 对请求排队或记录请求日志,以及支持可撤销的操作。
在游戏开发中我个人的理解是将请求(如:按下跳跃键)封装为一个对象(一个类),从而使得可以传递不同的请求来触发不同的反应,以便解耦和修改。
下面我举个例子
using UnityEngine;
public class CommandMode : MonoBehaviour
{
private void Update()
{
if (Input.GetKeyDown(KeyCode.W))
{
Debug.Log("向前走");
}
if (Input.GetKeyDown(KeyCode.A))
{
Debug.Log("向左走");
}
if (Input.GetKeyDown(KeyCode.Mouse0))
{
Debug.Log("开枪");
}
}
}
这个代码表示按下对应的键Debug对应的行为,但是他的缺点在于被写死了,在很多的游戏中我们会允许玩家自定义绑定对应的按键,所以我们可以把他等价于
using UnityEngine;
public interface ICommand
{
void Excute();
}
public class MoveForWardCommand : ICommand
{
public void Excute()
{
Debug.Log("向前走");
}
}
public class MoveLeftCommand : ICommand
{
public void Excute()
{
Debug.Log("向左走");
}
}
public class ShootCommand : ICommand
{
public void Excute()
{
Debug.Log("开枪");
}
}
using UnityEngine;
public class CommandMode : MonoBehaviour
{
ICommand moveForwardCommand;
ICommand moveLeftCommand;
ICommand shootCommand;
private void Start()
{
moveForwardCommand = new MoveForWardCommand();
moveLeftCommand = new MoveLeftCommand();
shootCommand = new ShootCommand();
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.W))
{
moveForwardCommand.Excute();
}
if (Input.GetKeyDown(KeyCode.A))
{
moveLeftCommand.Excute();
}
if (Input.GetKeyDown(KeyCode.Mouse0))
{
shootCommand.Excute();
}
}
}
我们创建一个接口定义了一个Excute方法,然后将我们想要的三个行为继承自接口,最后在主函数中初始化并调用,与刚开始相比虽然代码量多了很多,但是耦合性也降低了,三个请求都执行的是同一个接口中的方法。但是这里存在一个问题,我们是用debug模拟的玩家操作,那么在实际开发中我们需要让moveForwardCommand找到玩家并让他向前移动,这样很麻烦所以可以给他们添加一个玩家类,然后玩家类里面写好了玩家该执行的方法,并且因为三个请求都执行的是同一个接口中的方法,所以我们可以使用一个方法来判断是否按下了键,如果没有按下返回null,按下了则返回对应的请求,然后执行请求的Excute方法,代码如下
using UnityEngine;
public class CommandMode : MonoBehaviour
{
ICommand moveForwardCommand;
ICommand moveLeftCommand;
ICommand shootCommand;
private void Start()
{
moveForwardCommand = new MoveForWardCommand();
moveLeftCommand = new MoveLeftCommand();
shootCommand = new ShootCommand();
}
ICommand InputHandler()
{
if (Input.GetKeyDown(KeyCode.W))
{
return moveForwardCommand;
}
if (Input.GetKeyDown(KeyCode.A))
{
return moveLeftCommand;
}
if (Input.GetKeyDown(KeyCode.Mouse0))
{
return shootCommand;
}
return null;
}
private void Update()
{
var command = InputHandler();
if (command != null)
{
command.Excute(this);
}
}
public void MoveForward()
{
Debug.Log("向前走");
}
public void MoveLeft()
{
Debug.Log("向左走");
}
public void Shoot()
{
Debug.Log("开枪");
}
}
using UnityEngine;
public interface ICommand
{
void Excute(CommandMode mode);
}
public class MoveForWardCommand : ICommand
{
public void Excute(CommandMode mode)
{
mode.MoveForward();
}
}
public class MoveLeftCommand : ICommand
{
public void Excute(CommandMode mode)
{
mode.MoveLeft();
}
}
public class ShootCommand : ICommand
{
public void Excute(CommandMode mode)
{
mode.Shoot();
}
}
那么除了解耦的话这样的好处是什么呢,我们可以让玩家控制游戏中的任何角色(切换角色),只需要传入不同的命令,还有AI对象也只需要传入不同的命令就能执行对应的行为,比如
float timer = 0;
ICommand InputHandler()
{
//if (Input.GetKeyDown(KeyCode.W))
//{
// return moveForwardCommand;
//}
//if (Input.GetKeyDown(KeyCode.A))
//{
// return moveLeftCommand;
//}
//if (Input.GetKeyDown(KeyCode.Mouse0))
//{
// return shootCommand;
//}
//return null;
timer += Time.deltaTime;
if (timer>1f)
{
int n = Random.Range(0, 5);
if (n == 0)
{
return moveForwardCommand;
}
if (n == 1)
{
return moveLeftCommand;
}
if (n > 2)
{
return shootCommand;
}
timer = 0;
}
return null;
}
我只需要简单的修改请求条件即可让这个角色变得攻击更迅猛或者移动更快等,在改变敌人ai或者为玩家添加ai时很好用(比如玩家多久不动则切换自动战斗)
最后一个好处就是使用命令模式可以很好的实现撤销功能,比如火纹等策略游戏中的撤销功能,因为我们将玩家所有举动都封装好了,下面来试试,我们需要给ICommond新增一个方法Undo()来回滚Excute()函数,那么只需要在角色类中写好对应的方法然后在Undo(Commoand mode)中调用即可,完整代码如下
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CommandMode : MonoBehaviour
{
Queue<ICommand> revocations = new Queue<ICommand>();
ICommand moveForwardCommand;
ICommand moveLeftCommand;
ICommand shootCommand;
private void Start()
{
moveForwardCommand = new MoveForWardCommand();
moveLeftCommand = new MoveLeftCommand();
shootCommand = new ShootCommand();
}
float timer = 0;
ICommand InputHandler()
{
if (Input.GetKeyDown(KeyCode.W))
{
return moveForwardCommand;
}
if (Input.GetKeyDown(KeyCode.A))
{
return moveLeftCommand;
}
if (Input.GetKeyDown(KeyCode.Mouse0))
{
return shootCommand;
}
return null;
}
private void Update()
{
var command = InputHandler();
if (command != null)
{
revocations.Enqueue(command);
command.Excute(this);
}
if (Input.GetKeyDown(KeyCode.Space))
{
if (revocations.Count == 0)
{
Debug.Log("没有上一步");
}
else
{
var pre = revocations.Dequeue();
pre.Undo(this);
}
}
}
public void MoveForward()
{
Debug.Log("向前走");
}
public void MoveLeft()
{
Debug.Log("向左走");
}
public void Shoot()
{
Debug.Log("开枪");
}
public void UnMoveForward()
{
Debug.Log("撤销向后走");
}
public void UnMoveLeft()
{
Debug.Log("撤销向左走");
}
public void UnShoot()
{
Debug.Log("撤销开枪");
}
}
using UnityEngine;
public interface ICommand
{
void Excute(CommandMode mode);
void Undo(CommandMode mode);
}
public class MoveForWardCommand : ICommand
{
public void Excute(CommandMode mode)
{
mode.MoveForward();
}
public void Undo(CommandMode mode)
{
mode.UnMoveForward();
}
}
public class MoveLeftCommand : ICommand
{
public void Excute(CommandMode mode)
{
mode.MoveLeft();
}
public void Undo(CommandMode mode)
{
mode.UnMoveLeft();
}
}
public class ShootCommand : ICommand
{
public void Excute(CommandMode mode)
{
mode.Shoot();
}
public void Undo(CommandMode mode)
{
mode.UnShoot();
}
}