エディタ拡張でUdonBehaviourを編集しよう


はじめに

UdonがVRChatに現れてから早いもので1年が経過しました。
近頃はUdonを書く人も大分増えて、日本界隈でも市民権を得てきているなぁ、と思う今日この頃です。
本格的にUdonを触るようになって、エディタ拡張で自動化したいシチュエーションが増えてきたのですが、VRCTriggerと違ってUdonBehaviourはserializedObjectからUdonの変数に触る事は出来ないようです。
そんなわけでエディタ拡張からUdonの変数を設定する方法を調べてみました。

Udonの構造

UdonはUdonAssemblyProgramAssetを継承したクラス(Udonコンパイラ)によってコンパイルされます。
その際、シリアライズされた変数は型と変数名を合わせたSymbolという形で保存されます。
このSymbolに値は含まれておらず、値はPublicVariablesという、変数名と紐づけされた形でUdonBehaviourに保存されます。

Symbolの取得

SymbolはUdonコンパイラによって生成されるSerializedUdonProgramAssetが持っています。UdonBehaviourからは下記の経路でSymbolにアクセスできます。

using VRC.Udon;
using VRC.Udon.Common.Interfaces;

public class SymbolTable
{
    public UdonBehaviour udonBehaviour;

    public IUdonSymbolTable GetSymbolTable()
    {
        IUdonSymbolTable symbolTable = udonBehaviour.programSource.SerializedProgramAsset.RetrieveProgram().SymbolTable;

        return symbolTable;
    }
}

SymbolTableから下記関数を使用してSymbolの情報を取得できます。

  • ImmutableArray<string> GetExportedSymbols()
    • 変数名の配列を返す。
  • bool HasExportedSymbole(string publicVariableSymbol)
    • 指定した変数名がテーブルに含まれるならTrueを返す。
  • Type GetSymbolType(string exportedSymbol)
    • 変数名からSymbolの型を返す。

PublicVariablesの編集

PublicVariablesはUdonBehaviourのメンバ変数から取得できます。
IUdonVariableTable publicVariables = udonBehaviour.publicVariables;

PublicVariablesが持つ下記関数から変数の値にアクセスできます。

  • bool TryGetVariableValue(string exportedSymbol, out object variableValue)
    • 変数名からSymbolの値をobject型で取得する。失敗したらFalseを返す。
  • bool TrySetVariableValue(string exportedSymbol, object variableValue)
    • 変数名からSymbolの値をSetする。失敗したらFalseを返す。

サンプルスクリプト

Scene内の全てのAudioSourceを取得してUdonBehaviourに設定するエディタ拡張です。

using UnityEngine;
using UnityEditor;
using VRC.Udon;
using VRC.Udon.Common.Interfaces;

public class UdonAudioSetup : MonoBehaviour
{
    public UdonBehaviour udonBehaviour;
    public string symbolName;
}

[CustomEditor(typeof(UdonAudioSetup))]
public class UdonAudioSetupEditor : Editor
{
    UdonAudioSetup udonAudioSetup;

    private void OnEnable()
    {
        udonAudioSetup = (UdonAudioSetup)target;
    }

    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        EditorGUILayout.Space();
        EditorGUILayout.Space();

        // Symbolを取得出来なければボタンを無効化
        using (new EditorGUI.DisabledScope(!IsSetupReady(udonAudioSetup)))
        {
            if (GUILayout.Button("Setup"))
            {
                SetupUdonBehaviour(udonAudioSetup.udonBehaviour, udonAudioSetup.symbolName);
            }
        }
    }

    private void SetupUdonBehaviour(UdonBehaviour udonBehaviour, string exportedSymbol)
    {
        // PublicVariables取得
        IUdonVariableTable publicVariables = udonBehaviour.publicVariables;

        var audioSources = GameObject.FindObjectsOfType<AudioSource>();

        // Undo用意
        Undo.RecordObject(udonBehaviour, "Modify Public Variable");
        // 値をPublicVariablesに設定
        if (!publicVariables.TrySetVariableValue(exportedSymbol, audioSources))
        {
            Debug.Log("Error! Failed Setting Public Variables.");
            return;
        }

        Debug.Log("Setup Completed.");
    }

    private bool IsSetupReady(UdonAudioSetup udonAudioSetup)
    {
        // Symbolテーブル取得
        IUdonSymbolTable symbolTable = udonAudioSetup.udonBehaviour?.programSource?.SerializedProgramAsset?.RetrieveProgram()?.SymbolTable;
        if (symbolTable == null)
            return false;

        // Symbolが存在しなければFalse
        if (!symbolTable.HasExportedSymbol(udonAudioSetup.symbolName))
            return false;

        // Symbolの型がAudioSource[]でなければFalse
        if (symbolTable.GetSymbolType(udonAudioSetup.symbolName) != typeof(AudioSource[]))
            return false;

        return true;
    }
}

使用バージョン

  • VRCSDK3-WORLD-2021.03.22.18.27_Public
  • UdonSharp_v0.19.8