Aery的UE4 C++游戏开发之旅(3)蓝图

目录

  • 蓝图

    • 蓝图命名规范
    • 蓝图优化
  • 暴露C++至蓝图
    • 暴露C++类
    • 暴露C++属性
    • 暴露C++函数
    • 暴露C++结构体/枚举
    • 暴露C++接口
  • 蓝图和C++的结合方案
    • 使用继承重写蓝图
    • 使用组合重写蓝图
    • 方案比较
  • 参考

蓝图



大家都知道,蓝图是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()。

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

常用[specifiers]参数:

  • Blueprintable:将该类公开为可接受的用于创建蓝图的基类。默认值为不可蓝图化(NotBlueprintable),但以其他方式继承时除外。这是由子类继承的。
  • BlueprintType:
    将该类公开为可用于蓝图中的变量的类型。
  • NotBlueprintable:指定此类不是用于创建蓝图的可接受基类。否定指定了可蓝图化关键字的父类的效果。

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

//例子:
#include "AMyActor.generated.h"

UCLASS(Blueprintable)
class AMyActor : public AActor {
    GENERATED_BODY()
};

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

暴露C++属性

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

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

常用[specifier]参数:

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

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

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

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

暴露C++函数

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

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

常用[specifier]参数:

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

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

  1. 要声明一个新的虚函数,函数名为 FunctionName_Implementation;
  2. 对该函数的C++实现要转而对该虚函数进行;
  3. 无论C++或者蓝图调用该函数时,都是直接使用函数的原名。
// 例子:
// 头文件
UFUNCTION(BlueprintNativeEvent)
void CountdownHasFinished();

virtual void CountdownHasFinished_Implementation();

// 源文件
void ACountdown::CountdownHasFinished_Implementation() {
    CountdownText->SetText(TEXT(“Go!”));
}

void ACountdown::BeginPlay() {
    Super::BeginPlay();
    CountdownHasFinished();
}

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

暴露C++结构体/枚举

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

USTRUCT([Specifier, Specifier, ...])
struct StructName {
    GENERATED_USTRUCT_BODY()
}; 

常用[specifier]参数:

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

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

//例子:结构体
USTRUCT(BlueprintType)
struct FCharcterStatus {
    GENERATED_USTRUCT_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Character")
    float health;
}; 

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

//例子:枚举
UENUM(Meta = (Bitflags))
enum class EColorBits
{
    ECB_Red,
    ECB_Green,
    ECB_Blue
};

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

暴露C++接口

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

例如,某个游戏可能有这样一个系统,依靠该系统输入一个触发器体积可以激活陷阱、警告敌人或向玩家奖励点数。这可以通过针对陷阱、敌人和点数奖励器执行“ReactToTrigger”函数来实现。
然而,陷阱可能派生自“AActor”,敌人可能派生自专门的“APawn”或“ACharacter”子类,点数奖励可能派生自“UDataAsset”。所有这些类都需要共享功能,但它们没有除“UObject”之外的共同上级。在这种情况下,推荐使用接口。

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

常用[specifier]参数:

  • BlueprintType:表示接口可以在蓝图中使用。
//例子:
#include "ReactToTriggerInterface.generated.h"

UINTERFACE(Blueprintable)
class UReactToTriggerInterface : public UInterface
{
    GENERATED_BODY()
};

可参考:虚幻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 官方文档 | 中文文档 | 虚幻架构 创建和实现游戏性类的参考

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

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

UE4初学笔记

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

原文地址:https://www.cnblogs.com/KillerAery/p/12026395.html

时间: 2024-10-28 02:18:59

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

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

目录 资源的硬引用 硬指针 FObjectFinder<T> / FClassFinder<T> 资源的软引用 FSoftObjectPaths.FStringAssetReference TSoftObjectPtr<T> 同步加载资源 LoadObject/LoadClass TryLoad/LoadSynchronous 异步加载资源 FStreamableManager.RequestAsyncLoad() 卸载资源 创建对象 创建一般对象 创建Actor派生类

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

目录 C++基础类型规范 命名规范 头文件规范 字符串规范 字符集规范 参考 C++基础类型规范 由于PC.XBOX.PS4等各平台的C++基础类型大小可能不同(实际上绝大部分都是整型类型的大小不同),因此UE4提供了如下可移植基础类型的别名来统一规范类型大小: bool 代表布尔值(不会假定布尔尺寸). TCHAR 代表字符(不会假定TCHAR尺寸). uint8 代表无符号字节(1字节). int8 代表带符号字节(1字节). uint16 代表无符号"短"字符(2字节). int

Android游戏开发之旅 View类详解

自定义 View的常用方法: onFinishInflate() 当View中所有的子控件 均被映射成xml后触发 onMeasure(int, int) 确定所有子元素的大小 onLayout(boolean, int, int, int, int) 当View分配所有的子元素的大小和位置时触发 onSizeChanged(int, int, int, int) 当view的大小发生变化时触发 onDraw(Canvas) view渲染内容的细节 onKeyDown(int, KeyEvent

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

#include "HelloWorldScene.h"#include "SimpleAudioEngine.h"#include "MyHelloWorldScene.h" USING_NS_CC; Scene* HelloWorld::createScene(){ // 'scene' is an autorelease object auto scene = Scene::create(); // 'layer' is an autore

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

cocos2d-x 游戏开发之有限状态机(FSM) (四) 虽然我们了解了FSM,并且可以写自己的FSM,但是有更好的工具帮我们完成这个繁琐的工作.SMC(http://smc.sourceforge.net/)就是这样的工具.下载地址: http://sourceforge.net/projects/smc/files/latest/download 在bin下面的Smc.jar是用于生成状态类的命令行工具.使用如下命令: $ java -jar Smc.jar Monkey.sm 1 真实世

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

Cocos2d-x 3.x游戏开发之旅 钟迪龙 著   ISBN 978-7-121-24276-2 2014年10月出版 定价:79.00元 516页 16开 内容提要 <Cocos2d-x 3.x游戏开发之旅>是<Cocos2d-x游戏开发之旅>的升级版,修改了Cocos2d-x 2.0版进阶到3.0版后的一些内容,新增了对CocoStudio.UI编辑器.Cocos2d-x 3.x新特性以及网络方面的知识点.主要介绍常用的API使用方式:如何通过官方Demo获取更多关于Coc

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

cocos2d-x 游戏开发之有限状态机(FSM) (一) 参考:http://blog.csdn.net/mgphuang/article/details/5845252<Cocos2d-x游戏开发之旅>(钟迪龙) 基本上所有的软件都是有限状态机(finite-state machine,FSM).它是一个有向图,由一组节点和一组相应的转移函数组成.通俗点讲,它是一个事件驱动系统的模型,这个模型由有限数目的状态,若干输入和状态与状态之间转换的规则组成.在某一时刻,有一个或一组状态是FSM的当

【python游戏编程之旅】第八篇---pygame游戏开发常用数据结构

本系列博客介绍以python+pygame库进行小游戏的开发.有写的不对之处还望各位海涵. 上一个博客我们一起学习了pygame中冲突检测技术:http://www.cnblogs.com/msxh/p/5027688.html 这次我们来一起学习在pygame游戏里面常用的一些数据结构: 数据,列表,元组,队列,栈. 一.数组与列表 数组可以理解为简化的列表.像我们之前使用的pygame.sprite.Group这样的精灵组,也是一个列表.列表的元素是可变的,它具有添加.删除.搜索.排序等多种

最大的幻术-游戏开发-到底是先学游戏引擎还是先学游戏编程

学习游戏的目的 我们学习游戏制作,游戏开发,游戏编程,游戏XX,我们的目的只有一个,打造一个非常牛逼,非常屌,非常让人开心的虚拟体验.我们用自己的学识让玩家在虚拟世界征战,生活,一步一步的让玩家幸福!那么我们的目的只有一个,让玩家知道自己的幸福在哪里,并且学会追求自己的幸福.当然,每个人对幸福的定义不一样.那么,我们只好让玩家来体验我们所来表达的最通俗的,最普遍的幸福体验,然后慢慢引导玩家去寻找自己的幸福体验.可能,在最后玩家都会离开游戏,离开虚拟世界,(对,这是真的,玩家需要一步一步达到定点,