見出し画像

AnimationControllerで扉を開く

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

opening_door_device := class(creative_device):

    @editable
    InputTrigger:trigger_device = trigger_device{}  # 扉を開くトリガー
    @editable
    DoorProp:creative_prop = creative_prop{}  # 扉のプロップ

    RotationAngle:float = 90.0  # 開く角度
    OverTime:float = 3.0  # 開くのにかける時間

    OnBegin<override>()<suspends>:void=
        InputTrigger.TriggeredEvent.Await()
        OpenTheDoor()

    OpenTheDoor()<suspends>:void=
        if (AController := DoorProp.GetAnimationController[]):
            MovementKeyFrames:[]keyframe_delta = array:
                MakeKeyFrameDelta(
                    MakeRotationFromYawPitchRollDegrees(RotationAngle, 0.0, 0.0))
            AController.SetAnimation(MovementKeyFrames, ?Mode := animation_mode.OneShot)

            AController.Play()
            AController.AwaitNextKeyframe()

    MakeKeyFrameDelta(DeltaRotation : rotation):keyframe_delta=
        keyframe_delta:
            DeltaLocation := vector3 {X:= 0.0, Y:= 0.0, Z:= 0.0}
            DeltaRotation := DeltaRotation
            DeltaScale := vector3 {X:=1.0, Y:=1.0, Z:=1.0}
            Time := OverTime
            Interpolation := EaseOut
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Fortnite.com/Devices/CreativeAnimation }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /Fortnite.com/Devices/CreativeAnimation/InterpolationTypes }

# 扉を開くデバイスを定義するクラス
opening_door_device := class(creative_device):

    @editable
    InputTrigger:trigger_device = trigger_device{} # 扉を開くためのトリガー
    @editable
    DoorProp:creative_prop = creative_prop{} # 扉として使用するプロップ

    RotationAngle:float = 90.0 # 扉が開く角度
    OverTime:float = 3.0 # 扉が開くまでの時間(秒)

    # デバイスが開始されたときに実行される
    OnBegin<override>()<suspends>:void=
        # トリガーが作動するのを待つ
        InputTrigger.TriggeredEvent.Await()
        # 扉を開く
        OpenTheDoor()

    # 扉を開くメソッド
    OpenTheDoor()<suspends>:void=
        # アニメーションコントローラーを取得
        if (AController := DoorProp.GetAnimationController[]):
            # キーフレームの設定
            MovementKeyFrames:[]keyframe_delta = array:
                MakeKeyFrameDelta(
                    MakeRotationFromYawPitchRollDegrees(RotationAngle, 0.0, 0.0))
            # アニメーションの設定(1回限り)
            AController.SetAnimation(MovementKeyFrames, ?Mode := animation_mode.OneShot)

            # アニメーションの再生と完了待ち
            AController.Play()
            AController.AwaitNextKeyframe()

    # キーフレームデルタを作成するヘルパーメソッド
    MakeKeyFrameDelta(DeltaRotation : rotation):keyframe_delta=
        keyframe_delta:
            DeltaLocation := vector3 {X:= 0.0, Y:= 0.0, Z:= 0.0} # 位置の変更なし
            DeltaRotation := DeltaRotation # 回転を適用
            DeltaScale := vector3 {X:=1.0, Y:=1.0, Z:=1.0} # スケールの変更なし
            Time := OverTime # アニメーション時間
            Interpolation := EaseOut # イージングアウトで滑らかに
開く扉デバイス := class(creative_device):

    @editable
    入力トリガー:trigger_device = trigger_device{}  # 扉を開くトリガー
    @editable
    扉プロップ:creative_prop = creative_prop{}  # 扉のプロップ

    回転角度:float = 90.0  # 開く角度
    経過時間:float = 3.0  # 開くのにかける時間

    開始時<override>()<suspends>:void=
        入力トリガー.トリガー発動イベント.待機()
        扉を開く()

    扉を開く()<suspends>:void=
        if (アニメーションコントローラー := 扉プロップ.アニメーションコントローラー取得[]):
            移動キーフレーム:[]keyframe_delta = array:
                キーフレーム作成(
                    回転作成FromYawPitchRollDegrees(回転角度, 0.0, 0.0))
            アニメーションコントローラー.アニメーション設定(移動キーフレーム, ?Mode := animation_mode.OneShot)

            アニメーションコントローラー.再生()
            アニメーションコントローラー.次のキーフレーム待機()

    キーフレーム作成(回転デルタ : rotation):keyframe_delta=
        keyframe_delta:
            移動デルタ := vector3 {X:= 0.0, Y:= 0.0, Z:= 0.0}
            回転デルタ := 回転デルタ
            スケールデルタ := vector3 {X:=1.0, Y:=1.0, Z:=1.0}
            時間 := 経過時間
            補間 := イーズアウト
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Fortnite.com/Devices/CreativeAnimation }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /Fortnite.com/Devices/CreativeAnimation/InterpolationTypes }

開く扉デバイス := class(creative_device):

    @editable
    入力トリガー:trigger_device = trigger_device{}  # 扉を開くトリガー
    @editable
    扉プロップ:creative_prop = creative_prop{}  # 扉のプロップ

    回転角度:float = 90.0  # 開く角度
    経過時間:float = 3.0  # 開くのにかける時間

    開始時<override>()<suspends>:void=
        入力トリガー.TriggeredEvent.Await() #トリガーイベント,Await=待機
        扉を開く()

    # GetAnimationController=アニメーションコントローラー取得
    # 回転作成YawPitchRollDegrees #SetAnimation=アニメーション設定
    扉を開く()<suspends>:void=
        if (アニメーションコントローラー := 扉プロップ.GetAnimationController[]):
            移動キーフレーム:[]keyframe_delta = array:
                キーフレーム作成(
                    MakeRotationFromYawPitchRollDegrees(回転角度, 0.0, 0.0))
            アニメーションコントローラー.SetAnimation(移動キーフレーム, ?Mode := animation_mode.OneShot)

            アニメーションコントローラー.Play() #再生
            アニメーションコントローラー.AwaitNextKeyframe()#次のキーフレーム待機

    キーフレーム作成(回転デルタ : rotation):keyframe_delta=
        keyframe_delta:
            移動デルタ := vector3 {X:= 0.0, Y:= 0.0, Z:= 0.0}
            回転デルタ := 回転デルタ
            スケールデルタ := vector3 {X:=1.0, Y:=1.0, Z:=1.0}
            時間 := 経過時間
            補間 := イーズアウト

このスクリプトは、フォートナイトのクリエイティブモードで扉を開くための基本的なコードです。

✅<<追記:完全なスクリプト変更(NEW)>>

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

Hello_world_device := class(creative_device):

    @editable
    InputTrigger:trigger_device = trigger_device{}  # 扉を開くトリガー
    @editable
    DoorProp:creative_prop = creative_prop{}  # 扉のプロップ

    RotationAngle:float = 90.0  # 開く角度
    OverTime:float = 3.0  # 開くのにかける時間

    OnBegin<override>()<suspends>:void=
        InputTrigger.TriggeredEvent.Await()
        OpenTheDoor()

    OpenTheDoor()<suspends>:void=
        if (AController := DoorProp.GetAnimationController[]):
            AController.SetAnimation(MakeKeyFrameDelta(), ?Mode := animation_mode.OneShot)
            AController.Play()
            AController.AwaitNextKeyframe()

    MakeKeyFrameDelta():[]keyframe_delta=
        # キーフレーム配列を作成して返す
        return array:
            keyframe_delta:
                DeltaLocation := vector3 {X:= 0.0, Y:= 0.0, Z:= 0.0}
                DeltaRotation := MakeRotationFromYawPitchRollDegrees(RotationAngle, 0.0, 0.0)
                DeltaScale := vector3 {X:=1.0, Y:=1.0, Z:=1.0}
                Time := OverTime
                Interpolation := EaseOut

基本構造

opening_door_device := class(creative_device):

これは「扉を開くデバイス」を作るためのクラスです。`creative_device`を継承しているので、UEFNのエディタで配置できます。

必要な部品の設定

@editable
InputTrigger:trigger_device = trigger_device{}  # 扉を開くトリガー
@editable
DoorProp:creative_prop = creative_prop{}  # 扉のプロップ
  • `@editable`は「UEFNのエディタで設定できるよ」という印

  • `InputTrigger`: プレイヤーが扉を開くためのトリガー(ボタンやプレート)

  • `DoorProp`: 実際に開く扉のプロップ

扉の動き方の設定

RotationAngle:float = 90.0  # 開く角度
OverTime:float = 3.0  # 開くのにかける時間
  • `RotationAngle`: 扉が90度回転(普通のドアみたい)

  • `OverTime`: 扉が開くまでに3秒かかる

トリガーの待機と扉を開く処理

OnBegin<override>()<suspends>:void=
    InputTrigger.TriggeredEvent.Await()
    OpenTheDoor()
  1. ゲーム開始時に実行される関数

  2. `Await()`でトリガーが押されるのを待つ

  3. トリガーが押されたら`OpenTheDoor()`を実行

扉を開く具体的な処理

OpenTheDoor()<suspends>:void= if (AController := DoorProp.GetAnimationController[]):

  1. 扉のアニメーションコントローラーを取得

  2. `if`で取得できたか確認

アニメーションの設定

MovementKeyFrames:[]keyframe_delta = array:
    MakeKeyFrameDelta(
        MakeRotationFromYawPitchRollDegrees(RotationAngle, 0.0, 0.0))
  1. 扉の動きを定義するキーフレームを作成

  2. `RotationAngle`(90度)だけ回転させる設定を作る

アニメーションの実行

AController.SetAnimation(MovementKeyFrames, ?Mode := animation_mode.OneShot)
AController.Play()
AController.AwaitNextKeyframe()
  1. アニメーション設定を適用(`OneShot`は1回だけ再生)

  2. アニメーション開始

  3. アニメーション完了まで待機

アニメーションの詳細設定

MakeKeyFrameDelta(DeltaRotation : rotation):keyframe_delta=
    keyframe_delta:
        DeltaLocation := vector3 {X:= 0.0, Y:= 0.0, Z:= 0.0}  # 位置は動かない
        DeltaRotation := DeltaRotation  # 回転させる
        DeltaScale := vector3 {X:=1.0, Y:=1.0, Z:=1.0}  # サイズは変えない
        Time := OverTime  # 3秒かけて動かす
        Interpolation := EaseOut  # 最後がゆっくりになる動き

簡単な使い方

  1. UEFNエディタでこのデバイスを配置

  2. トリガー(ボタンなど)を配置

  3. 扉として使いたいプロップを配置

  4. デバイスの設定で、配置したトリガーと扉を指定

  5. プレイモードでトリガーを起動すると、扉が開く!

これで扉が3秒かけてスムーズに90度開くような動きを実現できます!🚪✨

Await関数の技術詳細

Await関数は、並行処理のための重要な関数で、以下のような特徴を持っています:

基本機能

Await<native>()<suspends>:t
  • 他のタスクが`Signal`を呼び出すまで、現在のタスクを一時停止します

  • 別の`Signal`呼び出し中に実行された場合でも、次の`Signal`呼び出し時まで一時停止して再開します

属性(Attributes)

`@native`属性があり:

  • C++で実装された関数であることを示します

  • Verse定義からC++定義が自動生成されます

エフェクト(Effects)

以下の重要なエフェクトを持っています:

  1. `<suspends>`

  • 非同期関数であることを示します

  • 関数本体に非同期コンテキストを作成します

  1. `<reads>`

  • 同じ入力でも常に同じ出力を返すとは限らないことを示します

  • メモリやモジュールバージョンなど、外部要因に依存する可能性があります

  1. `<writes>`

  • メモリ内の値を変更する可能性があることを示します

  1. `<allocates>`

  • メモリ内にオブジェクトをインスタンス化する可能性があります

  • uniqueクラスの割り当てには必須のエフェクトです

  1. `<no_rollback>`

  • デフォルトのエフェクトです

  • 関数による操作を元に戻せないことを示します

  • 失敗コンテキストでは使用できません

使用例

MyAsyncFunction()<suspends>:void=
    # 他のタスクからのSignalを待機
    Await()
    # Signalが呼ばれた後に実行される処理

特記事項

  • パラメータは受け取りません

  • 並行処理の同期制御に重要な役割を果たします

  • イベント駆動型のプログラミングに適しています

Await関数

基本的な働き

  • 「待つ」ための関数です

  • 他の誰かが「はい、どうぞ」(Signal)と言うまで待ちます

  • 待っている間は他の作業を邪魔しません

例え話で理解

お店のレジで並んでいる状況を想像してください:

  1. あなた(Await)はレジで待ちます

  2. レジ係(Signal)が「次のお客様どうぞ」と呼ぶまで待ちます

  3. 呼ばれたら次の作業(お会計)に進みます

コードの例

# レストランでの例
注文する()<suspends>:void=
    # 料理ができるまで待つ
    Await()
    # できたら食べ始める!

ポイント

  • 待っている間は他の人の邪魔をしません

  • 呼ばれたら次の作業にスムーズに移れます

  • 順番待ちのような整理された待ち方ができます

メリット

  • プログラムが混乱せずに待てます

  • 順番にきちんと処理できます

  • コンピュータのリソースを無駄遣いしません

つまり、Await関数は「お行儀よく待つための関数」だと覚えておけばOKです!🍌

Await関数は非同期処理の性質

  1. `<suspends>`属性

  • Await関数は`<suspends>`属性を持っています

  • この属性は、関数が非同期であることを示します

  • 関数本体に非同期コンテキストを作成します

  1. 実行の一時停止

  • 他のタスクが`Signal`を呼び出すまで、現在のタスクを一時停止します

  • 処理をブロックせずに、他のタスクを実行できます

  1. 制御フロー

  • タスクの実行を一時停止して、後で再開することができます

  • 非同期的な処理の流れを制御できます

  1. イベント駆動

  • イベントベースの処理に適しています

  • `Signal`が呼ばれるまで待機し、呼ばれた後に処理を再開します

このように、Await関数は非同期プログラミングの重要な要素として機能します。タスクを一時停止して他の処理を実行できる非同期処理の特徴を持っています。

Subscribe関数の基本

Subscribe<public>(Callback:type {__(:t):void})<transacts>:cancelable

主な機能

  • イベントを監視(購読)するための関数です

  • コールバック関数を登録して、特定のイベントが発生したときに実行されます

  • イベントの購読を解除できる`cancelable`オブジェクトを返します

使い方の例

# イベントの購読
取り消し可能 := イベント.Subscribe(
    # コールバック関数
    () : void = 
        # イベント発生時の処理
)

# 購読の解除
取り消し可能.Cancel()

重要なポイント

  1. コールバックの登録

  • イベントが発生したときに実行したい処理を指定できます

  • 複数のコールバックを登録可能です

  1. 購読解除の仕組み

  • 返される`cancelable`オブジェクトで購読解除が可能

  • `Cancel()`を呼び出すことで購読を停止できます

  1. エフェクト

  • `<reads>`: メモリの読み取りが発生

  • `<writes>`: メモリの書き込みが発生

  • `<allocates>`: メモリの割り当てが発生

実践的な使用例

# ボタンクリックイベントの購読
MyButtonHandler(Button:button_loud):void=
    Unsubscriber := Button.OnClick().Subscribe(
        # クリック時の処理
        (Message:widget_message):void =
            # ボタンがクリックされた時の処理を書く
    )

このように、Subscribe関数はイベント駆動型プログラミングの重要な要素として機能します。イベントの監視と処理を簡単に実装できる便利な機能です。🔄

Subscribe関数の説明🐒

これは何?

イベントを「見張る人」を置いておくための関数だよ!

例えば...

  • ボタンが押されたとき

  • プレイヤーがぶつかったとき

  • 時間が来たとき

こういう「何かが起きたとき」に「やりたいこと」を登録できるんだ!

使い方

# 見張り番を置く
見張り番 := ボタン.OnClick().Subscribe(
    () : void =
        # 何か起きたときにやりたいこと
        Print("バナナ見つけた!🍌")
)

# 見張り番をやめさせる
見張り番.Cancel()

ポイント

  1. 見張り番を置く

  • 「これが起きたら教えて!」って頼む

  • 複数の見張り番を置ける

  1. 見張り番をやめさせる

  • もう見張らなくていいよ!って言える

  • `Cancel()`で見張りをやめさせる

実際の例

バナナボタン(ボタン:button_loud):void=
    見張り番 := ボタン.OnClick().Subscribe(
        # ボタンが押されたら
        (Message:widget_message):void =
            # バナナをあげる
            Print("はい、バナナ!🍌")
    )

簡単でしょ?誰かが何かをしたときに「こうしたい!」ということを登録しておける便利な機能なんだ!🐒

Subscribe関数とAwait関数の違い

非同期処理の対応

  • Await関数: 非同期処理に対応(`<suspends>`属性を持つ)

  • Subscribe関数: 非同期処理に非対応

処理方法の違い

Await関数

- イベントが発生するまで処理を一時停止
- イベント発生後に処理を再開
- 一時停止中は他の処理を邪魔しない

Subscribe関数

- イベントの監視を登録するだけ
- 処理を停止せずに次の処理に進む
- イベント発生時にコールバック関数を実行

使い方の例

# Await関数の場合
OnBegin()<suspends>:void=
    イベント.Await()  # ここで処理が一時停止
    # イベント発生後の処理

# Subscribe関数の場合
OnBegin():void=
    イベント.Subscribe(
        # イベント発生時に実行される処理を定義
        () : void = 
            # コールバック処理
    )
    # すぐに次の処理に進む

まとめ

  • Await: イベントを「待つ」(非同期)

  • Subscribe: イベントを「見張る」(同期)

つまり、両者は異なる目的と動作方式を持つ関数であり、用途に応じて使い分ける必要があります。

以下、`OnBegin`メソッドの部分を猿でも分かるように解説します:

OnBeginメソッドの基本構造

OnBegin<override>()<suspends>:void=
    InputTrigger.TriggeredEvent.Await()
    OpenTheDoor()

1️⃣ OnBeginとは

  • これは「ゲームが始まったときに実行される特別な関数」だよ!

  • `<override>`というのは「親クラスの同じ関数を上書きするよ」という意味

  • `<suspends>`は「この関数は途中で待機することができるよ」という意味

2️⃣ InputTrigger.TriggeredEvent.Await()

  • `InputTrigger`は「トリガー(引き金)装置」だよ

  • `.TriggeredEvent`は「トリガーが引かれたときのイベント」

  • `.Await()`は「そのイベントが起きるまでじっと待つ」という意味

  • つまり「誰かがトリガーを引くまでここで待機する」というわけ!

3️⃣ OpenTheDoor()

  • トリガーが引かれたら、この`OpenTheDoor()`という関数を実行するよ

  • これは「扉を開く」ための関数

  • この関数の中身は別のところで定義されているよ

🍌まとめ

  1. ゲームが始まったら

  2. 誰かがトリガーを引くのを待つ

  3. トリガーが引かれたら扉を開く

簡単でしょ?🐒

Awaitの非同期処理を使用してそのまま扉を開く

主なポイント

  • Subscribeを使用した場合の問題点:

    • `OpenTheDoor()`がsuspends関数なので、直接コールバック内で呼び出せません

    • 非同期処理を実現するために、Spawn関数などの追加実装が必要になります

    • コードが複雑になってしまいます

Awaitを使用する利点

  • suspends関数である`OpenTheDoor()`をそのまま呼び出せます

  • トリガー発動から扉を開く処理まで、自然な非同期フローを実現できます

  • 実装がシンプルで分かりやすくなります

実装例(Subscribe使用時)

OnBegin<override>():void=
    InputTrigger.TriggeredEvent.Subscribe(HandleTrigger)

HandleTrigger(_:tuple()):void=
    # suspends関数は直接呼び出せないので追加実装が必要
    spawn{ OpenTheDoor() }

シンプルな非同期処理を実装する場合は、Awaitを使用する方が実装もクリーンで分かりやすくなります。

コールバック関数について、以下のように説明します:

コールバック関数とは

  • あらかじめ登録しておいた関数を、特定のイベントや条件が発生した時に呼び出す仕組み

  • イベント駆動型のプログラミングで広く使用される手法

  • 非同期処理を実現するための一般的な方法の1つ

Verseでの具体例

# Subscribeメソッドでコールバック関数を登録する例
InputTrigger.TriggeredEvent.Subscribe(HandleTrigger)

# コールバック関数の定義
HandleTrigger(_:tuple()):void=
    # イベント発生時に実行される処理

コールバック関数の特徴

  • イベントが発生するまで実行を待機

  • イベントの発生を監視し続ける

  • メインの処理フローとは別に非同期で実行される

  • 登録解除(Unsubscribe)の管理が必要

  • メモリリークの可能性がある

使用時の注意点

  • 適切なタイミングで登録解除する必要がある

  • 非同期処理の制御が複雑になる可能性がある

  • suspendsな関数は直接コールバックとして使用できない

コールバック関数は強力な機能ですが、適切な使用と管理が重要です。

コールバック関数の典型

OnBegin<override>():void=
    InputTrigger.TriggeredEvent.Subscribe(HandleTrigger)

HandleTrigger(_:tuple()):void=
    OpenTheDoor()  # suspendsな関数は直接呼べない

このコードにおける`HandleTrigger`は:

  • `Subscribe`メソッドに登録される関数

  • トリガーイベントが発生した時に呼び出される

  • イベント発生時の処理を定義する

  • イベントとイベントハンドラーを結びつける

しかし、このケースでは`OpenTheDoor()`がsuspends関数なので、コールバック関数からは直接呼び出せないという問題があります。そのため、先ほど議論したように`Await`を使用する方が適切なアプローチとなります。

コールバック関数を使用する場合は、非同期処理を行うために:

HandleTrigger(_:tuple()):void=
    spawn{ OpenTheDoor() }

のような追加実装が必要になります。

OpenTheDoor関数が非同期処理(suspends)である理由

1. アニメーションの完了を待つ必要性

  • アニメーションは時間をかけて実行される処理(この場合3秒)

  • `AController.AwaitNextKeyframe()`で扉のアニメーションが完了するまで待機する必要がある

  • これはリアルタイムで進行する処理なので、同期的に実行すると処理がブロックされてしまう

2. 他の処理への影響を防ぐ

  • 扉が開くアニメーション中も、ゲーム内の他の処理は継続して動く必要がある

  • 非同期にすることで、アニメーション中も:

    • プレイヤーの移動

    • 他のトリガーの検知

    • 物理演算
      などが正常に動作可能

3. コードの実行フロー

OpenTheDoor()<suspends>:void=
    # アニメーション設定
    # ↓
    AController.Play()  # アニメーション開始
    # ↓
    AController.AwaitNextKeyframe()  # 完了まで待機(3秒)

このように、時間のかかる処理を非同期にすることで、スムーズなゲームプレイ体験を実現できます。

扉を開くアニメーション処理の解説

1️⃣ アニメーションコントローラの取得

if (AController := DoorProp.GetAnimationController[]):
  • DoorPropからアニメーションコントローラを取得

  • `[]`は`decides`効果のある関数の成功チェック

  • 成功時のみ以降の処理を実行

2️⃣ キーフレームの作成

MovementKeyFrames:[]keyframe_delta = array:
    MakeKeyFrameDelta(
        MakeRotationFromYawPitchRollDegrees(RotationAngle, 0.0, 0.0))
  • キーフレームデルタの配列を作成

  • `MakeRotationFromYawPitchRollDegrees`で回転を指定

    • Yaw(Y軸): RotationAngle(90度)

    • Pitch(X軸): 0度

    • Roll(Z軸): 0度

3️⃣ アニメーションの設定と実行

AController.SetAnimation(MovementKeyFrames, ?Mode := animation_mode.OneShot)
  • アニメーションの設定

  • `OneShot`モード:1回だけ再生して終了

4️⃣ アニメーションの再生と待機

AController.Play()
AController.AwaitNextKeyframe()
  • `Play()`:アニメーション開始

  • `AwaitNextKeyframe()`:キーフレーム完了まで待機

🎯 結果として

  1. 扉が90度回転

  2. 3秒かけてEaseOut(徐々に遅くなる)補間で動作

  3. アニメーション完了まで待機

  4. 1回限りの動作

このコードにより、スムーズな扉の開閉アニメーションが実現できます!

アニメーションコントローラ(`animation_controller`)を取得する理由

1. プロップのアニメーション制御に必要

  • プロップ(この場合は扉)の動きを制御するために必須

  • アニメーションの再生、一時停止、停止などの操作を行うためのインターフェース

2. アニメーション関連の主要機能を提供

  • 位置の変更: `DeltaLocation`

  • 回転: `DeltaRotation`

  • スケール: `DeltaScale`

  • 補間: アニメーションの動きの滑らかさを制御

3. アニメーションの状態管理

  • 再生状態の取得

  • イベントの監視(完了、キーフレーム到達など)

  • アニメーションの進行状況の追跡

4. エラー処理の安全性

  • `GetAnimationController[]`は`decides`効果を持つ

  • アニメーション不可能なプロップの場合、安全に失敗を検出可能

  • `if`文での判定により、エラーを防止

つまり、アニメーションコントローラはプロップのアニメーションを安全かつ効果的に制御するための必須コンポーネントです。

Animation_Controllerを取得しない問題点

1. 実行時エラーの発生

# アニメーションコントローラなしで直接操作しようとした場合
DoorProp.Rotate()  # ← このような直接的な操作は不可能
  • プロップの移動や回転が実行できない

  • クラッシュやエラーの原因となる

2. アニメーション可能性の確認不足

# 問題のあるコード例
OpenTheDoor()<suspends>:void=
    # アニメーション可能かどうかのチェックがない!
    AController.SetAnimation(...)  # クラッシュの危険性
  • 一部のプロップはアニメーション非対応

    • 建物に接続された壁

    • トレジャー チェスト

    • ルート ラマ

  • これらに対してアニメーションを実行するとエラー

3. 状態管理の問題

  • アニメーションの進行状況が把握できない

  • 完了イベントを検知できない

  • 途中での制御(一時停止/停止)が不可能

4. リソース管理の問題

# 正しい実装
if (AController := DoorProp.GetAnimationController[]):
    # コントローラが有効な場合のみ実行
  • プロップが有効かどうかの確認ができない

  • メモリリークやリソースの無駄遣いの可能性

5. デバッグの困難さ

  • エラーの原因特定が難しい

  • アニメーションの問題解決が複雑化

  • 開発効率の低下

ベストプラクティス

OpenTheDoor()<suspends>:void=
    if (AController := DoorProp.GetAnimationController[]):
        # 安全にアニメーション処理を実行
    else:
        # エラー処理やフォールバック処理
        Warning("アニメーションコントローラの取得に失敗")

このように、アニメーションコントローラの適切な取得と確認は、安全で信頼性の高いコードのために重要です。

[]keyframe_delta`の`[]`とは

`MovementKeyFrames:[]keyframe_delta`の`[]`は配列を表す記法です。

詳しい説明

  1. `[]keyframe_delta`は「keyframe_deltaの配列型」を意味します

# 配列の宣言
MovementKeyFrames:[]keyframe_delta = array:
    MakeKeyFrameDelta(...)  # 配列の要素
  1. 配列が必要な理由:

  • 複数のキーフレームを順番に指定可能

  • アニメーションの一連の動きを表現できる

例:複数キーフレームの使用例

# 複数のキーフレームを持つ配列
MovementKeyFrames:[]keyframe_delta = array:
    MakeKeyFrameDelta(Rotation1)  # 1番目のキーフレーム
    MakeKeyFrameDelta(Rotation2)  # 2番目のキーフレーム
    MakeKeyFrameDelta(Rotation3)  # 3番目のキーフレーム

現在のコードでは

  • 単一のキーフレームのみ使用(90度の回転)

  • 将来的な拡張性のため配列形式を採用

  • `SetAnimation`メソッドが配列型を要求

このように、`[]`は複数のアニメーション状態を順序付けて管理するための配列型を示しています。

🍌 扉を開くスクリプト完全解説!

📚 必要な機能の読み込み

using { /Fortnite.com/Devices }                # デバイス関連
using { /Verse.org/Simulation }                # シミュレーション機能
using { /UnrealEngine.com/Temporary/Diagnostics }    # デバッグ用
using { /Fortnite.com/Devices/CreativeAnimation }    # アニメーション機能
using { /UnrealEngine.com/Temporary/SpatialMath }    # 空間計算用
using { /Fortnite.com/Devices/CreativeAnimation/InterpolationTypes }  # 補間タイプ

📝 クラスとプロパティの定義

Hello_world_device := class(creative_device):
    # エディタで設定できるプロパティ
    @editable
    InputTrigger:trigger_device = trigger_device{}  # 扉を開くトリガー
    @editable
    DoorProp:creative_prop = creative_prop{}      # 扉のプロップ

    # アニメーションの設定値
    RotationAngle:float = 90.0  # 開く角度
    OverTime:float = 3.0        # 開くのにかける時間

🎮 開始時の処理

OnBegin<override>()<suspends>:void=
    InputTrigger.TriggeredEvent.Await()  # トリガーの起動を待つ
    OpenTheDoor()                        # 扉を開く処理を実行

🚪 扉を開く処理

OpenTheDoor()<suspends>:void=
    # アニメーションコントローラの取得
    if (AController := DoorProp.GetAnimationController[]):
        # キーフレームの配列を作成
        MovementKeyFrames:[]keyframe_delta = array:
            MakeKeyFrameDelta(
                MakeRotationFromYawPitchRollDegrees(RotationAngle, 0.0, 0.0))
        
        # アニメーションの設定(一回だけ再生)
        AController.SetAnimation(MovementKeyFrames, ?Mode := animation_mode.OneShot)

        # アニメーション開始と完了待ち
        AController.Play()
        AController.AwaitNextKeyframe()

🎨 キーフレーム作成関数

MakeKeyFrameDelta(DeltaRotation : rotation):keyframe_delta=
    keyframe_delta:
        # 位置の変化(動かさない)
        DeltaLocation := vector3 {X:= 0.0, Y:= 0.0, Z:= 0.0}
        # 回転の設定
        DeltaRotation := DeltaRotation
        # スケールの変化(変えない)
        DeltaScale := vector3 {X:=1.0, Y:=1.0, Z:=1.0}
        # アニメーション時間
        Time := OverTime
        # 補間方法(終わりがゆっくり)
        Interpolation := EaseOut

💡 各部の詳しい説明

1. トリガー処理

  • `InputTrigger.TriggeredEvent.Await()`

    • プレイヤーがトリガーに触れるまで待機

    • 触れたら次の処理へ

2. アニメーションコントローラ

  • `DoorProp.GetAnimationController[]`

    • 扉のアニメーション制御機能を取得

    • 失敗する可能性があるため`if`文でチェック

3. キーフレーム設定

  • 位置(`DeltaLocation`): 変更なし

  • 回転(`DeltaRotation`): Y軸90度回転

  • スケール(`DeltaScale`): 変更なし

  • 時間(`Time`): 3秒

  • 補間(`Interpolation`): EaseOut(終わりがゆっくり)

4. アニメーション実行

  • `SetAnimation`: アニメーションの設定

  • `Play`: 開始

  • `AwaitNextKeyframe`: 完了まで待機

🌟 使用上の注意点

  1. トリガーと扉をちゃんとエディタで設定する

  2. アニメーション可能なプロップを使用する

  3. 回転角度は用途に応じて調整可能

  4. アニメーション時間も調整可能

これで完璧な扉のアニメーション制御ができます! 🎉

// OpenTheDoorメソッド
OpenTheDoor()<suspends>:void=
    if (AController := DoorProp.GetAnimationController[]):
        MovementKeyFrames:[]keyframe_delta = array:
            MakeKeyFrameDelta(
                MakeRotationFromYawPitchRollDegrees(RotationAngle, 0.0, 0.0))
        AController.SetAnimation(MovementKeyFrames, ?Mode := animation_mode.OneShot)
        AController.Play()
        AController.AwaitNextKeyframe()

// MakeKeyFrameDeltaメソッド
MakeKeyFrameDelta(DeltaRotation : rotation):keyframe_delta=
    keyframe_delta:
        DeltaLocation := vector3 {X:= 0.0, Y:= 0.0, Z:= 0.0}
        DeltaRotation := DeltaRotation
        DeltaScale := vector3 {X:=1.0, Y:=1.0, Z:=1.0}
        Time := OverTime
        Interpolation := EaseOut

両メソッドの役割の違い

OpenTheDoorメソッド

  • **アニメーションの実行を管理する「司令官」**のような役割

  1. アニメーションコントローラーの取得

  2. キーフレームの配列を作成

  3. アニメーションの設定(OneShotモード)

  4. アニメーションの再生と完了待機

MakeKeyFrameDeltaメソッド

  • **アニメーションの詳細を定義する「設計図」**のような役割

  1. 移動量(DeltaLocation)の設定

  2. 回転量(DeltaRotation)の設定

  3. スケール変更(DeltaScale)の設定

  4. アニメーション時間の設定

  5. 補間方法(EaseOut)の設定

簡単な例え

  • OpenTheDoor = 映画監督

    • 「はい、アクション!」と撮影を指示する

  • MakeKeyFrameDelta = 脚本家

    • 具体的な動きや演出を細かく指定する

このように、OpenTheDoorは全体の制御を、MakeKeyFrameDeltaは具体的な動きの定義を担当しています。

回転の設定を分離してみる

// OpenTheDoorメソッドで回転角度を指定
OpenTheDoor()<suspends>:void=
    if (AController := DoorProp.GetAnimationController[]):
        MovementKeyFrames:[]keyframe_delta = array:
            MakeKeyFrameDelta(
                MakeRotationFromYawPitchRollDegrees(RotationAngle, 0.0, 0.0))  # ここで回転を設定
// MakeKeyFrameDeltaメソッドで受け取った回転を適用
MakeKeyFrameDelta(DeltaRotation : rotation):keyframe_delta=
    keyframe_delta:
        DeltaRotation := DeltaRotation  # ここで受け取った回転を使用

より良い設計案

回転の設定をMakeKeyFrameDeltaメソッド側に移動させることで、責任の分離がより明確になります:

// OpenTheDoorメソッド
OpenTheDoor()<suspends>:void=
    if (AController := DoorProp.GetAnimationController[]):
        MovementKeyFrames:[]keyframe_delta = array:
            MakeKeyFrameDelta()  # 回転の指定を削除
        AController.SetAnimation(MovementKeyFrames, ?Mode := animation_mode.OneShot)
        AController.Play()
        AController.AwaitNextKeyframe()

// MakeKeyFrameDeltaメソッド
MakeKeyFrameDelta():keyframe_delta=
    keyframe_delta:
        DeltaLocation := vector3 {X:= 0.0, Y:= 0.0, Z:= 0.0}
        DeltaRotation := MakeRotationFromYawPitchRollDegrees(RotationAngle, 0.0, 0.0)  # ここで回転を設定
        DeltaScale := vector3 {X:=1.0, Y:=1.0, Z:=1.0}
        Time := OverTime
        Interpolation := EaseOut

メリット

  1. 責任の分離が明確

  2. アニメーションの設定は全てMakeKeyFrameDeltaメソッドで管理

  3. コードの可読性と保守性が向上

  4. OpenTheDoorメソッドはアニメーションの実行に専念

このように設計を変更することで、各メソッドの役割がより明確になり、より良いコード構造になります。

改善された点

  1. コードの責任分担が明確

  • OpenTheDoor: アニメーションの制御に専念

  • MakeKeyFrameDelta: アニメーションの詳細設定に専念

  1. シンプルな引数渡し

  • 以前: `MakeKeyFrameDelta(DeltaRotation : rotation)`と回転を引数で渡す

  • 現在: 引数なしで内部で回転を設定

  1. メンテナンス性の向上

  • アニメーションの設定変更が必要な場合、MakeKeyFrameDeltaメソッドのみを修正

  • OpenTheDoorメソッドに影響を与えない

  1. 可読性の向上

  • 各メソッドの役割が一目で理解しやすい

  • コードの流れがより論理的

このバージョンのコードの方が、オブジェクト指向プログラミングの原則に則った、より良い設計となっています。

ドア開閉プログラムの完全解説!

1️⃣ 最初に必要な道具を用意

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

これらは魔法の道具箱みたいなもの!アニメーションに必要な道具が入ってます。

2️⃣ デバイスの設定

Hello_world_device := class(creative_device):

    @editable
    InputTrigger:trigger_device = trigger_device{}  # 扉を開くトリガー
    @editable
    DoorProp:creative_prop = creative_prop{}  # 扉のプロップ

    RotationAngle:float = 90.0  # 開く角度
    OverTime:float = 3.0  # 開くのにかける時間
  • `@editable`:フォートナイトのエディターで設定できるようにする魔法の言葉

  • `InputTrigger`:押すボタン

  • `DoorProp`:動かすドア

  • `RotationAngle`:90度開く

  • `OverTime`:3秒かけて開く

3️⃣ 開始の準備

    OnBegin<override>()<suspends>:void=
        InputTrigger.TriggeredEvent.Await()  # トリガーが押されるのを待つ
        OpenTheDoor()                        # ドアを開く

ボタンが押されるのをずっと待って、押されたらドアを開けます!

4️⃣ ドアを開ける指示

    OpenTheDoor()<suspends>:void=
        if (AController := DoorProp.GetAnimationController[]):
            MovementKeyFrames:[]keyframe_delta = array:
                MakeKeyFrameDelta()
            AController.SetAnimation(MovementKeyFrames, ?Mode := animation_mode.OneShot)
            AController.Play()
            AController.AwaitNextKeyframe()
  1. ドアのコントローラーを準備

  2. 動きの設定を行う

  3. アニメーション開始!

  4. 終わるまで待つ

5️⃣ ドアの動き方を決める

    MakeKeyFrameDelta():keyframe_delta=
        keyframe_delta:
            DeltaLocation := vector3 {X:= 0.0, Y:= 0.0, Z:= 0.0}  # 位置は動かさない
            DeltaRotation := MakeRotationFromYawPitchRollDegrees(RotationAngle, 0.0, 0.0)  # 90度回転
            DeltaScale := vector3 {X:=1.0, Y:=1.0, Z:=1.0}  # 大きさは変えない
            Time := OverTime  # 3秒かける
            Interpolation := EaseOut  # なめらかに動かす

ドアの具体的な動き方を全部ここで決めます:

  • 位置:動かない

  • 回転:90度回る

  • サイズ:変わらない

  • 時間:3秒

  • 動き方:スムーズに

これで完成!🎉
ボタンを押すと → ドアが90度 → 3秒かけて → スムーズに開きます!

シンプルでしょ?🍌

✅キーフレームもまとめればよいのでは?

<<完全なスクリプト変更>>

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

Hello_world_device := class(creative_device):

    @editable
    InputTrigger:trigger_device = trigger_device{}  # 扉を開くトリガー
    @editable
    DoorProp:creative_prop = creative_prop{}  # 扉のプロップ

    RotationAngle:float = 90.0  # 開く角度
    OverTime:float = 3.0  # 開くのにかける時間

    OnBegin<override>()<suspends>:void=
        InputTrigger.TriggeredEvent.Await()
        OpenTheDoor()

    OpenTheDoor()<suspends>:void=
        if (AController := DoorProp.GetAnimationController[]):
            AController.SetAnimation(MakeKeyFrameDelta(), ?Mode := animation_mode.OneShot)
            AController.Play()
            AController.AwaitNextKeyframe()

    MakeKeyFrameDelta():[]keyframe_delta=
        # キーフレーム配列を作成して返す
        return array:
            keyframe_delta:
                DeltaLocation := vector3 {X:= 0.0, Y:= 0.0, Z:= 0.0}
                DeltaRotation := MakeRotationFromYawPitchRollDegrees(RotationAngle, 0.0, 0.0)
                DeltaScale := vector3 {X:=1.0, Y:=1.0, Z:=1.0}
                Time := OverTime
                Interpolation := EaseOut
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Fortnite.com/Devices/CreativeAnimation }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /Fortnite.com/Devices/CreativeAnimation/InterpolationTypes }

開く扉デバイス := class(creative_device):

    @editable
    入力トリガー:trigger_device = trigger_device{}  # 扉を開くトリガー
    @editable
    扉プロップ:creative_prop = creative_prop{}  # 扉のプロップ

    回転角度:float = 90.0  # 開く角度
    経過時間:float = 3.0  # 開くのにかける時間

    開始時<override>()<suspends>:void=
        入力トリガー.TriggeredEvent.Await() #トリガーイベント,Await=待機
        扉を開く()

    # GetAnimationController=アニメーションコントローラー取得
    # 回転作成YawPitchRollDegrees #SetAnimation=アニメーション設定
    扉を開く()<suspends>:void= #アニメーション制御
        if (アニメーションコントローラー := 扉プロップ.GetAnimationController[]):
            アニメーションコントローラー.SetAnimation(キーフレーム作成(), ?Mode := animation_mode.OneShot)
            アニメーションコントローラー.Play() #再生
            アニメーションコントローラー.AwaitNextKeyframe() #次のキーフレーム待機

    キーフレーム作成():[]keyframe_delta= #アニメーション詳細設定
        return array:
            keyframe_delta:
                移動デルタ := vector3 {X:= 0.0, Y:= 0.0, Z:= 0.0}
                回転デルタ := MakeRotationFromYawPitchRollDegrees(回転角度, 0.0, 0.0)
                スケールデルタ := vector3 {X:=1.0, Y:=1.0, Z:=1.0}
                時間 := 経過時間
                補間 := イーズアウト

はい、問題ありません!むしろ、この新しい実装は以下の点でさらに優れています:

  1. コードの簡潔さ

    • 不要な中間関数が削除され、コードがよりシンプルになりました。

  2. 責任の集中

    • アニメーションの設定に関する全ての処理が`MakeKeyFrameDelta`関数に集約されました。

  3. 可読性の向上

    • アニメーションの設定から実行までの流れがより直感的になりました。

  4. 保守性の向上

    • アニメーションの変更が必要な場合、`MakeKeyFrameDelta`関数のみを修正すれば良くなりました。

  5. パフォーマンス

    • わずかではありますが、関数呼び出しの回数が減ったため、実行効率が向上しています。

この変更により、コードの品質が向上し、より管理しやすい構造になりました。エラーがないということは、この新しい実装が正しく機能していることを示しています。素晴らしい改善です!👍

では、セクションごとに詳しく解説します:

1️⃣ 必要なパッケージの読み込み

using { /Fortnite.com/Devices }              # フォートナイトのデバイス機能
using { /Verse.org/Simulation }              # シミュレーション機能
using { /UnrealEngine.com/Temporary/Diagnostics }  # デバッグ機能
using { /Fortnite.com/Devices/CreativeAnimation }  # アニメーション機能
using { /UnrealEngine.com/Temporary/SpatialMath }  # 空間計算機能
using { /Fortnite.com/Devices/CreativeAnimation/InterpolationTypes }  # アニメーション補間機能

2️⃣ デバイスの基本設定

Hello_world_device := class(creative_device):
    @editable  # エディターでの編集を可能にする
    InputTrigger:trigger_device = trigger_device{}  # トリガーデバイス
    @editable
    DoorProp:creative_prop = creative_prop{}       # ドアのプロップ

    RotationAngle:float = 90.0  # ドアが開く角度
    OverTime:float = 3.0        # アニメーション時間

3️⃣ 開始時の処理

OnBegin<override>()<suspends>:void=
    InputTrigger.TriggeredEvent.Await()  # トリガーの作動を待機
    OpenTheDoor()                        # ドアを開く処理を実行

4️⃣ ドアを開く処理

OpenTheDoor()<suspends>:void=
    if (AController := DoorProp.GetAnimationController[]):  # アニメーションコントローラー取得
        AController.SetAnimation(MakeKeyFrameDelta(), ?Mode := animation_mode.OneShot)  # アニメーション設定
        AController.Play()  # アニメーション開始
        AController.AwaitNextKeyframe()  # アニメーション完了待機

5️⃣ アニメーション設定

MakeKeyFrameDelta():[]keyframe_delta=
    return array:
        keyframe_delta:
            DeltaLocation := vector3 {X:= 0.0, Y:= 0.0, Z:= 0.0}  # 位置の変化なし
            DeltaRotation := MakeRotationFromYawPitchRollDegrees(RotationAngle, 0.0, 0.0)  # 90度回転
            DeltaScale := vector3 {X:=1.0, Y:=1.0, Z:=1.0}  # サイズ変更なし
            Time := OverTime  # アニメーション時間
            Interpolation := EaseOut  # なめらかな動き

動作の流れ

  1. トリガーが作動するのを待機

  2. トリガー作動を検知したらドアを開く処理開始

  3. アニメーションコントローラーを取得

  4. アニメーションの設定(90度回転、3秒間)

  5. アニメーションを実行

  6. 完了まで待機

特徴

  • エディターで簡単に設定可能

  • スムーズな動きを実現

  • 明確な役割分担

  • シンプルで管理しやすい構造

このコードにより、トリガーを作動させると、ドアが3秒かけて90度スムーズに開く動作が実現されます!🚪✨

☑OpenTheDoor()`(扉を開く)関数 解説

1️⃣ メソッドの定義部分

OpenTheDoor()<suspends>:void=
  • `OpenTheDoor()`: メソッドの名前

  • `<suspends>`: 処理を一時停止できる(非同期処理)

  • `:void=`: 戻り値なしを示す

2️⃣ アニメーションコントローラーの取得

if (AController := DoorProp.GetAnimationController[]):
  • `if`: 条件分岐の開始

  • `AController`: アニメーションを制御する変数

  • `:=`: 変数への代入

  • `DoorProp`: 扉のプロップ(オブジェクト)

  • `.GetAnimationController[]`: アニメーションコントローラーを取得するメソッド

  • これが失敗したら以降の処理はスキップ

3️⃣ アニメーションの設定

AController.SetAnimation(MakeKeyFrameDelta(), ?Mode := animation_mode.OneShot)
  • `AController`: 取得したアニメーションコントローラー

  • `.SetAnimation()`: アニメーションを設定するメソッド

  • `MakeKeyFrameDelta()`: アニメーションの詳細設定を取得

  • `?Mode := animation_mode.OneShot`: 1回だけ実行するモード設定

4️⃣ アニメーション開始

AController.Play()
  • `AController`: アニメーションコントローラー

  • `.Play()`: アニメーションを開始する命令

5️⃣ アニメーション完了待機

AController.AwaitNextKeyframe()
  • `AController`: アニメーションコントローラー

  • `.AwaitNextKeyframe()`: アニメーションが完了するまで待機

処理の流れ図

開始
↓
アニメーションコントローラー取得
↓
成功? → No → 終了
↓ Yes
アニメーション設定
↓
アニメーション開始
↓
完了まで待機
↓
終了

例え話で理解

  • アニメーションコントローラー = 映画の監督

  • SetAnimation = 撮影の準備

  • Play = 「アクション!」の掛け声

  • AwaitNextKeyframe = 「カット!」まで待つ

このように、映画撮影のような流れで扉のアニメーションが実行されるんです!🎬✨

<suspends>を必要としている理由

はい、特定の処理が`suspends`(一時停止/待機)を必要とするため、それらのメソッドには`<suspends>`キーワードが付与されています。

コードの中で`suspends`が必要な理由は:

  1. `OnBegin`メソッド:

OnBegin<override>()<suspends>:void=
    InputTrigger.TriggeredEvent.Await()  # トリガーの待機
    OpenTheDoor()
  • `Await()`を使用しているため

  • トリガーが作動するまで処理を一時停止する必要がある

https://dev.epicgames.com/documentation/en-us/uefn/verse-api/fortnitedotcom/animation/playanimation/play_animation_instance/await

  1. `OpenTheDoor`メソッド:

OpenTheDoor()<suspends>:void=
    if (AController := DoorProp.GetAnimationController[]):
        AController.SetAnimation(MakeKeyFrameDelta(), ?Mode := animation_mode.OneShot)
        AController.Play()
        AController.AwaitNextKeyframe()  # アニメーション完了待機
  • `AwaitNextKeyframe()`を使用しているため

  • アニメーションが完了するまで処理を一時停止する必要がある

https://dev.epicgames.com/documentation/en-us/uefn/verse-api/fortnitedotcom/devices/creativeanimation/animation_controller/awaitnextkeyframe

つまり、`<suspends>`は非同期処理を行う際に必要なキーワードで、以下のような場合に使用されます:

  • イベントの待機

  • アニメーションの完了待機

  • その他の時間のかかる処理の待機

これにより、他の処理をブロックすることなく、特定の条件や完了を待つことができます。

SetAnimationは2つのパラメータを期待している

エラーメッセージの重要な部分:

This function parameter expects a value of type tuple([]keyframe_delta,?Mode:animation_mode), 
but this argument is an incompatible value of type []keyframe_delta

解説

  • `SetAnimation`は2つのパラメータを期待しています:

  1. `[]keyframe_delta`: キーフレームの配列

  2. `?Mode:animation_mode`: アニメーションモード(オプショナル)

エラーの修正方法

# 誤った書き方
AController.SetAnimation(MakeKeyFrameDelta())

# 正しい書き方
AController.SetAnimation(MakeKeyFrameDelta(), ?Mode := animation_mode.OneShot)

アニメーションモードのパラメータを省略したことでエラーが発生しました。
`?Mode`パラメータは省略可能ですが、その場合でも正しい形式で呼び出す必要があります。

SetAnimationのModeの省略方法

  1. `Mode`パラメータの省略形

AController.SetAnimation(MakeKeyFrameDelta())  # これはエラー
  1. 正しい省略形

AController.SetAnimation(MakeKeyFrameDelta(), ?Mode := animation_mode.OneShot)

`?`記号の付いたパラメータを省略する場合、以下のルールがあります:

  • `?Mode`のように`?`が付いているパラメータは、省略可能パラメータ(オプショナルパラメータ)

  • ただし、完全に省略するのではなく、`?Mode := animation_mode.OneShot`のように指定する必要がある

  • これは、Verseの言語仕様として定められたパラメータの省略方法

したがって、Verseでは単純な引数の省略はできず、必ずオプショナルパラメータとして明示的に指定する必要があります。

`?Mode`に`?`が付いている理由は:

# Sets the animation for the animation_controller. Animations are processed in the order provided in Keyframes.
SetAnimation<public>(Keyframes:[]keyframe_delta, ?Mode:animation_mode):void = external {}

`?Mode`に`?`が付いている理由は:

  1. アニメーションの大多数のケースでは、`OneShot`(一回限りの実行)が標準的な使用方法だから

  2. すべてのアニメーションで必ずしもモードを指定する必要がないため、デフォルト値を用意している

  3. APIの柔軟性を高めるため、必要な場合だけモードを指定できるようにしている

これにより:

  • シンプルな使用ケース:モードを指定せずに使用可能

  • 高度な使用ケース:必要に応じてモードを明示的に指定可能

このような設計は、APIの使いやすさと柔軟性のバランスを考慮した結果だと考えられます。

☑MakeKeyFrameDelta()(キーフレーム作成)関数部分 解説

    MakeKeyFrameDelta():[]keyframe_delta=
        # キーフレーム配列を作成して返す
        return array:
            keyframe_delta:
                DeltaLocation := vector3 {X:= 0.0, Y:= 0.0, Z:= 0.0}
                DeltaRotation := MakeRotationFromYawPitchRollDegrees(RotationAngle, 0.0, 0.0)
                DeltaScale := vector3 {X:=1.0, Y:=1.0, Z:=1.0}
                Time := OverTime
                Interpolation := EaseOut

関数の定義部分

MakeKeyFrameDelta():[]keyframe_delta=
  • `[]keyframe_delta`:キーフレームの配列を返すことを示します

  • 関数名は「キーフレームの変化量を作成する」という意味

戻り値の構造

return array:
    keyframe_delta:
  • `array:`:配列を作成して返す

  • `keyframe_delta:`:キーフレームの変化量を定義

各パラメータの設定

  1. 位置の変化(DeltaLocation)

DeltaLocation := vector3 {X:= 0.0, Y:= 0.0, Z:= 0.0}
  • 3次元ベクトルで位置を指定

  • X, Y, Z全て0.0なので、位置の変化なし

  1. 回転の変化(DeltaRotation)

DeltaRotation := MakeRotationFromYawPitchRollDegrees(RotationAngle, 0.0, 0.0)
  • Yaw(Y軸):`RotationAngle`(90度)

  • Pitch(X軸):0.0度

  • Roll(Z軸):0.0度

  • Y軸を中心に90度回転

  1. スケールの変化(DeltaScale)

DeltaScale := vector3 {X:=1.0, Y:=1.0, Z:=1.0}
  • 3次元ベクトルでスケールを指定

  • 全て1.0なのでサイズの変化なし

  1. アニメーション時間

Time := OverTime
  • `OverTime`(3.0秒)をアニメーション時間として設定

  1. 補間方法

Interpolation := EaseOut
  • `EaseOut`:最初が速く、終わりがゆっくりな動き

  • なめらかな停止モーションを実現

このように、扉の動きに必要な全てのパラメータをひとつのキーフレームとして定義しています。

`return array: keyframe_delta:`の構文

  1. `return array:`が必要な理由:

  • 関数の戻り値の型が`[]keyframe_delta`(キーフレームの配列)と定義されているため

  • `array:`は新しい配列を作成することを示す

  • キーフレームのデータを配列として返す必要がある

  1. `keyframe_delta:`が必要な理由:

  • `keyframe_delta`は、アニメーションの1フレームの情報を格納する構造体

  • 以下の要素を含む構造体を定義する必要がある:

    • `DeltaLocation`: 位置の変化

    • `DeltaRotation`: 回転の変化

    • `DeltaScale`: スケールの変化

    • `Time`: アニメーション時間

    • `Interpolation`: 補間方法

  1. 構文の階層構造:

return array:          # 配列を返す
    keyframe_delta:    # 配列の各要素がkeyframe_deltaタイプ
        # キーフレームの具体的なデータ

この構文は、Verseの型システムに従って、正しい形式でアニメーションデータを構築し返すために必要な記述方法となっています。

キーフレームを複数設定する場合

MakeKeyFrameDelta():[]keyframe_delta=
    return array:
        # 1つ目のキーフレーム
        keyframe_delta:
            DeltaLocation := vector3 {X:= 0.0, Y:= 0.0, Z:= 0.0}
            DeltaRotation := MakeRotationFromYawPitchRollDegrees(90.0, 0.0, 0.0)
            DeltaScale := vector3 {X:=1.0, Y:=1.0, Z:=1.0}
            Time := 1.0
            Interpolation := EaseOut
            
        # 2つ目のキーフレーム
        keyframe_delta:
            DeltaLocation := vector3 {X:= 0.0, Y:= 0.0, Z:= 50.0}
            DeltaRotation := MakeRotationFromYawPitchRollDegrees(45.0, 0.0, 0.0)
            DeltaScale := vector3 {X:=1.0, Y:=1.0, Z:=1.0}
            Time := 2.0
            Interpolation := EaseIn

理由:

  1. 配列内の要素は順序付けられており、インデックスから始まります

  2. アニメーションコントローラーは配列の順序通りに処理を行います

  3. `keyframe_delta`は時系列で実行される独立したフレーム情報として扱われます

そのため、PCはキーフレームの順序を明確に理解し、順番通りにアニメーションを実行することができます。

PCはキーフレームの順序を明確に理解できる

はい、その通りです!配列の仕組みについて詳しく説明します。

MakeKeyFrameDelta():[]keyframe_delta=
    return array:
        # 1つ目のキーフレーム (インデックス0)
        keyframe_delta:
            DeltaLocation := vector3 {X:= 0.0, Y:= 0.0, Z:= 0.0}
            DeltaRotation := MakeRotationFromYawPitchRollDegrees(90.0, 0.0, 0.0)
            DeltaScale := vector3 {X:=1.0, Y:=1.0, Z:=1.0}
            Time := 1.0
            Interpolation := EaseOut
            
        # 2つ目のキーフレーム (インデックス1)
        keyframe_delta:
            DeltaLocation := vector3 {X:= 0.0, Y:= 0.0, Z:= 50.0}
            DeltaRotation := MakeRotationFromYawPitchRollDegrees(45.0, 0.0, 0.0)
            DeltaScale := vector3 {X:=1.0, Y:=1.0, Z:=1.0}
            Time := 2.0
            Interpolation := EaseIn

配列の処理順序が保証される理由:

  1. インデックスベース

  • 配列内の各要素には0から始まる連番のインデックスが割り当てられる

  • この例では、1つ目のキーフレームは``、2つ目は`[1]`となる

  1. 順序の保証

KeyframeReachedEvent<native><public>:listenable(tuple(int, logic))
  • アニメーションコントローラーは配列のインデックス順に処理を行う

  • `KeyframeReachedEvent`でキーフレームのインデックスを追跡可能

  1. 時系列の制御

  • 各キーフレームの`Time`パラメータにより、アニメーションの実行時間を制御

  • 配列の順序とTimeパラメータにより、正確なアニメーションの順序が保証される

このように、配列のインデックスとアニメーションシステムが連携して、意図した通りの順序でアニメーションが実行されます。

`AController.AwaitNextKeyframe()`が必要な理由

  1. アニメーションの完了を待機

  • アニメーションが完了するまで次の処理を待機させる

  • 非同期処理(`<suspends>`)の同期ポイントとして機能

  1. 処理の順序制御

  • アニメーションが完全に終了する前に次の処理が実行されることを防ぐ

  • 例:複数のアニメーションを連続して実行する場合に、前のアニメーションの完了を確実に待つ

  1. エラーハンドリング

  • アニメーションが正常に完了したか、中断されたかを確認できる

  • `await_next_keyframe_result`を返し、以下の状態を確認可能:

    • `KeyframeReached`:次のキーフレームに到達

    • `NotPlaying`:アニメーション未実行

    • `AnimationAborted`:アニメーション中断

  1. リソース管理

  • アニメーション完了前に他の処理が実行されることによる予期せぬ動作を防ぐ

  • システムリソースの適切な管理を確保

この待機処理がないと、アニメーションの完了を確認できず、次の処理との同期が取れなくなる可能性があります。