文章先作为笔记记录,待整理
文章主要参考:官方文档http://api.unrealengine.com/CHN/GettingStarted/FromUnity/index.html
推荐几个不错的UE入门教程:
《Inside UE4》
《Unreal新手笔记(二)——编程架构》
视频教程:
《Unreal Engine 4 喵喵教程 (中文)》
《虚幻4游戏引擎官方入门自学到提高全218集(中文字幕)上部》
《虚幻4入门到精通教程【全集】》
什么是蓝图?
蓝图通常包含2部分:
- 蓝图资源:类似于Unity的Prefab
- 蓝图脚本:可视化的游戏脚本系统
一般我们所指的蓝图是狭义的蓝图,即可视化脚本系统。
对于开发者来说,我们可以把它理解为一种可视化的高级语言(C#等),它有基本的变量、函数、类型转换,支持继承、多态等。通过使用连线把节点、事件、函数及变量连接到一起,这样就可以创建复杂的游戏性元素。蓝图通过各种用途的节点构成图来进行工作,这些节点包括针对蓝图每个实例的对象构建、独立的函数、一般的游戏性事件,从而实现各种行为及其它功能。
中文 | 英文 |
---|---|
|
|
蓝图核心类:
- UObject:
- Actor:物件;类似于Unity的GameObject
- Pawn:棋子(由玩家或AI控制的游戏对象),士兵(由于UE是从FPS游戏进化来的缘故)
- Character:角色
- Controller:控制器,有些类似于Unity的MonoBehavior
-
Component:组件
蓝图:
蓝图,BluePrint,简称BP,是游戏的可视化脚本系统,Visual Scripting.
使用蓝图所定义的对象通常被直接称为“蓝图”。
问题:
- 蓝图类似于Unity的Prefab.
- 蓝图的逻辑节点其实跟Unity行为树的节点比较类似?
- 如何定义蓝图的一个节点?
- 蓝图的变量与行为树的黑板有什么区别吗?
- 如何将Prefab批量改为蓝图?
在虚幻 4 中编写游戏逻辑代码
好了,现在开始稍微深入一些。我们将谈论一下创建游戏所需要的编程话题。因为您了解 Unity,我们来面向 C# 的用户解释 C++ 的功能,当然您也可以使用蓝图来完成几乎所有的事情!我们尽可能的为范例提供 C++ 的同时也提供蓝图。
先说一下一些通用的游戏逻辑编程模式,以及如何在虚幻中实现。许多在 Unity 中的函数在虚幻中都有类似的函数。我们先从最常见的开始。
Instantiating GameObject / Spawning Actor
在 Unity 中,我们使用 Instantiate 函数来新建物体的实例。
该函数使用任意的 UnityEngine.Object 类型(GameObject,MonoBehaviour 等),并创建它的拷贝。
public GameObject EnemyPrefab;
public Vector3 SpawnPosition;
public Quaternion SpawnRotation;
void Start()
{
GameObject NewGO = (GameObject)Instantiate(EnemyPrefab, SpawnPosition, SpawnRotation);
NewGO.name = "MyNewGameObject";
}
在虚幻 4 中,根据不同的需要,有一些不同的函数用于创建物体。NewObject 用于创建新的 UObject 类型实例,而 SpawnActor 用于创建新的 AActor 类型实例。
首先我们总体说一下 UObject 和 NewObject。在虚幻中 UObject 的子类很像 Unity 中 ScriptableObject 的子类。对于游戏过程中,它们是那些不需要在游戏世界中创建并看见的存在。
在 Unity 中,如果要创建自己的 ScriptableObject 子类,可能会像下面这样的初始化:
MyScriptableObject NewSO = ScriptableObject.CreateInstance();
在虚幻中,如果要创建 UObject 的继承类,是像下面这样的初始化:
UMyObject* NewObj = NewObject();
那么 Actor 呢?Actor 的在世界(C++ 中的 UWorld)中生成是通过 SpawnActor 方法。如何获取 World 对象?有些 UObject 会提供一个 GetWorld 的方法,所有的 Actor 则都具有这个方法。
您可能已经注意到,并没有传递一个 Actor,我们传递了一个 Actor 的 “class” 来作为生成 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(ExistingActor->GetClass(), SpawnLocation, SpawnRotation, SpawnParams);
}
您也许想知道“从零开始创建”在这里具体是什么意思。每个对象类在创建时都有一个默认模板,包含了它的默认属性和组件。在创建时如果您并不像修改这些默认属性,并没有提供你自己的模板,虚幻将使用这些默认值来创建该对象。为了更好的说明这个,我们先来看一下 MonoBehaviour 的例子:
public class MyComponent : MonoBehaviour
{
public int MyIntProp = 42;
public SphereCollider MyCollisionComp = null;
void Start()
{
// Create the collision component if we don't already have one
if (MyCollisionComp == null)
{
MyCollisionComp = gameObject.AddComponent();
MyCollisionComp.center = Vector3.zero;
MyCollisionComp.radius = 20.0f;
}
}
}
在上面这个例子中,有一个 int 属性,默认是 42,并有一个 SphereCollider 组件默认半径是 20。
在虚幻 4 中,利用对象的构造函数也能达到同样的效果。
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
UPROPERTY()
int32 MyIntProp;
UPROPERTY()
USphereComponent* MyCollisionComp;
AMyActor()
{
MyIntProp = 42;
MyCollisionComp = CreateDefaultSubobject(FName(TEXT("CollisionComponent"));
MyCollisionComp->RelativeLocation = FVector::ZeroVector;
MyCollisionComp->SphereRadius = 20.0f;
}
};
在 AMyActor 的构造函数中,我们为这个类设置了属性的默认值。请注意 CreateDefaultSubobject 函数。我们可以用它来创建组件并赋予组件默认值。所有子对象都将使用这个函数作为默认模板来创建,也可以在子类或者蓝图中对它进行修改。
通过 GameObject / Actor 访问组件
Unity
MyComponent MyComp = gameObject.GetComponent();
C++
UMyComponent* MyComp = MyActor->FindComponentByClass();
类型转换
在这个例子中,获取了一个已知的组件,并将它转换为一个特定类型并有条件的做一些事情。
Unity C#:
Collider collider = gameObject.GetComponent;
SphereCollider sphereCollider = collider as SphereCollider;
if (sphereCollider != null)
{
// ...
}
虚幻 4 C++:
UPrimitiveComponent* Primitive = MyActor->GetComponentByClass(UPrimitiveComponent::StaticClass());
USphereComponent* SphereCollider = Cast(Primitive);
if (SphereCollider != nullptr)
{
// ...
}
RayCast vs 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;
}
虚幻 4 C++:
APawn* AMyPlayerController::FindPawnCameraIsLookingAt()
{
// You can use this to customize various properties about the trace
FCollisionQueryParams Params;
// Ignore the player's pawn
Params.AddIgnoredActor(GetPawn());
// The hit result gets populated by the line trace
FHitResult Hit;
// Raycast out from the camera, only collide with pawns (they are on the ECC_Pawn collision channel)
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 contains a weak pointer to the Actor that the trace hit
return Cast(Hit.Actor.Get());
}
return nullptr;
}
- Prefab vs 蓝图
Unity 的Prefab,在虚幻中的蓝图有着类似的功能。
但蓝图具有可扩展性,或者称为可继承性。(因为蓝图的本质其实是一个可视化的C++类?)
--
比如,在虚幻 4 中,可以创建一个蓝图类叫做 Monster,实现基本的怪物功能,比如追击人类。然后可以创建一个叫做 Dragon 的蓝图类来扩展它(某种特定的怪物,添加了火焰吐息的功能),再有一个 Grue(一种当它变黑是就有可能吃人的怪物),以及其他 8 种类型。这样一些 Monster 的子类都继承了基础的 Monster 类的功能,并在此基础上添加新的能力。
在 Unity 中,则需要创建很多不同的 GameObject 的 prefabs:为 Dragon 创建一个,为 Grue 创建一个,等等。假设这时希望为所有的怪物添加某个功能,比如使用一个 Speak 组件来说话,在 Unity 中则需要更新所有的 10 个 prefabs,拷贝粘贴到每个中。
在虚幻 4 中,只需简单的修改 Monster 的蓝图类,并为它添加新的 Speak 的能力,便做完了!Dragon,Grue 以及其他 8 种 Monster 的子类都会自动的继承这个说话的新功能,并不需要去修改这些子类。
- 如何为蓝图绑定脚本/添加脚本/AddComponent?
- 新建C++类:Monster,父类选择Character
- 新建蓝图类:BPMonster,分类选择Actor
- 双击蓝图类BPMonster,打开蓝图编辑器
-
BPMonster(自身)相当于Unity的transform:选择BPMonster(自身),然后视口面板选择类设置,细节窗口选择父类为Monster完成脚本绑定。