UnityのPlay Mode中に変更したTransformを保存するスクリプトメモ。孫オブジェクトまで対応【Editor拡張】

Unity 実行中 トランスフォーム値 保存 適用 Unity
※記事内に広告が含まれています。

バーチャルクリエイター神部まゆみです(*^_^*)

この記事はUnityのPlay Mode中に変更したTransformを保存するスクリプトをChatGPTパイセンに書いてもらったのでそのメモです。

●PRスペース●

〇まゆみマート|BOOTH

BOOTHでVRoidテクスチャやVRChat向けオブジェクトなどを販売しています。いいねしてくれると励みになります(*^-^*)

このスクリプトを作った理由:FinalIKで手足を固定しながら、再生中に手足のターゲットオブジェクトの位置調整をしてアニメーションを調整したかったから

FinalIKっていうUnityアセットを使うと手足を固定したりできるんだけど、どこに固定するかをオブジェクトで指定できるから、再生中にオブジェクトを動かすと手足の位置も変わるのでポーズやアニメーションの調整にも使える。

VeryAnimationとかのモーション録画機能を使うと再生中のモーションも保存できるので、動かしながら調整してアニメーションが作れる。

Very AnimationのAnimation Recorder機能を使ってみたメモ。アニメーションクリップが録画できた
Very AnimationのAnimation Recorder機能を使ってみました。アニメーションクリップが録画できました。

この方式でやる場合に、実行を止めてもトランスフォーム位置を記憶できると便利だなと思ってスクリプトを作りました。

UnityのPlay Mode中に変更したTransformを保存するスクリプト PlayModeTransformSaver.cs

エディター拡張の機能も使ってるっぽいです。

using UnityEngine;

#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine.SceneManagement;
using System.Collections.Generic;
#endif

public class PlayModeTransformSaver : MonoBehaviour
{
#if UNITY_EDITOR

    public void SavePlayModeTransforms()
    {
        if (!Application.isPlaying)
        {
            Debug.LogWarning(
                "SaveはPlay Mode中に実行してください。"
            );
            return;
        }

        // 今回保存するTransformを一時的に記録
        PlayModeTransformSaveData.Save(
            transform
        );
    }

#endif
}


#if UNITY_EDITOR

/// <summary>
/// Play Mode中に保存したTransformを一時的に保持し、
/// Play Mode終了後に編集モード側へ適用する。
/// </summary>
[InitializeOnLoad]
public static class PlayModeTransformSaveData
{
    private class TransformData
    {
        public string path;

        public Vector3 localPosition;
        public Quaternion localRotation;
        public Vector3 localScale;
    }

    private static List<TransformData> savedTransforms =
        new List<TransformData>();

    private static string scenePath;

    private static bool hasData = false;


    // Unity Editor起動時に登録
    static PlayModeTransformSaveData()
    {
        EditorApplication.playModeStateChanged +=
            OnPlayModeStateChanged;
    }


    /// <summary>
    /// Saveボタンが押されたときに呼ばれる。
    /// 自分自身 + 子 + 孫を保存する。
    /// </summary>
    public static void Save(Transform root)
    {
        savedTransforms.Clear();

        scenePath = root.gameObject.scene.path;

        // 自分自身
        SaveTransform(root);

        // 子
        for (int i = 0; i < root.childCount; i++)
        {
            Transform child = root.GetChild(i);

            SaveTransform(child);

            // 孫
            for (int j = 0; j < child.childCount; j++)
            {
                Transform grandChild =
                    child.GetChild(j);

                SaveTransform(grandChild);
            }
        }

        hasData = true;

        Debug.Log(
            $"[{root.name}] のTransformを保存予約しました。\n" +
            $"対象: {savedTransforms.Count}個\n" +
            "Play Modeを停止すると編集モード側へ適用されます。"
        );
    }


    private static void SaveTransform(
        Transform target)
    {
        TransformData data =
            new TransformData();

        data.path = GetRelativePath(
            target
        );

        data.localPosition =
            target.localPosition;

        data.localRotation =
            target.localRotation;

        data.localScale =
            target.localScale;

        savedTransforms.Add(data);
    }


    /// <summary>
    /// Play Mode状態が変化したとき
    /// </summary>
    private static void OnPlayModeStateChanged(
        PlayModeStateChange state)
    {
        // Play Modeを終了して
        // Edit Modeへ戻った瞬間
        if (state ==
            PlayModeStateChange.EnteredEditMode)
        {
            if (hasData)
            {
                ApplySavedTransforms();
            }
        }
    }


    /// <summary>
    /// 保存したTransformを編集モード側へ適用
    /// </summary>
    private static void ApplySavedTransforms()
    {
        if (string.IsNullOrEmpty(scenePath))
        {
            Clear();
            return;
        }

        Scene scene =
            EditorSceneManager.GetSceneByPath(
                scenePath
            );

        if (!scene.IsValid())
        {
            Debug.LogError(
                "編集モード側のシーンが見つかりません。"
            );

            Clear();
            return;
        }

        int appliedCount = 0;

        foreach (TransformData data
                 in savedTransforms)
        {
            Transform target =
                FindTransformInScene(
                    scene,
                    data.path
                );

            if (target == null)
            {
                Debug.LogWarning(
                    "Transformが見つかりません: " +
                    data.path
                );

                continue;
            }

            Undo.RecordObject(
                target,
                "Save Play Mode Transform"
            );

            target.localPosition =
                data.localPosition;

            target.localRotation =
                data.localRotation;

            target.localScale =
                data.localScale;

            EditorUtility.SetDirty(target);

            appliedCount++;
        }

        // Edit Modeに戻った後なので、
        // ここではMarkSceneDirtyが使用できる
        EditorSceneManager.MarkSceneDirty(
            scene
        );

        Debug.Log(
            $"Play ModeのTransformを適用しました。\n" +
            $"適用: {appliedCount}/{savedTransforms.Count}個"
        );

        Clear();
    }


    /// <summary>
    /// TransformのHierarchy上のパスを取得
    /// </summary>
    private static string GetRelativePath(
        Transform target)
    {
        string path = target.name;

        Transform current = target.parent;

        while (current != null)
        {
            path =
                current.name + "/" + path;

            current = current.parent;
        }

        return path;
    }


    /// <summary>
    /// シーン内からHierarchy PathでTransformを検索
    /// </summary>
    private static Transform FindTransformInScene(
        Scene scene,
        string path)
    {
        string[] parts =
            path.Split('/');

        if (parts.Length == 0)
            return null;

        GameObject[] roots =
            scene.GetRootGameObjects();

        foreach (GameObject root in roots)
        {
            if (root.name != parts[0])
                continue;

            Transform current =
                root.transform;

            bool found = true;

            for (int i = 1;
                 i < parts.Length;
                 i++)
            {
                current =
                    current.Find(parts[i]);

                if (current == null)
                {
                    found = false;
                    break;
                }
            }

            if (found)
                return current;
        }

        return null;
    }


    private static void Clear()
    {
        savedTransforms.Clear();
        scenePath = null;
        hasData = false;
    }
}


/// <summary>
/// InspectorにSaveボタンを表示
/// </summary>
[CustomEditor(typeof(PlayModeTransformSaver))]
public class PlayModeTransformSaverEditor : Editor
{
    public override void OnInspectorGUI()
    {
        DrawDefaultInspector();

        EditorGUILayout.Space(10);

        PlayModeTransformSaver saver =
            (PlayModeTransformSaver)target;

        GUI.enabled =
            Application.isPlaying;

        if (GUILayout.Button(
            "Save Play Mode Transforms",
            GUILayout.Height(35)))
        {
            saver.SavePlayModeTransforms();
        }

        GUI.enabled = true;

        EditorGUILayout.Space(5);

        if (Application.isPlaying)
        {
            EditorGUILayout.HelpBox(
                "現在のTransformを記憶します。\n" +
                "Play Modeを停止すると、" +
                "編集モード側へ適用されます。",
                MessageType.Info
            );
        }
        else
        {
            EditorGUILayout.HelpBox(
                "Play Mode中にSaveを押してください。",
                MessageType.Info
            );
        }
    }
}

#endif

使い方:実行中に動かしたトランスフォーム値を保存したいオブジェクトに付ける。孫オブジェクトまで対応

こんな階層構造にして、IKTarget_Girlにスクリプトを付けて、実行中に子~孫オブジェクトを動かしてFinalIKで固定した手足の位置を調整してみた。

Unity 実行中 トランスフォーム値 保存 適用

実行中にSave Play Mode Transforms ボタンを押すと、その時点での孫オブジェクトまでのトランスフォーム値が保存され、実行が終わると保存した値が適用されます。

Unity 実行中 トランスフォーム値 保存 適用

空のオブジェクトでカメラの移動ルートや敵の巡回ルートを指定する方式にも使えそう

これ結構使い道はありそうだなと思っていて、空のオブジェクトでカメラの移動ルートや敵の巡回ルートを指定してその通り動かす場合、実行中に位置を調整してプレビューできるから良さそう。

良い感じに調整して保存しておける。

おわりに

手軽にできて良かった。

まぁちょっと試行錯誤してる段階だからこのスクリプトをどのくらい使うかは分からないけども(^_^;)

使い道があるかもしれないので記事にしてみました。

また何かあれば追記します(*^_^*)

タイトルとURLをコピーしました