559666429
来,大家一起找答案,共同进步1. 谈谈过往,UE1和UE2.
我知道在那个时候咱们一样挥霍美好的童年在玩游戏而不是做游戏,当然做游戏更挥霍你的青春XD
UE1和UE2是为FPS设计的,使用UnrealScript来进行编程。说实话UnrealScript是最好的学习面向对象编程的语言。
2.UE3
添加了kismet。更多的模块类。但是仍旧以FPS为核心设计的
3.UE4
UnrealScript被下岗了,恭喜你咱们下岗了~。变成了Blueprint,这时候关卡设计师们大声叫好:”不再需要求着愚蠢的程序员帮我们实现系统了,哈哈“。
游戏类型随意,Paper2D让你轻松制作2维游戏。
还有许多工程实例供你学习。
好了咱们向前看~
5.UnrealScript vs.C++ vs.Blueprint
这是许多人的疑问,看好戏吧
6.UnrealScript
①面向对象编程,和C,C++,Java比较像。但是还是有区别的
②虚拟机编译,这和java一样,方便移植,缺点是速度慢效率低
③添加了许多有用的特性,State(说实话状态写AI简直是最完美的方案,我的游戏AI便是强依赖State).Timers(非常方便延迟执行),Delegates(容易引起崩溃,注意使用哦)
7.Blueprint
①对艺术家和设计师来说就是逃离恶心程序员”这不可能实现“的最有力武器,你可以做到”你行你上啊~“。Kismet的伙计应该很容易上手
②还是虚拟机,和UnrealScript是一样的。因此,如果你很重视一个环节的执行效率,那么就是用C++
③和UnrealScript一样,但一些方面比UnrealScript强。例如你在添加Component中时可以不用关闭编辑器直接修改模型。实在是工农大解放啊…
8.C++
①UE编程始终可以基于C++,除非你用UDK,你这个穷鬼XD
②紧密结合虚拟机,因为Blueprint变量和方法有时是需要和C++交互的
③为了替换UnrealScript为开发者已经大幅提升了?
9.UE4基础元素
①Actor
我们又见面了Actor,Actor是在一个关卡中持续存在的,通常他包含几个Actor组件。支持网络复制和多人游戏。
Actor不包含位置,方向。这些东西在Root Component中存储。对于UE3 中的Pawn也由PlayerCharacter继承了,因为他有MovementComponent包含跳跃,速度等属性
由SpawnActor() 生成
必须由Destroy来销毁
在游戏中不能被垃圾回收
②什么是ActorComponent?
能被复用的功能可以添加进入Actor
包含一些最有趣的函数和事件
能被Blueprint访问~
③组件例子
Scene Component 添加形状和连接
Primitive Component 添加碰撞和渲染
UAudioComponent,UArrowComponent,UInputComponent,ULightComponent,UMeshComponent,UParticleSystemComponent等等…
写过UnrealScript会对组件深有体会
④PrimitiveComponent组件事件举例
Hit- 再碰到强的时候调用
Begin/EndOverlap -进出一个Trigger
Begin/EndCursorOver 没用过
Clicked/Released 不解释
InputTouchBegin/End
Begin/EndTouchOver
10.你就只认得个Pawn
Pawn就是你的阿凡达
这个傀儡被Controller操纵着
通常处理运动和输入控制
实现HP的好位置
通常没有运动或是输入反馈的代码,你可以在Controller中写
11.来见见老朋友Controller
控制Pawn的傀儡师
可以理解做就是玩家
AIController就是控制AI的
一次只能Possess一个Pawn
当Pawn死的时候可以继续存在,在我的游戏中让pawn死亡,游戏假重置,就是controller直接Possess一个新Pawn的
PlayerControlelr : 玩家阿凡达的接口
处理点击,手柄,键盘
显示和隐藏鼠标指针
不需要Pawn表示的好地方
菜单,语音聊天…
一些别的有用选项
12.Character
这是使用过UE3的人学习UE4最困惑的地方,有了Pawn为什么还要扩展PlayerCharacter?
这是一种能走的Pawn,因为包含了MovementComponent和上边一样,添加了一些有用的组件
处理碰撞
处理客户端角色运动
比UE3有更大的升级
13.HUD
还有一些绘制API
没有构建HUD的工具
UMG直接提供所有的东西,我讨厌老旧UI系统。UMG让人解放了
14.GameMode
还记得GameInfo吗,实现游戏规则用的
配置Pawn,Controller和HUD是谁
能在任何地方被访问,我来告诉你API GetGameMode()
只在服务器和单机实例中存在
GameState是被用来在客户端复制状态用的
默认游戏类型可以在Project Settings中被设置
每个地图都有自己的GameMode,需要你亲手设置
15.Input
这是我对UE4最好的改进之一,你直接在编辑器中添加Bind而不是让你眼花的配置文件中
在Project Settings中设置去吧
获取指令
PlayerController
Level Blueprint
Possessed Pawn
16.碰撞
有多种碰撞处理的方法
线性检测 Line Trace
体积扫过 Geometry Sweeps
重叠测试 Overlap tests
简单的碰撞
Box,胶囊,球,多边形
在运动和物理模拟的时候都需要
复杂碰撞
实际用于多边形
武器和反向动力
在这里再发个牢骚就是。UE4比起U3D ,那简直规矩太多了,他好像什么都想给客户弄好,但是最后弄的臃肿繁琐。记不住的就很容易犯错。这难道就是U3D 能火的原因之一。就像中文和英文,中文每个字差不多就代表一种具象或者抽象的事物,合起来有可以代表其他各种事物,但是这样下来就有好多好多字得记,真佩服古代造字人的智慧,比起智慧中文完胜英文。但是比起易学,易用!有可数数量的字母,相互组合,就能拼接成各种单词。就像给了你基本的框架,你用这些搭建就好了。这样在 学习还有传播 方便 占据 优势。所以,UE4像不像中文?博大而精神,高深而智慧? U3D 像不像 英文? 极简归一,实用易懂?
类命名前缀
虚幻引擎为您提供在构建过程中生成代码的工具。这些工具拥有一些类命名规则。如命名与规则不符,将触发警告或错误。下方的类前缀列表说明了命名的规则。
A
,如 AController。U
,如 UComponent。E
,如 EFortificationType。I
,如 IAbilitySystemInterface。T
,如 TArray。S
,如 SButton。F
,如 FVector。数据类型
数字类型
因为不同平台基础类型的尺寸不同,如 short、int 和 long,UE4 提供了以下类型,可用作替代品:
int8/uint8 :8 位带符号/不带符号 整数
int16/uint16 :16 位带符号/不带符号 整数
int32/uint32 :32 位带符号/不带符号 整数
int64/uint64 :64 位带符号/不带符号整数
标准 浮点 (32-bit) 和 双倍(64-bit)类型也支持浮点数。
虚幻引擎拥有一个模板 TNumericLimits,用于找到数值类型支持的最小和最大范围。如需了解详情,请查阅此 链接 。
字符串
FString
FString 是一个可变字符串,类似于 std::string。FString 拥有许多方法,便于简单地使用字符串。使用 TEXT() 宏可新建一个 FString:
FString MyStr = TEXT("Hello, Unreal 4!")
FText
FText 与 FString 相似,但用于本地化文本。使用 NSLOCTEXT 宏可新建一个 FText。此宏拥有默认语言的命名空间、键和一个数值。
FText MyText = NSLOCTEXT("Game UI", "Health Warning Message", "Low Health!")
也可使用 LOCTEXT 宏,只需要在每个文件上定义一次命名空间。确保在文件底层取消它的定义
// 在 GameUI.cpp 中
#define LOCTEXT_NAMESPACE "Game UI"
//...
FText MyText = LOCTEXT("Health Warning Message", "Low Health!")
//...
#undef LOCTEXT_NAMESPACE
// 文件末端
FName
FName 将经常反复出现的字符串保存为辨识符,以便在对比时节约内存和 CPU 时间。FName 不会在引用完整字符串的每个对象间对其进行多次保存,而是使用一个映射到给定字符串的较小存储空间 索引。这会单次保存字符串内容,在字符串用于多个对象之间时节约内存。检查 NameA.Index 是否等于 NameB.Index 可对两个字符串进行快速对比,避免对字符串中每个字符进行相等性检查。
TCHAR
TCHARs 用于存储不受正在使用的字符集约束的字符。平台不同,它们也可能存在不同。UE4 字符串在后台使用 TCHAR 阵列将数据保存在 UTF-16 编码中。使用返回 TCHAR 的重载解引用运算符可以访问原始数据。
类 | 说明 | 对应U3D 的 |
---|---|---|
TArray | 常用的主要容器。作用与std::vector 类似 |
List |
TMap | 键值对的合集,与 std::map 相似 | Dictionary |
TSet | 保存唯一值的合集,与 std::set 相似 | HashSet |
虚幻C++部分代码解释
C++和 蓝图
虚幻提供两种创建游戏:C++ 和蓝图。程序员可以通过C++ 来添加基础游戏性体统。设计师(美术人员)即可在这个系统上创建关卡或者游戏的自定义游戏性。简单来说,程序员编写的C++是一个游戏的主要关键功能的构建方。而设计师所掌握的蓝图,则起到游戏功能的辅助作用。
类向导
而在创建一个UE4C++对象的时候。类中的会有一下结构。
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
public:
// 设置该 actor 属性的默认值
AMyActor();
// 游戏开始时或生成时调用
virtual void BeginPlay() override;
// 每帧调用
virtual void Tick( float DeltaSeconds ) override;
};
字段 | 作用 |
---|---|
BeginPlay() | 和U3D 的 Start()方法一样。在进入游戏状态的时候运行 |
Tick() | 没帧调用和U3D 的 Update() 一样。如果不需要此功能。必须在构造函数中说明。如下代码 |
AMyActor::AMyActor()
{
// 将此 actor 设为每帧调用 Tick()。不需要时可将bCanEverTick设置为false以关闭此功能,来提高性能。
PrimaryActorTick.bCanEverTick = true;
}
UPROPERTY()
来轻松的将属性公开至编辑器中可视。比如:UPROPERTY(EditAnywhere)
宏即可。UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
UPROPERTY(EditAnywhere)
int32 TotalDamage;
...
};
之后。你就可以在编辑器中看到变量TotalDamage对应的数值。并且可以对数值进行调节。
除此之外还可以加入其他的属性。
属性 | 作用 | 用法 |
---|---|---|
EditAnywhere | 使这个变量出现在编辑器中可编辑 | EditAnywhere |
Category | 使此属性与相关属性出现在一个部分 | Category=”Damage” |
BlueprintReadWrite | 使属性为可读取和可编写状态 | BlueprintReadWrite |
BlueprintReadOnly | 使属性为可只读取状态 | BlueprintReadOnly |
VisibleAnywhere | 意味着属性在虚幻编辑器中为可见状态,但是不可编辑 | VisibleAnywhere |
Transient | 意味着无法从硬盘对其进行保存或加载 | Transient |
更多属性修饰符 |
除此之外,还可以通过宏定义来是一些属性在其关联属性被更改的时候也做出相应的改变:
void AMyActor::PostInitProperties()
{
Super::PostInitProperties();
CalculateValues();
}
void AMyActor::CalculateValues()
{
DamagePerSecond = TotalDamage / DamageTimeInSeconds;
}
//通过WITH_EDITOR定义使这个函数的目标对象在编辑器中被更改时引擎将通知和运行这个函数。
#if WITH_EDITOR
void AMyActor::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
CalculateValues();
Super::PostEditChangeProperty(PropertyChangedEvent);
}
#endif
UFUNCTION(BlueprintCallable, Category="Damage")
void CalculateValues();
如上。 可以通过 UFUNCTION
宏来把C++函数对反射系统公开。 而 BlueprintCallable将其对蓝图虚拟机公开。这样在在蓝图中,右键点击快捷键菜单就可以使用这个函数。
函数 | Are |
---|---|
BeginPlay() | 和U3D 的 Start()方法一样。在进入游戏状态的时候运行 |
Tick() | 每帧调用和U3D 的 Update() 一样。如果不需要此功能。必须在构造函数中说明。 |
EndPlay | 对象离开游戏进程时调用 |
UCLASS()
等等。这些都是UE4使用其自身的反射实现,来启动动态功能,比如,垃圾回收,序列化,网络复制和蓝图/C++通信。这些功能你可以根据需求选择的加入,只要为相应的类型添加正确的标记就可生成反射数据。 一下是一些基础标记:标记 | 说明 |
---|---|
UCLASS() | 告知虚幻引擎生成类的反射数据。类必须派生自 UObject。 |
USTRUCT() | 告知虚幻引擎生成结构体的反射数据。 |
GENERATED_BODY() | UE4 使用它替代为类型生成的所有必需样板文件代码。就是说这个类不直接使用父类的声明。但是,你必须得去实现,自己去声明,否则就会报错。更多信息 |
UPROPERTY() | 使 UCLASS 或 USTRUCT 的成员变量可用作 UPROPERTY。UPROPERTY 用途广泛。它允许变量被复制、被序列化,并可从蓝图中进行访问。垃圾回收器还使用它们来追踪对 UObject 的引用数。 |
UFUNCTION() | 使 UCLASS 或 USTRUCT 的类方法可用作 UFUNCTION。UFUNCTION 允许类方法从蓝图中被调用,并在其他资源中用作 RPC |
eg:
#include "MyObject.generated.h" //这个头文件包含虚幻引擎所有反射数据。必须在声明类型的头文件中将此文件作为最后的 include 包含。
UCLASS(Blueprintable)
class UMyObject : public UObject
{
GENERATED_BODY()
public:
MyUObject();
UPROPERTY(BlueprintReadOnly, EditAnywhere)
float ExampleProperty;
UFUNCTION(BlueprintCallable)
void ExampleFunction();
};
您还会注意到,可以在标记上添加额外的说明符。此处已添加部分常用说明符用于展示。通过说明符可对类型拥有的特定行为进行说明。
Blueprintable - 此类可由蓝图延展。
BlueprintReadOnly - 此属性只可从蓝图读取,不可写入。
Category - 定义此属性出现在编辑器 Details 视图下的部分。用于组织。
BlueprintCallable - 可从蓝图调用此函数。
说明符太多,无法一一列举于此,以下链接可用作参考:
UCLASS 说明符列表
UPROPERTY 说明符列表
UFUNCTION 说明符列表
USTRUCT 说明符列表
// 将找到当前所有的 UObjects(此处可以写入你自定义的类的类名) 实例
for (TObjectIterator It; It; ++It)
{
UObject* CurrentObject = *It;
UE_LOG(LogTemp, Log, TEXT("Found UObject named:%s"), *CurrentObject.GetName());
}
但是这个在PIE(Play In Editor)
中使用的话可能产生意外后果。 因为编辑器已被加载,除编辑器正在使用的对象外,对象迭代器还将返回为游戏世界实例创建的全部 UObject。
而 Actor 迭代器 呢还和 对象迭代器的使用不一样,但套路就那么多。
APlayerController* MyPC = GetMyPlayerControllerFromSomewhere();
UWorld* World = MyPC->GetWorld();
// 和对象迭代器一样,您可提供一个特定类,只获取为该类的对象
// 或从该类派生的对象
for (TActorIterator It(World); It; ++It)
{
// ...
}
这个迭代只能用于派生自 AActor 对象。
创建 actor 迭代器时,需要为其赋予一个指向 UWorld 实例的指针。许多 UObject 类(如 APlayerController)会提供 GetWorld 方法,助您一臂之力。如不确定,可在 UObject 上检查 ImplementsGetWorld 方法,确认其是否应用 GetWorld 方法。
常见情况下 actors 带有 Uobject 属性。
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
public:
UPROPERTY()
MyGCType* SafeObject;
MyGCType* DoomedObject;
AMyActor(const FObjectInitializer& ObjectInitializer)
:Super(ObjectInitializer)
{
// 创建两个对象。他们自动成为根集的一部分。
// 这个指定了UPROPERTY。SafeObjet将不会被垃圾回收,因为他从根集对象出到达。
SafeObject = NewObject<MyGCType>();
//DoomedObject 因为未将标记为 UPROPERTY,因此回收器并不知道其正在被引用,而会将它逐渐销毁。
DoomedObject = NewObject<MyGCType>();
}
};
void SpawnMyActor(UWorld* World, FVector Location, FRotator Rotation)
{
World->SpawnActor<AMyActor>(Location, Rotation);
}
由上面代码。可知,UObject 被垃圾回收时,对其的所有 UPROPERTY 引用将被设为 nullptr。那么 上面的 属性 SafeObject 将会为 nullptr。 所以为了安全的检测一个对象是否被垃圾回收。我们应该在使用的时候进行判断:
if (MyActor->SafeObject != nullptr)
{
// 使用 SafeObject
}