使用虚幻引擎中的C++导论(一-生成C++类)
使用虚幻引擎中的C++导论(一)
第一,这篇是我翻译的虚幻4官网的新手编程教程,原文传送门,有的翻译不太好,但大体意思差不多,请支持我O(∩_∩)O谢谢。
第二,某些细节操作,这篇文章省略了,如果有不懂的,去看其他教程。
第三,没有C++编程经验,但有其他OOP语言经验的朋友。要先了解C++的基础。
虚幻C++非常棒
这个向导是帮您了解怎么去写C++代码。别担心,虚幻4编程很有趣,不难开始。我们把虚幻C++理解为“辅助C++”,因为我们有很多特性去帮助每一个人更好的去使用C++。
在我们开始之前,你已经熟悉了C++编程语言或者其他编程语言这很重要。这篇文章假设你已经有了C++的编程经验,或者你已经有了C#,JAVA,JavaScript,你也可以发现很多相似之处。
如果你完全没有编程经验,我们也考虑到了!你可以使用蓝图可视化脚本向导走你自己的路(意思就是不用敲代码啦),你可以用蓝图来创建自己的游戏。
在虚幻4引擎中会写一些“朴实老旧的C++代码”,但是通过向导学习以及学习虚幻4引擎模型你会写的很好,balabala,加油。
C++和蓝图
虚幻引擎提供2种方法,C++代码和蓝图可视化脚本(后简称:蓝图),来新增游戏元素。使用C++,程序员添加基础游戏系统,让美术能够在其之上建立游戏元素。在这些情况下,C++程序员在工作中使用他们最爱的IDE(VS2013,VS2015,Xcode),美术在工作中使用虚幻蓝图编辑器。
游戏API和工作框架在这2种方法中,都是可用的。可以单独使用,但是要真正的发挥他们各自的力量,需要完美结合。我想说的是,最好的虚幻项目应该,程序员使用C++建立游戏空物体,美术使用空物体添加有趣的东西。
如我们所说,我们将要演示一下典型的工作流,程序员为美术提供空物体,在这种情况下,我们将用蓝图类扩展这个空物体。在这个C++类中,我们将添加几个属性,并且派生几个属性到虚幻引擎中,我们使用引擎提供的工具和C++宏命令,这些过程非常简单。
类向导
第一件事,我们将用虚幻引擎中的类向导创建基础C++类,这个类将在之后用蓝图扩展,下图显示向导第一步,我们新建一个Actor

第二步,修改类的名称,在这里我们用默认的类名称。

当你第一次新建类的时候,向导将生成新的文件夹并且打开你的IDE,为了是你能够编辑。下面是类向导自动生成的类代码,更多关于类向导的信息,去官网看去。
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AMyActor();
// Called when the game starts or when spawned
virtual void BeginPlay() override;
// Called every frame
virtual void Tick( float DeltaSeconds ) override;
};
这个类向导生成的类中有BeginPlay() 和Tick()的特定重载,BeginPlay()是一个事件,当Actor进入游戏时触发,这个方法中可以初始化一些游戏逻辑或类属性。Tick()是每一帧都会调用,这里你可以加经常性的逻辑。然而如果你确定你不需要某些函数功能,最好在代码中去掉。如果你去掉了Tick(),同时要确保去掉了构造函数中的关于Tick的这行代码,否则会出问题。
AMyActor::AMyActor()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you do not need it.
PrimaryActorTick.bCanEverTick = true;
}
添加属性使其在虚幻编辑器中显示
我们已经新建了类,所以现在我们新增一些属性,让这些属性可以在虚幻引擎中进行设置。让一个属性在引擎中显示很简单,我们使用特殊宏命令,UPROPERTY()。你所需要做的就是使用UPROPERTY(EditAnywhere)宏命令,放在属性定义的上面就可以了。
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
//注意这里默认是私有属性,共有需要加PUBLIC修饰
UPROPERTY(EditAnywhere)
int32 TotalDamage;
...
};
这就是你需要做的全部,这里有更多控制属性的途径,需要在宏命令中添加更多信息。比如,你想要TotalDamage属性显示在与之相关的属性面板,你可以使用“分类”特性,定义如下。
UPROPERTY(EditAnywhere, Category="Damage")
int32 TotalDamage;
当用户看到这个属性时,这个属性已经在名称为“Damage”的面板下方了,并且其他任何属性都可以加上这个分类,使其放在该面板下方,这是一个很好的方法去放置物体的属性。
现在让我们了解下属性关于蓝图的宏命令(下面的属性必须是public修饰)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Damage")
int32 TotalDamage;
你可以看到,这个BlueprintReadWrite特定参数使属性可以被蓝图读写,这里有分离的其他选项,BlueprintReadOnly,用这个可以让属性在蓝图中只读(也就是常量),这有一些选项可用去控制属性如何在引擎中展现。想要看更多的关于属性的控制,请看这里。 传送门
在继续下一部分之前,让我们在样本类中添加一对属性,现在已经有了TotalDamage可以在引擎中展示总伤害,让我们更进一步的使伤害发生在每次伤害调用时,下面的代码展示了设计者可以设置的属性,并且其中有1个属性仅可显示,但不能修改。
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Damage")
int32 TotalDamage;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Damage")
float DamageTimeInSeconds;
UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Transient, Category="Damage")
float DamagePerSecond;
...
};
DamageTimeInSeconds 是一个可以再引擎中修改的属性。DamagePerSecond 用来计算伤害的值(看下一部分)。VisibleAnywhere 标志让属性可以在引擎看到,但不能修改。Transient 标志意味着该属性值将不会存储在磁盘;它将派生,非持久。下图是在引擎中的效果。

在构造函数中设置默认值
在构造函数中设置默认值与传统的C++是一样一样的。下面有2个例子是设置默认值的函数写法,他们的效果是一样的,只能用其中的一个哦。
AMyActor::AMyActor()
{
TotalDamage = 200;
DamageTimeInSeconds = 1.f;
}
AMyActor::AMyActor() :
TotalDamage(200),
DamageTimeInSeconds(1.f)
{
}
设置默认值之后,引擎会有同样的改变。

为了支持设计师在每次赋值之后,其他值能够重新装载(重新计算)。这些需要计算的数据是在构造函数之后,你可以添加默认的值,并且挂钩(构造完后自动调用)PostInitProperties()。这个例子关于使用,TotalDamage和DamageTimeInSeconds 来计算每次伤害,当你明智的赋值了之后(除数不能为0之类的),每次伤害会改变。
注意:如果你没有给属性赋默认值,引擎将会自动赋值0或者空指针(指针类的情况)
void AMyActor::PostInitProperties()
{
Super::PostInitProperties();
DamagePerSecond = TotalDamage / DamageTimeInSeconds;
}
同样的面板信息,在添加了PostInitProperties()代码之后,值被计算出来了。

热重装
若你用C++开发其他的项目,虚幻有个非常酷的特性会让你大吃一惊。你可以编译你的C++代码的修改而不需要关闭虚幻引擎!这儿有2种方法去做这件事。
1.在引擎任然运行的情况下,像往常一样编译你的项目(生成/重新生成之类的),虚幻引擎会检测最新的编译DLLs和立即重新装载你的改变。
(主意:如果你选择的是Debbuger,你需要先分离?不明白,反正别用Debbuger模式就行啦)
2.或者简单的点击引擎工具条上的“编译”按钮。

你可以在后面的内容中使用这个特性,真的节省时间!
通过蓝图扩展C++类
直到现在,我们已经用C++类向导建立了一个简单的游戏类,并且可以在引擎中被设计师赋值。现在让我们一起来看看,设计师如何从小做起(from our humble beginnings)创造一个独一无二的类。
第一件事,以MyActor为父类新建一个蓝图类。注意图片下方的类型名称选择,用MyActor代替AMyActor。这里隐含了命名规范,请把名字起得好一点,别用汉语拼音。

当你选择之后,一个新的、默认的蓝图类已经为你建立好了。重命名为CustomActor1,在Content Browser中可以看到。

这是我们作为设计师建立的第一个自定义蓝图类,第一件事,我们将要改变伤害的默认值。TotalDamage 赋值 300,DamageTimeInSeconds 赋值 2,意味着每秒进行2次伤害。下图展示了现在面板信息。

我们计算出的伤害信息并不是我们所期望的,DamagePerSecond 应该是150而不是200。原因是,我们仅仅在属性被初始化时,计算每次伤害值,并装载。运行时的改变并不属于初始化事件。有个简单解决这个问题的方法,当我们的目标在引擎中改变时。下面代码展示如何与改变操作挂钩。
void AMyActor::PostInitProperties()
{
Super::PostInitProperties();
CalculateValues();
}
void AMyActor::CalculateValues()
{
DamagePerSecond = TotalDamage / DamageTimeInSeconds;
}
#if WITH_EDITOR
void AMyActor::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
CalculateValues();
Super::PostEditChangeProperty(PropertyChangedEvent);
}
#endif
关于PostEditChangeProperty() 方法有一点要注意,它包含在特定的#ifdef中,还有把计算部分重构,封装成单独的方法,减少代码量。编译后,DamagePerSecond 属性就是我们所期望的值了。

在C++与蓝图之间调用方法
直至现在,我们已经使变量可以在蓝图中看到,但是在我们深入研究引擎前,还有最后一个话题需要介绍。在新建了游戏系统之间,设计师需能够在蓝图类中,如同C++开发者一样,调用C++类中的方法。让我们开始做第一步,让CalculateValues()方法可以在蓝图类中被调用。让方法在蓝图类中可调用是和属性一样简单,只需要在方法的上一行定义一个宏命令!代码如下:
UFUNCTION(BlueprintCallable, Category="Damage")
void CalculateValues();
UFUNCTION()宏命令句柄可以对反射系统展示此方法,BlueprintCallable 选项使它(CalculateValues()方法)暴露在Blueprints Virtual Machine(蓝图虚拟机),每个在蓝图类中暴露的方法,都需要一个相关联的类别,只有如此,右键单击弹出的内容菜单才能正常的显示方法。下图展示暴露方法后的在蓝图中的效果。

你可以看到,在Damage 类别中可以选择到这个方法。下面的蓝图脚本(连线)展示了,设置了TotalDamage 后,怎么重新计算每次伤害。

这里我们使用我们之前添加过得方法。很多方法都通过UFUNCTION()宏命令暴露给了蓝图,所以设计师可以不用写C++代码。然而,最好的办法是使用C++建立基本的游戏系统,用蓝图展现关键代码被用来自定义游戏行为,或者用C++组合行为。
现在我们的设计师可以调用C++代码(方法)了,让我们探索更多关于跨越C++与蓝图之间的有力的方法。这个途径允许C++代码调用蓝图中定义的方法。我们常常使用这个方法去通知设计者,他们能够响应他们自己觉得合适的一个事件,通常包括效果、视觉碰撞、隐藏/显示物体对象等这些连接操作。下面的代码片段的代码显示了由蓝图实现的功能。
UFUNCTION(BlueprintImplementableEvent, Category="Damage")
void CalledFromCpp();
如同其他的C++方法调用一样,在其背后的逻辑是,虚幻引擎生成了一个基本的C++方法实现,它可以知道如何去调用蓝图虚拟机中的内容。This is commonly referred to as a Thunk.(通常就是咚的一声)。如果蓝图有问题则不会对这个方法提供方法体,这时方法的行为就像C++代码中没有方法体的方法一样:它不做任何事,如果你想提供一个C++默认实现同时允许蓝图覆盖此方法?UFUNCTION()宏命令也有个选项提供此功能。下面代码片段展示如何实现这种需求。
UFUNCTION(BlueprintNativeEvent, Category="Damage")
void CalledFromCpp();
像上面这样写,虚幻引擎任然会咚的一声生成一个方法,使其能够调用蓝图虚拟机。所以怎么去提供默认的方法实现?引擎同样生产了一个新的方法定义“_Implementation()”,你必须提供这个版本的方法,否则你的项目将无法连接。这个实现代码如下。
void AMyActor::CalledFromCpp_Implementation()
{
// Do something cool here
}
像上面这样写,当蓝图方法有问题时,则不会重写这个方法。有件事需要注意,在未来版本的编译工具,自动生成“_Implementation()”定义将要放弃,you'll be expected to explicitly add that to the header.(不明白),在4.7版本,这个生成定义还是可以使用哒。
现在,我们浏览了常规的开发者工作流,开发者与设计师一起去建造游戏特性,现在选择你自己的大冒险。你也可以继续阅读这个文档,更多的了解使用C++引擎,或者跳转至我们的例子(游戏例子),在登录器中有更多手把手的教学内容。
使用虚幻引擎中的C++导论(一-生成C++类)的更多相关文章
- 使用虚幻引擎中的C++导论(四-内存管理与垃圾回收)(终)
使用虚幻引擎中的C++导论(四)(终) 第一,这篇是我翻译的虚幻4官网的新手编程教程,原文传送门,有的翻译不太好,但大体意思差不多,请支持我O(∩_∩)O谢谢. 第二,某些细节操作,这篇文章省略了,如 ...
- 使用虚幻引擎中的C++导论(三-反射系统与迭代器)
使用虚幻引擎中的C++导论(三) 第一,这篇是我翻译的虚幻4官网的新手编程教程,原文传送门,有的翻译不太好,但大体意思差不多,请支持我O(∩_∩)O谢谢. 第二,某些细节操作,这篇文章省略了,如果有不 ...
- 使用虚幻引擎中的C++导论(二-UE4基类)
使用虚幻引擎中的C++导论(二) 第一,这篇是我翻译的虚幻4官网的新手编程教程,原文传送门,有的翻译不太好,但大体意思差不多,请支持我O(∩_∩)O谢谢. 第二,某些细节操作,这篇文章省略了,如果有不 ...
- 虚幻引擎中的数组---TArray: Arrays
本文章由cartzhang编写,转载请注明出处. 所有权利保留. 文章链接: http://blog.csdn.net/cartzhang/article/details/45367171 作者:ca ...
- [原][unreal][UE][spark]分析unreal engine 虚幻引擎的粒子编辑器:Cascade
参考:https://www.raywenderlich.com/270-unreal-engine-4-particle-systems-tutorial (使用了一个飞机射击游戏的粒子来展示,全英 ...
- Unreal Engine 虚幻引擎宣布对开发者免费
虚幻引擎4现在可供每个人免费使用,而且所有未来的更新都将免费!您可以下载引擎并将其用于游戏开发的各个方面,包括教育.建筑以及可视化,甚至虚拟现 实.电影和动画. 当您发布游戏或应用时,在您的每个游戏在 ...
- 虚幻引擎4设置Visual Studio
转自:http://www.unrealchina.net/portal.php?mod=view&aid=149 设置Visual Studio和虚幻引擎4协同工作有利于提高开发人员使用UE ...
- 【UE4】虚幻引擎技术直播汇总(含中英文直播)
B站虚幻引擎官方账号 中文直播 [中文直播]第35期 | 使用GIS在UE中创造真实地球风貌 | Epic 周澄清 [中文直播]第34期 | 包教包会的Epic MegaGrants申请之道 | Ep ...
- 宣布与Epic Games合作,为虚幻引擎创造Cesium
Cesium中文网:http://cesiumcn.org/ | 国内快速访问:http://cesium.coinidea.com/ 没有什么能比支持史诗游戏和史诗巨无霸计划(Epic MegaGr ...
随机推荐
- Android 学习第11课,android 实现拨打电话的功能
1. 先布局界面,界面采用线性垂直方式来布局 在layout 界面文件中 activity_main.xml 中 <LinearLayout xmlns:android="http:/ ...
- 基于GRPC+consul通信的服务化框架(转)
原文:http://blog.csdn.net/yeyincai/article/details/51470475 -.背景 谈论服务化框架的时候,我们首先先了解这些概念:SOA.ESB.OSGi.s ...
- 使用notepad++进行格式转换
由于历史原因,导致Windows.Unix/Linux.Mac三者之间,对于文本中所用回车换行符,表示的方法,都不一样.这就导致了很多人都会遇到回车换行符的困惑,同时需要在不同格式间进行转换. 1)查 ...
- For xml path
Select * from tb for xml path('') 特点: 1. 以xml形式展示查询数据. 2. 自定义数据展示类型. 实例: 1. Select * from tb for xml ...
- C# 中Join( )的理解
在MSDN中对Join( )的解释比较模糊:在继续执行标准的 COM 和 SendMessage 消息泵处理期间,阻塞调用线程(线程A),直到某个线程终(线程B)止为止. 首先来看一下有关的概念: 我 ...
- ABBYY有哪些图像处理选项
ABBYY PDF Transformer+ 这款Ocr图文识别软件提供多种图像处理选项,可提高源图像的质量,便于准确地识别光学字符.我们扫描纸质文档或从图像文件创建 PDF 时,务必选择合适的图像处 ...
- 设计模式-UML类图的各符号含义(转)
UML类图的各符号含义 类图基本符号可拆分为虚线,箭头,实线,空心右三角,实心右三角,空心菱形和实心菱形.由这些基本的图形进行组合构成了类图的基本符号.这里要注意这几个符号的顺序,代表了类与类之间关系 ...
- 知识积累:CGI,FastCGI,PHP-CGI与PHP-FPM
CGICGI全称是“公共网关接口”(Common Gateway Interface),HTTP服务器与你的或其它机器上的程序进行“交谈”的一种工具,其程序须运行在网络服务器上.CGI可以用任何一种语 ...
- Apache Shiro系列教程之二:十分钟上手Shiro
在本教程中,我们会写一个简单的.仅仅输出一些内容命令行程序,从而对Shiro有一个大体的感觉. 一.准备工作 本教程需要Java1.5+,并且我们用Maven生成项目,当然Maven不是必须的,你也可 ...
- 怎么 才能显示Eclipse中Console的全部内容
可以如下设置 preference->run/debug->console 设置limit console output 为false,方便调试时,查看全部console. 这个真是太有用 ...