Unity3D开发之撤回命令开发

    这篇博客是我上个月就想写了,但是由于项目加身没时间来做,所以一直推脱到现在。昨天看了下未完成事项,这个事已经拖了一个月,所以,从昨天开始就赶紧把这个功能准备记录下来。拖延症害死人。-。-

    在类似操作考核的项目中我们经常会遇到回到上一步的需求。所以我们有必要对每一个状态点的所有参与交互的物体的状态进行记录。好了,下面就是代码的实现:

首先肯定要创建命令的基类,

public class BaseCommand
{

    //执行命令
    public virtual void ExcuteCommand() { }


    //撤销命令
    public virtual void RevocationCommand() { }

}

因为我们控制物体的类似移动改变方式不同,有可能是直接移动的,有可能是用的dotween来操作移动,所以我们的执行命令函数可能对于我们没有实现的必要。下面我们要创建一个用来管理命令的脚本,供我们添加和移除命令,同时这个脚本我们给设置为单例,可供全局调用。如下:

public class CommandManager : MonoBehaviour
{
    public static CommandManager Instance = null;
    //管理命令
    private Stack<BaseCommand> commandStack = new Stack<BaseCommand>();

    private void Awake()
    {
        Instance = this;
    }


    //增加命令  
    public void AddCommand(BaseCommand baseCommand)
    {
        commandStack.Push(baseCommand);
    }


    //移除命令 并且撤销一步操作
    public void RemoveCommand()
    {
        if(commandStack.Count>0)
        {
            BaseCommand baseCommand = commandStack.Pop();
            baseCommand.RevocationCommand();
        }
    }
}

    有个命令基类,有了管理类,下面我们就要对不同命令进行专门的功能实现了。

一.移动类命令

//保存模型的位置 角度 大小信息
public class TransformCommand : BaseCommand
{
    private Transform target;
    private Vector3 pos;
    private Vector3 rota;
    private Vector3 scale;
    private Transform parent;
//我们在构造函数里直接传进来我们改变状态前的Transform信息
    public TransformCommand(Transform target, Vector3 pos, Vector3 rota, Vector3 scale,Transform parent)
    {
        this.target = target;
        this.pos = pos;
        this.rota = rota;
        this.scale = scale;
        this.parent = parent;
    }

    public override void ExcuteCommand()
    {
        base.ExcuteCommand();
    }

    public override void RevocationCommand()
    {
        target.SetParent(parent);
        target.transform.localPosition = pos;
        target.transform.localEulerAngles = rota;
        target.transform.localScale = scale;
    }
}

下面是我们写的测试脚本挂在到摄像头上,

public class CameraMove : MonoBehaviour {


	// Update is called once per frame
	void Update ()
    {
	    if(Input.GetKeyDown(KeyCode.W))
        {
            TransformCommand cmd = new TransformCommand(transform,transform.localPosition,
                transform.localEulerAngles,transform.localScale,null);
            CommandManager.Instance.AddCommand(cmd);
            transform.Translate(Vector3.forward,Space.Self);
        }
	}
}

还需要写一个输入的类来调用撤退命令,代码如下:

public class Test : MonoBehaviour {

    private void Update()
    {
        if(Input.GetKeyDown(KeyCode.Escape))
        {
            CommandManager.Instance.RemoveCommand();
        }
    }
}

二.UI类用户控制改变状态

    下面以InputField为例,介绍UI交互类的撤退命令,这个要比普通的麻烦一些。我找了官方文档好久也没发现值改变前得回掉函数,所以只能自己来实现了。首先我们要自己写一个脚本用来记录改变之前的值。挂载在场景里的InputField上。代码如下:

public class MyInputField : MonoBehaviour,IPointerClickHandler
{
    private InputField input;
    private string preString;//用来记录改变前得值

    private bool IsValueChange = false;//判断值是否发生改变

    private bool IsClick = false;//用来判断InputField是否被选中交互



    private void Awake()
    {
        input = GetComponent<InputField>();
        input.onValueChanged.AddListener(delegate { IsValueChange=true; });
        input.onEndEdit.AddListener(OnEndEditCallBck);
    }

    //结束编辑得回调函数
    void OnEndEditCallBck(string content)
    {
        IsClick = false;
        //如果值没变 直接返回  没必要增加命令
        if (IsValueChange == false) return;
        InputFieldCommand cmd = new InputFieldCommand(input,preString);
        CommandManager.Instance.AddCommand(cmd);
    }

    //当被点击时,我们的UI组件被交互  有被更改值得可能 所以要记录当前值
    public void OnPointerClick(PointerEventData eventData)
    {
        Debug.Log("点击");
        if (IsClick) return;
        preString = input.text;
        IsClick = true;
    }
}
public class InputFieldCommand : BaseCommand
{
    private InputField targetInput;
    private string content;


    public InputFieldCommand(InputField inputField,string content)
    {
        this.targetInput = inputField;
        this.content = content;
    }


    public override void RevocationCommand()
    {
        base.RevocationCommand();
        targetInput.text = content;
    }
}

上面就是在用户对InputField输入内容改变时自动记录上一步得命令。对于其他得UI组件类似Toggle Slider同样适用。

三.销毁生成的物体

    我们在当前一步实例化很多物体,返回上一步就需要销毁所有实例化的物体。这里我们不建议直接销毁,而是利用对象池技术进行回收,还节约性能。对象池技术博客地址:点击打开链接  代码如下:

public class DestoryCommand : BaseCommand
{
    private List<GameObject> objects;

    public DestoryCommand(List<GameObject> objects)
    {
        this.objects = objects;
    }

    public override void RevocationCommand()
    {
       for(int i=0;i< objects.Count;i++)
        {
            //利用对象池技术回收物体  这里就不写了 因为还要把对象池的脚本添加进来
        }
    }
}

这个命令是在我们实例化物体后才能增加。其实就是把实例化的物体加入到List数组然后传入一个新声明的DestoryCommand构造函数中。以上效果图如下:


    希望本博客对你有帮助!

猜你喜欢

转载自blog.csdn.net/qq_33994566/article/details/80235362