1、什么是命令模式
最近看了《游戏编程模式》这本书,里面介绍了游戏开发时常用的设计模式,当然这些设计模式不只是在开发游戏时才管用,它们同样适用于其他软件开发,适用于各种语言。这里我记录一下自己的学习笔记以及结合unity的使用方法。命令模式是常用的设计模式之一,它的定义是这样:将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。这个定义听起来似乎晦涩难懂,下面用unity游戏开发的例子来说明:
2、对客户进行参数化
比如在游戏开发中,产品经理给你提了这样一个需求:按下按键A,控制角色攻击;按下按键B,控制角色奔跑;按下按键C,控制角色跳跃。面对这样一个简单的需求,我们或许会这样写:
void HandleInput()
{ if (Input.GetKeyDown(KeyCode.A))
{
Attack();
} else if (Input.GetKeyDown(KeyCode.B))
{
Run();
} else if (Input.GetKeyDown(KeyCode.C))
{
Jump();
}
}然后,产品经理又提了需求,用户可以自定义按键功能,在很多游戏中都有做这样的功能,为了实现这样的功能,我们应该将这些对Attack()和Run()的调用转化成可以变换的东西,下面用命令模式来重写一下这个功能:
先定义一个抽象类Command作为基类,再定义具体的子类来重写Excute();
public abstract class Command{ public abstract void Excute(GameActor actor);
}public class AttackCommand : Command
{ public override void Excute()
{ //攻击逻辑 }
}public class RunCommand : Command
{ public override void Excute()
{ //奔跑逻辑 }
}public class JumpCommand : Command
{ public override void Excute()
{ //跳跃逻辑 }
}在MonoBehaviour的Update函数中,每帧去监听用户输入,并返回对应的command
public class GameControl : MonoBehaviour
{ private Command buttonA; private Command buttonB; private Command buttonC; private void Start()
{
buttonA = new AttackCommand();
buttonB = new JumpCommand();
buttonC = new RunCommand();
} private void Update()
{
Command cmd = HandleInput(); if (cmd != null)
{
cmd.Excute(actor);
}
} //处理用户输入
private Command HandleInput()
{ if (Input.GetKeyDown(KeyCode.A))
{ return buttonA;
} else if (Input.GetKeyDown(KeyCode.B))
{ return buttonB;
} else if (Input.GetKeyDown(KeyCode.C))
{ return buttonC;
} else
{ return null;
}
}
}这样,在按键触发和函数调用中间就加了一层Command,如果要自定义按键功能,直接修改Button对应的Command就行了。现在我们也可以修改一下上面的代码,让我们可以用这套机制去控制任意角色对象,只需将要控制的角色对象传进来即可:
using System.Collections;using System.Collections.Generic;using UnityEngine;public class GameActor { }public class Actor1 : GameActor { }public class Actor2 : GameActor { }public abstract class Command{ public abstract void Excute(GameActor actor);
}public class AttackCommand : Command
{ public override void Excute(GameActor actor)
{ //攻击逻辑 }
}public class RunCommand : Command
{ public override void Excute(GameActor actor)
{ //奔跑逻辑 }
}public class JumpCommand : Command
{ public override void Excute(GameActor actor)
{ //跳跃逻辑 }
}public class GameControl : MonoBehaviour
{ private Command buttonA; private Command buttonB; private Command buttonC; private GameActor actor; private void Start()
{
buttonA = new AttackCommand();
buttonB = new JumpCommand();
buttonC = new RunCommand();
actor = new Actor1();
} private void Update()
{
Command cmd = HandleInput(); if (cmd != null)
{
cmd.Excute(actor);
}
} //处理用户输入
private Command HandleInput()
{ if (Input.GetKeyDown(KeyCode.A))
{ return buttonA;
} else if (Input.GetKeyDown(KeyCode.B))
{ return buttonB;
} else if (Input.GetKeyDown(KeyCode.C))
{ return buttonC;
} else
{ return null;
}
}
}3、支持可撤销的操作
命令模式在需要支持可撤销操作的情况下也能轻松应对,假如我们需要给玩家提供撤销移动操作的功能时,我们可以先把玩家输入产生的command存入栈中(或者其他数据结构),在撤销时,从栈中取出栈顶的Command,再调用该Command的Undo(),就实现了撤销功能(Undo()为撤销方法,与Excute()相反),代码如下:
using System.Collections;using System.Collections.Generic;using UnityEngine;public class GameActor { public Transform selfTra; public void Move(Vector3 offset)
{
selfTra.Translate(offset);
}
}public class Actor1 : GameActor { }public class Actor2 : GameActor { }public abstract class Command{ public abstract void Excute(GameActor actor);//执行
public abstract void Undo(GameActor actor);//撤销}public class MoveCommand : Command
{ public Vector3 moveOffset; public MoveCommand(Vector3 offset)
{
moveOffset = offset;
} public override void Excute(GameActor actor)
{
actor.Move(moveOffset);
} public override void Undo(GameActor actor)
{
actor.Move(-moveOffset);
}
}public class CommandControl : MonoBehaviour
{ private Command moveCommand; private GameActor actor; private Stack<Command> commandStack; private void Start()
{
moveCommand = new MoveCommand(Vector3.one);
actor = new Actor1();
commandStack = new Stack<Command>();
} private void Update()
{
Command cmd = HandleInput(); if (cmd != null)
{
commandStack.Push(cmd);
cmd.Excute(actor);
}
} //需要撤销操作时调用这个函数
public void PlayReverse()
{ if (commandStack.Count > 0)
{
commandStack.Pop().Undo(actor);
}
} //处理用户输入
public Command HandleInput()
{ if (Input.GetKeyDown(KeyCode.A))
{ return new MoveCommand(new Vector3(2, 4, 5));
} if (Input.GetKeyDown(KeyCode.B))
{ return new MoveCommand(new Vector3(1, 2, 4));
} else
{ return null;
}
}
}上面代码中, 每次产生一个command时就将它存到Stack中,当需要撤销操作时,就取出Stack顶部的command,并执行它的Undo(),按照这种方法,可以实现多重撤销。
4、总结
通过上面的例子,我们再看命令模式的定义:将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。现在我们差不多明白了命令模式的用法,它优点很明显,缺点也是有的:第一个优点是类间解耦,调用者和接收者之间没有任何依赖关系,调用者在实现功能时只需调用Command抽象类的Excute方法即可,不需要关注是哪个接收者执行;第二个优点是可扩展性,Command的子类可以很容易地扩展;缺点是如果有大量命令,那么Command的子类将会非常庞大。我们在实际开发中,应该发挥出命令模式的优点,并结合其他模式,减少Command子类庞大的问题。
共同学习,写下你的评论
评论加载中...
作者其他优质文章
