🎉

【UEFN/Verse/初心者向け】トリガーを押したらPropがプレイヤーについてくるDeviceを作ってみた

2023/11/15に公開

はじめに

とあるゲームの企画でプレイヤーがペットのような相棒を持たせるようにしたい、、というところからどのように実装するのか自分なりに考えてみました。

まだまだVerseに関する記事が少ないのでどんどん書いていきたいと思っています!

Verseの書き方

Verseエクスプローラーを開く

Verseエクスプローラーで右クリックしてAdd new...を押す

deviceの名前を決め作成をクリックしVerseエクスプローラーから編集したいVerseファイルをダブルクリックするとVSCodeが開き編集できます!

本題

動作の映像になります。
https://youtu.be/ily3Fzo_ueo

とりあえずこれが全体コードになります。

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

grab_device := class(creative_device):

    @editable
    target_props:creative_prop = creative_prop{}

    @editable
    grab_trigger:trigger_device = trigger_device{}

    @editable
    ungrab_trigger:trigger_device = trigger_device{}

    var is_grab:logic = true

    OnBegin<override>()<suspends>:void=
        grab_trigger.TriggeredEvent.Subscribe(OnGrab)
        ungrab_trigger.TriggeredEvent.Subscribe(UnGrab)

    OnGrab(QAgent:?agent):void=
        if(Agent := QAgent?; FortCharacter := Agent.GetFortCharacter[]){
            set is_grab = true
            spawn:
              FollowProps(FortCharacter)    
        }

    UnGrab(QAgent:?agent):void=
        UnFollowProps()

    FollowProps(fortChara: fort_character)<suspends>:void=
        loop:
            if (is_grab = true):
                PlayerPosition : vector3 = fortChara.GetTransform().Translation
                target_props.MoveTo(PlayerPosition, rotation{}, 1.0)
            else:
                break

    UnFollowProps<public>():void=
        set is_grab = false

意外とシンプル?かな。
個人的に知っておくと今後役に立ちそうなことを軽く書き出していきます。

@editableについて

これはUnityエンジニアならおなじみの[SerializeField]と同等の機能を持つものです。
UEFNエディタで見てみると以下のようにエディタ上で実際のデバイスを変更したり数字をいじれるようになったりします。

OnBeginについて

こちらゲームが始まった一番最初に呼ばれる部分になります。今回はトリガーのイベント待ちが宣言されてますね。

trigger_deviceの使い方

以下がtrigger_device内のコードになります。

# Used to relay events to other linked devices.
    trigger_device<public> := class<concrete><final>(trigger_base_device):
        # Signaled when an `agent` triggers this device.
        # Sends the `agent` that used this device. Returns `false` if no `agent` triggered the action (ex: it was triggered through code).
        TriggeredEvent<public>:listenable(?agent) = external {}

        # Triggers this device with `Agent` being passed as the `agent` that triggered the action. Use an `agent` reference when this device is setup to require one (for instance, you want to trigger the device only with a particular `agent`.
        Trigger<public>(Agent:agent):void = external {}

        # Triggers this device, causing it to activate its `TriggeredEvent` event.
        Trigger<public>():void = external {}

    # Base class for various specialized trigger devices. See also: * `trigger_device` * `perception_trigger_device` * `attribute_evaluator_device`
    trigger_base_device<public> := class<abstract><epic_internal>(creative_device_base):
        # Enables this device.
        Enable<public>():void = external {}

        # Disables this device.
        Disable<public>():void = external {}

        # Resets the number of times this device has been activated. This will set `GetTriggerCountRemaining` back to `0`
        Reset<public>():void = external {}

        # Sets the maximum amount of times this device can trigger.
        #  * `0` can be used to indicate no limit on trigger count.
        #  * `MaxCount` is clamped between [0,20].
        SetMaxTriggerCount<public>(MaxCount:int):void = external {}

        # Gets the maximum amount of times this device can trigger.
        #  * `0` indicates no limit on trigger count.
        GetMaxTriggerCount<public>()<transacts>:int = external {}

        # Returns the number of times that this device can still be triggered before hitting `GetMaxTriggerCount`.
        # Returns `0` if `GetMaxTriggerCount` is unlimited.
        GetTriggerCountRemaining<public>()<transacts>:int = external {}

        # Sets the time (in seconds) after triggering, before the device can be triggered again (if `MaxTrigger` count allows).
        SetResetDelay<public>(Time:float):void = external {}

        # Gets the time (in seconds) before the device can be triggered again (if `MaxTrigger` count allows).
        GetResetDelay<public>()<transacts>:float = external {}

        # Sets the time (in seconds) which must pass after triggering, before this device informs other external devices that it has been triggered.
        SetTransmitDelay<public>(Time:float):void = external {}

        # Gets the time (in seconds) which must pass after triggering, before this device informs other external devices that it has been triggered.
        GetTransmitDelay<public>()<transacts>:float = external {}

今回は一番使うTriggeredEventについてザックリ解説していきます。

TriggeredEventについて

今回僕が書いたコードをもとに説明します。

    OnBegin<override>()<suspends>:void=
        grab_trigger.TriggeredEvent.Subscribe(OnGrab)
        ungrab_trigger.TriggeredEvent.Subscribe(UnGrab)
	
    OnGrab(QAgent:?agent):void=
        if(Agent := QAgent?; FortCharacter := Agent.GetFortCharacter[]){
            set is_grab = true
            spawn:
              FollowProps(FortCharacter)    
        }

    UnGrab(QAgent:?agent):void=
        UnFollowProps()

OnBeginで宣言しているから起動時にすぐ何かが起きるわけではありません。
例えるならゲーム開始時にトリガーが誰かに押されるのを監視し始めたという理解がいいかもしれません。

監視した状態で誰かがトリガーの上に行くとSubscribe内のコードが呼ばれます。
今回はOnGrabUnGrabが呼ばれてその中身のコードが呼ばれていくわけです。

注意してほしいのがSubscribe内で宣言したメソッドの引数には絶対に?agentを入れておかないとコンパイルエラーが起きます。 このagentとはトリガーを押したプレイヤーそのものの情報が取得できる?という考えでいいかもしれません。
今回の例で行くとAgentからフォートナイトゲーム上の情報を簡単にとるためのfort_characterクラスを取得してそのプレイヤーの位置情報を取得するということをしています。

    if(Agent := QAgent?; FortCharacter := Agent.GetFortCharacter[]){
            set is_grab = true
            spawn:
              FollowProps(FortCharacter)    
        }
	
	
    FollowProps(fortChara: fort_character)<suspends>:void=
        loop:
            if (is_grab = true):
                PlayerPosition : vector3 = fortChara.GetTransform().Translation
                target_props.MoveTo(PlayerPosition, rotation{}, 1.0)
            else:
                break

参考にしたもの

プレイヤーにオブジェクトがついて来させる方法

https://dev.epicgames.com/community/learning/tutorials/9VV9/fortnite-make-props-follow-your-player

agentからfort_characterを取得する方法

https://forums.unrealengine.com/t/how-do-you-get-fort-character-from-type-agent/882713/2

【余談】これだけは理解しておいて損はないやつ<<関数エフェクト>>

Verseを書いていくうちにぶち当たるコンパイルエラー赤波線のたいていの原因はこれでした。
https://colory-games.net/site/uefn-verse-programming-begginer-function-effect/

UEFN界隈盛り上げていくぞー

MidraLab(ミドラボ)

Discussion