Python插件
放在Python文件夹中的init_unreal.py会在编辑器启动时自动加载,官方也给py提供了一些ue的api和装饰器,可以通过这些api来让编辑器生成对应的UClass数据,这也是可以用Python编写编辑器蓝图函数库的基础。
其中一部分unreal.py中的py侧装饰器:
def uclass(): '''decorator used to define UClass types from Python''' def _uclass(type): generate_class(type) return type return _uclass
通过这部分代码可以看出@unreal.uclass解释器执行了generate_class函数,可以跟踪找到在c++插件中的一写代码:
PyMethodDef PyCoreMethods[] = { { "log", PyCFunctionCast(&Log), METH_VARARGS, "x.log(str) -> None -- log the given argument as information in the LogPython category" }, { "log_warning", PyCFunctionCast(&LogWarning), METH_VARARGS, "x.log_warning(str) -> None -- log the given argument as a warning in the LogPython category" }, { "log_error", PyCFunctionCast(&LogError), METH_VARARGS, "x.log_error(str) -> None -- log the given argument as an error in the LogPython category" }, { "log_flush", PyCFunctionCast(&LogFlush), METH_NOARGS, "x.log_flush() -> None -- flush the log to disk" }, { "reload", PyCFunctionCast(&Reload), METH_VARARGS, "x.reload(str) -> None -- reload the given Unreal Python module" }, { "load_module", PyCFunctionCast(&LoadModule), METH_VARARGS, "x.load_module(str) -> None -- load the given Unreal module and generate any Python code for its reflected types" }, { "new_object", PyCFunctionCast(&NewObject), METH_VARARGS | METH_KEYWORDS, "x.new_object(type, outer=Transient, name=Default, base_type=Object) -> Object -- create an Unreal object of the given class (and optional outer and name), optionally validating its type" }, { "find_object", PyCFunctionCast(&FindObject), METH_VARARGS | METH_KEYWORDS, "x.find_object(outer, name, type=Object, follow_redirectors=True) -> Object -- find an already loaded Unreal object with the given outer and name, optionally validating its type" }, { "load_object", PyCFunctionCast(&LoadObject), METH_VARARGS | METH_KEYWORDS, "x.load_object(outer, name, type=Object, follow_redirectors=True) -> Object -- load an Unreal object with the given outer and name, optionally validating its type" }, { "load_class", PyCFunctionCast(&LoadClass), METH_VARARGS | METH_KEYWORDS, "x.load_class(outer, name, type=Object) -> Class -- load an Unreal class with the given outer and name, optionally validating its base type" }, { "find_asset", PyCFunctionCast(&FindAsset), METH_VARARGS | METH_KEYWORDS, "x.find_asset(name, type=Object, follow_redirectors=True) -> Object -- find an already loaded Unreal asset with the given name, optionally validating its type" }, { "load_asset", PyCFunctionCast(&LoadAsset), METH_VARARGS | METH_KEYWORDS, "x.load_asset(name, type=Object, follow_redirectors=True) -> Object -- load an Unreal asset with the given name, optionally validating its type" }, { "find_package", PyCFunctionCast(&FindPackage), METH_VARARGS, "x.find_package(name) -> Package -- find an already loaded Unreal package with the given name" }, { "load_package", PyCFunctionCast(&LoadPackage), METH_VARARGS, "x.load_package(name) -> Package -- load an Unreal package with the given name" }, { "get_default_object", PyCFunctionCast(&GetDefaultObject), METH_VARARGS, "x.get_default_object(type) -> Object -- get the Unreal class default object (CDO) of the given type" }, { "purge_object_references", PyCFunctionCast(&PurgeObjectReferences), METH_VARARGS | METH_KEYWORDS, "x.purge_object_references(obj, include_inners=True) -> None -- purge all references to the given Unreal object from any living Python objects" }, { "generate_class", PyCFunctionCast(&GenerateClass), METH_VARARGS, "x.generate_class(type) -> None -- generate an Unreal class for the given Python type" }, { "generate_struct", PyCFunctionCast(&GenerateStruct), METH_VARARGS, "x.generate_struct(type) -> None -- generate an Unreal struct for the given Python type" }, { "generate_enum", PyCFunctionCast(&GenerateEnum), METH_VARARGS, "x.generate_enum(type) -> None -- generate an Unreal enum for the given Python type" }, { "flush_generated_type_reinstancing", PyCFunctionCast(&FlushGeneratedTypeReinstancing), METH_NOARGS, "x.flush_generated_type_reinstancing() -> None -- flush any pending reinstancing requests for Python generated types" }, { "get_type_from_class", PyCFunctionCast(&GetTypeFromClass), METH_VARARGS, "x.get_type_from_class(class) -> type -- get the best matching Python type for the given Unreal class" }, { "get_type_from_struct", PyCFunctionCast(&GetTypeFromStruct), METH_VARARGS, "x.get_type_from_struct(struct) -> type -- get the best matching Python type for the given Unreal struct" }, { "get_type_from_enum", PyCFunctionCast(&GetTypeFromEnum), METH_VARARGS, "x.get_type_from_enum(enum) -> type -- get the best matching Python type for the given Unreal enum" }, { "register_python_shutdown_callback", PyCFunctionCast(&RegisterPythonShutdownCallback), METH_VARARGS, "x.register_python_shutdown_callback(callable) -> _DelegateHandle -- register the given callable (with no input arguments) as a callback to execute immediately before Python shutdown"}, { "unregister_python_shutdown_callback", PyCFunctionCast(&UnregisterPythonShutdownCallback), METH_VARARGS, "x.unregister_python_shutdown_callback(handle) -> None -- unregister the given handle from a previous call to register_python_shutdown_callback"}, { "NSLOCTEXT", PyCFunctionCast(&CreateLocalizedText), METH_VARARGS, "x.NSLOCTEXT(ns, key, source) -> Text -- create a localized Text from the given namespace, key, and source string" }, { "LOCTABLE", PyCFunctionCast(&CreateLocalizedTextFromStringTable), METH_VARARGS, "x.LOCTABLE(id, key) -> Text -- get a localized Text from the given string table id and key" }, { "is_editor", PyCFunctionCast(&IsEditor), METH_NOARGS, "x.is_editor() -> Bool -- tells if the editor is running or not" }, { "get_interpreter_executable_path", PyCFunctionCast(&GetInterpreterExecutablePath), METH_NOARGS, "x.get_interpreter_executable_path() -> str -- get the path to the Python interpreter executable of the Python SDK this plugin was compiled against" }, { nullptr, nullptr, 0, nullptr } };
将函数名与一个CFunction关联,在python调用该函数时解释器就会执行该c++函数。而generated_class函数被绑定到了GeneratedClass函数上。
Py脚本加载
我们可以在c++侧来获取到python侧已经加载并生成的uclass数据,在python的插件中,该UClass的类为UPythonGeneratedClass。
自己写的获取Python插件生成的Uclass:
static TArray<UClass*> GetPythonClasses() { TArray<UClass*> Ret; UClass* PythonGeneratedClass = FindObject<UClass>(ANY_PACKAGE, TEXT("PythonGeneratedClass")); if (!PythonGeneratedClass) return Ret; for (TObjectIterator<UClass> It; It; ++It) { if (UClass* Class = It->GetClass()) { if (Class == PythonGeneratedClass) { Ret.Add(*It); } } } return Ret; }
如果有获取不到的UClass实例,需要先确保该python类使用unreal.uclass进行修饰,并且该python文件被加载。可以通过init_unreal.py去import其他python来达到编辑器启动时初始化生成的目的。
虽然编辑器在启动后会执行Python文件夹内的init_unreal.py文件,但是当你编写插件时无论将插件的LoadingPhase设置为什么都无法通过这个函数获取到UClass,这是因为Python插件读取脚本的初始化操作并不是在引擎启动阶段完成的。可以通过查看代码,在PythonScriptPlugin.cpp中可以找到FTSTicker::GetCoreTicker().AddTicker([](){ Tick(); })的语句,翻看Tick代码可以发现,在第一次插件被Tick时才会去搜索和加载init_unreal.py文件,所以想获取到Py生成的UClass信息,最稳妥的办法就是等Tick开始后在去获取。
C++调用通过反射系统调用Python函数
在多数情况下c++执行Python脚本使用插件公开的接口就可以,但可以使用的接口基本只有通过文本来执行代码这种级别,没有对外开放更多的Python交互,这时我们想自由的调用Python脚本就需要手动处理这些情况。
想获取到Py类型的信息,首先就要在py类中使用@unreal.uclass @unreal.ufunction等装饰器才会生成相应数据。随后在C++中可以通过UClass使用名字获取到UFunction,然后执行UFunction->Invoke,反射系统会去执行UPythonGeneratedClass::CallPythonFunction函数(PyWrapperObject.cpp文件),该函数里主要是对类型与实例进行一些判断和处理,例如在py侧@unreal.ufunction没有将函数标记为static=True时,py函数需要一个self实例,而UPythonGeneratedClass::CallPythonFunction函数将会使用Invoke传入的第一个参数(UClass)来生成个临时对象,最后传入PyGenUtil::InvokePythonCallableFromUnrealFunctionThunk中,通过反射系统提供的数据与python解释器之间的桥接调用。
一段c++调用python函数获取字符串的代码:
Python:
import unreal @unreal.uclass() class Tools(unreal.BlueprintFunctionLibrary): @unreal.ufunction(ret = str, static=True) def GetStr(): return "ABCDEFG"
C++:
UClass* PyGenClass = GetPythonClasses()[0]; UFunction* Func = PyGenClass->FindFunctionByName("GetString"); uint8* Buffer = new uint8[Func->ParmsSize]; memset(Buffer, 0, Func->ParmsSize); FFrame Frame(nullptr, Func, Buffer, nullptr, Func->ChildProperties); Func->Invoke(PyGenClass, Frame, Buffer); FString Ret = *Func->FindPropertyByName("ReturnValue")->ContainerPtrToValuePtr<FString>(Buffer + Func->ReturnValueOffset); delete[] Buffer;
文章评论