様々な編集ソフトではUndo(元に戻す)やRedo(やり直す)などの機能があります。
Unityもエディターで同様の機能が使えますが、あくまでエディターの機能です。実際の動作画面では使えません。
なので実行画面でUndo/Redoが使える機能を実装しました。
実装
using System;
using System.Collections.Generic;
using UnityEngine;
public class DoRecord<T>
{
public bool enableRedo => curtIndex < size - 1;
public bool enableUndo => 0 < curtIndex;
public int size { get; private set; } = 0;
private Action<T> record = null;
private List<T> cache = new List<T>();
private int curtIndex = -1;
public DoRecord(T first, Action<T> record)
{
this.record = record;
Reset(first);
}
public void Reset(T first)
{
cache.Clear();
curtIndex = -1;
size = curtIndex + 1;
Commit(first, true);
}
public void Reset()
{
Reset(cache[0]);
}
public void Commit(T value, bool excute = false)
{
cache.Insert(++curtIndex, value);
size = curtIndex + 1;
if (excute) record?.Invoke(value);
}
public void Redo() => Do(curtIndex + 1);
public void Undo() => Do(curtIndex - 1);
private void Do(int index)
{
curtIndex = Mathf.Clamp(index, 0, size - 1);
record?.Invoke(cache[curtIndex]);
}
}
C#でジェネリッククラスで実装を行いました。組み込み方は次項のサンプルをご確認ください。
1. 値の登録
UndoとRedoの実現のために、可変長配列が型に合わせた経過の値を保持しています。
Commit関数を実行すると値がリストに挿入され、さらにリストの(仮想的な)最大値を更新します。
Undoを実行した後にCommitが行われると上書きされる仕様となっています。
2. 登録した値の再利用
クラスのコンストラクタにメソッドを渡しておくことでUndo/Redoの時に値の再利用ができます。
Do関数ではインデックスの丸め込みのみを行っているので、Undo/Redoが実行できるかはそれぞれのenableを確認して組み込むクラスで制御します。
3. 保持した値の破棄
Reset関数で可変長配列の内容を破棄し、値を初期化します。
コンストラクタの時にもReset関数を使用していますが、引数に最初の値を渡すことで初期値を必ず登録します。
つまり必ず1つ以上の値は保持している状態になります。
サンプル
using UnityEngine;
using UnityEngine.UI;
public class Sample : MonoBehaviour
{
[SerializeField] private ColorPanel panel = default;
[SerializeField] private Button commit = default;
[SerializeField] private Button reset = default;
[SerializeField] private Button redo = default;
[SerializeField] private Button undo = default;
private DoRecord<Color> record = null;
private void Start()
{
record = new DoRecord<Color>(panel.color, (color) => {
panel.color = color;
});
commit.onClick.AddListener(() => record.Commit(panel.color));
reset.onClick.AddListener(() => record.Reset());
redo.onClick.AddListener(() => record.Redo());
undo.onClick.AddListener(() => record.Undo());
}
private void Update()
{
redo.interactable = record.enableRedo;
undo.interactable = record.enableUndo;
}
}
サンプルには色情報の経過を使用しています。色の編集には【Unity】実行画面で色を編集できる機能を作るのColorPanelクラスを使っています。
(GIFでは若干わかりにくいですが)色の編集の度にCommitを実行し、左に配置したUndoを実行すると元に戻し、右に配置したRedoを実行するとやり直します。最後にResetを実行すると最初の色に戻ります。
DoRecordクラスにはプリミティブ型やUnityコンポーネント、自作クラスなども使用できます。
どの範囲の情報を管理するかは組み込むクラスで決めることができるようになっています。