📐

常に失敗の可能性を持つGetTransform | Verse, UEFN

2024/08/31に公開

前書き

レベル上に配置してあるオブジェクトのtransform情報を取得する関数に「GetTransform」というものが存在します。positionalインターフェースから継承されたクラスで使用することができ、3D空間で操作を行うUEFNにおいては必須の機能となります。

さて、今回はそのpositionalがレベル上に存在しない場合の対応について話していきます。

例えばcreative_prop(小道具)はpositionalを持っています。しかし、フォートナイト由来のオブジェクトなため、武器やアイテムで破壊してしまうことが可能です。その場合、基本的には復元不可[1]のため、Verse上では参照しているものの、座標の取得ができない状態となってしまいます。

少しの改善

この状態でGetTransform()を行った場合、以前のバージョンではVerseそのものがクラッシュし、マップの進行に影響が出るという問題がありました。しかし、v30.00でのアップデートにて少しの改善が行われ、クラッシュではなく空のtransformが返るように変更が行われています。
https://dev.epicgames.com/documentation/ja-jp/uefn/30-00-release-notes-in-unreal-editor-for-fortnite

しかし、これでこの記事の問題がすべて解決するというわけではありません。

専用の関数を作る

さて、本記事の主題でもある存在しない場合に対応するためには、positional用の拡張メソッドを作ることで対応できると考えました。

(InObject:positional).GetSafeTransform<public>()<decides><transacts>:transform=
    if(ValidObject := invalidatable[InObject]):
        ValidObject.IsValid[]
        InObject.GetTransform()
    else if(FCObject := fort_character[InObject]):
        FCObject.IsActive[]
        InObject.GetTransform()
    else if(HealthObject := healthful[InObject]):
        HealthObject.GetHealth() > 0.0
        InObject.GetTransform()
    else:
        InObject.GetTransform()

関数の流れを見ていきましょう。

  1. <decides>エフェクトがついている失敗する可能性のある関数
  2. positionalの拡張メソッドでGetTransform()関数を持つすべてのクラスに対応
  3. creative_propといったpositionalinvalidatableインターフェースを両立するクラスをキャストし、IsValid[]が成功した場合にtransformを返す処理を最初のifで行う
  4. fort_characterは独自にIsActive[]を持つためキャストし、IsActive[]が成功した場合にtransformを返す処理を行う
  5. IsValid[], IsActive[]を持たないが、healthfulという体力の情報を取得できるインターフェースを持つ場合にGetHealth()が0.0より上の場合(つまり生き残っている場合)にtransformを返す
  6. それ以外は常に成功してtransformを返す
    というものです。
if(Transform := Prop.GetSafeTransform[]):
    Print("PropTransform: {Transform.Translation}")
else:
    Print("Invalid Prop")

if(FortChar := Agent.GetFortCharacter[]):
    if(Transform := FortChar.GetSafeTransform[]):
        Print("FortCharTransform: {Transform.Translation}")
    else:
        Print("Invalid FortChar")

for(Vehicle : Vehicles):
    if(Transform := Vehicle.GetSafeTransform[]):
        Print("VehicleTransform: {Transform.Translation}")
    else:
        Print("Invalid Vehicle")

使用例としてこちらのコードを用意しました。

もしPropがレベル上に存在する場合、PropTransformが表示されます。存在しない場合はInValid Propが表示されます。

同様にFortCharが生きている場合はFortCharTransform, 生きていない場合はInvalid FortChar。

Vehicleにはfort_vehicleが入りますが、乗り物はhealthfulという体力の情報を持つためGetSafeTrasform[]での失敗かの確認が行えます。

ケース・バイ・ケース

(InObject:positional).GetSafeTransform<public>()<decides><transacts>:transform=
    if(ValidObject := invalidatable[InObject]):
        ValidObject.IsValid[]
        InObject.GetTransform()
    else if(FCObject := fort_character[InObject]):
        FCObject.IsActive[]
        InObject.GetTransform()
    else if(HealthObject := healthful[InObject]):
        HealthObject.GetHealth() > 0.0
        InObject.GetTransform()
    else:
        Distance(InObject.GetTransform().Translation, vector3{}) > 0.0
        InObject.GetTransform()

オブジェクトがレベル上に存在しない場合には空のtransformが入ることを配慮し、(X: 0.0, Y: 0.0, Z: 0.0)との距離がゼロの場合に失敗する処理を付け足すことでインターフェースでのフィルターの対象外でも対応できます。

しかし、マップの設計上、そのオブジェクトが絶対に原点まで移動しないということを配慮しないといけません。もし何らかのタイミングで原点(0.0, 0.0, 0.0)に移動し、GetSafeTransformを呼び出すと、そのオブジェクトはレベル上に存在しないという結果が返ってしまいます。

もちろん、やりようなので問題なければ上記のコードを使うのもアリだとは思います。が、私は保証はしません。

活用と〆

では最後に、どのような場面で使用するかについて考えていきましょう。

この記事を読んでいるということはある程度目星がついているとは思いますが、例えばあるオブジェクトからの相対位置で別のオブジェクトや自信を動かす場合。自身からどれだけ離れた位置かを取得するために自身のtransformを取得します。

ケースとして、loop内で繰り返し処理を行う場合などには、実行時と経過時でオブジェクトの状態が常に保証されているとは限りません。10秒後には誰かが壊してるなんてこともあるはずです。

そんな時に今回作ったGetSafeTransform関数を呼び出すことで、オブジェクトが存在する場合はtransformが返り、存在しない場合はifが失敗します。

成功の場合はその座標をもとに処理を実行すればよいのですが、失敗した場合はelse:の部分を実行できます。つまり、破壊時にloopを終わらす/破壊されたイベントを発するなどをelseに書き込めば、transformの取得と壊れているかの確認が両立できるのです[2]

脚注
  1. v31.00時点 ↩︎

  2. ケース・バイ・ケースでの注意点もあります ↩︎

Discussion