UE4入门(从Unity3D转学UE4)

参考:https://docs.unrealengine.com/4.27/en-US/BuildingWorlds/LightingAndShadows/QuickStart/
参考:https://docs.unrealengine.com/4.27/en-US/Basics/UnrealEngineForUnityDevs/

Unity里新建的场景是默认有一个Camera和一个Directional Light光源的,但是UE4创建的空场景啥也没有,所以这里先还原一下在UE4里创建类似Unity的空场景的操作。


搭建空工程

创建一个Empty Project,StartContent文件夹里的东西可以清空(如果有的话)


创建Level,拖入Cube

点击new level,创建一个Empty Level,里面由于没加光源,目前一片漆黑,我拖了一个Plane进去,但是看不到,所以可以用线框模式预览,如下图所示:
UE4入门(从Unity3D转学UE4)_第1张图片


添加光源

我直接拖了个directional light进去,提示我要rebuild light,然后我在build选项下面选择build light only,重新更新灯光,就出现东西了(我之前放了个平面,正好没有接收到光,以为没有光照呢)

UE4入门(从Unity3D转学UE4)_第2张图片
画面如下图所示:

点击build之后会多一个名为MyLevel_BuiltData的文件,后缀为.uasset


添加天空盒

我看到有搜BP_Sky_Sphere拖拽进去的,也有教程让搜 Atmospheric Fog拖拽进去的,感觉效果是差不多的。


添加Camera

可以在左边里搜索Camera,拖到场景中,只要在Hierarchy里选中Camera时,就会出现一个小的相机窗口,如下图所示:

然而按Play之后,画面没有任何变化。因为UE4里只有Viewport窗口,不存在Game窗口。Unity则是默认有个MainCamera,Scene对应的Viewport和Game窗口各自有一个Camera,Unity里把main camera放到任何一个位置,按Play后就会自动播放那个相机看到的画面。

在UE4,也可以来实现这种效果,但是没有那么Unity那么简单,需要通过blueprint来实现,让拖拽进来的CameraActor在游戏开始时作为画面的Camera,后面再细说。


到这里,一个空场景基本就搭建完了,接下来介绍一些其他的基本操作。


UE4在Viewport窗口下的操作按键

跟Unity一样的地方就不介绍了,这里主要介绍不一样的地方

  • UE4里想要实现视角以物体A为中心旋转,可以选中该物体按F,然后按Alt加鼠标右键旋转
  • UE4里按住鼠标中键拖动,效果正好跟Unity的相反,会让人很不适应,可以在Editor Preferences里面去设置,如下图所示:
    UE4入门(从Unity3D转学UE4)_第3张图片

UE4 项目文件夹介绍

如下图所示,是我创建空白工程时生成的文件夹:
UE4入门(从Unity3D转学UE4)_第4张图片


创建的空工程默认是不含C++代码的,如果在Content Browser里添加C++空类,项目结构会改变成下图所示,红色的区域是新增的内容,应该是Binaries文件夹负责放C++代码编译得到的二进制文件,而Source文件夹里放工程的C++代码:
UE4入门(从Unity3D转学UE4)_第5张图片


UE4各个文件夹的作用

参考:https://docs.unrealengine.com/4.27/en-US/Basics/DirectoryStructure/
这里说的文件夹,一般会出现在两个地方,一个是UE4引擎源码对应的地方,另一个是实际的Game Project对应的地方,两个地方都有相同命名的文件夹,它们的作用也是差不多的,这里只说Game项目里的文件夹的作用:

  • Config:存放项目配置文件
  • Source:里面的子文件夹是按照Module来区分的,每个Module对应一个子文件夹,每个子文件夹都会分为Classes、Private和Public这三个子文件夹,
  • Content:存放资源的,including asset packages and maps.
  • Saved:主要是自动保存(autosaves)的文件、配置.ini文件和log文件,Source Control里不需要考虑此文件夹
  • Intermediate:顾名思义,一些temp文件存放的地方,还存放了Shaders,Source Control里不需要考虑此文件夹
  • Binaries:Contains executable files or other files created during compiling.

在这里插入图片描述


UE4文件夹的版本控制

参考:https://gamedev.stackexchange.com/questions/72248/which-unreal-engine-4-project-files-can-i-ignore-in-source-control

下面是一个常用的.gitignore文件:

Engine/Binaries/
Engine/DerivedDataCache/
Engine/Intermediate/
Engine/Plugins/*/*/Binaries/
Engine/Plugins/*/*/Intermediate/
Engine/Programs/UnrealHeaderTool/
Engine/Programs/UnrealPak/
Engine/Saved/
Engine/Shaders/Binaries/
Engine/Source/*/*/*/obj/
Engine/Source/*/*/obj/
Engine/Source/*/obj/

UE4.opensdf
UE4.sdf
UE4.sln
UE4.suo
UE4.*.suo

GameName/Binaries/
GameName/DerivedDataCache/
GameName/Intermediate/
GameName/Saved/


Unity的GameObject与UE4的Actor

虽然二者理念类似,但实际上也有区别,虽然UE4和Unity都用的是EC(Entity Component)的设计理念,但二者还是有区别的。Unity的GameObject是一个Entity,本身不带任何功能,如果要让其带特定的功能,需要添加对应的Component,也就是说,GameObject的功能实现在其Component上,如果要写代码,必须写在Component里;然而UE4就不一样,UE4的Actor跟代码层的概念更相似,Actor是可以被继承的基类,虽然Actor也可以挂在Component,Component也可以写代码,但是Actor本身的功能一般都主要写在Actor自身带的代码区域里,如下图所示,阴影区为可以自定义代码的地方,注意,UE4里Component之间也可以有父子层级,但Unity不可以,如下图所示,划线部分代表可以写代码的地方:
UE4入门(从Unity3D转学UE4)_第6张图片

额外说一点,UE4所有跟Input和逻辑相关的东西(比如说角色撞到硬币加十分),UE4希望用户写在Actor对应的脚本位置里面,而不是其Component里,所以有很多相关的函数,只可以在Actor的函数里调用,比如说鼠标相关的API只有Actor可以调用,但是键盘的API都可以调用(Component调用有点麻烦),如下图所示:
UE4入门(从Unity3D转学UE4)_第7张图片

UE4所有的Component,都可以去被继承,然后做别的事(不知是否准确),但是Unity就很不同,Unity为了简化操作,很少用到继承

关于UE4的Components

UE4有两种自定义的Component,蓝图对应的Component和脚本对应的Component,后者类似于Unity的继承于MonoBehaviour的脚本,由于是C++文件,这里既有头文件,也有cpp文件,头文件如下:

// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "Components/ActorComponent.h"
#include "NewActorComponent1.generated.h"

// 我自己定义的Component名字叫NewActorCOmponent1,这里在前面给我加了个U字母
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class MYFIRSTPROJECT_API UNewActorComponent1 : public UActorComponent
{
	GENERATED_BODY()

public:	
	// Sets default values for this component's properties
	UNewActorComponent1();
protected:
	// Called when the game starts
	virtual void BeginPlay() override;
public:	
	// Called every frame
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
};


UE4的Transform Component

In Unreal Engine 4 Actors are composed of components, every Actor has a base Scene Component which is referred to as the Root Component of that Actor and provides its world transform.

Unity里每一个GameObject都有一个Transform Component,UE4里也差不多,每个Actor都有一个基本的Scene Component,它记录了Actor的Transform,还是Actor上所有Components的Root。这意味着:UE4的Component之间是可以有Hierarchy关系的

如下图所示,这里物体的Transform除了有基本的位置、旋转和大小外,还有个Mobility:
UE4入门(从Unity3D转学UE4)_第8张图片


Component里的Mobility

关于Mobility,有三种模式:

  • Static:This mobility is reserved for Actors that are not intended to move or update in any way during gameplay.
  • Stationary:This mobility is reserved for Actors that can change during gameplay but not move.
  • Moveable:This mobility is reserved for Actors that need to be added, removed, or moved during gameplay.

UE4的Tooltip里的解释为:

Mobility for primitive components controls how they can be modifed in game and therefore how they interact with lighting and physics. A movable primitive component can be changed in game,but requres dynamic lighting and shadowing from lights which have a large performance cost. A static primitve component can’t be changed in game, but can have its lighting baked, which allows rendering to be very efficient.

UE4里Transform会带一个Mobility设置,用于指定对应的Actor与光照和物理系统交互的方式:

  • Movable component会接受动态光照和阴影,渲染消耗是最高的
  • Static component就是静态的,可以将其光照和阴影信息烘焙到light map里,渲染消耗是最低的
  • Stationary则在二者之间,不会移动,但是会改变,可以使用cached lighting model,比如cached dynamic shadows

The Mobility setting controls whether an Actor will be allowed to move or change in some way during gameplay. This primarily applies to Static Mesh Actors and Light Actors.

PS: 我有次点击Play,进入PlayMode后,发现物体不可以通过Transform对应的三个箭头进行拖拽,但是可以改变Details里的Transform进行改变,后来发现原因是物体的Mobility被标记为static,而不是movable


Mobility对于不同类别的Actor代表的效果不同

由于每一个Actor都会有各自的Mobility,介绍Mobility的时候,这里的Actor可以分为两种:

  • Static Mesh Actor:就是Unity的带正常Mesh的物体
  • Light Actor: 我理解的就是光源

对于Static Mesh Actor而言,Mobility的三种模式功能简单介绍如下:

  • Static: 该物体的阴影会事先计算好,存在光照贴图中,而且该物体不可以被移动位置
  • Stationary: 该物体的阴影不会事先计算好,而是会实时计算,该物体同样不可以被移动位置
  • Movable: 该物体的阴影是实时生成的,而且效果比Staionary还要精细,可以移动位置、被删除和增加

对于光源 Actor而言,Mobility的三种模式功能简单介绍如下:

  • Static: 该光源的光会预先生成对应的lightmap,同时参与动态光照计算(应该只是加入进去,像环境光一样,永远不会更改的)
  • Stationary:该光源可以改变颜色、强度等属性,但是不可以改变位置,光源既会参与预先生成的lightmap,也会参与实时光照计算
  • Movable: they can only cast dynamic shadows,性能消耗较大,但是non-shadowing Movable Lights are very inexpensive to calculate due to Unreal Engine’s Deferred rendering system

关于三种光源的补充介绍如下图所示:
UE4入门(从Unity3D转学UE4)_第9张图片

蓝图

Unity里的自定义Components都是通过MonoBehaviour写脚本实现的,而UE4里提供了蓝图,让用户无需写代码即可自定义Components。

Blueprint可以挂载给任何场景里的物体,场景本身就有一个Blueprint,可以理解为Level的GameManager类,类比于Unity,类似于在Unity的Scene里面挂一个空的Gameobject的Handle,然后挂载脚本。

点击Blueprints下面的Open Level Blueprint,如下图所示:
UE4入门(从Unity3D转学UE4)_第10张图片
得到的蓝图如下所示,Event BeginPlay相当于Unity的Start函数,Event Tick相当于Unity的Update函数,如下图所示:
UE4入门(从Unity3D转学UE4)_第11张图片

设置刚刚创建的Camera为Main Camera

UE4里本身是没有Main Camera这个概念的,为了让Play的画面由指定的Camera的画面显示,需要用蓝图或者脚本进行指定,可以在Level的Blueprint里的Start函数里进行指定,设置游戏开始时的Viewport到场景里拖拽的CameraActor上。

打开Level的Blueprint编辑窗口,右键搜索SetVuewTargetWithBlend,创建该节点,如下图所示:
UE4入门(从Unity3D转学UE4)_第12张图片
这里的Blend可能是在BlendTime里逐步转换画面的意思,这里直接设置为0.0s,意思是瞬间转换View,这里的白色箭头代表函数调用顺序,ViewTarget会从原本的Target转换到New View Target上。

这里的New View Target自然就是新创建的CameraActor了,为了在蓝图里获取它的引用。这里介绍一个把物体的ref拖进蓝图的方式,在打开蓝图的同时,选中场景里的物体,然后在蓝图里面点击右键,选择Add reference,如下图所示:
UE4入门(从Unity3D转学UE4)_第13张图片然后获取原本的Target即可,这里创建GetPlayerController节点,拖出下面蓝图的样子即可:
UE4入门(从Unity3D转学UE4)_第14张图片

再直接进入PlayMode,就可以播放该Camera的画面了


关于GameMode

Play当前场景后,会发现场景里多了个球,按WASD可以控制它的移动,这是因为默认的GameMode创建了一个Sphere作为玩家角色。点击Play后,hierarchy里面会多出很多组件,如下图所示:
UE4入门(从Unity3D转学UE4)_第15张图片

黄色的都是runtime系统自己添加的东西,DefaultPawn代表默认的玩家,这也是为了方便玩家自己做多人在线游戏的工具(PS:UE4很适合做多人在线游戏)

如果要想去掉默认的GameMode,可以点击Settings打开world settings,如果没有world settings窗口,可以在Window栏下面找到并打开,找到下面的GameMode,如下图所示,想要去掉默认的GameMode,需要自己创建一个空的GameMode,然后Override原本的默认GameMode:
UE4入门(从Unity3D转学UE4)_第16张图片
通过蓝图创建一个空的GameMode,然后指定进去,改变Default Pawn Class,如下图所示:
UE4入门(从Unity3D转学UE4)_第17张图片
再点击Play,按WASD就不会有任何反应了,此时Play的效果就跟Unity默认的Play效果是一样的了。



UE4里的Prefab

Unity’s workflow is based on prefabs. In Unity you build a set of GameObjects with components, then create a prefab from them. You can then place instances of the prefab in your world, or instantiate them at runtime.


UE4’s corresponding workflow is based on Blueprint Classes. In UE4, you build an Actor with components, select it, and click the Blueprint / Add Script button (in the Details panel). Then, choose a place to save your Blueprint Class, and click Create Blueprint to save your new Blueprint Class!

UE4通过蓝图(Blueprint)实现类似Unity的Prefab,感觉也是挺神奇的,这里制作Prefab并不是像Unity那样拖拽到Asset文件夹即可,而是要转换为Blueprint Class

具体做法如下图所示,我之前创建了一个Actor,它带Camera和我自定义的蓝图组件:
UE4入门(从Unity3D转学UE4)_第18张图片

选中Actor,这里通过把选中的Actor转换为Blueprint class的方式,存为一个Prefab,如下图所示:
UE4入门(从Unity3D转学UE4)_第19张图片
存完之后,会多一个文件,这就是prefab了,可以拖拽很多个到场景里,而且仍然可以用键盘操作让它移动,如下图所示:
UE4入门(从Unity3D转学UE4)_第20张图片

打开文件会进入一个窗口,很像Unity的Prefab Mode,如下图所示:



UE4导出资源

这里的机制跟Unity不太一样,Unity不会更改导入的资源文件本身,而是把相关导入信息放到library下,相关引用和设置用对应的.meta文件记录。而UE4里的资源好多都是导入变成.uasset文件的,我直接从UE4里的项目里copy,然后放到别的UE4项目里,会显示导入报错。

看了下,UE4里也有类似Unity的导出.unitypackage的操作,选中资源,右键:UE4入门(从Unity3D转学UE4)_第21张图片

它这里可不是仅仅导出该资源就完事了,比如我导出一个动画文件blendspace.uasset,它会把这个资源依赖的所有文件都导出,比如人物模型、物理资源、动画sequence资源、贴图等等,还挺科学。
UE4入门(从Unity3D转学UE4)_第22张图片
这里的导出好像要直接选择其他项目的Content文件夹,它好像没有.unitypackage这样的中间件(我不确定),选择之后新的项目里就出现了该资源和对应引用的资源了:
UE4入门(从Unity3D转学UE4)_第23张图片


UE4的命令行Console Command

在别人的UE4工程里的Level BP里看到这个,所以好奇这是啥意思:
UE4入门(从Unity3D转学UE4)_第24张图片
参考:https://www.youtube.com/watch?v=NwypPq_2hxc&ab_channel=MathewWadstein

这里首先要搞清楚什么UE4的Console Command怎么使用的,它可以在这里的蓝图使用,也可以在Play Mode下使用,在Play Mode下按~键(按两次可以让窗口更大),可以输入命令,比如输入Stat_FPS可以看游戏的FPS数据:
在这里插入图片描述
视频里还介绍了一些指令:

  • t.MaxFPS + 数字:限制最大的每秒帧数
  • r.SetRes + 800*600:设置分辨率
  • Stat Unit: 查看包含draw call和GPU相关的每帧信息
  • DumpConsoleCommands:在output log里打印所有可用的commands,dump是倾倒的意思

至于最上面的图里的,就是另外一种在代码里可以PlayMode下自动执行的(不知道是不是一定是打包后的runtime),通过蓝图来输入,后面有个Specific Player指令,用于表示Player Controller Reference,用于表示对应的player,适用于多个玩家的系统,像这个关卡就一个Player,所以可以不指定,就用默认的就行。

不进入PlayMode,按~也可以输入指令,但是一定要注意,这里的Command都是永久生效的,不管你是不是在Play Mode下输入的Commands

上面提到的ToneMapping的指令,看了下是HDR颜色mapping到LDR颜色范围的东西,跟渲染有关,没多大影响:https://docs.unrealengine.com/4.26/en-US/RenderingAndGraphics/PostProcessEffects/ColorGrading/



附录一些小知识

UE4里进入PlayMode再退出PlayMode后,会改变相机位置

对于一个空场景,Unity里按Play按钮不会有任何反应,只会展示Camera位置看到的图。

而UE4里,对一个空场景,点击play,会发现相机可以按WASD移动,而且按ESC退出后,相机的位置也改变了。

如果不想在Play阶段改变相机的位置,可以点击左上角的Edit->EditorPreferences->Viewports->Lock and Feel->Use Camera Location in Play Mode,如下图所示,取消勾选后,Camera会回到点击Play进入游戏之前的位置:
UE4入门(从Unity3D转学UE4)_第25张图片


UE4与Unity中实现云和雾的效果

UE4里自带云和雾的特效,对于雾效,在左边搜寻fog就可以了,对于云的效果,在Skybox下面的属性里面直接可以调:
UE4入门(从Unity3D转学UE4)_第26张图片

Unity实现就麻烦一些,需要安装插件,如下图所示:

UE4入门(从Unity3D转学UE4)_第27张图片



.generated文件的作用

MyClass.generated.h is the include file generated by the UnrealHeaderTool (before UnrealBuildTool compilation) while parsing unreal macros in MyClass.h.All the UCLASS, USTRUCT, UPROPERTY, UFUNCTION macros produce code in the generated.h and so the Blueprint magic can occur in your project with a minimal effort from your side.

相当于把UE4的这些特殊的用于编辑器下的宏,用工具读取出来,生成对应的头文件,就叫.generated.h的头文件,有了这些文件,才可以在Editor下对其进行操作。


蓝图里的Set Timer节点

Timer是计时器的意思,所以很好理解,就是设置计时器,如下图所示,在过了输入的时间之后,会调用event或者调用函数:
UE4入门(从Unity3D转学UE4)_第28张图片
可以勾选loop让这个计时器不断循环调用,它会比一直在Tick里每帧去查询要效率高,不过它这个不保证间隔的时间一定精准,它可能会受到当前frame时间,和frameRate影响,只会保证尽可能的接近输入的Timer的时间值。


UE4如何让一个蓝图引用另外一个蓝图

我目前的理解,UE4的蓝图既像Unity的Prefab,又像Unity的MonoBehaviour。Unity里,如果组件A想引用B,那么A里面声明一个public变量,然后在场景里把B对应的Component拖拽到A对应的槽位里即可,或者在代码里Get也行。

参考:https://docs.unrealengine.com/4.26/en-US/ProgrammingAndScripting/ProgrammingWithCPP/ReferenceAssets/

当一个新的Actor的引用被加入到蓝图里的时候,被加入的蓝图里会多出一些该Actor可以使用的蓝图节点选项,具体如何加引用,可以分为如下几种情况:

1. 如果要记录引用的蓝图是Level蓝图,而且被引用的Actor在Level里
这种情况下比较简单,鼠标选中场景中的物体,在关卡蓝图右键添加引用即可,直接从场景里拖物体进去也行。


2. 使用函数,当Actor在level里被创建的时候,获取其引用
比如说我有一个角色的蓝图, 场景里有一个负责枪口粒子特效的Actor,我想获取它的引用,那么需要使用函数,在该Actor被创建的时候去获取。在角色的蓝图里,创建一个Spawn Actor节点,选择对应Actor的类型Blueprint_Effect_Fire ,然后把节点连接到角色的Event Begin Play上,如下图所示:
UE4入门(从Unity3D转学UE4)_第29张图片
目前这个操作,其实是创建了一个Actor,并且将其位置放置在了Player的Transform上,目前二者是拥有的关系。

接下来,从Return Value往外拖拽,选择Promote to Variable:
UE4入门(从Unity3D转学UE4)_第30张图片
这样做,就可以把它作为Variable,在该蓝图里随便使用了。这种方法,其实只是在蓝图里创建Actor,然后把它的引用作为变量记录在蓝图内而已,还没有涉及到蓝图之间的引用。


3. 蓝图之间的直接引用(Direct Blueprint Communication)
假如Level里有两个蓝图物体,一个是火焰A,一个是火光B,想要的效果为,游戏开始时,两个都存在,但是2秒后,A来控制,把B给Deactivate,那么此时A要记录B

打开A的蓝图Blueprint_Effect_Fire ,给它添加一个蓝图变量,搜索火光B对应的类,进行添加,如下图所示:
UE4入门(从Unity3D转学UE4)_第31张图片
把这个变量命名为Blueprint_Effect_Sparks_C,注意,在UE4里,_C代表引用,应该是const ref吧。

Reference Blueprint Actors are denoted with a _C following their name as seen above.

把这个变量拖拽到蓝图里,然后就可以调用它里面自己的public函数了,如下图所示:
UE4入门(从Unity3D转学UE4)_第32张图片
到目前为止,已经在A里创建了B类型的变量,但是目前还是没有把场景里的B实例和A里的B引用绑定到一起,此时编译会报错:

Accessed None 'Target Blueprint' fro node Construction Script in blueprint Blueprint_Effect_Fire

意思是把蓝图节点转换为脚本的 时候,从Target Blueprint节点里没有获取到任何东西。

为了建立联系,需要把该变量变为public,然后在Level拖入对应蓝图的槽位中(这就跟Unity一样了),如下图所示:
UE4入门(从Unity3D转学UE4)_第33张图片

4. Cast To Referencing
第三种方法,只适用于都在场景里出现的blueprint对象,而这种方法可以获取不在Level里的对象的引用。 In this case, you can use a Cast To node to send the reference to your Target Blueprint.

比如说,我有个两个蓝图A和B,A提前存在于Level里,B不在,需要通过A来实例化B,具体到应用场景,假设A是人物角色,B是枪口的火焰,接下来进行如下步骤:

  1. 选中这个B,打开Level蓝图,在里面创建B的引用对应的节点,把B拖进去也行。
  2. 在Level蓝图里,右键添加Event Begin Play和Get Player Character节点
  3. 打开A也就是Character的蓝图,在里面加入B这个类型(Blueprint_Effect_Fire_C )对应的Variable,把它命名为Target Blueprint,如下图所示:
    UE4入门(从Unity3D转学UE4)_第34张图片
  4. 在Character的蓝图里,把这个Variable拖拽出来,继续拖拽调用其public函数,Get P Fire,如下图所示,按F键时会关闭这个Variable:
    UE4入门(从Unity3D转学UE4)_第35张图片
  5. 接下来就是把A里的变量引用和Level蓝图里的变量引用连接起来了,这里直接获取Player Character的节点,把它转换为A类的对象,这里的A类为ThirdPersonCharacter,然后直接调用它的Set函数,如下图所示:
    UE4入门(从Unity3D转学UE4)_第36张图片

总结来说,就是把不存在于Level的物体,其引用存在Level的蓝图里,然后跟第三种方法类似,在A的类对象里创建B类型的变量,最后在Level蓝图里使用GetPlayerCharacter来获取Player,然后转换为A对象,再给它赋值。

问题: 那如果A是普通的Actor,不能使用GetPlayerCharacter节点,那么怎么办呢

应该会涉及到更多的Casting操作


你可能感兴趣的