這篇博客是我上個月就想寫了,但是由於項目加身沒時間來做,所以一直推脫到現在。昨天看了下未完成事項,這個事已經拖了一個月,所以,從昨天開始就趕緊把這個功能準備記錄下來。拖延症害死人。-。-
在類似操作考覈的項目中我們經常會遇到回到上一步的需求。所以我們有必要對每一個狀態點的所有參與交互的物體的狀態進行記錄。好了,下面就是代碼的實現:
首先肯定要創建命令的基類,
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構造函數中。以上效果圖如下:
希望本博客對你有幫助!