【UEFN】フォートナイトのダメージイベントをVerseで制御する (Verseの学習⑦)

ダメージイベントで大爆発を追加

UEFNのVerseにおけるイベント処理

UEFNのVerseでは、ゲーム内で発生したイベント(事象)に対する処理を登録しておくことで、イベント発生時に独自の処理を行うことができます。

登録可能なイベントは現状あまり多くはありませんが、その中に、プレイヤーがダメージを受けるということがイベントとして準備されており、プレイヤーがダメージを受けた際に独自の処理を付け加えることが可能になっています。

ダメージ時に爆発のVFXを追加したサンプル

イベント処理は現在のVerseによるUEFN開発のもっともコアとなる部分です。

今回は、このプレイヤーがダメージを受けるというイベントに対して独自の処理を追加する方法を説明していきます。

1. ダメージイベントに対するOnDamagedEvent関数の登録

では、さっそく制作を開始していきます。

Verseによるクリエイティブデバイスを一つ作成し、OnBegin部分を以下のように記述します。

    OnBegin<override>()<suspends>:void=
        for(Player : GetPlayspace().GetPlayers()):
            if(FortniteCharacter : fort_character = Player.GetFortCharacter[]):
                FortniteCharacter.DamagedEvent().Subscribe(OnDamagedEvent)
        return

流れとしては、

  1. プレイスペースにいるすべてのプレイヤーを取得

  2. プレイヤーがFortniteCharacterを保有しているかどうか判断

  3. 保有している場合は、そのFortniteCharacterに対してOnDamagedEventを登録(Subscribe)

となっています。

このSubscribeがイベントを登録するための関数となっており、カッコの中に登録したい関数名を記載します。

今回の例では、OnDamagedEventがこの後に整備するイベントが発生したときに自動的に実行される関数です。

(※この段階では、まだOnDamagedEvent関数を準備していないため、赤色の波線でエラー表示されています。無視してください)

これで、ゲーム開始時に、存在するすべてのプレイヤーに対して、ダメージを受けた際に、"OnDamagedEvent" が実行されるように登録されたことになります。

2. OnDamagedEvent関数の基本形

次に、OnDamage関数を以下のように準備します。

OnDamagedEvent(DamageResult : damage_result) : void =
    Print("Damaged")
    return

関数名自体は、Subscribe部分と一致していれば、この名前でなくても問題ありません。

注意点として、DamagedEventに登録する関数は、damage_result型の引数を与えておく必要があります。

この段階で一度実行してみます。

プレイヤーがダメージを受けたタイミングで”Damaged”と画面に記載されれば成功です。

DamagedがLog表示されている

3. damage_resultを使った処理の変更

damage_result型に関して

DamagedEventは、どういった形でダメージが与えられたかという情報をdamage_result変数の中にいれて、登録した関数が実行した際に使用できるようにしてくれています。 damage_resultのデータ構成は以下のようになっています。

damage_resultのデータ構造

ダメージを受けた人(Target)、ダメージ量(Amount)、ダメージを起こした人(Instigator)、ダメージを起こしたソース(Source)の情報が取得できるようです。

InstigatorとSourceは、前に「?」マークが付いており、オプション型であることを意味しています。

ダメージを受けた理由が、環境による場合やVerseから直接与えられた場合などがあり、直接的に該当するものが存在しない可能性があるため、オプション型となっています。

ダメージを受けた、与えたプレイヤーの取得

こうした処理では、ダメージを受けた、与えたのがどのプレイヤーか判断し、その情報の取得をしたい場合があります。 例として、ダメージを受けた対象がプレイヤーかどうかを判断し、プレイヤーであればその位置情報を取得してみます。

その場合のコードは以下のようになります。

### get damaged character position
 var DamagedCharacterPosition : ?vector3 = false
 if(DamagedPlayer : fort_character = fort_character[DamageResult.Target]):
     set DamagedCharacterPosition = option{DamagedPlayer.GetTransform().Translation}

damage_resultのTargetがキャラクターなのかを判断し、キャラクターであれば、その位置情報を保存しています。

少しわかりづらいのは以下の部分ではないでしょうか?

fort_character[DamageResult.Target]

damage_resultのTargetはdamagable型となっています。

なぜ、こんなことができるのでしょうか?

これは、プログラム的にいうと、damagable型からfort_character型へのキャストを試みているコードになります。

プログラムに不慣れな方は、

「きゃ、キャスト・・・???」

となってしまうかと思いますので、簡単にキャストを説明します。

例として、fort_characterで、説明していきます。

fort_characterをFortnite.digest.verseで参照すると以下のようになっています。

Fortnite.digest.verseから抜粋したfort_character

細かな部分は説明を省きますが、ざっくりと説明すると、

fort_characterは(positional、 healable、 healthful、 damageable、 shieldable、 game_action_instigator、 game_action_causer)という7個の機能からできている

ということを意味しています。

positionalは位置取得をすることができる機能damageableはダメージを受けることができる機能といった感じで考えてください。

damageableという機能をもったものは他にも存在し、たとえば乗り物(fort_vehicle)は以下のようになっています。

Fortnite.digest.verseから抜粋したfort_vehicle

DamagedEventはdamageable機能で実装されているイベントであり、damageable機能を内部的にもっているものであれば、なんでもDamagedEventを発生することができます。

そのため、damage_resultでは、あくまでも「damageableの機能を持った何かだよ」という情報しか提供していません。

そこで、プログラマー側が、「あなたは、damageable機能をもった何かだそうですが、fort_characterとして扱っていいでしょうか?」という確認をとらなくてはなりません。

この行為がキャストになります。

これは、当然失敗することが考えられるため、ifのブロックの中で実行されています。

Verseのキャストに関しては、土屋つかささんがブログで詳細に説明してくれいますので、ぜひそちらも参考にされるといいかと思います。

zenn.dev

同様に、ダメージを与えた側の位置情報は以下のように取得できます。

### get attacker
var AttackerPosition : ?vector3 = false
if(attacker : fort_character = fort_character[DamageResult.Instigator?]):
    set AttackerPosition = option{attacker.GetTransform().Translation}

Instigatorはオプション型のため、「?」で値を取得している部分だけ差異がありますが、あとは同様のコードとなります。

完成コード

今回のコードをまとめると以下のようになります。

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


damage_process_manager_device_for_blog := class(creative_device):

    OnBegin<override>()<suspends>:void=
        for(Player : GetPlayspace().GetPlayers()):
            if(FortniteCharacter : fort_character = Player.GetFortCharacter[]):
                FortniteCharacter.DamagedEvent().Subscribe(OnDamagedEvent)
        return

    OnDamagedEvent(DamageResult : damage_result) : void =

        Print("{DamageResult.Amount}")

        ### get damaged character position
        var DamagedCharacterPosition : ?vector3 = false
        if(DamagedPlayer : fort_character = fort_character[DamageResult.Target]):
            set DamagedCharacterPosition = option{DamagedPlayer.GetTransform().Translation}

        ### get attacker
        var AttackerPosition : ?vector3 = false
        if(attacker : fort_character = fort_character[DamageResult.Instigator?]):
            set AttackerPosition = option{attacker.GetTransform().Translation}

        var StartPosition : vector3 = vector3{}
        var EndPosition : vector3 = vector3{}

        if((set StartPosition = AttackerPosition?) and (set EndPosition = DamagedCharacterPosition?)):
            spawn:
                DoSomething(StartPosition, EndPosition)
        return

    DoSomething(StartPosition : vector3 , EndPosition : vector3)<suspends> : void =
        #Do something here
        return

あとは、DoSomething部分などに、独自の処理を書いていくことになります。 自分の作例では、DoSomething部分で、VFXの爆発を発生させる処理を書いています。

ダメージ時に爆発のVFXを追加したサンプル

まとめ

今回は、DamagedEventを例に、Eventを登録し、その情報を使用して制御を変更する流れを説明しました。

現状、Verseで制御できるEventはそれほど多くはありませんが、今後増えていくことが予想されます。

他のイベントも今回と同様の手法で制御できると思います。

今回、VisualStudioCodeの中に自動生成されている、Digestファイルの中身を参考にしながらコーディングを行いまいした。

Verseを追加すると自動的に存在するDigestファイル

実は、機能構成などは、このDigestファイルが一番参考になるかと思います。(オンラインドキュメントは現状そのあたりが不足している)

Verseにある程度なれたら、Digestファイルを一度見てみることをお勧めします。

RingoGamesではUEFNに関するさまざまな情報を発信していきます。Twitterでお知らせしていきますので、よろしければ、Twitterのフォローをしていただけると幸いです。

Twitterはこちら

twitter.com

基本的な検証が大枠できたので、自分もついにリリースするためのゲームを作り始めました:)

頑張って6月までにはリリースしたいです。