前言


 

之前做好了一个小Demo,但是打包后突然发现了点问题,就是打包后无法使用蓝图,就是在之前文章中使用LoadClass来动态创建Actor。后面自己琢磨了下解决办法就有了这篇文章。同样的C++基础的东西不会过多的讲解。这篇文章大多数都是我的脑洞,如果感觉有其他的方法你们也可以用你们的办法。

使用ConstructorHelpers::FClassFinder静态加载蓝图


既然动态创建不行那么要不试试静态方法?抱着这个想法我就开始尝试这个方法,先说结果,已经成功了,那么就来说下怎么实现的。

首先这个函数是只能在每个类的构造函数中使用。函数原型是

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的指针嘛,那我直接自己声明不就行了。不过嘛,我实验过后确实这个方法不好所以就当一个脑洞算了。