游戏结构
我们来玩玩引擎吧,先来来说一说一个普通虚幻游戏的框架吧(好对照着引擎说)
上图是我在ununreal 官网抓下来的。现在我们来梳理一遍。可以看见 一个游戏(Game)的核心是游戏模式(GameMode)和游戏状态(GameState)。他们决定了游戏怎么玩(准确说怎么结束)。之后我们看见他把一个玩家控制器(PlayController) 加入(join)进去了,而这个控制器他控制(possess)着Pawn(不知道怎么翻译,暂且认为旗子吧)。而这个Pawn也可能是有AI控制器控制着(AIController)。再来看看玩家控制器中还有一个包含(Contains)这玩家界面(HUD),输入(input),玩家相机管理器(PlayerCameraManger)。
引擎的初步认识
Viewport
上图红色区域叫做Viewport,他实时显示着我们的游戏世界,一下是操作,请自己试试
- 鼠标左,右键的拖拽和两个键一起拖拽
- 像上面那样按住鼠标按键之后我们可以按q,w,e,a,s,d,z,,c建
- alt 建+鼠标左,右键拖拽
- 鼠标左键选择好ViewPort中的物体之后按F键会把窗口聚焦到该物体上。
World Outliner
绿色的World Outliner ,显示这我们游戏世界(Viewport)中所有的东西,无论是看得见还是看不见。
Content Browser
黄色区域Content Browser对应这游戏工程在硬盘中的Content文件夹下的文件结构。file->open project->右键某个工程->show in explorer
modes
蓝色modes 窗口,是一些预设的东西,你可以把想要的东西拖拽到ViewPort中,这样World outliner中也会出现该东西。
Details
其实右下角还有一个窗口 Details ,当你在在world outliner 或者Viewport中选择了相应的东西之后,Details 就会显示该物件的详细信息,因为我们没有选择任何东西,所以上图时空的。
tips:
如果你不小心把上面的窗口给关闭了,可以在左上角菜单栏window中找到所有窗口
我们都知道unreal engine 是用c++最为开发语言的,而c++虽然很强大,但是因为c++编译语言的特性导致一些开发效率上的问题。所以epic 在开发unreal 的时候给他开发了很多有用的工具可以解放很多生产力。而这些工具放心使用的前提是你要遵守unreal 代码的书写规范。我会在以后陆续归纳其中的规则。
类命名前缀
虚幻引擎为您提供在构建过程中生成代码的工具。这些工具的使用必须遵循一些类命名规则。如命名与规则不符,将触发警告或错误。下方的类前缀列表说明了命名的规则。
派生自 Actor 的类前缀为 A,如 AController。
派生自 对象 的类前缀为 U,如 UComponent。
枚举 的前缀为 E,如 EFortificationType。
接口 类的前缀通常为 I,如 IAbilitySystemInterface。
模板 类的前缀为 T,如 TArray。
派生自 SWidget(Slate UI)的类前缀为 S,如 SButton。
其余类的前缀均为 字母 F ,如 FVector。(为什么以F开头 官方给了链接 )
UObject和AActor
UObject和AActorn的是c++类名,名字遵循之前讲的类命名前缀
UObject很像java 中的Object类,是Unreal engine 中绝大数类的父类,然而UObject类还不是根类,他”上面“还有 UObjectBaseUtility, UObjectBaseUtility”上面“还有UObjectBase。但是相对于UObjcet的父类来说,UObject是Unreal engine 最小的 ”单位“了,就像细胞内部其实还有东西,但是相对于人体来说我们可以把细胞看成最小的单位。这样你说你可能还是觉得有点抽象,那么我们来看看作为最为最小”单位“UObject能完成什么工作吧。(官方给的)
- 垃圾回收
- 引用更新
- 映象
- 序列化
- 默认属性变化自动更新
- 自动属性初始化
- 自动编辑器整合
- 运行时类型信息可用
- 网络复制
你可能不知道上面各项具体意思,没关系我也不知道。但是我们可以知道,官方认为上面的功能是Unreal engine 中每一个“东西”都可以有,但是不一定有的,所以就把这些功能写在了UObject类中了。
现在来看看AActor。字面意思“演员”,但是不要被它迷惑,其实它是能被放入游戏世界场景的所有游戏性对象的基础类(不论能不能看见)。其实官方给了一个更好的解释,“AActor是一堆UActorComponent的容器” 现在你可能不知道什么是UActorComponent,没关系以后你自然会知道。还有一点很重要AActor继承自UObject,所以一个AActor也是一个UObject。
知道这些之后我们就可以在来玩一玩unreal了。下面其实是一个官方案例,我觉得很不错就拿他来说一说了(其实就是懒)
首先我们要新建一个AActor,
在content Browser 中-> Add New->New C++ Class
然后你会看见Actor,注意不是AActor,为什么会这样呢,因为unreal 在设计时就定了一个小目标,那就是无论你是不是程序员都可以来开发游戏,
所以写程序的人和设计游戏的人工作环境时相互独立的(visual studio 和unreal engine),程序员在visual studio 写的代码必须遵循之前所说的类命名前缀,之后官方开发的”小“工具就会把这些类的名字前缀在unreal engine中给屏蔽掉,所以设计人员在unreal engine 中看见的都是没有类命名前缀的代码。事实上官方为了设计分离还做了一个叫做blueprint的东西,
好了回到之前的话题,选择它在选择Next,之后就是起名,我就起一个”FloatingActor“,路径就不要碰了(除非用文件夹分类)。不然会出现意想不到的错误(比如 ,必须在Source目录下才能存储我们创建的类)。然后 Create Class。之后会出现自动启动Visual studio 。
tips:
如果你 因为不可抗逆原因打不开的话,你可以unreal工程文件夹下,有一个以你工程名命名.sln结尾的文件,用visual studio 点开它 然后在Games-> Source->工程名 ,也会出现你AActor
Hot Reloading
现在来说一说Hot Reloading 。这是个什么东西呢。我们都知道c++是一个需要编译的语言,解决方案分为两个你部分一个Engine,一个Game。Engine就是我们的unreal engine的本体了,也就是我们之前的操作界面,当我们编译运行上面代码时,会把unreal 引擎和我们的游戏代码一起编译运行(不信你可以试试)那么问题就来了,如果每一次我们修改代码都要编译一次。我的天,那么每一次都要关掉之前的界面,在打开编译的界面。于是hot reloading技术就诞生了。在unreal界面仍在运行时直接以普通方式从 Visual Studio 或 Xcode 进行编译。unreal 界面将检测到新编译的 DLL 文件是否修改并即时重载变更!当我们修改完代码我们可以
然后就出现
这样我们的unreal 就会自动更新我们编译之后的代码了
或者
也可以完成上面的效果
回到之前,我们创建完floatingActor 我们就会在unreal 的content browser中看见
unreal property system (reflection)
接着我们回到visual studio中,先看我们
//防止多次include
#pragma once
//AActor定义在一下头文件中,看起来像文件夹结果,其实就是
#include "GameFramework/Actor.h"
//很重要单独说
#include "FloatingActor.generated.h"
//很重要单独说
UCLASS()
class STUDY_API AFloatingActor : public AActor
{
//很重要单独说
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AFloatingActor();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
};
学习过c++的人看见这个类,有一种似曾相识的感觉。但是又有一点不一样,因为这是编辑器给我自动生产的代码。很多代码的运转,都有底层的一些”小“工具(程序)来协同运转。而上面有很多代码都是写给这些“小”工具看的。下面我们一一道来。#include "FloatingActor.generated.h"
这句预编译语句非常重要。想要解释我们必须知道一个叫做 unreal property system 又叫reflection,这个套系统为程序赋予运行时进行自我检查的能力。很像java 中的反射机制。翻译成大白话就是当我们的游戏程序跑起来的时候,让unreal知道类中都写了什么。当知道程序中写了什么之后。这写信息就可以被用于诸如 编辑器中的细节面板、序列化、垃圾回收、网络复制、以及蓝图与C++交互等功能。是不是很强大。上面你看见的宏 UCLASS(),GENERATED_BODY(),这些宏标记了需要反射的东信息,当我们编译上述源代码时,UnrealBuildTool工具就会调用UnrealHeaderTool工具,UnrealHeaderTool工具会生成一些代码,~.generated.h,~.generated.cpp就是其生成的代码,其中包含了实现反射的详细代码。在C++定义内部它还包含一个GENERATED_UCLASS_BODY()的宏。GENERATED_UCLASS_BODY() / GENERATED_USTRUCT_BODY()在需要反射的类或者结构体里面是必要的,因为它们会加入额外的函数和typedef到类的内部。这方面的内容有很多我暂时也不懂,不想因为这个打乱学习接着只能不求甚解了,以后遇到会接着说。
参考:
https://www.unrealengine.com/blog/unreal-property-system-reflection
翻译:
http://www.cnblogs.com/ghl_carmack/p/5698438.html
BeginPlay(),Tick(float DeltaTime)
可以看见自动生成的代码覆盖了两个方法,BeginPlay()是一个事件,将告知您 Actor 已以可操作状态进入游戏中。现在便适合开始类的游戏性逻辑。Tick() 每帧调用一次,参数对应的时间量为自上次调用传入的实际运算时间。在此可创建反复逻辑。
现在我们来看看源代码
// Fill out your copyright notice in the Description page of Project Settings.
#include "Study.h"
#include "FloatingActor.h"
// Sets default values
AFloatingActor::AFloatingActor()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
}
// Called when the game starts or when spawned
void AFloatingActor::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AFloatingActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
对于以项目名命名的头文件Study.h大家可以先忽略,如果像了解可以参考下面链接
参考
http://www.cnblogs.com/wellbye/p/5837108.html
这个类只有一个可以说的PrimaryActorTick.bCanEverTick = true;
。设置这个参数之后,就会不间断调用Tick函数,如不需要此功能,最好将其移除(设置为false),以节约少量性能开销。如要移除此功能,必须将构建函数中说明 tick 应该发生的代码行删除。
AActor在世界的表现
现在我们会到unreal中,把我们创建FloatingActor拖到Viewport中
可以看见右边的world outliner中已经有我们的Actor,但是你在ViewPort中我们什么也看不见啊。先别急,我之前说过AActor是能被放入游戏世界场景的所有游戏性对象的基础类。但是还有一个更好的解释,**“一堆component的容器”现在就是你理解这句话的意思的最好时候,现在请安下面图片进行操作
现在来你应该知道了,AActor派生的类其实就是一个容器,而UComponent派生的类就是真正的功能模块,对于component以后会说,现在有一个了解就可以了。
现在我们我们回到Visual studio ,在floatingActor.h中加入下代码
....
float RunningTime;
....
在floatingActor.cpp加入下面代码
...
void AFloatingActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
FVector NewLocation = GetActorLocation();
float DeltaHeight = (FMath::Sin(RunningTime + DeltaTime) - FMath::Sin(RunningTime));
NewLocation.Z += DeltaHeight * 20.0f; //把高度以20的系数进行缩放
RunningTime += DeltaTime;
SetActorLocation(NewLocation);
}
...
然后像上面热重载章节所说的那样选择一种方法 来编译。之后点击ViewPort上面的Play 按钮浏览一下画面
Tips:
shift+1或esc来退出