Loading... # UE5 中用 Python 接口创建 Level Sequence 与设置 TriggerEvent 本文内容可能只能在 UE5 下有用,未在 UE4 环境下实验过。 ## 背景 遇到了一个美术需求,需要批量读取一段动画,制作成 UE 中的 Level Sequence,然后给动画添加几个 Event Track。随后,需要在 Event Track 中添加 Trigger Event,设置插件 uDraper 布料的缓存数据路径。总之,最终效果如下: ![20221017111235](https://images-1300215216.cos.ap-guangzhou.myqcloud.com/Blog/20221017111235.png) _无视上图中红色的报错部分,这是因为我截图的时候没有打开对应的关卡,Sequence 找不到相关的引用。打码部分是动画名字,工程内容不太方便暴露所以打码_ 至于为什么非得要用 Event Track 来设置路径,而不是在 Actor 的 Component 相关属性中直接设置路径,然后添加到 Sequence 中,只能说这是 uDraper 插件的问题,直接设置会弹出个弹窗说“路径缺少 xxxx 文件”(因为该路径只有缓存数据而没有布料相关的数据),但是如果在 Event Track 中通过 Event 帧调用函数`Cache(缓存路径)`设置就没这个问题,能够正常读取缓存文件。因此,就只能这么干了。 可能有点绕,其实就是我需要在动画的第一帧调用 uDraper 提供的蓝图函数 `Cache`,并传入`DirectoryPath`类型的对象来指定布料缓存数据路径。 另外,如果读者不太清楚或者没试过在 Level Sequence 中触发 Event,可以看看[官方介绍文档](https://docs.unrealengine.com/5.0/en-US/fire-blueprint-events-during-cinematics-in-unreal-engine/),里面详细说明了如何在 Sequence 中添加 Event 帧,在指定的帧调用函数,从而实现在某个特定时刻执行某种行为、打开门、生成角色等功能。此文档中的操作流程和我们在代码中相关流程是一致的,因此后面我不会解释代码中为什么会出现某个步骤。 ## Python 脚本 直接进入正题,我们先看一眼完整的 Python 脚本: ```Python def main(): # 拿到场景编辑 subsystem level_editor = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem) # 从关卡中获取 actor,注意获取的时候确认名字就叫这个,最好选中后用指令打印出来名字 # 因为在编辑器的场景预览中显示的名字不一定就是引擎设置的真名 actor = unreal.find_object(level_editor.get_current_level(), "guzhuang_C_1") camera_actor = unreal.find_object(level_editor.get_current_level(), "Camera_0") # 获取组件 cloth = unreal.find_object(actor, "cloth") # 这里 asset_list 是一个列表,里面全是 animation 的路径,类似于 /Game/Anim/dance.dance for i in asset_list: # 取个名字 cur_anim = i.split("/")[-1].split(".")[0] asset_tools = unreal.AssetToolsHelpers.get_asset_tools() # 传入参数创建新的 sequence,seq_path 是一个全局变量,配置了 sequence 的保存路径 lvl_seq = unreal.AssetTools.create_asset( asset_tools, asset_name=cur_anim, package_path=seq_path, asset_class=unreal.LevelSequence, factory=unreal.LevelSequenceFactoryNew()) # _fps 也是全局配置,保存了 sequence 的帧率 frame_rate = unreal.FrameRate(numerator=_fps, denominator=1) lvl_seq.set_display_rate(frame_rate) # 创建 actor binding actor_binding = lvl_seq.add_possessable(actor) # 添加场景中的 camera,其实可以在脚本中创建新的,但是我发现创建 camera 的话在脚本执行完后新建的 # Camera 会一直保留在场景中,所以最终还是选择直接用场景中现有的 camera cam_cut_track = lvl_seq.add_master_track( unreal.MovieSceneCameraCutTrack) cam_cut_section = cam_cut_track.add_section() # 添加 camera section cam_binding = lvl_seq.add_possessable(camera_actor) cam_binding_id = lvl_seq.make_binding_id( cam_binding, unreal.MovieSceneObjectBindingSpace.LOCAL) cam_cut_section.set_camera_binding_id(cam_binding_id) # 添加动画 loaded_asset = unreal.AnimSequence.cast( unreal.EditorAssetLibrary.load_asset(i)) frame_num = loaded_asset.get_editor_property( 'number_of_sampled_keys') frame_rate = loaded_asset.get_editor_property('target_frame_rate') # 设置 sequence 有内容的范围 lvl_seq.set_playback_start(0) lvl_seq.set_playback_end(frame_num) unreal.LevelSequenceEditorBlueprintLibrary.refresh_current_level_sequence() ### 添加动画 track ### anim_track = actor_binding.add_track( unreal.MovieSceneSkeletalAnimationTrack) # 添加动画 anim_section = anim_track.add_section() anim_section.set_range(0, frame_num) anim_section.params.animation = loaded_asset # 设置动画范围 cam_cut_section.set_range(0, frame_num) ### draper settings ### # cloth cloth_binding = lvl_seq.add_possessable(cloth) cloth_track = cloth_binding.add_track(unreal.MovieSceneEventTrack) cloth_section = cloth_track.add_event_trigger_section() cloth_section.set_range(0, frame_num) # 其实今天的重点在这里 cloth_channel = cloth_section.get_channels()[0] cloth_cache_binding = unreal.SequencerTools.create_quick_binding( lvl_seq, cloth, "Cache", False) cloth_new_event = unreal.SequencerTools.create_event(lvl_seq, cloth_section, cloth_cache_binding, ["(path=\"D:/UGit/XiaoLan_PC/content/XiaoLan/Effects/Character_Effects/BoWuGuan/ZhengChangShuoHua/cloth\")"]) cloth_channel.add_key(time=unreal.FrameNumber( 0), new_value=cloth_new_event) # 此处省略对 skirt 和 waist 的操作,因为和上面 cloth 的步骤一样 # 保存最终生成的 seq unreal.EditorAssetLibrary.save_loaded_asset(lvl_seq, False) unreal.log("=== INFO: Seq Creation is Completed ===") ``` 比较简短,但是我们还是来看看具体做了什么,以及涉及到的相关接口、类。 ### 查找场景中的 Actor 首先 `level_editor = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)`,这里通过`unreal.get_editor_susystem`函数([相关文档](https://docs.unrealengine.com/5.0/en-US/PythonAPI/module/unreal.html))获取了 Subsystem `unreal.LevelEditorSubsystem`([相关文档](https://docs.unrealengine.om/5.0/en-US/PythonAPI/class/LevelEditorSubsystem.html))。这里其实就是获取了场景编辑器 Subsystem 后面方便我们通过这个 subsystem 对场景中的 Actor 进行访问甚至修改。 顺带一提,其 Python 调用函数可以想象成在蓝图中调用函数,实际上确实也差不太多,都是通过反射实现的,所以蓝图能调用、访问 Python 都可以调用。 在获得了 Level Editor Subsystem 之后,我们就可以调用 `unreal.find_object` 函数,在当前打开了的场景中寻找到我们需要绑定到 sequence 的 actor 。这里需要注意一下,`find_object`中传入的 actor 名字一定要确认是引擎标识的名字,而不是在 Level Editor 中看到的名字(例如我遇到过在场景中物品名称叫做`guzhuang2`,实际上引擎中记载的名字是`guzhuang_C_1`),最好在场景编辑器中通过 Python(REPL),选中 actor 并执行: ```Python actor_system = unreal.get_editor_subsystem(unreal.EditorActorSubsystem) actor = actor_system.get_selected_level_actors()[0] print(actor) ``` 确认 actor 的真实名称以及其是否保存在当前场景中(有些 actor 看起来是在当前场景中实际上可能是别的场景的 actor 的引用,可能是因为直接复制了别的场景的 actor 并粘贴到当前场景下),如果名字不对或者不是保存在当前场景中那么无法通过上面的`unreal.find_object(level_editor.get_current_level(), "guzhuang_C_1")`方法找到需要的 actor。 除了寻找 actor,实际上`find_object`也可以用来获取 actor 身上挂载的组件,例如上方例程中就通过`find_object`来获取名为`cloth`的组件。 在获取到我们需要的 actor 之后就可以开始 sequence 的创建了。 ### 创建新的 sequence 其实就这部分: ```python asset_tools = unreal.AssetToolsHelpers.get_asset_tools() # 传入参数创建新的 sequence,seq_path 是一个全局变量,配置了 sequence 的保存路径 lvl_seq = unreal.AssetTools.create_asset( asset_tools, asset_name=cur_anim, package_path=seq_path, asset_class=unreal.LevelSequence, factory=unreal.LevelSequenceFactoryNew()) # _fps 也是全局配置,保存了 sequence 的帧率 frame_rate = unreal.FrameRate(numerator=_fps, denominator=1) lvl_seq.set_display_rate(frame_rate) ``` 先获取到 UE 辅助创建 asset 的工具类,直接`AssetTools.create_asset`并传入对应参数就行,这里没什么坑也没什么可以说的。创建好之后要注意`frame_rate`这块,一定要确保 sequence 的帧率是你想要的帧率。最后通过 LevelSequence 类函数`set_display_rate`设置帧率即可。 ### 添加 Binding、Track、动画 然后我们看回到一开始的 sequence 截图,对照着截图来看会更容易理解接下来要做的事情。 ![20221017111235](https://images-1300215216.cos.ap-guangzhou.myqcloud.com/Blog/20221017111235.png) 可以看到首先 Sequence 中会有一个对某个 actor 的引用,actor 下面有一个组件的引用(如 cloth 组件的引用),组件引用下面还有一个 Track;或者 actor 的引用下面就是直接一个 Track(如 Animation Track)。 因此对应的代码: ```python # 创建 actor binding actor_binding = lvl_seq.add_possessable(actor) # ... 省略一部分无关代码 for i in asset_list: # 取个名字 cur_anim = i.split("/")[-1].split(".")[0] # 省略创建 sequence 的代码... # 添加动画 loaded_asset = unreal.AnimSequence.cast(unreal.EditorAssetLibrary.load_asset(i)) frame_num = loaded_asset.get_editor_property('number_of_sampled_keys') frame_rate = loaded_asset.get_editor_property('target_frame_rate') # 设置 sequence 有内容的范围 lvl_seq.set_playback_start(0) lvl_seq.set_playback_end(frame_num) unreal.LevelSequenceEditorBlueprintLibrary.refresh_current_level_sequence() ### 添加动画 track ### anim_track = actor_binding.add_track(unreal.MovieSceneSkeletalAnimationTrack) # 添加动画 anim_section = anim_track.add_section() anim_section.set_range(0, frame_num) anim_section.params.animation = loaded_asset ``` 还是比较简单明了的,通过`unreal.EditorAssetLibrary.load_asset`加载指定路径`i`的资产,并转换为`AnimSequence`格式,读取动画长度并创建 track,这里需要指定 track 的类型,例如如果是控制动画的 track,那么给 actor binding 创建 track 的时候就要指定是 `unreal.MovieSceneSkeletalAnimationTrack`。创建 track 后给 track 添加 section,最后内容、有效区间都是要在 section 上进行设定。 对于本例子中 `cloth` 等组件过程也是类似的步骤: ```python # ... cloth = unreal.find_object(actor, "cloth") ### draper settings ### for i in asset_list: # 取个名字,这里用动画名字,实际上可以用别的名字 cur_anim = i.split("/")[-1].split(".")[0] # 省略上面提到过的部分 ... # cloth cloth_binding = lvl_seq.add_possessable(cloth) cloth_track = cloth_binding.add_track(unreal.MovieSceneEventTrack) cloth_section = cloth_track.add_event_trigger_section() cloth_section.set_range(0, frame_num) # 其实今天的重点在这里 cloth_channel = cloth_section.get_channels()[0] cloth_cache_binding = unreal.SequencerTools.create_quick_binding( lvl_seq, cloth, "Cache", False) cloth_new_event = unreal.SequencerTools.create_event(lvl_seq, cloth_section, cloth_cache_binding, ["(path=\"D:/UGit/XiaoLan_PC/content/XiaoLan/Effects/Character_Effects/BoWuGuan/ZhengChangShuoHua/cloth\")"]) cloth_channel.add_key(time=unreal.FrameNumber( 0), new_value=cloth_new_event) ``` 前面解释过的添加绑定、创建 track、section 部分这里跳过。重点主要在创建 `MovieSceneEvent` 帧这里。这里主要通过 `unreal.SequencerTools.create_quick_binding` 创建新的 `MovieSceneEvent` ,并且将此 Event 与传入的成员函数(要能在蓝图中调用的函数,这里也是借助了反射来找到要绑定的函数)进行绑定。顺带一提,这里绑定的是`cloth`的成员函数`Cache`,实际上也可以是其他类的成员函数。例如说我们的 Actor 有一个用来更新状态的成员函数`KillSelf`,那么可以变成`unreal.SequencerTools.create_quick_binding(lvl_seq, actor, "KillSelf", False)`,这里的 `actor` 是指向 Actor 的指针。`create_quick_binding` 函数的用法、参数可以在[官方文档](https://docs.unrealengine.com/5.0/en-US/PythonAPI/class/SequencerTools.html)查看。 创建了函数绑定之后就可以通过 `unreal.SequencerTools.create_event` 创建 `MovieSceneEvent` 了。比较值得注意的是 Payload 传入参数部分 `["(path=\"D:/UGit/XiaoLan_PC/content/XiaoLan/Effects/Character_Effects/BoWuGuan/ZhengChangShuoHua/cloth\")"]`。`Cache` 函数需要的参数是 `unreal.DirectoryPath` 类型的,而这里相当于通过传入 `unreal.DirectoryPath` 成员变量 `path` 的内容构造了一个 `unreal.DirectoryPath` 对象,并传入给 `Cache` 函数。 创建完成 `MovieSceneEvent` 后通过 `channel.add_key` 添加到 Track 中。上面提到的 `Payload` 的部分最终会通过 `ImportText` 函数处理,转换成 `unreal.DirectoryPath` 对象(所以如果你参数输入格式不对的话会提示 `ImportText xxxx 错误`)。最终结果: ![20221019093854](https://images-1300215216.cos.ap-guangzhou.myqcloud.com/Blog/20221019093854.png) 点开这些刚刚创建的帧,就会打开蓝图看到这个帧调用的函数: ![企业微信截图_16660116796034](https://images-1300215216.cos.ap-guangzhou.myqcloud.com/Blog/企业微信截图_16660116796034.png) 上面步骤完成后,`unreal.EditorAssetLibrary.save_loaded_asset(lvl_seq, False)` 保存即可(第二个参数是只有内容被更改后才保存,这里不做这种判断,直接保存。其实 Python 调用的 API 都可以参考蓝图的[文档](https://docs.unrealengine.com/5.0/en-US/BlueprintAPI/EditorScripting/Asset/SaveLoadedAsset/))。 ## 参考 1. [官方文档:Sequencer 概述](https://docs.unrealengine.com/5.0/zh-CN/unreal-engine-sequencer-movie-tool-overview/) 2. [官方文档:Sequencer 中的 Python 脚本](https://docs.unrealengine.com/5.0/zh-CN/python-scripting-in-sequencer-in-unreal-engine/):这个还是挺有用的,必看 3. [官方文档:Scripting the Unreal Editor Using Python](https://docs.unrealengine.com/5.0/en-US/scripting-the-unreal-editor-using-python/):必看,关于如何在编辑器中使用 UE 4. 官方例程,在`Engine\Plugins\MovieScene\MovieRenderPipeline\Content\Python`路径下、 5. [官方文档:unreal.MovieSceneEvent](https://docs.unrealengine.com/5.0/en-US/PythonAPI/class/MovieSceneEvent.html#unreal.MovieSceneEvent) 6. [官方文档:unreal 模块的 PythonAPI](https://docs.unrealengine.com/5.0/en-US/PythonAPI/module/unreal.html) 7. [UDN文档:UE4 Sequencer Python Cookbook](https://udn.unrealengine.com/s/article/UE4-Sequencer-Python-Cookbook):可能需要开发者账号,不过内容不多可以不看 8. [Python in Unreal Engine | Inside Unreal](https://www.youtube.com/watch?v=0guOMTiwmhk&ab_channel=UnrealEngine) 9. [PythonSamples](https://github.com/ue4plugins/PythonSamples) 10. [官方文档:Setting up Autocomplete for Editor Python Scripting](https://docs.unrealengine.com/4.27/en-US/ProductionPipelines/ScriptingAndAutomation/Python/Autocomplete/) 最后修改:2022 年 10 月 21 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 1 随缘
1 条评论