最近读了《游戏编程模式》,受到很多启发。虽然之前也读过设计模式的书,但是基本看过就忘了,实际开发中也很难和它们建立联系。这本书从实际应用出发讲解了游戏开发中常用的设计模式,很适合有一定开发经验但是不太会用设计模式的开发者。
为了学以致用,我在Unity中将几种基本的设计模式实现出来了,下面从第一个设计模式命令模式开始。
1. 定义
书中的定义是:命令就是面向对象化的回调。
好像还是有些抽象。简单来说命令模式就是将对象行为的状态和行为进行封装后按照一定的规则进行处理的模式。
命令模式最常见的应用场景就是回放系统——将所需回放的对象的回放过程的状态和行为按时间序存储下来,再根据规则重新对该对象按时间序操作一次就可以实现回放。
2. 原理
通常我们对单位进行操作的时候,会直接将操作方法在Update中作用于对象上立即生效。在命令模式中,我们要做的是封装一个命令对象,让该命令对象可以调用待操作对象,这样,我们任何时候调用该命令对象就可以实现对待操作对象的操作。
为了讲解,我们首先定义一个很简单的操作:
using UnityEngine;
using System.Collections;
public class commandControl : MonoBehaviour {
Transform _transform;
void Start () {
_transform = transform;
}
public void move(Vector3 T) {
_transform.Translate(T);
}
}
这个很简单的类,它的作用就是将他挂到对象上,然后调用move方法,就可以让对象按照传入的参数进行移动。下面我们看看我们如何使用命令模式来操作他。
先上代码:
class command {
protected float _time;
public float time
{
get { return _time; }
}
public virtual void execute(commandControl avator) { }
public virtual void undo(commandControl avator) { }
}
这就是一个命令基类,其中time很明显是时间序列标识,它用来在进行回放等操作的时候进行时间进度的判断,以还原当时的实际效果,当然你也可以用其他的标识。
execute是执行操作的方法,只要将带操作的对象avator作为参数传递给它它就会对该对象执行对应的操作,具体操作缺省,在实际的命令子类中实现。
undo和execute类似,不过它是execute的逆向操作,用于实现时光倒流等效果的方法。
下面看看具体的命令子类,这里根据物体的move方法定义一个很简单的move命令类:
class commandMove:command{
Vector3 _trans;
public commandMove(Vector3 m,float t) {
_trans = m;
_time = t;
}
public override void execute(commandControl avator)
{
avator.move(_trans);
}
public override void undo(commandControl avator)
{
Debug.Log("undo:"+ _time);
avator.move(-_trans);
}
}
可以看到,在继承command类后,我们定义了一个Vector3的状态类,它用来保存该命令执行的操作。我们从构造函数中初始化该操作和操作时间序。
我们重写了execute和undo,分别对其添加了对命令对象的实际操作:在execute中,我们执行了move操作,在undo中,我们将_trans乘以-1后执行相反的移动操作来实现时光倒流效果。
3. 调用
直接上代码:
using UnityEngine;
using System.Collections.Generic;
public class commandManager : MonoBehaviour {
//待操作对象
public commandControl avator;
//操作序列
Stack<command> commandStack;
float callBackTime;
bool isRun = true;
// Use this for initialization
void Start () {
commandStack = new Stack<command>();
callBackTime = 0;
}
// control进行运行控制,runCallBack执行回放操作
void Update () {
if (isRun)
control();
else
runCallBack();
}
//控制对象运行,添加命令到命令序列
void control() {
callBackTime += Time.deltaTime;
command cmd = InputHandler();
if (cmd != null)
{
commandStack.Push(cmd);
cmd.execute(avator);
}
}
//回放操作
void runCallBack() {
callBackTime -= Time.deltaTime;
if (callBackTime < commandStack.Peek().time)
{
commandStack.Pop().undo(avator);
}
}
//根据输入获取操作命令
command InputHandler() {
if (Input.GetKey(KeyCode.W))
return new commandMove(new Vector3(0,1,0), callBackTime);
if (Input.GetKey(KeyCode.S))
return new commandMove(new Vector3(0, -1, 0), callBackTime);
if (Input.GetKey(KeyCode.A))
return new commandMove(new Vector3(-1, 0, 0), callBackTime);
if (Input.GetKey(KeyCode.D))
return new commandMove(new Vector3(1, 0, 0), callBackTime);
return null;
}
//切换到回放模式
public void callBack() {
isRun = false;
}
//切换到运行模式
public void run() {
isRun = true;
}
}
转载请注明出处:http://blog.csdn.net/ylbs110/article/details/53706003
代码很简单,就是使用InputHandler获取命令后将其存储在命令序列commandStack中,回放的时候,根据时间序列依次从commandStack顶部取出对象来进行回放。这里不一定用堆,队列,链表都可以,根据你的需求来选择。
使用的时候将它挂到一个对象上,然后将待操作对象(挂载了commandControl 脚本)添加给它,就可以执行了。调用callBack切换到回放模式,调用run切换到控制模式。