LoginSignup
4

More than 3 years have passed since last update.

UAssemblyを読んでみる

Last updated at Posted at 2019-12-20

はじめに

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
image.png

ノードで組んだものをUAssemblyという独自のコードにコンパイルして、
バイトコードにアセンブルされてUdonVMで実行されるようです。
AssemblyCodeはUdonBehaviourのCompiledGraphAssemblyを開くことで見ることができます。
image.png

ちなみにこの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を回転させています。
image.png

Signal_Cube

Signal_Cubeのノードはこれです。
image.png
実行したときに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

実際に回転するほうのノードとUAssemblyです。
image.png

.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を
見てみると良さそうです。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4