一 · 概述
1.系游戏编程模式中命令模式一节笔记
2.在unity下实现该模式,其应具有撤回,重做,重放等功能
二 · 命令模式简述
(一):
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化; 对请求排队或记录请求日志,以及支持可撤销的操作[1]。
要点
-
将一组行为抽象为对象,这个对象和其他对象一样可以被存储和传递,从而实现行为请求者与行为实现者之间的松耦合,这就是命令模式。
-
命令模式是回调机制的面向对象版本。
-
命令模式的本质是对命令进行封装,将发出命令的责任和执行命令的责任分割开。
-
命令模式的优点有:对类间解耦、可扩展性强、易于命令的组合维护、易于与其他模式结合,而缺点是会导致类的膨胀。
-
命令模式有不少的细分种类,实际使用时应根据当前所需来找到合适的设计方式。
使用场合
-
命令模式很适合实现诸如撤消,重做,回放,时间倒流之类的功能。
-
基于命令模式实现录像与回放等功能,也就是执行并解析一系列经过预录制的序列化后的各玩家操作的有序命令集合。
引申与参考
-
最终我们可能会得到很多不同的命令类。为了更容易实现这些类,定义一个具体的基类,包含一些能定义行为的高层方法,往往会有帮助。可以将命令的主体execute()转到子类沙箱中。
-
对象可以响应命令,或者将命令交给它的从属对象。如果我们这样实现了,就完成了一个职责链模式。
-
对于等价的实例,可以用享元模式提高内存利用率。
ps:http://blog.youkuaiyun.com/poem_qianmo/article/details/53240330* 以上来自浅墨毛星云的分析,希望在编写实例中理解。*
(二):
命令是具现化的方法调用[1]。
在某些语言中的反射允许你在程序运行时命令式地和类型交互。 你可以获得类的 类型对象,可以与其交互看看这个类型能做什么。换言之,反射是具现化类型的 系统。[1] 命令模式是一种回调的面向对象实现[1]
①即在函数中传入对象,并调用对象中的方法(具现化的方法,方法调用被存储在对象中)。
②类似“回调”,“第一公民函数”,“函数指针”,“闭包”,“偏函数”,区别在于使用了对象中的方法。
三 · unity实现
简述:
使用一个可阶段移动和可变色的小球,来体现命令模式;
(1)添加小球及跟随移动的摄像头,简单测试功能
相关脚本
镜头跟随:
public Transform playertransform;
public Vector3 offest;
// Use this for initialization
void Start () {
//计算偏移量
offest = transform.position - playertransform.position;
}
// Update is called once per frame
void Update () {
//设置摄像坐标
transform.position = playertransform.position + offest;
}
(2)添加相关脚本以实现命令模式
①将发布命令和执行命令的责任分离;使用一个InputHandler类来配置用户输入后,游戏对象所执行的行为,起到一个发布命令的作用。也可由AI(AIRobt类)生成所需的命令。
小结:
通过在命令和角色间增加了一层重定向, 即可在选择命令的AI和展现命令的游戏角色间解耦;
即,职责分明,模块化编程,可读性高;
相关脚本
//InputHandler.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class InputHandle : MonoBehaviour {
public GameObject objToMove;
public List<MoveCommand> commands = new List<MoveCommand>();
private int currentCommandNum = 0;
// Use this for initialization
void Start () {
if (objToMove == null)
{
Debug.LogError("objectToMove must be assigned via inspector"); //即通过unity面板指定一个对象
this.enabled = false;
}
}
private void Move(MoveDir dir)
{
MoveCommand moveCommand = new MoveCommand(objToMove, dir);
moveCommand.Execute();
currentCommandNum++;
commands.Add(moveCommand);
}
public void Undo()
{
if (currentCommandNum > 0)
{
MoveCommand moveCommand = commands[currentCommandNum - 1];
moveCommand.UnExecute();
currentCommandNum--;
}
}
public void Redo() //即重复动作
{
if (currentCommandNum < commands.Count)
{
MoveCommand moveCommand = commands[currentCommandNum];
currentCommandNum++;
moveCommand.Execute();
}
}
public void RePlay() //重放
{
if (currentCommandNum < commands.Count)
{
MoveCommand moveCommand = commands[currentCommandNum];
currentCommandNum++;
moveCommand.Execute();
}
}
public void MoveUp() { Move(MoveDir.up); }
public void MoveDown() { Move(MoveDir.down); }
public void MoveLeft() { Move(MoveDir.left); }
public void MoveRight() { Move(MoveDir.right); }
// Update is called once per frame
void Update () {
//配置用户输入
if (Input.GetKeyDown(KeyCode.UpArrow))
{
MoveUp();
}
if (Input.GetKeyDown(KeyCode.DownArrow))
{
MoveDown();
}
if (Input.GetKeyDown(KeyCode.RightArrow))
{
MoveRight();
}
if (Input.GetKeyDown(KeyCode.LeftArrow))
{
MoveLeft();
}
if (Input.GetKeyDown(KeyCode.R))
{
Redo();
}
if (Input.GetKeyDown(KeyCode.U))
{
Undo();
}
}
}
②定义和实现命令相关类;命令类中传入游戏对象,且对象中应定义有命令实施所需要的方法。可将移动的具体实现封装起来。实现撤销,重放的功能;
小结:
命令是具体调用(传入游戏对象)的好处——延迟到调用执行时再知道;
即,我们可以让玩家控制游戏中的任何角色,只需向命令传入不同的角色;把控制角色的命令变为第一公民对象,去除直接方法调用中严厉的束缚。 将其视为命令队列,或者是命令流:一些代码(输入控制器或者AI)产生一系列命令放入流中。 另一些代码(调度器或者角色自身)调用并消耗命令。 通过在中间加入队列,我们解耦了消费者和生产者。如果将这些指令序列化,我们可以通过网络流传输它们。 我们可以接受玩家的输入,将其通过网络发送到另外一台机器上,然后重现之。这是网络多人游戏的基础。
相关脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum MoveDir { up, down, left, right};
public class MoveCommand : ICommand
{
private GameObject _obj;
private MoveDir _dir;
private float _distance;
//初始化执行行为所需的参数
public MoveCommand(GameObject obj, MoveDir dir)
{
this._obj = obj;
this._dir = dir;
this._distance = 1;
}
//必须实现接口中的方法
public void Execute()
{
MoveOper(_obj, _dir, _distance);
}
public void UnExecute()
{
MoveOper(_obj, GetUnDir(_dir), _distance);
}
//获取反向动作
public MoveDir GetUnDir(MoveDir dir)
{
switch (dir)
{
case MoveDir.up:
return MoveDir.down;
case MoveDir.down:
return MoveDir.up;
case MoveDir.left:
return MoveDir.right;
case MoveDir.right:
return MoveDir.left;
default:
Debug.Log("no define Move dir");
break;
}
return dir;
}
public void DirPrint(MoveDir dir)
{
switch (dir)
{
case MoveDir.up:
Debug.Log("up");
break;
case MoveDir.down:
Debug.Log("down");
break;
case MoveDir.left:
Debug.Log("left");
break;
case MoveDir.right:
Debug.Log("right");
break;
default:
Debug.Log("no define Move key");
break;
}
}
//封装移动函数
public void MoveOper(GameObject obj, MoveDir dir, float distance)
{
DirPrint(dir);
switch (dir)
{
case MoveDir.up:
MoveY(obj, distance);
break;
case MoveDir.down:
MoveY(obj, -distance);
break;
case MoveDir.left:
MoveX(obj, -distance);
break;
case MoveDir.right:
MoveX(obj, distance);
break;
default:
Debug.Log("no define Move key");
break;
}
}
public void MoveY(GameObject obj, float distance)
{
Vector3 newPos = obj.transform.position;
newPos.z += distance;
obj.transform.position = newPos;
}
public void MoveX(GameObject obj, float distance)
{
Vector3 newPos = obj.transform.position;
newPos.x += distance;
obj.transform.position = newPos;
}
//end
}
ps:
反射:运行中获取类的变量和方法。
反射(Reflection)其实是通过允许在运行时存取程序数据,以改变程序行为的程序设计技术[1]。他 认为,反射其实是一种“语义同像(Semantic Homoiconicity)”。具有语义同像性的语言必须把程序中的一些内部状态,比如符号表、指令指针,暴露给程序员。这样,程序员就可以把这些东西当作 First-Class-Value 而很容易地操作它们。链接:https://www.zhihu.com/question/28570203/answer/41428199
回调[2]:一般用于一个模块将自己的阶段性输出传递给另一个模块,由另一个模块进行更细致的处理。
参考:
[1]编程模式-命令模式
[2]http://blog.youkuaiyun.com/happen23/article/details/50295567 回调的面向对象
[3]http://blog.youkuaiyun.com/poem_qianmo/article/details/53240330 大牛的读书笔记