蓝图跟C++交互
概述
蓝图可以继承C++类,从而使得程序员可以在代码中创建新的游戏性类,而关卡设计人员可以使用蓝图来继承该类并对其进行修改。 有很多种修饰符可以改变C++类和蓝图系统间交互方式,其中某些修饰符会在本示例中突出介绍。
可以通过查看以下内容来快速了解:
- 虚幻引擎快速入门视频教程第五章,见引用[1]
- 官方文档
类设置
在类设置的第一部分中,使用C++类向导创建一个名称为LightSwitchBoth 的类。
LightSwitchBoth类中的大部分代码设置都和 仅使用C++的LightSwitch示例 类似。尽管您可以让一个蓝图继承LightSwitchCodeOnly类,但蓝图图表并不能访问该类中创建的组件、属性及函数。该示例将使用 UPROPERTY() 和 UFUNCTION() 修饰符,这两个修饰符使得LightSwitchBoth作为继承它的蓝图的模板。
这个头文件是从 仅使用C++的LightSwitch示例 改编而来,添加了以下功能:
- PointLightComponent和SphereComponent是BlueprintReadOnly(仅蓝图可读的),并且将显示在 我的蓝图 选卡中的 Switch Components(切换组件) 类目中。
- OnOverlap现在是一个BlueprintNativeEvent,将会显示在 我的蓝图 选卡中的 Switch Functions(切换函数) 类目中。
- DesiredBrightness是BlueprintReadWrite(蓝图可读写的),将显示在 我的蓝图 选卡中的 Switch Properties(切换属性) 类目中。
- DesiredBrightness现在是EditAnywhere(随处可编辑的),而不是VisibleAnywhere(随处可见的)。
在LightSwitchBoth的源文件中,构造器仍然是一样的。但是,需要对 OnOverlap 函数做一点修改。这个函数现在是一个BlueprintNativeEvent。这意味着 在继承这个类的蓝图中,可以放置一个覆盖 OnOverlap 的事件,当正常调用该函数时会执行此事件。如果该事件不存在,那么则是执行那个函数的 C++实现。要想使这个设置正常工作,该C++函数需要重命名为 OnOverlap_Implementation 。稍后在本示例中将介绍这个蓝图设置。对 OnOverlap 函数 进行了修改后,LightSwitchBoth 的源文件如下所示:
当创建类时,新的 UCLASS() 、UFUNCTION() 或 UPROPERTY() 宏意味着该代码必须在Visual Studio 或Xcode中进行编译,然后使用虚幻编辑器重新加载它们。 关闭虚幻编辑器,在Visual Studio 或Xcode中编译该项目,然后打开编辑器并重新加载该项目,以确保正确地重新加载该游戏模块。
当重新打开虚幻编辑器并重新打开您的项目后,便可以创建一个新的类蓝图了。 在本示例中,选择LightSwitchBoth作为该蓝图的父类,蓝图名称为 LightSwitchBoth_BP 。
在 C++代码中添加的PointLightComponent和SphereComponent 也会显示在 Blueprint Editor(蓝图编辑器) 的 组件模式中的 Components(组件) 选卡内。 它们的图标是深蓝色,表示它们是从父类LightSwitchBoth类继承而来的原生组件。而刚刚添加到 LightSwitchBoth_BP 蓝图中的新组件的图标 是浅蓝色的
在 Graph Mode(图标模式) 中, My Blueprint(我的蓝图) 选卡显示了在C++中添加到LightSwitchBoth类中的 PointLightComponent 和SphereComponent 。这是因为 BlueprintReadOnly 修饰符存在的缘故。 通过在 我的蓝图 选卡中点击并拖拽这些组件的名称到图表中,可以将这些组件的节点添加到图表中。然后,您可以把这些节点连接到改变像可见性 或光源颜色这样属性的节点上。DesiredBrightness 属性也会出现在 我的蓝图 选卡中。因为它是一个属性,而不是一个组件,所以可以使用 BlueprintReadWrite 修饰符。这意味着在蓝图图表中可以创建节点来获取及设置 DesiredBrightness 的值。
有两个图表用于设置 LightSwitchBoth_BP 类的行为。第一个是构造脚本图表,它包含了一个专用的 Construction Script(构建脚本)事件。如果没有该 Construction Script 设置,那么新的 LightSwitchBoth_BP Actor 将仅使用LightSwitchBoth的构造函数。然而,当Actor在关卡中移动时,及当 Desired Brightness 发生改变时,都会执行 Construction Script 。使用 Construction Script 意味着,可以轻松地改变暴露给蓝图的Actor属性, 并且可以快速地看到这些修改的效果。
在 LightSwitchBoth_BP 类中, Construction Script 事件连接到了 Set Brightness 节点上,以便当在关卡中添加或移动Actor时或者 Desired Brightness 发生改变时,将 Point Light 1 (PointLightComponent) 的亮度设置为 Desired Brightness 的值。
在 LightSwitchBoth_BP 类中, Construction Script 事件连接到了 Set Brightness 节点上,以便当在关卡中添加或移动Actor时或者 Desired Brightness 发生改变时,将 Point Light 1 (PointLightComponent) 的亮度设置为 Desired Brightness 的值。
LightSwitch_BPOnly 类中设置的另一个图表是 事件图表 。EventGraph的执行是由事件触发的。在这个示例中, 任何时候当调用C++函数 OnOverlap 时, OnOverlap 就会执行。在LightSwitchBoth的源文件中,设置了代理,以便当一个Actor进入或离开SphereComponent时会执行 OnOverlap 。
在变量的设置中, DesiredBrightness 变量设置为 EditAnywhere (随处可编辑) ,所以在 蓝图编辑器 的默认模式中它是可见的,并且可以进行编辑。 这意味着对于类的每个实例,这个变量是可以变化的,所以每个Actor可以有其自己的 DesiredBrightness 。因为 DesiredBrightness 也是 BlueprintReadWrite(蓝图可读写) 的,且 Construction Script 中使用了它,所以更新它还会导致再次执行 Construction Script 。
其他Class Blueprints(类蓝图)可以继承由蓝图创建的类,通过以下两种方式实现:使用 Class Viewer(类别查看器) 中的类附近的下拉列表按钮来创建一个新蓝图, 或者通过右击该蓝图并选择 Create New Blueprint Based on This(基于此蓝图创建一个新蓝图) 。
蓝图函数库
有时候有一些帮助函数,我们需要在蓝图中使用,这时候我们可以创建一个蓝图函数库。它是一些静态函数的集合。
创建蓝图库跟使用UFUNCITON()暴露一些函数给蓝图很相似。只需要继承UBlueprintFunctionLibrary即可。它们也应该包含静态函数。
下面是StartSession的实现
编译后,就可以在蓝图内里面调用这些方法了。
属性修饰符
此处只列举了一些常用的属性,具体参照官方文档
- AdvancedDisplay
- AssetRegistrySearchable
- BlueprintAssignable仅能用于Multicast代理。应显示该属性,以供在蓝图中分配。
- BlueprintCallable仅能用于Multicast代理。应显示该属性,以在蓝图代码中调用。
- BlueprintReadOnly 只读
- BlueprintReadWrite 读写
- Category定义属性的分类
- Config
- Const
- DuplicateTransient
- EditAnywhere表示该属性可从编辑器内的属性窗口编辑。
- EditDefaultsOnly表示该属性可通过属性窗口来编辑,但仅能对原型编辑。
- EditFixedSize
- EditInline
- EditInstanceOnly
- …
函数修饰符
函数声明
UFUNCTION([specifier, specifier, ...], [meta(key=value, key=value, ...)])
ReturnType FunctionName([Parameter, Parameter, ...])
函数修饰符
在声明函数时,声明上可添加修饰符以控制引擎和编辑器的不同方面的属性表现。
- BlueprintAuthorityOnly
- BlueprintCallable 该函数可在蓝图或关卡蓝图图表内执行。
- BlueprintCosmetic 此函数为修饰函数而且无法运行在专属服务器上
- BlueprintImplementableEvent此函数可以在蓝图或关卡蓝图图表内进行重载。
- BlueprintNativeEvent 此函数将由蓝图进行重载,但同时也包含native类的执行。提供一个名称为[FunctionName]_Implementation的函数本体而非[FunctionName];自动生成的代码将包含转换程序,此程序在需要时会调用实施方式。
- BlueprintPure 此函数不会以任何方式影响其从属对象,并且可在蓝图或关卡蓝图图表中执行。
- Category 当在蓝图编辑工具中显示时,定义函数的分类。
- Client此函数仅在该函数从属对象所从属的客户端上执行。提供一个名称为[FunctionName]_Implementation的函数主体,而不是[FunctionName]; 自动生成的代码将包含一个转换程序来在需要时调用实现方法。
- CustomThunk UnrealHeaderTool(虚幻头文件工具)的代码生成器将不会为此函数生成execFoo转换程序; 可由用户来提供
- Exec此函数可从游戏中的控制台中执行。Exec命令仅在特定类中声明时才产生作用。
- NetMulticast无论Actor的NetOwner为何值,此函数都会在服务器上被本地执行且将被复制到所有的客户端
- Reliable此函数在网络间进行复制,并会忽略带宽或网络错误而被确保送达。仅在与客户端或服务器共同使用时可用
- Server此函数仅在服务器上执行。提供一个名称为[FunctionName]_Implementation的函数主体,而不是[FunctionName]; 自动生成的代码将包含一个转换程序来在需要时调用实现方法。
- Unreliable此函数在网络间复制,但可能会由于带宽限制或网络错误而传送失败。仅在与客户端或服务器一起使用时有效。
类修饰符
在声明类时,声明上可添加修饰符以控制引擎和编辑器的不同方面的类表现。
有太多修饰符,此处仅列举一些常用的,其它请参考官方文档。
- Blueprintable指定该类为创建蓝图的可接受基类。除非被继承,否则默认值为NotBlueprintable。它由子类继承。
- BlueprintType此类可作为蓝图中的一种变量类型使用
- Abstract Abstract 类修饰符将类声明为"抽象基类",这样会阻止用户在虚幻编辑器中向这个世界中添加这个类的Actor,或者在游戏过程中创建这个类的实例。
- Deprecated该类已被废弃,并且该类的对象在序列化时将不会被保存。该标识由子类继承。
- …
元数据修饰符
在声明类、函数和接口时,声明上可添加元数据修饰符以控制其在引擎和编辑器的不同方面的表现。
对元数据修饰符的使用按常规类、函数和接口修饰符而不同。
类元数据修饰符
- BlueprintSpawnableComponent
函数元数据修饰符
- BlueprintInternalUseOnly
- BlueprintProtected
- DeprecatedFunction
- DeprecationMessage
- UnsafeDuringActorConstruction
接口元数据修饰符
- CannotImplementInterfaceInBlueprint
蓝图技术指南
蓝图编程指南
当决定使用C++或者蓝图时,有两个主要考虑的因素
- 速度
- 表达式复杂度
除了这两个因素外,其它的来自游戏的复杂程序以及团队的组成。如果你有比程序员更多的设计师,那么蓝图代码将会比C++代码多很多。相反,如果你有更多的程序员,那么他们可能更倾向于使用C++。我们(Epic)期望在这中间有一个平衡点。在Epic,大多工作流程是这样的,内容创建者会做一个非常复杂的蓝图,程序员通过C++编写一个新的蓝图节点来把前面所创建的蓝图的大部分转成C++代码,他把那部分功能转成了C++函数。一个好的经验是大量使用蓝图,然后把它转换成C++代码,当它们达到一定复杂度的时候,它需要一个关于该功能一个更精确的表达式(或者对于一个非程序员太复杂时),或者执行速度过慢需要转换成C++时。
速度
就速度来说,蓝图肯定会比C++慢(8~10倍见上方引用部分),并不是说性能就非常差,但是如果你做的事情需要很多的计算,或者执行频率非常高,那么最多是使用C++而不是蓝图。然而,很好地结合两者来为你们团队很好的工作和取得一个不错的性能是很有可能的。如果一个蓝图类有很多功能,那么你可以把一些功能转换成C++代码来加速,但是保留其它的部分来保证灵活性。如果你的性能分析发现蓝图中的某个操作很耗时,那么可以把那部分转移到C++,其它的保留在蓝图中。
假如要使用蓝图来控制几千个Actor,那么这种情况下就最好用C++来处理决策、寻路等,然后把一个可调节的属性和控制函数暴露给蓝图。
复杂度
就表达式复杂来说,有些东西用C++来做确实比蓝图容易。蓝图在很多方面做的很好,但是有些东西在蓝图结点内却并不好表达。比如操作大量数据,字符串操作,大量数据的数学操作等都很复杂,且在一个可视化系统里面并不容易云做。这些东西最好用C++来实现,因为它们容易看出来到底在做什么。
参考
- https://docs.unrealengine.com/latest/CHN/Gameplay/ClassCreation/CodeAndBlueprints/index.html
- https://docs.unrealengine.com/latest/CHN/Programming/UnrealArchitecture/Reference/Classes/Specifiers/index.html
- https://docs.unrealengine.com/latest/INT/Engine/Blueprints/TechnicalGuide/Guidelines/index.html