UE开发的一点心得经验(二)

zhangly 2023-06-19 22:30:37
Categories: Tags:

前言

上期讲了怎么用Python去导入fbx或abc,怎么去找到并修改一个对象的属性等等。
这期是一些关于Sequencer的操作技巧。

大致内容有:

这个系列的主旨不是直接贴代码告诉结果,而是想更多分享过程上的东西。
有了chatGPT后,我们可能不再需要靠猜测推断来得知实现目标方法的代码(也许?)。

文章写于chatGPT前,所以是在一个没有chatGPT的角度下去处理和分析问题。

创建Sequencer

通过上一篇可以知道,sequencer是asset的一种,
所以搜索一下有没有create_asset相关的方法。

果然是有的,然后根据文档试一下。

首先看下create_asset的四个必要参数。

前面三个根据参数名和文档释义很容易看出来,依次是:asset名称,路径和asset类对象。
名称和路径都是字符串。
要得知asset_class要传什么对象进去,可以先手动创建一个LevelSequencer,调用__class__方法。

这里比较有疑问的是最后一个factory(工厂),不知道该传入什么参数。
先点进去看看它是个什么对象。

那直接把Factory代入进去试试(其实文档描述可以看出来,它应该是一个factory类型的基类)

asset_tools.create_asset('test_seq',
                         '/Game/test',
                         unreal.LevelSequence,
                         unreal.Factory())

果然执行报错了:Error: Exception: Factory: Class 'Factory' is abstract
既然它是一个抽象类(factory的基类),那么看看有没有LevelSequencer的Factory,搜索一下:

带入进去再试试:

unreal.AssetTools.create_asset('test_seq',
                               '/Game/test',
                               unreal.LevelSequence,
                               unreal.LevelSequenceFactoryNew())

还是报错了:TypeError: descriptor 'create_asset' requires a 'AssetTools' object but received a 'str'
不过这次没有说factory的参数问题,字面意思是’create_asset’需要一个’AssetTools’对象。

那试试先实例化呢asset_tools = unreal.AssetTools(),得到反馈说它也是个抽象类。
那直接在帮助文档搜索AssetTools看看,搜索结果中看到有个AssetToolsHelpers,看名称是个有关Asset_tools辅助类。

它可以返回AssetTools对象。

用它试试,果然就可以进行sequencer的创建了。

asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
sequence = asset_tools.create_asset('test_seq',
                                    '/Game/test',
                                    unreal.LevelSequence,
                                    unreal.LevelSequenceFactoryNew())
print(sequence)

其实对于这样的基础操作,更快捷的方式是在google上直接进行搜索“unreal how to create sequence with python”

我这里搜出来的第二个结果就可以看到:

这里是想分享一个"处理"问题的过程。

导入相机fbx到sequencer

需要执行这样一个操作,选择camera的fbx导入到sequencer中

用代码实现的话,首先想到的是从sequencer这个对象里找方法。
查看文档,没有能导入fbx的函数。于是又找到它的父级unreal.MovieSceneSequence,依旧是没有相关方法。

那就需要换个思路,在文档连续搜索了几个关键词:

在最后的关键词找到了一个SequencerTools.import_level_sequence_fbx,这个跟AssetToolsHelpers有点像。

有五个必要参数需要传入:

world 在上一篇获取所有Actor的例子中知道使用unreal.EditorLevelLibrary.get_editor_world()来获取,代表的是当前Game世界。
sequenceLevelSequence类对象,获取当前选择的asset对象就行。
bindings 不知道是个什么,好像是数组,这里暂时给个空列表试试。
import_fbx_settings 是导入的设置选项,这个在上一节也讲过类似的。
直接实例化MovieSceneUserImportFBXSettings,然后传参就行了。
import_filename 是路径字符串。
带入到代码中试试。

# 导入的摄像机fbx路径
file_path = r'C:\maya\projects\default\scenes\camera.fbx'
# 选择的sequence对象
sequence = unreal.EditorUtilityLibrary.get_selected_assets()[0]
# 当前World
world = unreal.EditorLevelLibrary.get_editor_world()
# 导入设置
import_options = unreal.MovieSceneUserImportFBXSettings()
import_options.set_editor_property('bReduceKeys', False)

# 执行导入操作
unreal.SequencerTools.import_level_sequence_fbx(world, sequence, [], import_options, file_path)

成功了,相机被导入到了Sequencer中。
不过到这里还是没有明白bindings参数会对导入产生什么影响,去文档里探索一下。
使用搜索大法:

这里看到一个眼熟的对象,也就是方才提到的LevelSequence的父级unreal.MovieSceneSequence,且有一个get_binding的方法。
打印一下看它是个什么:

sequence = unreal.EditorUtilityLibrary.get_selected_assets()[0]
bindings = sequence.get_bindings()
for i in bindings:
    print(type(i), i)

返回的是SequencerBindingProxy对象组成的数组。

再试试创建一个新的Sequencer,查看这个属性呢。
结果返回的是一个空列表[]。所以当Sequencer里面没有东西的时候,传入一个空列表是允许的。
如果要添加新的对象进去,可能就需要将binding先获取再传参给unreal.SequencerTools.import_fbx

到现在也没有弄清楚这个参数是什么意思,感兴趣的朋友可以研究研究,也欢迎大佬告知一下。

复制文本大法

前面都是讲的如何通过搜索和联想来找到所需要的方法和参数。
这里讲一个复制文本的技巧(脑洞来自于Nuke的编程思路)。

我当时遇到一个问题,如何在levelSequencer中添加actor,并为其添加动画。
在软件中,是这样操作:

得到这样的结果:

但作为TD得需要知道,如何用脚本去实现它。
第一步是找到如何添加actor到levelSequencer中,首先想到的是在unreal.LevelSequence 文档中找方法。

一眼望去没有相似的方法可以用,那么去它的父级看看

根据经验来说,添加一个什么东西,函数一般以add开头。
这个对象下大概有7个函数方法是以add开头的,看看每个函数的传参可以过滤出来,
下面两个比较像是能够传入一个actor对象。

那么从第一个add_possessable方法开始尝试一下


# 获取选择的rig actor
rig_actor = unreal.EditorLevelLibrary.get_selected_level_actors()[0]

# 获取选择的sequencer
sequencer = unreal.EditorUtilityLibrary.get_selected_assets()[0]

# 为level sequencer添加actor
sequencer.add_possessable(rig_actor)

添加成功了!
不过感觉少了点什么。

和手动添加的对比,发现少了TransfromAnimation
发现man_rig后有个小+号,点击+号可以添加它们。
并且知道了这个东西叫作Track

那怎么用代码去添加这个Track,找到刚刚的函数,
返回一个叫MovieSceneBindingProxy的对象

发现这个对象有个add_track的方法,参数是track_type类型

找到了函数方法,那么要传什么参数类型进去,这个类型当时困扰了一会。
后来脑洞大开,想到unreal会不会可以像nuke一样,复制这个”节点“,然后粘贴到文本编辑器,能看到这个”节点“的参数细节。

于是我先手动创建了一个 Transfrom Track,并选中它,按下 Ctrl + C
打开文本编辑器,Ctrl + V

于是在第二行找到了这个对象,去文档搜索发现是有的:

修改代码:

rig_actor = unreal.EditorLevelLibrary.get_selected_level_actors()[0]
sequencer = unreal.EditorUtilityLibrary.get_selected_assets()[0]

possessable = sequencer.add_possessable(rig_actor)

# 添加 Transform track
transform_track = possessable.add_track(track_type=unreal.MovieScene3DTransformTrack)

执行后发现,虽然有 Transfrom Track了,但依旧比手动添加时,少一些元素,需要再点击Section按钮添加。

这个问题也好解决,根据之前的经验找对应的文档方法就行了。

添加完Section,发现时间范围没有被设置,从文档中找到set_range方法。

完整的代码如下

# 获取 asset和actor对象
rig_actor = unreal.EditorLevelLibrary.get_selected_level_actors()[0]
sequencer = unreal.EditorUtilityLibrary.get_selected_assets()[0]

# 将actor添加到level sequencer中
possessable = sequencer.add_possessable(rig_actor)

# 添加 Transform track
transform_track = possessable.add_track(track_type=unreal.MovieScene3DTransformTrack)

# 添加 section
transform_section = transform_track.add_section()
transform_section.set_range(1, 120)

最后是添加 Animation Track,通过复制文本得知track对象名称

那么代码应该就是这样的

ani_track = possessable.add_track(track_type=unreal.MovieSceneSkeletalAnimationTrack)
ani_section = ani_track.add_section()

那如何把动画资产给添加进去

一开始是在unreal.MovieSceneSkeletalAnimationTrackunreal.MovieSceneSkeletalAnimationSection对象中找方法,
但是好像没有一个可以去设置动画asset的函数。

于是乎又开脑洞,手动添加一下动画asset,复制文本。
与之前没有动画asset的文本进行对比看看。

找到了 /Game/test/man_ani.man_ani 这个资产是被放在Params里的。
这样的话,MovieSceneSkeletalAnimationSection里就有一个params的属性。

接下来的事情就轻车熟路了。
找到MovieSceneSkeletalAnimationParams文档,实例化它,赋予属性。

完整的添加Animated代码如下


# 创建 track和section
ani_track = possessable.add_track(track_type=unreal.MovieSceneSkeletalAnimationTrack)  
ani_section = ani_track.add_section()

# 获取选中的动画资产
ani_asset = unreal.EditorUtilityLibrary.get_selected_assets()[0]

# 实例化params对象
params = unreal.MovieSceneSkeletalAnimationParams()  
params.set_editor_property('Animation', ani_asset)

# 添加asset资产,修改时间范围
ani_section.set_editor_property('Params', params)
ani_section.set_range(1, 120)

以上就是通过复制文本来获取关键信息,从而找到实现功能的函数模块。

总结

之前使用Python对UE开发,因为文档比较难以阅读,网上的资料也很少,大多命令方法都要靠推断和尝试。

如今有了chatGPT以及一段时间的过渡,一些简单功能的实现可以直接从Google和chatGPT获得答案。当然chatGPT的答案准确率依然不高,但它能给予提示和灵感,或是一个寻找答案的方向。

最近的工作暂时告别UE开发了,对于UE更深度的开发使用C++更适合一些(正在学)。
那这个系列基本结束,感谢阅读和支持。

(完)