命令模式的描述:
命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
意图: 将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。
需求描述:给定两种不同的工具:锤子、刷子;锤子可以用来砸开泥土表层,刷子可以用来清理泥土表面的灰尘。要求:当选中的工具为锤子时,鼠标点击屏幕的任意位置,锤子移动到鼠标点击的位置。当选中的工具为刷子时,鼠标点击屏幕的任意位置,刷子移动到鼠标点击的位置,并且如果不放开鼠标,可以继续拖动刷子(即刷子跟随鼠标移动)。下面分别使用常规方法和命令模式来实现。
首先给出一种最直接的、没有应用任何设计模式的实现方法,如下:
(代码仅作演示)
void Update () {
if (CurrentToolType == "刷子") {
if (Input.GetMouseButtonDown (0)) {
//计算鼠标相对刷子的偏移量
}
if (Input.GetMouseButton (0)) {
//根据偏移量改变刷子的偏移量,并让刷子跟随鼠标移动
}
} else if (CurrentToolType == "锤子") {
if (Input.GetMouseButtonDown (0)) {
//将锤子的位置改变为鼠标的位置
}
}
///******* */
}
这种实现方法并没有什么错误,但是不利于扩展,比如说我现在需要加入镰刀、剪刀、菜刀等工具;且、这些工具的使用方法和刷子、锤子有相同和不同之处,那我就需要疯狂的在if语句里加判断,导致if语句臃肿;而且如果我需要为其中一些工具添加单独的限制条件,比如限制可拖动边界和可点击范围,那将是巨大的灾难。幸好,命令模式可以很好的帮我们解决这个问题。
首先对问题进行分析,我们有两个工具,用户可通过操作UI在工具之间进行切换;不同的工具有不同的使用方法;锤子需要通过点击鼠标来进行操作,刷子需要拖动鼠标来进行操作。两者都需要通过鼠标操作,那么我们可以为这个操作提供一个入口,抽象出一个基类来,代码如下:
public abstract class BaseCommand {
/// <summary>
/// 执行
/// </summary>
public abstract void Execute(Transform t);
/// <summary>
/// 边界限制
/// </summary>
public abstract bool BoundClamp();
}
接下来实现具体的操作工具的命令类。
操作锤子工具的命令类,如下:
public class ClickCommand : BaseCommand {
public override void Execute (Transform t) {
if (Input.GetMouseButtonDown (0)) {
Vector3 pos = Camera.main.ScreenToWorldPoint (Input.mousePosition);
pos = new Vector3 (pos.x, pos.y, Camera.main.nearClipPlane);
//Debug.Log(pos);
t.transform.position = pos;
}
}
/// <summary>
/// 锤子工具没有边界限制,可以全屏点击
/// </summary>
/// <returns></returns>
public override bool BoundClamp () {
return true;
}
}
操作刷子工具的命令类,如下:
public class SliderCommand : BaseCommand {
private Vector3 offset = Vector3.zero;
private bool isDown = false;
public override void Execute (Transform t) {
if (Input.GetMouseButtonDown (0)) {
isDown = true;
var pos = Camera.main.ScreenToWorldPoint (Input.mousePosition);
offset = t.transform.position - pos;
offset = new Vector3 (offset.x, offset.y, 0);
if (!ClickClamp (new Vector3 (pos.x, pos.y, pos.z), t)){
if (!BoundClamp())return;
t.transform.position=new Vector3(pos.x,pos.y,t.position.z);
offset=Vector3.zero;
}
}
if (isDown) {
if (Input.GetMouseButton (0)) {
if (!BoundClamp())return;
var pos = Camera.main.ScreenToWorldPoint (Input.mousePosition);
pos = new Vector3 (pos.x, pos.y, t.transform.position.z);
t.transform.position = pos + offset;
}
}
if (Input.GetMouseButtonUp(0)){
isDown=false;
}
}
private bool ClickClamp (Vector3 pos, Transform t) {
pos = new Vector3 (pos.x, pos.y, Camera.main.nearClipPlane);
if (t.GetComponent<BoxCollider> ().bounds.Contains (pos)) {
return true;
}
return false;
}
/// <summary>
/// 为刷子添加一些拖动范围的限制,例如,
/// 限制可拖动范围为距离屏幕边缘上200px、下100px
/// </summary>
/// <returns></returns>
public override bool BoundClamp(){
var pos=Input.mousePosition;
//Debug.Log(pos);
Rect rect=new Rect(200,100,Screen.width-200,Screen.height-100);
if (pos.x<rect.x||pos.x>rect.width){
return false;
}
if (pos.y<rect.y||pos.y>rect.height){
return false;
}
return true;
}
}
有的时候我们需要为每个工具做一些单独的配置,比如更换不同的图片,选择使用不同的操作命令之类的;那么,我们可以为工具增加一个配置类用来实例化不同的操作命令,配置类如下:
///利用C#的反射机制,通过给定的字符串来实例化相应的的命令
public class ToolConfig : MonoBehaviour {
public BaseCommand command;
public CommandType commandType;
void Start () {
Type type = Type.GetType (commandType.ToString ());
if (type != null) {
var temp = Activator.CreateInstance (type);
command = temp as BaseCommand;
}
else
{
Debug.LogError("未找到要实例化的类,请检查类名是否和枚举值相同");
}
}
}
//通过枚举来定义命令类型
public enum CommandType
{
ClickCommand,//锤子工具的命令
DragCommand//刷子工具的命令
}
接下来就是具体的调用工具命令的类:
(控制器根据当前选择的工具来实例化相应的命令)
public class ToolController : MonoBehaviour {
public BaseCommand _currentBasecommand;
public Transform currentTran;
public ToggleGroup _toggleGroup;
public Toggle[] _toggles;
void Start () {
foreach (var item in _toggles) {
item.onValueChanged.AddListener ((ison) => {
_currentBasecommand = ison?item.GetComponent<ToolConfig> ().command : null;
});
}
}
void Update () {
if (_currentBasecommand!=null){
_currentBasecommand.Execute(currentTran);
}
}
}
Demo演示如下:
扫描关注->当前文章末尾->获取工程地址: