はじめに
VRChatでノードを組んでギミックを作ることができるUdonのオープンアルファ版が公開されました。
https://twitter.com/VRDesignGuy/status/1207911393011191811
これで独自の言語が使われており、その仕様がここで公開されています。
https://ask.vrchat.com/t/getting-started-with-udon-assembly/84
チュートリアルで紹介されているノードを追いながら読んでいってみます。
気になるところがあればコメントください。
Udonとは
プログラミングなしでノードを組んでVRChat上でオブジェクトを操作したりできるようになるプログラミング言語です。
https://ask.vrchat.com/t/getting-started-with-udon/80
ノードで組んだものをUAssemblyという独自のコードにコンパイルして、
バイトコードにアセンブルされてUdonVMで実行されるようです。
AssemblyCodeはUdonBehaviourのCompiledGraphAssemblyを開くことで見ることができます。
ちなみにこのUAssemblyは実行時(再生ボタンを押したとき)に実行前に更新されるようです。
UAssemblyを読む
Udonのチュートリアルがこちらで公開されています。
https://ask.vrchat.com/t/spinning-cube-example-series/81
この中のVideo 4 - Program Variablesで作られているノードで生成されるUAssemblyの内容を見ていきます。
このチュートリアルでは
2つのCubeにUdonBehaivorとノードが組まれており,
片方のCube(Signal_Cube)で設定した回転速度(speed)をもう一方のCube(Rotating_Cube)に渡して,
Rotating_Cubeを回転させています。
Signal_Cube
Signal_Cubeのノードはこれです。
実行したときにspeedの値を取得してspeedProgramにセットしています。
speedはpublicなので他のノードからも参照できます。
そして, これがそのときのUAssemblyです。
.data_start
.export speed
instance_0: %VRCUdonUdonBehaviour, this
name_0: %SystemString, null
value_0: %SystemObject, null
speed: %SystemSingle, null
.data_end
.code_start
.export _start
_start:
PUSH, speed
PUSH, value_0
COPY
PUSH, instance_0
PUSH, name_0
PUSH, value_0
EXTERN, "VRCUdonCommonInterfacesIUdonEventReceiver.__SetProgramVariable__SystemString_SystemObject__SystemVoid"
JUMP, 0xFFFFFF
.code_end
データブロックとコードブロックに分かれています。
データブロック
.data_startから.data_endまでがデータブロックで変数などを定義しています。
.data_start
.export speed
instance_0: %VRCUdonUdonBehaviour, this
name_0: %SystemString, null
value_0: %SystemObject, null
speed: %SystemSingle, null
.data_end
それぞれの変数は
型名: 型の種類, 初期値
という書き方がされているようです。
例えば, instance_0はUdonBehaivorをいれる型で自分自身をいれています。
また, speedはSingle型で初期値はなしのようです。
また, nameをspeedとしてpublicにチェックを入れたFloatノードを置いたので
.export speed
が書かれています。
これによってpublic扱いになり, 他のコードからもこの値を参照できるようになります。
コードブロック
.code_startから.code_endまでがコードブロックで実際の動く部分を書いています。
.code_start
.export _start
_start:
PUSH, speed
PUSH, value_0
COPY
PUSH, instance_0
PUSH, name_0
PUSH, value_0
EXTERN, "VRCUdonCommonInterfacesIUdonEventReceiver.__SetProgramVariable__SystemString_SystemObject__SystemVoid"
JUMP, 0xFFFFFF
.code_end
主な処理は事前に用意した関数に実行されています。
Unityでは実行タイミングを決めることができ,
例えばStartは開始時に1度だけ, Updateは毎フレームごとになどいろいろあります。
Startノードを置いたので_start:
が追加されています。
UAssemblyはインデント(字下げ)でブロックを決めているようで
_start:からインデントが下がっている範囲が開始時に1度だけ実行されます。
アセンブリでは他の関数で実行する際に必要な値をpushして関数を読んでいます。
UAssemblyでも同じようにやっています。
COPY
では特定の変数の値を特定の変数にコピーしています。
ここでは事前に
PUSH, speed
PUSH, value_0
をすることで
speedの参照値をvalue_0にコピーしているようです。
関数を呼び出す部分が
EXTERN, "VRCUdonCommonInterfacesIUdonEventReceiver.__SetProgramVariable__SystemString_SystemObject__SystemVoid"
です。
この関数はVRCUdonCommonInterfacesIUdonEventReceiver.SetProgramVariableという名前で
SystemStringとSystemObjectのデータを受け取ってSystemVoidを返す(つまり何も返さない)ようです。
よって実行前に必要なデータを
PUSH, name_0
PUSH, value_0
で渡しているようです。
そして, すべてが終了したのでJUMP, 0xFFFFFF
で終わります。
これはメモリの最後の部分まで飛んで終了したことを示しています。
Rotating_Cube
.data_start
.export signal
instance_0: %UnityEngineTransform, this
axis_0: %UnityEngineVector3, null
angle_0: %SystemSingle, null
Single_0: %SystemSingle, null
Single_1: %SystemSingle, null
instance_1: %VRCUdonUdonBehaviour, this
name_0: %SystemString, null
signal: %VRCUdonUdonBehaviour, this
.data_end
.code_start
.export _update
_update:
PUSH, Single_0
EXTERN, "UnityEngineTime.__get_deltaTime__SystemSingle"
PUSH, signal
PUSH, instance_1
COPY
PUSH, instance_1
PUSH, name_0
PUSH, Single_1
EXTERN, "VRCUdonCommonInterfacesIUdonEventReceiver.__GetProgramVariable__SystemString__SystemObject"
PUSH, Single_0
PUSH, Single_1
PUSH, angle_0
EXTERN, "SystemSingle.__op_Multiplication__SystemSingle_SystemSingle__SystemSingle"
PUSH, instance_0
PUSH, axis_0
PUSH, angle_0
EXTERN, "UnityEngineTransform.__Rotate__UnityEngineVector3_SystemSingle__SystemVoid"
JUMP, 0xFFFFFF
.code_end
書き方は同じなのでSignal_Cubeの解説部分を見ればだいたい何をしているか分かるかと思います。
今回はUpdateノードを置いたので_update:
が書かれています。
また、先ほどのVRCUdonCommonInterfacesIUdonEventReceiver.SetProgramVariable
では返り値がVoid(なし)でしたが
EXTERN, "VRCUdonCommonInterfacesIUdonEventReceiver.__GetProgramVariable__SystemString__SystemObject"
では返り値がSystemObjectなので
それを受け取るために事前に引数用のPUSH, name_0
に加えて
返り値を受け取るためのPUSH, Single_1
もしています。
これで返り値はSingle_1に代入されるので次の
EXTERN, "UnityEngineTransform.__Rotate__UnityEngineVector3_SystemSingle__SystemVoid"
に渡しています。
最後に
拡張した言語もつくれるようになっているようです。
興味がある方は同封されている
UdonAssemblyProgramAsset.csをUdonGraphProgramAsset.csを
見てみると良さそうです。