前面几篇博客谈了几种常用的蓝图扩展方式,其中也对蓝图的底层机制进行了部分的解析,但是还不够整体。这篇文章谈一下目前我对蓝图技术架构的系统性的理解,包括蓝图从编辑到运行的整个过程。
蓝图的发展历程
蓝图是一个突破性的创新,它能够让游戏设计师亲手创造自己想要的“游戏体验”。使用可视化编程的方式,可以大大的加速那种“以体验为核心”的游戏开发的迭代速度,这是一次大胆的尝试,也是一次成功的尝试!(蓝图对于国内流行的那种“以数值成长为核心,以挖坑为目的”的游戏开发,可能没有那么大的意义)
就像很多其他的创新一样,它也是有一个渐进的过程的。它的萌芽就是Unreal Engine 3时代的Kismet。在Unreal Engine 3中,Unreal Script还是主要开发语言,但是可以使用Kismet为关卡添加可视化的事件处理脚本,类似于今天的Level Blueprint。
Unreal Engine 3 官方文档:Kismet Visual Scripting
Blueprint 这个名字很可能是UE4开发了一大半之后才定的。这就是为啥UE4源码里面那么多蓝图相关的模块都以Kismet命名,连蓝图节点的基类也是class UK2Node啦,又有少量模块用的是Blueprint这个名字,其实指代的都是同一系统。
以实例理解蓝图的整个机制
这篇博客的目的是把蓝图的整个体系结构完整的梳理一遍,但是如果只是讲抽象的框架的,会很枯燥,所以我打算以“案例分析”的方式,从一个最简单的蓝图入手,讲解每一步的实际机制是怎样的。
这个案例很简单
- 新建一个从Actor派生的蓝图
- 在它的Event Graph中,编辑BeginPlay事件,调用PrintString,显示一个Hello World!
我尽量细的讲一下我这个案例涉及到的每一步的理解!
新建蓝图:BP_HelloWorld
这个过程的核心是创建了一个 class UBlueprint
对象的实例,这个对象在编辑器中可以被作为一种Asset Object来处理。class UBlueprint
是一个class UObject
的派生类。理论上任何UObject都可以成为一个Asset Object,它的创建、存储、对象引用关系等都遵循Unreal的资源管理机制。
具体到代码的话:当我们在编辑器中新建一个蓝图的时候,Unreal Editor会调用UBlueprintFactory::FactoryCreateNew()
来创建一个新的class UBlueprint
对象;
UObject* UBlueprintFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn, FName CallingContext)
{
// ......
// 略去非主干流程代码若干
// ......
UClass* BlueprintClass = nullptr;
UClass* BlueprintGeneratedClass = nullptr;
IKismetCompilerInterface& KismetCompilerModule = FModuleManager::LoadModuleChecked<IKismetCompilerInterface>("KismetCompiler");
KismetCompilerModule.GetBlueprintTypesForClass(ParentClass, BlueprintClass, BlueprintGeneratedClass);
return FKismetEditorUtilities::CreateBlueprint(ParentClass, InParent, Name, BPTYPE_Normal, BlueprintClass, BlueprintGeneratedClass, CallingContext);
}
/** Create a new Blueprint and initialize it to a valid state. */
UBlueprint* FKismetEditorUtilities::CreateBlueprint(UClass* ParentClass, UObject* Outer, const FName NewBPName, EBlueprintType BlueprintType,
TSubclassOf<UBlueprint> BlueprintClassType, TSubclassOf<UBlueprintGeneratedClass> BlueprintGeneratedClassType, FName CallingContext)
{
// ......
// 略去细节处理流程代码若干
// ......
// Create new UBlueprint object
UBlueprint* NewBP = NewObject<UBlueprint>(Outer, *BlueprintClassType, NewBPName, RF_Public | RF_Standalone | RF_Transactional | RF_LoadCompleted);
NewBP->Status = BS_BeingCreated;
NewBP->BlueprintType = BlueprintType;
NewBP->ParentClass = ParentClass;
NewBP->BlueprintSystemVersion = UBlueprint::GetCurrentBlueprintSystemVersion();
NewBP->bIsNewlyCreated = true;
NewBP->bLegacyNeedToPurgeSkelRefs = false;
NewBP->GenerateNewGuid();
// ......
// 后面还有一些其他处理
// . Create SimpleConstructionScript and UserConstructionScript
// . Create defaul