Let's Enjoy Unreal Engine

Unreal Engineを使って遊んでみましょう

UEFN VerseでTickモドキっぽいものを作ってみる

前回の記事ではUEFNでVerseを使った導入をやってみました。

unrealengine.hatenablog.com

今回は少しだけ進展して、Verseを使ってフレームごとの更新ができる機能、つまりUnreal Engineではお馴染みのTickに近い機能を実装してみようと思います。

ということは結論から言いますと、VerseにはTickに相当する機能がありません。Unreal Engineを使っている人からすると「えぇー!?」と思われるかもしれません。なので、Tickに近いことをしようとすると結構面倒くさいですが、出来なくはないです。

Verseが導入出来ていればすぐに出来ますので、早速やってみましょう。

Verse Deviceの作成

まずはVeser Explorerから新規のVeserファイルを作成しましょう。名前はなんでも大丈夫です。空のスクリプトファイルを作成で始めます。

早速最小限のスクリプトコードを追加してみましょう。

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

tick_device := class(creative_device):
    OnBegin<override>()<suspends>:void=
        Print("tick device")

最小限の機能を持つ『creative_device』クラスを作成し、ゲームが始まると『OnBegin』関数が呼び出されます。これのコードを追加後に、Verse Deviceをレベル上に追加しましょう。

これで『セッションを開始』を行うと、『tick device』というログが確認できればVerse Deviceの作成は完了です。

spawn式でTickを追加する

ここでTickを追加をするためにspawn式というものを使います。以下は実際のコード例です。

tick_device := class(creative_device):
    Tick()<suspends>:void=
        Print("Tick Execute")
    
    OnBegin<override>()<suspends>:void=
        spawn{Tick()}

dev.epicgames.com

spawn式はそれを使うことで非同期関数をその場で呼び出すことができるようになります。つまり、ここではTickという非同期関数を呼び出しているということになります。非同期関数を呼び出すためには、Tick関数に< suspends >という指定子をつけておく必要があります。

これで非同期処理が可能なTick関数が実行されるようになりました。早速『セッションを開始』を押して実行してみましょう!

Tick Execute

と、ログに出力されていればOKです。

ただし、これではTickになっているとは言えませんので、ここで更にコードを追加します。

tick_device := class(creative_device):
    Tick()<suspends>:void=
        loop:
            Print("Tick Execute")
            Sleep(0.0)
    
    OnBegin<override>()<suspends>:void=
        spawn{Tick()}

loop式とSleep関数を追加しました。loop式はbreakかreturnが呼び出されるまで無限に処理のループを行ないます。普通に無限ループしてしまうと、処理が止まってしまってゲームが進行しなくなりますが、Tick関数は非同期関数なので独自に実行されます。更にSleep関数に0.0を渡すことで、そのフレームをスキップし、次のフレームまで非同期処理を停止させることができます。

この性質を利用することで実質的に非同期にTick処理として動作する関数ができたということになります!実際に実行すると画像のように、左側にPrintの出力文字が永遠と出力されているはずです。

遂にTickに近い処理が実行できました!これで毎フレームごとに処理を行いたい場合はこの方法で処理可能です。UEのTickと違い、非同期処理となりますのでメインスレッドではなくワーカースレッドでおそらく動きますので、処理負荷も低いはずです。ただし、実際のTickと違い、FPSは30として処理されているようなので高速な処理をすることには向いていません。

Tickの中でPropを回す

次に配置しているProp(小道具)とDevice(仕掛け)を連動させてPropを回してみましょう!ここでは新規に『rotate_device』というVerseファイルを作成しています。

まずは先に完全なスクリプトコードを載せておきます。

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/SpatialMath }

rotate_device := class(creative_device):
    @editable Prop:creative_prop = creative_prop{}
    @editable Speed:float = 0.5
    var Count:int = 0

    Tick()<suspends>:void=
        loop:
            Print("Tick Execute Count : {Count}")
            set Count = Count + 1
            var t:transform = Prop.GetTransform()

            if:
                Prop.TeleportTo[t.Translation, t.Rotation.ApplyLocalRotationZ(Speed)]

            Sleep(0.0)
    
    OnBegin<override>()<suspends>:void=
        spawn{Tick()}

このスクリプトでは『creative_prop』インスタンスの定数をもたせておき、『@editable』属性を指定することで、UEFN内でPropを指定することができるよになります。このスクリプトコンパイルして、UEFN内に戻ります。

UEFNのコンテンツブラウザからPropを探してなんでもいいので配置しておきましょう。そしてこのPropとDeviceを連動させます。Verseで作ったProp定数に対して配置済みPropを指定します。

これで準備ができましたので、『セッションを開始』してみましょう!開始すると、画像ではわかりませんが、Propが回転するようになったはずです。

次に少しややこしいことをやっている部分を解説します。

loop:
    Print("Tick Execute Count : {Count}")
    set Count = Count + 1
    var t:transform = Prop.GetTransform()

    if:
        Prop.TeleportTo[t.Translation, t.Rotation.ApplyLocalRotationZ(Speed)]

    Sleep(0.0)

loop式以下では、フレームごとのカウンター処理とPropのTransformを取得して、Prop.TeleportToで移動と回転を行います。ここで少し疑問となるのがTeleportTo前行にif式がついている部分です。

TeleportToという関数は『failure context (失敗コンテキスト)』と呼ばれる処理が発生し、もし処理が失敗した際には実際には式を評価せずにロールバックするフローが発生します。これをVerseでは『speculative execution (投機的実行)』と呼びます。

つまりif式が失敗する結果を返した時にはその失敗は実際には式を評価せずにロールバックすることで副作用がないことを証明することができるということですね。これは凄く面白いことですが、ここではあまりその効果はありませんので難しく考えずに使いましょう。

Tickを使ってもいいのか

最後にまとめとしてUnreal EngineユーザーだとTickは使うべきではない、という認識がありますのでこれも避けるべき方法と思うかもしれませんが、まずVerseではloop式と非同期関数を使って処理しているので、通常のTickとはまず処理方法が違います。実際のTickと同じようにFPSと連動させて動作するわけではない点も注意が必要です。

また並列処理も行いやすいのでこのような処理方法そのものを否定する必要はないと思いますが、非同期処理自体がバグになりやすいのでその点では少し注意が必要と言えるでしょう。実際には色々な使い方があると思いますので、注意しながらぜひ使ってみてください!