前言
最近有个需求,需要在工具里面调用 EditorUtilityBlueprint 写好的功能。 于是踩坑回顾一下。
call_method 调用内部函数
蓝图里面添加了连接好的函数 Function ,比如如下图所示
如何通过 Python 来调用这个自定义的函数呢?
1 2 3 4 5 6 7 bp, = unreal.EditorUtilityLibrary.get_selected_assets() bp_path = bp.get_path_name() gc = unreal.load_object(None , "%s_C" % bp_path) cdo = unreal.get_default_object(gc) cdo.call_method("TestCall" ,args=(unreal.World(),))
经过我的测试,可以利用 _ObjectBase 内置的 call_method
调用对象内置的方法 但是这个方案有很多问题,上面只是一个简单的蓝图连接,就需要在 args 上加上莫名奇妙的 对象补充才能正常运行函数。
否则会提示缺少参数而无法执行。 如果蓝图连接得非常复杂的化,会更加麻烦,需要填充非常多的参数才可以。 于是出于好奇,我就去找了源码
从源码上可以看到是参数的识别出问题了。 因此如果用纯 Python 调用,需要解决大量的参数调用,会非常地麻烦,唯一的好处是可以不用写 C++
UFunction 获取
既然这个方法不同,于是我想到了之前的思路,先找一下有没有 Function 相关的 UObject 操作链接 通过这个方法可以获取, Unreal 里面完整的 UObject 列表 链接
1 2 3 4 5 6 7 8 [ "/Engine/Transient.REINST_NewEditorUtilityBlueprint_C_229:call" , "/RedArtToolkit/Resources/UVCapture/NewFolder/NewEditorUtilityBlueprint.NewEditorUtilityBlueprint_C:TestCall" , "/Engine/Transient.REINST_NewEditorUtilityBlueprint_C_229:TestCall" , "/RedArtToolkit/Resources/UVCapture/NewFolder/NewEditorUtilityBlueprint.NewEditorUtilityBlueprint_C:call" ]
从上面的路径的确可以获取到对应函数命名的 UObject 用 Python 加载试试。
1 2 3 4 5 6 7 import unrealobj = unreal.load_object(None ,"/RedArtToolkit/Resources/UVCapture/NewFolder/NewEditorUtilityBlueprint.NewEditorUtilityBlueprint_C:TestCall" ) print (obj)print (dir (obj))['__class__' , '__delattr__' , '__dir__' , '__doc__' , '__eq__' , '__format__' , '__ge__' , '__getattribute__' , '__gt__' , '__hash__' , '__init__' , '__init_subclass__' , '__le__' , '__lt__' , '__ne__' , '__new__' , '__reduce__' , '__reduce_ex__' , '__repr__' , '__setattr__' , '__sizeof__' , '__str__' , '__subclasshook__' , '_post_init' , '_wrapper_meta_data' , 'call_method' , 'cast' , 'get_class' , 'get_default_object' , 'get_editor_property' , 'get_fname' , 'get_full_name' , 'get_name' , 'get_outer' , 'get_outermost' , 'get_path_name' , 'get_typed_outer' , 'get_world' , 'modify' , 'rename' , 'set_editor_properties' , 'set_editor_property' , 'static_class' ]
可以看到获取到了一个 Function 对象,然而神奇的是 文档里面只有 FunctionDef 对象,并没有 unreal.Function 的说明 尝试用 dir
打印 Function 对象提供的函数,然而也只能获取到 UObject 的白板方法。 经过网上查阅,可以知道这个就是 C++ 的 UFunction 对象。 官方文档 看来要实现 UFunction 的调用只能借助 C++ 了
UFunction 调用
如何才能解决 UFunction 的调用呢? 我想到的还是去抄 Unreal 的源码,最容易想到就是 EditorUtilityObject 官方的直播和文档里面都有提到,如果想要用蓝图扩展右键菜单,可以用 EditorUtilityBlueprint 继承相应的对象。
只要继承 AssetActionUtility 对象,就可以在右键菜单的 Scripted Action 里面调用蓝图相关的功能。 官方文档教程 相必,可以查 C++ 源码知道里面的函数是怎么被调用的。
经过一番努力,我定位到代码出发的关键在 ProcessEvent 上。 于是基于上面的代码,我可以封装一个蓝图库函数,来实现蓝图函数的调用。
1 2 3 4 5 6 7 8 9 10 11 void UPyToolkitBPLibrary::RunFunction (UObject *CDO, UFunction *Function) { UObject *TempObject = NewObject <UObject>(GetTransientPackage (), Cast <UObject>(CDO)->GetClass ()); TempObject->AddToRoot (); FScopedTransaction Transaction (NSLOCTEXT("UnrealEd" , "BlutilityAction" , "Blutility Action" )) ; FEditorScriptExecutionGuard ScriptGuard; TempObject->ProcessEvent (Function, nullptr ); TempObject->RemoveFromRoot (); }
类似于上面的蓝图库函数。 Python 的这边就要获取 蓝图的 CDO 对象和 UFunction 对象,然后传入到 RunFunction 的 C++ 调用即可。
1 2 3 4 5 6 7 8 9 10 11 12 import unrealpy_lib = unreal.PyToolkitBPLibrary func_name = "TestCall" bp, = unreal.EditorUtilityLibrary.get_selected_assets() gc_path = "%s_C" % bp.get_path_name() gc = unreal.load_object(None , gc_path) cdo = unreal.get_default_object(gc) func = unreal.load_object(None ,"%s:%s" % (gc_path,func_name)) py_lib.run_function(cdo,func)
通过上面的方式就可以实现直接调用蓝图内部函数了。 实测只有 EditorUtilityBlueprint 的对象可以起作用,如果是普通的蓝图还无法调用。
总结
我之前还花了不少时间,研究有没有可能通过 inspect 或者 异常处理 来实现纯 Python 的调用。 然而经过我的测试 inspect 获取参数是不行的,毕竟不是 Python 的原生函数。 利用异常可以解决部分问题,但是需要解析每个报错的信息,然后再将对应的参数提供到 args 里面 如果是简单的蓝图连接还能应付,如果很复杂的话,需要异常出发很多次来处理出一个正确的参数序列,最后我还是放弃了。 实践的部分代码在我的 CodeBase 里面 链接