Pythonからジオメトリキャッシュを登録する


この記事は[Unreal Engine (UE) Advent Calendar 2024](https://qiita.com/advent-calendar/2024/ue)の3日目の記事です。


UnrialEngineでPythonをさわる機会があり、少し勉強してみました。
折角なので、何かテーマを決めようと思い、レベルシーケンスにスケルタルメッシュを登録するサンプルはありますが、ジオメトリキャッシュを登録するサンプルは無かった為、今回は、それを作成してみました。


# 確認環境
Windows 11 Pro
UnrealEngine 5.3.2

# Pythonサンプル
Pythonサンプルは、以下のフォルダにあります。

```
\エンジンルート\Engine\Plugins\MovieScene\SequencerScripting\Content\Python
```

サンプルは以下のものが用意されています。
Pythonの書き方やクラスの使い方の雰囲気は、読み取れると思います。

- sequencer_anim_examples.py
- sequencer_examples.py
- sequencer_fbx_examples.py
- sequencer_key_examples.py
- sequencer_tools_examples.py



# 作成したスクリプト

ここからが、本題です。
サンプルとして作成したものは以下のスクリプトです。
.pyにそのままコピペして使用してもらえれば、動きます。

:::note warn
ジオメトリキャッシュは、事前にプロジェクト内に作成しておいてください。
:::

```:geometrycache_examples.py

import unreal

# Name   : bind_geometrycache
# Param1 : level_sequence       // LevelSequence class
# Param1 : geometry_cache_asset // GeometryCache class
def bind_geometrycache(level_sequence, geometry_cache_asset):
    unreal.log(unreal.SystemLibrary.get_display_name(level_sequence))
    unreal.log(unreal.SystemLibrary.get_display_name(geometry_cache_asset))

    # check LevelSeqeunce
    if level_sequence != None:
        level_sequence = unreal.LevelSequenceEditorBlueprintLibrary.get_current_level_sequence()

    # LevelSequence framerate
    fps_rate = 24
    
    # main
    if level_sequence != None:
        unreal.log(unreal.SystemLibrary.get_display_name(level_sequence))
        # levelsequence param
        level_sequence.set_display_rate(unreal.FrameRate(fps_rate,1))
        
        # Bind Class
        if geometry_cache_asset != None:
            possessable = level_sequence.add_spawnable_from_instance(geometry_cache_asset)
            
            # 
            # Add geometrycache track
            # 
            geometrycache_track = possessable.add_track(unreal.MovieSceneGeometryCacheTrack)
            start_frame = geometry_cache_asset.get_editor_property('start_frame')
            end_frame   = geometry_cache_asset.get_editor_property('end_frame')
            geometrycache_section = geometrycache_track.add_section()
            # MovieSceneGeometryCacheParams Document
            # https://dev.epicgames.com/documentation/en-us/unreal-engine/python-api/class/MovieSceneGeometryCacheParams?application_version=5.4#unreal.MovieSceneGeometryCacheParams
            params = unreal.MovieSceneGeometryCacheParams()
            # The animation this section plays
            params.set_editor_property("geometry_cache_asset",geometry_cache_asset)
            # The offset for the first loop of the animation clip
            # params.set_editor_property("first_loop_start_frame_offset ", unreal.FrameNumber(0)) # 5.3 runtime error
            params.first_loop_start_frame_offset = unreal.FrameNumber(0)
            # The offset into the beginning of the animation clip
            # params.set_editor_property("start_frame_offset ", unreal.FrameNumber(0)) # 5.3 runtime error
            params.start_frame_offset = unreal.FrameNumber(0)
            # The offset into the end of the animation clip
            # params.set_editor_property("end_frame_offset ", unreal.FrameNumber(0)) # 5.3 runtime error
            params.end_frame_offset = unreal.FrameNumber(0)
            # The playback rate of the animation clip
            # params.set_editor_property("play_rate ", 1) # 5.3 runtime error
            params.play_rate = 1
            # Reverse the playback of the animation clip
            # params.set_editor_property("reverse ", False) # 5.3 runtime error
            params.reverse = False
            geometrycache_section.set_editor_property("Params", params)
            geometrycache_section.set_range(start_frame, end_frame)
            
            # 
            # Add transform track
            # 
            transform_track = possessable.add_track(unreal.MovieScene3DTransformTrack)
            transform_section = transform_track.add_section()
            transform_section.set_range(start_frame, end_frame)

        # refresh levelsequence
        unreal.LevelSequenceEditorBlueprintLibrary.refresh_current_level_sequence()
```

### 説明
- 基本的には、『**Spawnable** ➡ **トラックの追加** ➡ **セクションの追加** ➡ **パラメータの設定**』という流れです
- スケルタルメッシュにしろ、ジオメトリキャッシュにしろ、他のトラックにしろ、流れは変わらないですが、設定する『**トラック**』と『**セクション**』と『**パラメータ**』が変わってきます
- 一つ注意して欲しい点は、『**トラックの追加関数**は、**トラックの追加**しかされない』『セクションの追加は、**セクションの追加関数**しかされない』されません。何気なく行っている**ドラック&ドロップでレベルシーケンスにアクターを配置する**事を再現するだけでも、**結構コードを書く**必要があります。




# スクリプトの動かし方

スクリプトの呼び出しは、『**ExecutePythonScript**』で行っています。
**『level_sequence』には、レベルシーケンス**を、**『geometry_cache_asset』には、ジオメトリキャッシュのオブジェクト**を渡してください。 

![ブループリントの設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/b17f0b1f-2b83-cd8f-0ea0-f9c488492a5a.png)

ブループリントの全体だと以下のようなになります。
『CacheGeometryCache』は、ジオメトリキャッシュタイプです。

![サンプルのブループリント.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/2254c274-04a2-f86d-fed1-878420faa81b.png)

:::note info
上の写真はWidgetのブループリントになります。
:::


# 注意点
UnrealPythonをさわる上での注意点です。

- **プラグインの追加**
プラグインの追加に関しては、[こちらの記事](https://dev.epicgames.com/documentation/ja-jp/unreal-engine/scripting-the-unreal-editor-using-python)を参考にしてください。

- **Pythonを認識させ方**
デフォルトのパスについては、[こちらの記事](https://historia.co.jp/archives/10493/)を見てもらうのが良いかと思います。
『.py』をパスのフォルダにおいても、認識されず、以下のエラーが発生します。

```
LogScript: Error: Script Msg: Traceback (most recent call last):
  File "<string>", line 1, in <module>
ModuleNotFoundError: No module named 'ActasPython'
LogScript: Error: Script call stack:
    /Game/Test.Test_C.ExecuteUbergraph_Test <---
    /Game/Test.Test_C.BndEvt__Test_EditorUtilityButton_0_K2Node_ComponentBoundEvent_0_OnButtonClickedEvent__DelegateSignature
LogBlueprintUserMessages: [Test_C_10] @@@ Success
```

その為、一度、エディタを再起動してください。再起動すると、.pycが作成され、スクリプトが認識されます。

- **スクリプトの修正**
スクリプトを修正したのに、反映されていないと思ったら、エディタを立ち上げ直してください。
私が詳しくないだけですが、ホットリロードができるかも知れません。

- 名前の取得

私は、デバッグで、ログに名前をよく出力するのですが、『GetDisyplayName』『GetObjectName』を取るのか最初分かりませんでした。

- **親子関係**
クラスの親子関係は、以下のようになっており、ほとんどのクラスの基底クラスは『_ObjectBase』となっているようです。

```
#基底クラスは、_ObjectBase
_ObjectBase → Object → StreamableRenderAsset → SkinnedAsset → SkeletalMesh
_ObjectBase → ActorComponent → SceneComponent → PrimitiveComponent → MeshComponent → SkinnedMeshComponent → SkeletalMeshComponent
_ObjectBase → Object → AnimationAsset → AnimSequenceBase → AnimSequence
_ObjectBase → Object → Actor
_ObjectBase → Object → MovieSceneSignedObject → MovieSceneSequence

#これだけ違う
object → _WrapperBase → StructBase → MovieSceneBindingProxy
```

# 最後に

Pythonを丁度さわっていたので、今回はUnrealPythonの記事を投稿してみました。
[シリーズ3の5日目の記事]()にブループリントで同じことをやる場合の記事を投稿します。興味ある方は、そちらも見て、Pythonとブループリントでの書き方の違いを比較してください。


# 参考URL

- [[UE4] Pythonを使って、開発を自動化してみよう！](https://historia.co.jp/archives/10493/)
- [【UE4】【Python】4.26からPythonPluginがPython3.7.7に対応したので色々まとめてみた　【★★～★★★★】](https://kinnaji.com/2021/01/08/unrealpythonsummary/)
- [UE4 Sequencer Python Cookbook](
https://dev.epicgames.com/community/learning/knowledge-base/0qK6/unreal-engine-ue4-sequencer-python-cookbook?locale=ja-jp)
- [Python を使用した Unreal Editor のスクリプティング](https://dev.epicgames.com/documentation/ja-jp/unreal-engine/scripting-the-unreal-editor-using-python)
- [Python Scripting in Sequencer](https://dev.epicgames.com/documentation/en-us/unreal-engine/python-scripting-in-sequencer-in-unreal-engine)
- [Unreal Python API Documentation](https://dev.epicgames.com/documentation/en-us/unreal-engine/python-api/index?application_version=5.4)



明日は、**alwei**さんです。よろしくお願いいたします！