参考: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文件夹里的东西可以清空(如果有的话)
点击new level,创建一个Empty Level,里面由于没加光源,目前一片漆黑,我拖了一个Plane进去,但是看不到,所以可以用线框模式预览,如下图所示:
我直接拖了个directional light进去,提示我要rebuild light,然后我在build选项下面选择build light only,重新更新灯光,就出现东西了(我之前放了个平面,正好没有接收到光,以为没有光照呢)
点击build之后会多一个名为MyLevel_BuiltData的文件,后缀为.uasset
。
我看到有搜BP_Sky_Sphere拖拽进去的,也有教程让搜 Atmospheric Fog拖拽进去的,感觉效果是差不多的。
可以在左边里搜索Camera,拖到场景中,只要在Hierarchy里选中Camera时,就会出现一个小的相机窗口,如下图所示:
然而按Play之后,画面没有任何变化。因为UE4里只有Viewport窗口,不存在Game窗口。Unity则是默认有个MainCamera,Scene对应的Viewport和Game窗口各自有一个Camera,Unity里把main camera放到任何一个位置,按Play后就会自动播放那个相机看到的画面。
在UE4,也可以来实现这种效果,但是没有那么Unity那么简单,需要通过blueprint来实现,让拖拽进来的CameraActor在游戏开始时作为画面的Camera,后面再细说。
到这里,一个空场景基本就搭建完了,接下来介绍一些其他的基本操作。
跟Unity一样的地方就不介绍了,这里主要介绍不一样的地方
创建的空工程默认是不含C++代码的,如果在Content Browser里添加C++空类,项目结构会改变成下图所示,红色的区域是新增的内容,应该是Binaries文件夹负责放C++代码编译得到的二进制文件,而Source文件夹里放工程的C++代码:
参考:https://docs.unrealengine.com/4.27/en-US/Basics/DirectoryStructure/
这里说的文件夹,一般会出现在两个地方,一个是UE4引擎源码对应的地方,另一个是实际的Game Project对应的地方,两个地方都有相同命名的文件夹,它们的作用也是差不多的,这里只说Game项目里的文件夹的作用:
.ini
文件和log文件,Source Control里不需要考虑此文件夹参考: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/
虽然二者理念类似,但实际上也有区别,虽然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所有跟Input和逻辑相关的东西(比如说角色撞到硬币加十分),UE4希望用户写在Actor对应的脚本位置里面,而不是其Component里,所以有很多相关的函数,只可以在Actor的函数里调用,比如说鼠标相关的API只有Actor可以调用,但是键盘的API都可以调用(Component调用有点麻烦),如下图所示:
UE4所有的Component,都可以去被继承,然后做别的事(不知是否准确),但是Unity就很不同,Unity为了简化操作,很少用到继承
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;
};
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:
关于Mobility,有三种模式:
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与光照和物理系统交互的方式:
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
由于每一个Actor都会有各自的Mobility,介绍Mobility的时候,这里的Actor可以分为两种:
对于Static Mesh Actor而言,Mobility的三种模式功能简单介绍如下:
对于光源 Actor而言,Mobility的三种模式功能简单介绍如下:
Unity里的自定义Components都是通过MonoBehaviour写脚本实现的,而UE4里提供了蓝图,让用户无需写代码即可自定义Components。
Blueprint可以挂载给任何场景里的物体,场景本身就有一个Blueprint,可以理解为Level的GameManager类,类比于Unity,类似于在Unity的Scene里面挂一个空的Gameobject的Handle,然后挂载脚本。
点击Blueprints下面的Open Level Blueprint,如下图所示:
得到的蓝图如下所示,Event BeginPlay相当于Unity的Start函数,Event Tick相当于Unity的Update函数,如下图所示:
UE4里本身是没有Main Camera这个概念的,为了让Play的画面由指定的Camera的画面显示,需要用蓝图或者脚本进行指定,可以在Level的Blueprint里的Start函数里进行指定,设置游戏开始时的Viewport到场景里拖拽的CameraActor上。
打开Level的Blueprint编辑窗口,右键搜索SetVuewTargetWithBlend
,创建该节点,如下图所示:
这里的Blend可能是在BlendTime里逐步转换画面的意思,这里直接设置为0.0s,意思是瞬间转换View,这里的白色箭头代表函数调用顺序,ViewTarget会从原本的Target转换到New View Target上。
这里的New View Target自然就是新创建的CameraActor了,为了在蓝图里获取它的引用。这里介绍一个把物体的ref拖进蓝图的方式,在打开蓝图的同时,选中场景里的物体,然后在蓝图里面点击右键,选择Add reference,如下图所示:
然后获取原本的Target即可,这里创建GetPlayerController
节点,拖出下面蓝图的样子即可:
再直接进入PlayMode,就可以播放该Camera的画面了
Play当前场景后,会发现场景里多了个球,按WASD可以控制它的移动,这是因为默认的GameMode创建了一个Sphere作为玩家角色。点击Play后,hierarchy里面会多出很多组件,如下图所示:
黄色的都是runtime系统自己添加的东西,DefaultPawn代表默认的玩家,这也是为了方便玩家自己做多人在线游戏的工具(PS:UE4很适合做多人在线游戏)
如果要想去掉默认的GameMode,可以点击Settings打开world settings,如果没有world settings窗口,可以在Window栏下面找到并打开,找到下面的GameMode,如下图所示,想要去掉默认的GameMode,需要自己创建一个空的GameMode,然后Override原本的默认GameMode:
通过蓝图创建一个空的GameMode,然后指定进去,改变Default Pawn Class,如下图所示:
再点击Play,按WASD就不会有任何反应了,此时Play的效果就跟Unity默认的Play效果是一样的了。
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和我自定义的蓝图组件:
选中Actor,这里通过把选中的Actor转换为Blueprint class的方式,存为一个Prefab,如下图所示:
存完之后,会多一个文件,这就是prefab了,可以拖拽很多个到场景里,而且仍然可以用键盘操作让它移动,如下图所示:
打开文件会进入一个窗口,很像Unity的Prefab Mode,如下图所示:
这里的机制跟Unity不太一样,Unity不会更改导入的资源文件本身,而是把相关导入信息放到library下,相关引用和设置用对应的.meta文件记录。而UE4里的资源好多都是导入变成.uasset
文件的,我直接从UE4里的项目里copy,然后放到别的UE4项目里,会显示导入报错。
看了下,UE4里也有类似Unity的导出.unitypackage的操作,选中资源,右键:
它这里可不是仅仅导出该资源就完事了,比如我导出一个动画文件blendspace.uasset
,它会把这个资源依赖的所有文件都导出,比如人物模型、物理资源、动画sequence资源、贴图等等,还挺科学。
这里的导出好像要直接选择其他项目的Content文件夹,它好像没有.unitypackage这样的中间件(我不确定),选择之后新的项目里就出现了该资源和对应引用的资源了:
在别人的UE4工程里的Level BP里看到这个,所以好奇这是啥意思:
参考: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/
对于一个空场景,Unity里按Play按钮不会有任何反应,只会展示Camera位置看到的图。
而UE4里,对一个空场景,点击play,会发现相机可以按WASD移动,而且按ESC退出后,相机的位置也改变了。
如果不想在Play阶段改变相机的位置,可以点击左上角的Edit->EditorPreferences->Viewports->Lock and Feel->Use Camera Location in Play Mode,如下图所示,取消勾选后,Camera会回到点击Play进入游戏之前的位置:
UE4里自带云和雾的特效,对于雾效,在左边搜寻fog就可以了,对于云的效果,在Skybox下面的属性里面直接可以调:
Unity实现就麻烦一些,需要安装插件,如下图所示:
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下对其进行操作。
Timer是计时器的意思,所以很好理解,就是设置计时器,如下图所示,在过了输入的时间之后,会调用event或者调用函数:
可以勾选loop让这个计时器不断循环调用,它会比一直在Tick里每帧去查询要效率高,不过它这个不保证间隔的时间一定精准,它可能会受到当前frame时间,和frameRate影响,只会保证尽可能的接近输入的Timer的时间值。
我目前的理解,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上,如下图所示:
目前这个操作,其实是创建了一个Actor,并且将其位置放置在了Player的Transform上,目前二者是拥有的关系。
接下来,从Return Value往外拖拽,选择Promote to Variable:
这样做,就可以把它作为Variable,在该蓝图里随便使用了。这种方法,其实只是在蓝图里创建Actor,然后把它的引用作为变量记录在蓝图内而已,还没有涉及到蓝图之间的引用。
3. 蓝图之间的直接引用(Direct Blueprint Communication)
假如Level里有两个蓝图物体,一个是火焰A,一个是火光B,想要的效果为,游戏开始时,两个都存在,但是2秒后,A来控制,把B给Deactivate,那么此时A要记录B
打开A的蓝图Blueprint_Effect_Fire
,给它添加一个蓝图变量,搜索火光B对应的类,进行添加,如下图所示:
把这个变量命名为Blueprint_Effect_Sparks_C
,注意,在UE4里,_C
代表引用,应该是const ref吧。
Reference Blueprint Actors are denoted with a _C following their name as seen above.
把这个变量拖拽到蓝图里,然后就可以调用它里面自己的public函数了,如下图所示:
到目前为止,已经在A里创建了B类型的变量,但是目前还是没有把场景里的B实例和A里的B引用绑定到一起,此时编译会报错:
Accessed None 'Target Blueprint' fro node Construction Script in blueprint Blueprint_Effect_Fire
意思是把蓝图节点转换为脚本的 时候,从Target Blueprint节点里没有获取到任何东西。
为了建立联系,需要把该变量变为public,然后在Level拖入对应蓝图的槽位中(这就跟Unity一样了),如下图所示:
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是枪口的火焰,接下来进行如下步骤:
Target Blueprint
,如下图所示:总结来说,就是把不存在于Level的物体,其引用存在Level的蓝图里,然后跟第三种方法类似,在A的类对象里创建B类型的变量,最后在Level蓝图里使用GetPlayerCharacter来获取Player,然后转换为A对象,再给它赋值。
问题: 那如果A是普通的Actor,不能使用GetPlayerCharacter节点,那么怎么办呢
应该会涉及到更多的Casting操作