Edited at
UnityDay 1

MoveAssetToTrashでUndo/Redo

More than 5 years have passed since last update.

Unityユーザー助け合い所のこの投稿を見て少し試してみました。

AssetDatabase.DeleteAssetを使いProject内のファイルを削除するエディタスクリプトを書いているのですが、Hierarchy内ではなく、Project内のFileを消した場合にUndoさせる方法があれば教えて頂きたいです。


ファイルを削除した時のUndoは出来ない

Unityの仕様で出来ないようになっています。





終了

















まぁ、どうにかしたら出来るんじゃないの?ということで試してみました。


AssetDatabase.MoveAssetToTrashを使う

ファイルが完全に削除されるAssetDatabase.DeleteAssetを使わずに、ゴミ箱に 移動する AssetDatabase.MoveAssetToTrashを使用します。


Undoを実装するまでの注意点(今回のような特殊な場合のみ)


  1. ゴミ箱に移動する前にユニークな名前に変更する。

同じファイル名をゴミ箱に入れてしまうと自動でユニークなファイル名に変更されてしまい、こちら側で追跡が難しくなります。ですので予めこちらでユニークなファイル名に変更します。

2. HideFlags.HideAndDontSave

今回はUnity側が用意しているUndoクラスでは実現することが出来ません。なので少し工夫が必要です。

UndoにはUndo.RegisterCreatedObjectUndoがあるのでGameObjectのUndo/Redoによって今回の特殊なUndo/Redo処理を実現させます。

ですので、本来の使い方をしないGameObjectはHierarchyウィンドウに表示するのは不適切です。そこで、HideFlags.HideAndDontSaveを使用し、Hierarchyに表示しないHide設定とSceneファイルに保存しないDontSave設定を行います。

コード的にはこんな感じ


Example.cs

GameObject g = EditorUtility.CreateGameObjectWithHideFlags ("Trash", HideFlags.HideAndDontSave);

GameObject.DontDestroyOnLoad (g);

3. GameObjectにAddComponent

Undo/Redoのための情報を保持するコンポーネントを作成します。今回はGameObjectを使うのでコンポーネントです。


TrashAsset.cs

#if UNITY_EDITOR

using UnityEditor;
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class TrashAsset : MonoBehaviour
{
public string originalAssetPath;
public string trashAssetPath;
}
#endif


ユニークなファイル名に変更する前のパス(Undoのため)とゴミ箱に移動した時のパスを保持します。これを先程作成したGameObjectにアタッチ。

4. Undoがかっこ良く実装できなかった!

「Undo/Redoが実行された」というイベントは取得できるが、何のUndoが実行されたの?という問題に直面しました。Undo名が取得できない。

なので、すこし不満だけど以下の様に実装しました。


Undo処理.cs

trashAssets = Resources.FindObjectsOfTypeAll<TrashAsset> ();

Undo.undoRedoPerformed += () => {

foreach (var trashAsset in trashAssets) {

if (!trashAsset) {
//Undo
System.IO.File.Move (trashAsset.trashAssetPath, trashAsset.originalAssetPath);
System.IO.File.Move (AssetDatabase.GetTextMetaDataPathFromAssetPath (trashAsset.trashAssetPath), AssetDatabase.GetTextMetaDataPathFromAssetPath (trashAsset.originalAssetPath));
AssetDatabase.ImportAsset (trashAsset.originalAssetPath);
ArrayUtility.Remove (ref trashAssets, trashAsset);
}
}
};


Resources.FindObjectsOfTypeAllはHide状態のGameObjectも取得できます。GameObject.FindObjectOfTypeでは取得できないことに注意。

ここではUnity仕様のnullを使って実装してます。Undoを行う時は、GameObjectは削除されているはずです。従ってGameObjectにアタッチされているTrashAssetはnullになる。だけど、アクセスできる。(実際にはnullじゃないってこと。ここの説明はいつか...)

5. Redoはもっと残念な実装になった!

Unity4.2から実装されたMonoBehaviourのコールバック関数OnValidateを使用します。Redo時にOnValidateが呼ばれる。正確には2回。

なのでRedo処理はこのように実装しました


Redo処理.cs

#if UNITY_EDITOR

using UnityEditor;
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class TrashAsset : MonoBehaviour
{
public string originalAssetPath;
public string trashAssetPath;
private int count;

void OnValidate ()
{
if (count++ == 0) {
// Redo
UndoExtention.MoveAssetToTrash (this);
}
}
}
#endif


これで大まかな説明は終わり。

ソースはココ gist

今回はAPIとして使用できます。

UndoExtention.MoveToTrash (Object obj, string name = "Move to Trash")

UndoExtention.MoveToTrash (Object[] objs, string name = "Move to Trash")

AssetDatabase.MoveToTrashのUndo出来るバージョンって感じです。

動作はこんな感じ

Undo

Redo

あ、Macでしか動かない!


Windowsでゴミ箱にアクセスが面倒

Macのようにお手軽にアクセスできるわけではないみたい。Shell32.dllを使用すればいけるみたいだけど、そこまでするか?ということで実装してない。



これは、今回のよりも簡単だと思う。