之前做好了一个小Demo,但是打包后突然发现了点问题,就是打包后无法使用蓝图,就是在之前文章中使用LoadClass来动态创建Actor。后面自己琢磨了下解决办法就有了这篇文章。同样的C++基础的东西不会过多的讲解。这篇文章大多数都是我的脑洞,如果感觉有其他的方法你们也可以用你们的办法。
既然动态创建不行那么要不试试静态方法?抱着这个想法我就开始尝试这个方法,先说结果,已经成功了,那么就来说下怎么实现的。
首先这个函数是只能在每个类的构造函数中使用。函数原型是
template struct FClassFinder : public FGCObject { TSubclassOf Class; FClassFinder(const TCHAR* ClassToFind) { CheckIfIsInConstructor(ClassToFind); FString PathName(ClassToFind); StripObjectClass(PathName, true); Class = ConstructorHelpersInternal::FindOrLoadClass(PathName, T::StaticClass()); ValidateObject(*Class, PathName, *PathName); } bool Succeeded() { return !!*Class; } virtual void AddReferencedObjects( FReferenceCollector& Collector ) override { UClass* ReferencedClass = Class.Get(); Collector.AddReferencedObject(ReferencedClass); Class = ReferencedClass; } virtual FString GetReferencerName() const override { return TEXT("FClassFinder"); } };
使用的话其实很简单的,大概就像这样
ConstructorHelpers::FClassFinderActor_BP(TEXT("你蓝图的路径"));
但是为了凑点字数(其实是因为这个流程挺有意思的),我还是想讲解下这个操作流程。重点就是这个部分
TSubclassOf Class; FClassFinder(const TCHAR* ClassToFind) { CheckIfIsInConstructor(ClassToFind); FString PathName(ClassToFind); StripObjectClass(PathName, true); Class = ConstructorHelpersInternal::FindOrLoadClass(PathName, T::StaticClass()); ValidateObject(*Class, PathName, *PathName); }
当输入了蓝图路径后会先调用CheckIfIsInConstructor函数来获取到你要的蓝图在这个游戏线程中的位置。这里稍微说下虚幻引擎的启动顺序,当资源加载完成的时候(比如你地图啊模型啊之类的)就会开始加载蓝图中这个时候就会调用蓝图/C++类的构造函数,这个也是为什么有时候编译完成后引擎会崩溃的问题。这个时候你的蓝图/C++类已经在游戏线程中了。
void ConstructorHelpers::CheckIfIsInConstructor(const TCHAR* ObjectToFind) { auto& ThreadContext = FUObjectThreadContext::Get(); UE_CLOG(!ThreadContext.IsInConstructor, LogUObjectGlobals, Fatal, TEXT("FObjectFinders can't be used outside of constructors to find %s"), ObjectToFind); }
FUObjectThreadContext::Get()继承于TThreadSingleton这个类中
OK,那么现在获取到了线程中的路径了,那么就要加载UClass类了,但是这里就是比较有意思的地方了。
Class = ConstructorHelpersInternal::FindOrLoadClass(PathName, T::StaticClass());
这里的话其实逻辑上就是一个StaticLoadClass的封装而已,那么我为什么说有趣呢,其实就在这个StaticLoadClass上,之前因为一直使用LoadClass也是没怎么在意,但是翻了下源代码后突然找到了个比较有意思的地方
UClass* StaticLoadClass( UClass* BaseClass, UObject* InOuter, const TCHAR* InName, const TCHAR* Filename, uint32 LoadFlags, UPackageMap* Sandbox ) { check(BaseClass); UClass* Class = LoadObject( InOuter, InName, Filename, LoadFlags, Sandbox ); if( Class && !Class->IsChildOf(BaseClass) ) { FFormatNamedArguments Arguments; Arguments.Add(TEXT("ClassName"), FText::FromString( Class->GetFullName() )); Arguments.Add(TEXT("BaseClassName"), FText::FromString( BaseClass->GetFullName() )); const FString Error = FText::Format( NSLOCTEXT( "Core", "LoadClassMismatch", "{ClassName} is not a child class of {BaseClassName}" ), Arguments ).ToString(); SafeLoadError(InOuter, LoadFlags, *Error); // return NULL class due to error Class = NULL; } return Class; }
注意到了这里了嘛
UClass* Class = LoadObject( InOuter, InName, Filename, LoadFlags, Sandbox );
在生成UClass的时候其实是从UObject中派生出来的,这里的话其实说实在的如果仔细看过UObject和UClass的关系的应该不会太惊讶,我感觉这个地方其实有点发挥的空间。不过嘛估计也是一些歪门邪道了。就当看个乐子吧。
官方推荐的话其实就是不要将蓝图带到C++中处理,而是用蓝图作为一个C++类,将C++作为一个函数嵌入到蓝图中使用,说直白点就是用C++写好代码块后在蓝图里连线。
这个其实是我和老王聊的时候突然想到的一个歪门邪道的办法,其实说到底,蓝图都是要编译成C++来运行的,那我为什么不将蓝图编译产生的代码文件拿来用呢。这里我就说个思路算了,因为确实有点长而且效果确实不怎么样。
蓝图的工具栏里有个将蓝图编译成C++的选项
编译成功后就会产生一个.h文件和一个.cpp文件将这个导入到你的代码中,因为虚幻引擎创建Actor实际上就是创建一个指向你Actor的指针嘛,那我直接自己声明不就行了。不过嘛,我实验过后确实这个方法不好所以就当一个脑洞算了。