蓝图


大家都知道,蓝图是UE4提供的极其容易上手的一种可视化脚本,更具体的就不说了。

纯靠蓝图搭建的UE4游戏是存在的,但是这类游戏往往优化很差(除非游戏玩法本身的性能需求不高)。更合适的流程往往需要程序员编写C++代码创建一些蓝图可用元素,而设计师再通过蓝图快速搭建游戏。

蓝图命名规范

蓝图命名:"BP"+类别缩写+"_"+名字

例如: BPA_Player

蓝图类别 前缀
蓝图Actor BPA_
蓝图结构 BPS_
蓝图枚举 BPE_
蓝图接口 BPI_
蓝图函数库 BFL_
蓝图宏库 BML_

蓝图优化

在Project Settings -》 Packaging -》 Experimental 下面有个选项:Nativize Blueprint Assets

如果勾选这个选项,那么在打包时会将蓝图脚本编译成C++代码。

暴露C++至蓝图


C++中常常使用UE4中的一些宏来设置想要暴露于蓝图的类、属性、方法等(实质内部是UE4的反射机制)。

暴露C++类

一般使用UCLASS([specifiers])暴露类至蓝图,并添加头文件#include "XXX.generated.h",在类里第一行使用GENERATED_BODY()。

  1. UCLASS([specifier, specifier, ...], [meta(key=value, key=value, ...)])
  2. class ClassName : public ParentName
  3. {
  4. GENERATED_BODY()
  5. }

常用[specifiers]参数:

  • Blueprintable:将该类公开为可接受的用于创建蓝图的基类。默认值为不可蓝图化(NotBlueprintable),但以其他方式继承时除外。这是由子类继承的。
  • BlueprintType:

    将该类公开为可用于蓝图中的变量的类型。
  • NotBlueprintable:指定此类不是用于创建蓝图的可接受基类。否定指定了可蓝图化关键字的父类的效果。

至于meta(元数据说明符)参数,主要是规范(限制)用,相对没有specifiers常用。此处不做多讲,下文同理,感兴趣可以参考具体官方文档。

  1. //例子:
  2. #include "AMyActor.generated.h"
  3. UCLASS(Blueprintable)
  4. class AMyActor : public AActor {
  5. GENERATED_BODY()
  6. };

可参考:虚幻4文档——游戏性类

暴露C++属性

使用UPROPERTY([specifiers])宏暴露属性至蓝图。

  1. UPROPERTY([specifier, specifier, ...], [meta(key=value, key=value, ...)])
  2. Type VariableName;

常用[specifier]参数:

  • EditAnywhere:可通过“属性”窗口在原型和实例上进行编辑。
  • VisibleAnywhere:该属性在“属性”窗口中可见,但无法编辑,与EditAnywhere不兼容。
  • BlueprintReadOnly:此属性可以由蓝图读取,但不能修改。
  • BlueprintReadWrite:此属性可以通过蓝图读取或写入。
  • BlueprintAssignable:应公开属性以在蓝图中分配。
  • BlueprintCallable:应公开属性以在蓝图图表中调用。
  • Category = “XXX”:给该属性分类以便在虚幻编辑器中查询。

此处注意EditAnyWhere和BlueprintReadWrite的区别,前者表示在虚幻编辑器中可以在“属性”窗口中对该属性值进行编辑。然而若需要在蓝图脚本编辑器中设置该属性,则需要使用BlueprintReadWrite,相当于为该属性自动添加了get和set方法。

  1. //例子:
  2. UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Character")
  3. float health;

可参考:虚幻4文档——属性

暴露C++函数

使用UPROPERTY([specifiers])宏暴露属性至蓝图,如:

  1. UFUNCTION([specifier, specifier, ...], [meta(key=value, key=value, ...)])
  2. ReturnType FunctionName([Parameter, Parameter, ...]);

常用[specifier]参数:

  • BlueprintCallable:表示此函数可以直接在蓝图中执行,函数的实现只能在C++中进行。
  • BlueprintImplementableEvent:该函数的具体实现只能在蓝图中进行。对于没有返回值的函数,可以当做一种事件来处理,不必有具体的实现。而对于有返回值的函数,则需要在蓝图编辑器中的左边栏查找该函数并进行覆写。其调用还只能在C++原生代码中进行。
  • BlueprintNativeEvent:蓝图可以调用该函数,该函数的默认实现在C++中已经完成了,但是蓝图可以对该函数进行覆盖重写。这个参数可以实现最灵活的函数调用。

注意:对于BlueprintNativeEvent函数,需要一些特殊处理:

  1. 要声明一个新的虚函数,函数名为 FunctionName_Implementation;
  2. 对该函数的C++实现要转而对该虚函数进行;
  3. 无论C++或者蓝图调用该函数时,都是直接使用函数的原名。
  1. // 例子:
  2. // 头文件
  3. UFUNCTION(BlueprintNativeEvent)
  4. void CountdownHasFinished();
  5. virtual void CountdownHasFinished_Implementation();
  6. // 源文件
  7. void ACountdown::CountdownHasFinished_Implementation() {
  8. CountdownText->SetText(TEXT(“Go!”));
  9. }
  10. void ACountdown::BeginPlay() {
  11. Super::BeginPlay();
  12. CountdownHasFinished();
  13. }

可参考:虚幻4文档——函数

暴露C++结构体/枚举

结构体可包含变量,包括UProperty变量、函数和运算符,且只需用USTRUCT([specifiers])标记该结构体和内置一句GENERATED_USTRUCT_BODY(),无需继承。

  1. USTRUCT([Specifier, Specifier, ...])
  2. struct StructName {
  3. GENERATED_USTRUCT_BODY()
  4. };

常用[specifier]参数:

  • BlueprintType:表示结构体可以在蓝图中使用。

与UObject不同的是,UStruct不会被垃圾回收,必须自行管理其生命周期。UStruct应该是纯传统数据类型,包含UObject反射支持,可以在虚幻编辑器、蓝图操控、序列化、联网等中编辑。

  1. //例子:结构体
  2. USTRUCT(BlueprintType)
  3. struct FCharcterStatus {
  4. GENERATED_USTRUCT_BODY()
  5. UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Character")
  6. float health;
  7. };

枚举则直接用UENUM([specifiers])标记。

  1. //例子:枚举
  2. UENUM(Meta = (Bitflags))
  3. enum class EColorBits
  4. {
  5. ECB_Red,
  6. ECB_Green,
  7. ECB_Blue
  8. };

可参考:虚幻4文档——结构体

暴露C++接口

接口类有助于确保一组(可能)不相关的类实现一组通用函数。在某些游戏功能可能被大量复杂而不同的类共享的情况下,这非常有用。

例如,某个游戏可能有这样一个系统,依靠该系统输入一个触发器体积可以激活陷阱、警告敌人或向玩家奖励点数。这可以通过针对陷阱、敌人和点数奖励器执行“ReactToTrigger”函数来实现。

然而,陷阱可能派生自“AActor”,敌人可能派生自专门的“APawn”或“ACharacter”子类,点数奖励可能派生自“UDataAsset”。所有这些类都需要共享功能,但它们没有除“UObject”之外的共同上级。在这种情况下,推荐使用接口。

  1. UINTERFACE([specifier, specifier, ...], [meta(key=value, key=value, ...)])
  2. class UClassName : public UInterface
  3. {
  4. GENERATED_BODY()
  5. };

常用[specifier]参数:

  • BlueprintType:表示接口可以在蓝图中使用。
  1. //例子:
  2. #include "ReactToTriggerInterface.generated.h"
  3. UINTERFACE(Blueprintable)
  4. class UReactToTriggerInterface : public UInterface
  5. {
  6. GENERATED_BODY()
  7. };

可参考:虚幻4文档——接口

蓝图和C++的结合方案


一个将蓝图和C++结合的常见方案是先大量使用蓝图制作项目,后续再用C++把复杂的蓝图重写一遍,从而提升优化效果。

然而现在蓝图有Nativize Blueprint Assets的功能,可以将蓝图编译成C++代码。所以个人认为绝大部分蓝图并不需要重写,直接Nativize Blueprint Assets即可。而对于某些包含复杂逻辑计算的蓝图,则才适合用C++来替代蓝图。

推荐看参考里面的[UE4]使用C++重写蓝图,SpawnObject根据类型动态创建UObject博客来加深理解。

使用继承重写蓝图

  1. 创建一个C++类作为蓝图的父类(C++类继承蓝图一样的父类),在UE4中修改蓝图的父类。

  2. 用C++中实现好的方法逐个替换蓝图中方法,每次替换一个方法就必须要运行游戏进行详细测试,防止修改太多万一出错无法定位问题所在(尽量避免出现要同时替换2个以上蓝图方法才能正常运行游戏。这一点非常重要。同样也是防止修改太多万一出错无法定位问题所在)

如下图所示:保留原蓝图的实现,方便C++代码查错。

使用组合重写蓝图

  1. 创建一个继承自UObject的C++类,一般加后缀Helper,并且加上BlueprintType标签,共蓝图作为变量类型使用。

  2. 在蓝图中添加一个名为MyHelper的变量,类型是第一步创建的C++类型。 

  3. 在使用helper对象之前,必须先实例化。接着要初始化helper的成员变量值。其中当前蓝图对象的引用(也就是self)要传递给me参数,这是关键,用helper的成员对象保存起来。 

  4. 最终用helper对象的C++方法一个一个替换原来用蓝图写的功能。

方案比较

两个方案都要注意的事项:

  • C++类中的方法、成员变量与蓝图一一对应,并且方法和成员变量名称不能与蓝图的重复。
  • 在替换蓝图的过渡时期,代表相同含义的蓝图变量和C++类变量可能同时存在。那么给变量赋值时,应注意2个变量都要同时赋值,以保证蓝图方法和C++方法都能正常运行。
  • A蓝图不能直接使用B蓝图的变量,A蓝图把要公开的变量封装在函数内返回,并且只返回UE4自带的基础变量类型,不能返回自定义类型,以方便C++重写时返回C++中的成员变量。

参考博客原文的结论是:使用继承和组合都可以实现C++重写蓝图,但是组合比继承要更好,耦合度更低。

参考


虚幻引擎4 官方文档 | 虚幻架构 创建和实现游戏性类的参考

[UE4]使用C++重写蓝图,SpawnObject根据类型动态创建UObject

UE4初学笔记

系列其他文章:Aery的UE4 C++开发之旅系列文章

Aery的UE4 C++游戏开发之旅(3)蓝图的更多相关文章

  1. Aery的UE4 C++游戏开发之旅(1)基础对象模型

    目录 UObject Actor种类 AActor APawn(可操控单位) AController(控制器) AGameMode(游戏模式) AHUD(HUD) ... Component种类 UA ...

  2. Aery的UE4 C++游戏开发之旅(4)加载资源&创建对象

    目录 资源的硬引用 硬指针 FObjectFinder<T> / FClassFinder<T> 资源的软引用 FSoftObjectPaths.FStringAssetRef ...

  3. Aery的UE4 C++游戏开发之旅(2)编码规范

    目录 C++基础类型规范 命名规范 头文件规范 字符串规范 字符集规范 参考 C++基础类型规范 由于PC.XBOX.PS4等各平台的C++基础类型大小可能不同(实际上绝大部分都是整型类型的大小不同) ...

  4. Aery的UE4 C++游戏开发之旅(5)字符&字符串

    目录 TCHAR 字符 使用TEXT()宏包裹字符串字面量 转换字符编码 FString 字符串 FString 剖析 FString 使用 FName 字符串 FName 剖析 FName 使用 F ...

  5. Android游戏开发之旅 View类详解

    Android游戏开发之旅 View类详解 自定义 View的常用方法: onFinishInflate() 当View中所有的子控件 均被映射成xml后触发 onMeasure(int, int) ...

  6. 《cocos2d-x游戏开发之旅》问题2016-10-7

    今天按书上做,遇到问题卡住了 书P115 项目是 littlerunner

  7. Cocos2d-x 3.x游戏开发之旅 笔记

    #include "HelloWorldScene.h"#include "SimpleAudioEngine.h"#include "MyHello ...

  8. Cocos2d-x 3.x游戏开发之旅

    Cocos2d-x 3.x游戏开发之旅 钟迪龙 著   ISBN 978-7-121-24276-2 2014年10月出版 定价:79.00元 516页 16开 内容提要 <Cocos2d-x ...

  9. cocos2d-x 游戏开发之有限状态机(FSM) (四)

    cocos2d-x 游戏开发之有限状态机(FSM) (四) 虽然我们了解了FSM,并且可以写自己的FSM,但是有更好的工具帮我们完成这个繁琐的工作.SMC(http://smc.sourceforge ...

随机推荐

  1. 【RN - 基础】之TextInput使用简介

    TextInput组件允许用户在应用中通过键盘输入文本信息,其使用方法和Text.Image一样简单,实例代码如下: <TextInput placeholder={'请输入用户名'} styl ...

  2. Docker 自建私有Registry 私有仓库

    目录 说明 介绍 原理 搭建 查看配置文件 启动 上传和下载镜像测试 测试上传镜像 测试下载镜像 说明 记录搭建 docker 私有仓库步骤 介绍 docker镜像可以托管到dockerhub中,跟代 ...

  3. No provider available for the service com.xxx.xxx 错误解决

    HTTP Status 500 - Servlet.init() for servlet springmvc threw exception type Exception report message ...

  4. MariaDB和Apache安装

    5月24日任务 课程内容: 11.6 MariaDB安装11.7/11.8/11.9 Apache安装扩展apache dso https://yq.aliyun.com/articles/6298a ...

  5. Mac从拆箱到入门

    Mac从拆箱到入门   记录首次使用Mac的我的历程,不是专业的Mac使用教程,只是简单的记录.还有我在使用过程中一些用到的功能都一些小提示吧.  1.首次开机配置,对于一个完全的新手来说(也就是我) ...

  6. 《Windows内核安全与驱动开发》 2.3 重要的数据结构

    <Windows内核安全与驱动开发>阅读笔记 -- 索引目录 <Windows内核安全与驱动开发> 2.3 重要的数据结构 一.驱动对象  Windows内核采用__的编程方式 ...

  7. 第七章 jQuery中的事件与动画

    事件的分类 基础事件: 鼠标事件 键盘事件 window事件 表单事件 复合事件: 鼠标光标悬停 鼠标连续点击 基础事件: 实例: mouseenter()和mouseover()用法的区别: mou ...

  8. hdu2199,double二分

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2199 题意:给一个Y,求满足8*x^4 + 7*x^3 + 2*x^2 + 3*x + 6 == Y的 ...

  9. B.Beautiful Numbers

    题意:你被给予了一个序列 p = [p1, p2, ..., pn](1 ~ n的整数),如果存在l, r左右端点(1 <= l <= r <= n),使得[pl, pl+1,... ...

  10. C和C++从零开始系列(二)

    今天说一下 C和C++ 的if 条件语句. 在实际编程中,会经常有逻辑判断,比如,输入的数值参数中,如果是奇数,输出This is uneven. 如果是偶数,输出 This is even. 我们在 ...