目录
比较GameObject/Actor和MonoBehaviour/ActorComponent上的标签
本篇学习官方收录的《为Unity开发者准备的虚幻引擎指南》
术语简表
类别 | Unity | Unreal Engine |
GamePlay类型 | Component | 组件 |
GameObject | Actor |
|
Prefab | 蓝图类 |
|
编辑器界面 Editor UI |
Hierarchy Panel | 世界大纲视图 |
Inspector | 细节面板 |
|
Project Browser | 内容浏览器 |
|
Scene View | 关卡视图 |
|
网格体 Meshes |
Mesh | 静态网格体 |
Skinned Mesh | 骨骼网格体 |
|
材质 Materials |
Shader | 材质、材质编辑器 |
Material | 材质实例 |
|
特效 Effects |
Particle Effect | 特效、粒子、Niagara Effect, Particle, Niagara |
游戏界面 Game UI |
UI | UMG |
动画 Animation |
Animation | 骨骼网格体动画系统 |
Mecanim | 动画蓝图 |
|
Sequences | Sequencer | |
2D | Sprite Editor | Paper2D |
编程 Programming |
C# | C++ |
Script、Bolt | 蓝图 |
|
物理 Physics |
Raycast | Line Trace, Shape Trace |
Rigidbody | 碰撞、物理 |
|
运行时平台 Runtime Platforms |
iOS Player, Web Player | 支持的平台 |
文件夹作用
Content:保存项目的所有资产
Config:保存项目的配置 (.ini)文件
Source:保存项目中的C++文件
支持的文件格式
类型 | 支持 |
3D | .fbx、.obj |
纹理 | .png、.jpeg、.bmp、.tga、.dds、.exr、.psd、.hdr |
声音 | .wav |
字体 | .ttf、.otf |
视频 | .mov、.mp4、.wmv |
Datasmith | 3ds Max、SketchUp、Revit等各种CAD应用程序中的3D场景和资产 |
LiDAR点云插件 | LiDAR点云。请参阅LiDAR点云插件概述,查看支持的文件类型列表。 |
USD导入器 | .usd(通用场景描述) |
Alembic文件导入器 | .abc |
场景保存
在Unity是保存Scene,虚幻使用 Map file 。它保存 Level 的数据。
项目设置
从虚幻编辑器的主菜单,转至 编辑(Edit)> 项目设置(Project Settings)
Unity 中的 Player Settings = 虚幻中的 平台设置(Platform Settings),在 项目设置(Project Settings) 窗口的 平台(Platforms) 分段下配置
源文件位置
在Unity中,将C#源文件放在 Assets 文件夹中。在虚幻引擎中,源文件的位置会根据虚幻项目的类型不同而在不同的位置:
- C++项目 在虚幻项目目录中有一个 Source 文件夹。Source 文件夹包含各种文件,包括C++源文件(
.cpp
)和头文件(.h
),以及一些编译用的脚本(Build.cs
、Target.cs
) - 蓝图项目 没有 Source 文件夹。蓝图可以放在虚幻项目的 Content 文件夹中的任意位置
- 如果想将C++代码添加到蓝图项目,请从虚幻编辑器的主菜单转至 工具(Tools)> 新C++类(New C++ Class)
从GameObject到Actor
与Unity的Game Object相对应的概念是Actor
虚幻还预制了特殊的Actor,例如 Pawn(用于可以由玩家或AI控制的Actor),或角色(用于需要复杂移动和交互的可控玩家角色)。就像空Actor那样。
虚幻引擎有一种Gameplay框架来处理这些特殊Actor。
虚幻中的Actor与Unity中的GameObject稍有不同。在Unity中GameObject是C#类,不能直接扩展。在虚幻引擎中,Actor是C++类,可以使用继承来扩展和自定义
组件
在Unity中使用组件向GameObject添加功能。在虚幻中,将组件添加到Actor。选中要添加组件的Actor点击 细节(Details) 面板中的 添加组件(Add Component) 按钮, 然后选择要添加的组件。
从Prefab到蓝图类
虚幻中的蓝图类可以做到类似Unity中预制件的工作流程
- 在Actor的 细节(Details) 面板中,点击 蓝图(Blueprint) →添加脚本(Add Script),向其添加蓝图。
- 保存这个新的蓝图类。
- 点击 创建蓝图(Create Blueprint)
从Script到C++Component
与在Unity的GameObject添加C#脚本类似,在虚幻中从Actor的 细节(Detail) 面板或 编辑Actor(Editor Actor) 窗口,点击 添加组件(Add Component)。
在Unity中创建新的MonoBehaviour时,会有一个骨架类文件,其中包含Start(),Update()函数。虚幻中也有类似的函数
- InitializeComponent(),其作用与Start()相同
- TickComponent(),其作用与Update()相同
可编写脚本的Actor蓝图类
新的Actor蓝图类会有自己的蓝图可视化脚本。
除了支持可视化脚本的蓝图类之外,虚幻还支持使用代码来实现C++类。
虚幻中的组件与Actor
虚幻中有许多特殊类型的Actor,它们附带一定的功能,并始终包含特定组件。例如,角色(Character) 始终包含 角色移动组件(character movement component)
下面是一些常用的虚幻Actor:
- Pawn:一种Actor,用于表示可控制的游戏对象,比如玩家操控的角色。玩家和AI都可以通过所属控制器移动Pawn
- 角色(Character):一种版本更特殊的Pawn,用于双足类型的角色(角色有类似于人类的骨骼结构,用两腿行走)
- 控制器(Controller):占有并控制Pawn。通过将Pawn与控制器分离,可以编写AI控制器,用于控制Pawn,并且和玩家控制Pawn采用相同的接口
- 玩家控制器(Player Controller):一种更特殊的控制器,用于获取玩家的输入信息,并将这些信息映射到Pawn或角色的行为中
Actor与Object与GamePlay框架
Actor是虚幻中最常用于GamePlay的类,并且是唯一可以被生成(Spawned)到世界(World)中的类型。换句话说,放入关卡中的一切内容都将是Actor。
另一个重要的类型是Object。它实际上是所有虚幻的类的基类,包括Actor。它是比Actor低得多的类,但作为虚幻中的类,它仍拥有一些基本功能,例如 反射(Reflection) 和 序列化(Serialization) 。Object是一个非常基础的类,Actor组件是所有类型的基类,派生自Object。
虚幻有一个额外的层,称为Gameplay框架。如果使用特定图元类并遵循特定规范,游戏将自动获得原本实现起来困难又费时的额外功能(例如,完全多玩家支持)
要使用GamePlay框架,需要了解如何使用和自定义虚幻随附的内置Actor类,例如 Pawn、角色(Character) 和 玩家控制器(Player Controller)。例如,想实现多玩家,还需要熟悉虚幻中网络和复制的工作方式。
在虚幻中编写代码
有时必须手动刷新Visual Studio项目文件。
- 从虚幻引擎的主菜单,转至 工具(Tools) > 刷新Visual Studio项目 (Refresh Visual Studio Project)
- 右键单击 项目目录中的 .uproject 文件, 然后选择 Generate Visual Studio project files
在虚幻中,可以在Actor本身上编写代码,而不是仅仅对新组件类型编码。
虚幻中有一组类似Unity的Start(), Ondestroy(), Update()函数方法
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
// 游戏开始时调用。
void BeginPlay();
// 销毁时调用。
void EndPlay(const EEndPlayReason::Type EndPlayReason);
// 每帧调用以更新此Actor。
void Tick(float DeltaSeconds);
};
在虚幻中必须调用父类版本的方法。
例如在Unity的C#中可能是调用 base.Update(), 但在虚幻C++中将使用Super::TickComponent():
void UMyComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
// 此处添加自定义更新内容
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
}
在C++中一些类使用以 A 开头,而另一些类以 U 开头。A 表示 Actor 子类,而 U 表示 Object 子类。另一个常用前缀是 F ,它用于大部分普通数据结构和非UObject类
在子类中使用 Super,是对父类成员函数、成员变量的调用
实例化GameObject/生成Actor
有两个不同的函数来实例化对象:
- NewObject 用于创建新的 UObject
- SpawnActor 用于生成 AActor 类型
UObject和NewObject
在虚幻中创建 UObject 的子类与在Unity中创建 ScriptableObject 的子类非常相似。对于不需要生成到世界中或像Actor那样绑定了组件的GamePlay类,这些很有用。
在Unity中,如果你创建自己的 ScriptableObject
子类,你会如下所示将其实例化
MyScriptableObject NewSO = ScriptableObject.CreateInstance<MyScriptableObject>();
在虚幻引擎中,如果你创建自己的 UObject
派生类型,你可以如下所示将其实例化:
UMyObject* NewObj = NewObject<UMyObject>();
AActor和SpawnActor
Actor使用World(C++中的 UWorld
)对象上的 SpawnActor
方法生成。一些UObject提供了 GetWorld
方法(例如,所有Actor都如此)。你可以采用此方法获取World对象
请注意,在下面的示例中,我们传入了我们想生成的Actor的类,而不是传入另一个Actor。在我们的示例中,该类可以是AMyEnemy的任意子类
如何创建另一个对象的副本,就像在Unity的Instantiate函数那样?
NewObject
和 SpawnActor
函数也能给一个"模板"对象来工作。虚幻引擎将创建该对象的副本,而不是从头创建新对象。这将复制其所有UPROPERTY和组件
AMyActor* CreateCloneOfMyActor(AMyActor* ExistingActor, FVector SpawnLocation, FRotator SpawnRotation)
{
UWorld* World = ExistingActor->GetWorld();
FActorSpawnParameters SpawnParams;
SpawnParams.Template = ExistingActor;
World->SpawnActor<AMyActor>(ExistingActor->GetClass(), SpawnLocation, SpawnRotation, SpawnParams);
}
- GetWorld():获取world当中的东西 (要加头文件#include “Engine/World.h”)
- GetWorld()→SpawnActor<类型名>(名字,位置,旋转):在世界中生成物体
类型转换
类似Unity中
Collider collider = gameObject.GetComponent<Collider>;
SphereCollider sphereCollider = collider as SphereCollider;
虚幻C++中
UPrimitiveComponent* Primitive = MyActor->GetComponentByClass(UPrimitiveComponent::StaticClass());
USphereComponent* SphereCollider = Cast<USphereComponent>(Primitive);
从组件访问GameObject/Actor
// Unity中
GameObject ParentGO = MyComponent.gameObject;
// 虚幻C++中
AActor* ParentActor = MyComponent->GetOwner();
从GameObject/Actor访问组件
// Unity 中
MyComponent MyComp = gameObject.GetComponent<MyComponent>();
// 虚幻C++ 中
UMyComponent* MyComp = MyActor->FindComponentByClass<UMyComponent>();
查找 GameObject/Actor
// Unity 中
GameObject MyGO = GameObject.Find("MyNamedGameObject");
// 按类型查找Object
MyComponent[] Components = Object.FindObjectsOfType(typeof(MyComponent)) as MyComponent[];
// 按标签查找GameObject
GameObject[] GameObjects = GameObject.FindGameObjectsWithTag("MyTag");
// 虚幻 中
// 按名称查找Actor(也适用于UObject)
AActor* MyActor = FindObject<AActor>(nullptr, TEXT("MyNamedActor"));
// 按类型查找Actor(需要UWorld对象)
for (TActorIterator<AMyActor> It(GetWorld()); It; ++It)
{
AMyActor* MyActor = *It;
// ...
}
// 按类型查找UObject
for (TObjectIterator<UMyObject> It; It; ++it)
{
UMyObject* MyObject = *It;
// ...
}
// 按标签查找Actor(也适用于ActorComponent,需要改用TObjectIterator)
for (TActorIterator<AActor> It(GetWorld()); It; ++It)
{
AActor* Actor = *It;
if (Actor->ActorHasTag(FName(TEXT("Mytag"))))
{
// ...
}
}
向GameObject/Actor添加标签
// Unity 中
MyGameObject.tag = "MyTag";
// 虚幻 中
// Actor可以有多个标签
MyActor.Tags.AddUnique(TEXT("MyTag"));
比较GameObject/Actor和MonoBehaviour/ActorComponent上的标签
// Unity 中
if (MyGameObject.CompareTag("MyTag"))
{
// ...
}
// 检查它绑定到的GameObject上的标签
if (MyComponent.CompareTag("MyTag"))
{
// ...
}
// 虚幻中
// 检查某个Actor是否有此标签
if (MyActor->ActorHasTag(FName(TEXT("MyTag"))))
{
// ...
}
// 检查某个ActorComponent是否有此标签
if (MyComponent->ComponentHasTag(FName(TEXT("MyTag"))))
{
// ...
}
物理:刚体与图元组件
在Unity中,假如要为GameObject赋予物理特征,首先必须为其提供刚体组件。
在虚幻引擎中,任何图元组件(C++中的 UPrimitiveComponent
)都可以是物理对象。一些常见图元组件如下:
- 形状组件(胶囊体、球体和盒体)
- 静态网格体组件
- 骨骼网格体组件
Unity将碰撞和可视性划分到不同的组件中,虚幻引擎则将"潜在的物理碰撞"和"潜在的可视效果"组合到了单个图元组件中。凡是在世界中具有形状的组件,只要能通过物理方式渲染或交互,都是 PrimitiveComponent
的子类
RayCast与RayTrace
Unity C#
GameObject FindGOCameraIsLookingAt()
{
Vector3 Start = Camera.main.transform.position;
Vector3 Direction = Camera.main.transform.forward;
float Distance = 100.0f;
int LayerBitMask = 1 << LayerMask.NameToLayer("Pawn");
RaycastHit Hit;
bool bHit = Physics.Raycast(Start, Direction, out Hit, Distance, LayerBitMask);
if (bHit)
{
return Hit.collider.gameObject;
}
return null;
}
虚幻 C++
APawn* AMyPlayerController::FindPawnCameraIsLookingAt()
{
// 你可以在这里自定义有关追踪的各种属性
FCollisionQueryParams Params;
// 忽略玩家的Pawn
Params.AddIgnoredActor(GetPawn());
// 击中结果由线路追踪填充
FHitResult Hit;
// 来自摄像机的Raycast,仅与Pawn碰撞(它们在ECC_Pawn碰撞通道上)
FVector Start = PlayerCameraManager->GetCameraLocation();
FVector End = Start + (PlayerCameraManager->GetCameraRotation().Vector() * 1000.0f);
bool bHit = GetWorld()->LineTraceSingle(Hit, Start, End, ECC_Pawn, Params);
if (bHit)
{
// Hit.Actor包含指向追踪所击中的Actor的弱指针
return Cast<APawn>(Hit.Actor.Get());
}
return nullptr;
}
触发器体积
Unity C#
public class MyComponent : MonoBehaviour
{
void Start()
{
collider.isTrigger = true;
}
void OnTriggerEnter(Collider Other)
{
// ...
}
void OnTriggerExit(Collider Other)
{
// ...
}
}
虚幻 C++
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
// 我的触发器组件
UPROPERTY()
UPrimitiveComponent* Trigger;
AMyActor()
{
Trigger = CreateDefaultSubobject<USphereComponent>(TEXT("TriggerCollider"));
// 两个碰撞物都需要将此项设置为true,才能触发事件
Trigger.bGenerateOverlapEvents = true;
// 设置碰撞物的碰撞模式
// 此模式仅为光线投射、扫描和重叠启用碰撞物
Trigger.SetCollisionEnabled(ECollisionEnabled::QueryOnly);
}
virtual void NotifyActorBeginOverlap(AActor* Other) override;
virtual void NotifyActorEndOverlap(AActor* Other) override;
};
刚体运动
Unity C#
public class MyComponent : MonoBehaviour
{
void Start()
{
rigidbody.isKinematic = true;
rigidbody.velocity = transform.forward * 10.0f;
}
}
在虚幻引擎4中,碰撞组件和刚体组件是同一个组件。其基类是 UPrimitiveComponent
,它有许多子类(USphereComponent
、UCapsuleComponent
等)
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
UPROPERTY()
UPrimitiveComponent* PhysicalComp;
AMyActor()
{
PhysicalComp = CreateDefaultSubobject<USphereComponent>(TEXT("CollisionAndPhysics"));
PhysicalComp->SetSimulatePhysics(false);
PhysicalComp->SetPhysicsLinearVelocity(GetActorRotation().Vector() * 100.0f);
}
};
输入事件
Unity C#
public class MyPlayerController : MonoBehaviour
{
void Update()
{
if (Input.GetButtonDown("Fire"))
{
// ...
}
float Horiz = Input.GetAxis("Horizontal");
float Vert = Input.GetAxis("Vertical");
// ...
}
}
虚幻C++
UCLASS()
class AMyPlayerController : public APlayerController
{
GENERATED_BODY()
void SetupInputComponent()
{
Super::SetupInputComponent();
InputComponent->BindAction("Fire", IE_Pressed, this, &AMyPlayerController::HandleFireInputEvent);
InputComponent->BindAxis("Horizontal", this, &AMyPlayerController::HandleHorizontalAxisInputEvent);
InputComponent->BindAxis("Vertical", this, &AMyPlayerController::HandleVerticalAxisInputEvent);
}
void HandleFireInputEvent();
void HandleHorizontalAxisInputEvent(float Value);
void HandleVerticalAxisInputEvent(float Value);
};
需要对项目设置中的输入属性进行配置
如何抛异常?
不同于Unity,虚幻引擎并不处理异常。请改用 check() 函数来触发严重的断言错误。你可以传入错误信息提示。如果你想报告错误,但不希望打断程序,请改用 ensure() 这将记录一个带有完整调用堆栈的错误信息,但程序会继续执行。如果你附加了调试器,那么这两个函数都会暂定并进入调试器。check()
虚幻的容器类和库
.NetFramework | 虚幻引擎 |
String | FString,FText |
List | TArray |
Dictionary | TMap |
HashSet | check() |
还可以去文档中了解其他容器