本文内容
第11章 介绍预制件
第11章 介绍预制件
介绍
注意:您可以在 GitHub 上找到本章的代码和资源: https://github.com/AMZN-Olex/O3DEBookCode2111/tree/ch11_prefabs
本章介绍:
- 什么是预制件?
- 什么是可生成对象?
- 生成预制件。
- 从 C++ 生成预制件。
什么是预制件?
预制件是一组实体。还有更多内容,但这就是本质。回想一下我们在 “第 3 章 实体和组件简介” 一章中由 4 个实体组成的复合对象。
一组实体
如果我们想拥有许多这样的对象,手动重新创建它的副本将是乏味且容易出错的。但是,如果我们将这组实体转换为预制件,那么我们可以轻松地一遍又一遍地复制它。
创建预制件
本节将介绍如何在 Editor 中创建预制件。在开始之前,我们需要两个在处理预制件时非常有用的 Editor 面板:Entity Outliner 和 Asset Browser。您可以从 Editor 的 Tools (工具) 菜单中启动这两个选项。
图 11.1.如何调出 Entity Outliner
Tools→ Entity Outliner
图 11.2.如何调出 Asset Browser
Tools→ Asset Browser
上一章已经创建了一个 Root Entity 作为其父实体的复杂对象。
当前场景
右键单击 Root Entity 并选择 Create Prefab…
右键单击 Root Entity → Create Prefab
这将显示一个 另存为…对话框中,您可以选择预制件的文件名。
另存为 Complex_Object.prefab
预制件应位于 C:\git\book\MyProject\Prefabs\Complex_Object.prefab。Entity Outliner 将从 “Root Entity” 更改为预制件的名称,并将图标变为蓝色。
工具提示将说明它现在是预制件的一部分。
预制件实例
预制件也将出现在 Asset Browser 面板中。
资源浏览器中的预制件
注意:可生成项是可在运行时生成的预制件的衍生产品,而预制件是设计时产品。我们将在本章后面介绍可生成对象。
编辑预制件
请注意,预制件中的实体结构现在处于隐藏状态。您必须进入预制件的编辑模式才能查看其实体。您可以通过双击它或单击 Prefab 左侧的图标来执行此作。
编辑预制件
当您编辑预制件时,Editor 视区将变为焦点模式,在该模式下,仅允许对预制件实体进行更改,直到您退出预制件编辑模式。
对预制件进行更改后,其名称将在 Entity Outliner 中标有 *。
要保存预制件,请在 Entity Outliner 中右键单击它,然后选择 Save Prefab to file (将预制件保存到文件)。
提示:您还可以使用 CTRL+S 保存整个关卡及其中的所有预制件。
您可以使用 ESC 退出预设件编辑模式,也可以单击预设件标题左侧的图标。
Prefab 也是一个 File
注意:预制件的 JSON 序列化是 O3DE 引擎的内部事务。您无需担心将预制件写入 Complex_Object.prefab 的方式,但有时当由于无法解释的原因而出现问题时,了解基础知识会很有用,这样您就可以在结构上达到峰值。 有时这将帮助您解决错误。
预制件将作为 JSON 文件保存到磁盘。如果您要打开它,您可以在其内容中搜索我们创建并附加到 Root Entity 的组件之一 OscillatorComponent。
例 11.1.带有 Root Entity 实体的 Complex_Object.prefab 代码段
"Entities": {
"Entity_[621262670860]": {
"Id": "Entity_[621262670860]",
"Name": "Root Entity",
在此元素下,将有对 OscillatorComponent 的引用。
例 11.2.带有 OscillatorComponent 的 Complex_Object.prefab 代码段
"Component_[12076214170871786334]": {
"$type": "GenericComponentWrapper",
"Id": 12076214170871786334,
"m_template": {
"$type": "OscillatorComponent"
}
},
生成预制件
在 Editor 中,您可以通过复制关卡中的现有预制件来添加更多预制件实例。
使用 Right Click 复制预制件 → Duplicate
或者通过右键单击在关卡中实例化一个,然后“Instantiate Prefab…”
实例化预制件
然后选择您选择的预制件。
选择预制件
从 C++ 生成预制件
如果您想要更多的功能和控制权,那么我们必须求助于 C++(或脚本)。API 是 AzFramework::SpawnableEntitiesInterface::Get()中的生成方法,在 AzFramework/Spawnable/SpawnableEntitiesInterface.h 中定义。以下是从预制件的可生成产品生成所有实体的方法:
AzFramework::SpawnableEntitiesInterface::Get()->SpawnAllEntities(...);
例 11.3.方法 SpawnAllEntities
//! Spawn instances of all entities in the spawnable.
//! @param ticket Stores the results of the call. Use this ticket
//! to spawn additional entities or to despawn them.
//! @param optionalArgs Optional additional arguments,
//! see SpawnAllEntitiesOptionalArgs.
virtual void SpawnAllEntities(
EntitySpawnTicket& ticket,
SpawnAllEntitiesOptionalArgs optionalArgs = {}) = 0;
什么是可生成对象?可生成对象是预制件的运行时产品。可生成对象是您可以在运行时在游戏中生成的产品。您可以看到,每个预制件在 Asset Browser 中都有相应的可生成项。
对于 Complex_Object.prefab,有 Asset Processor 为您生成的 complex_object.spawnable。
MySpawnerComponent
注意:本节的源代码和级别可在以下位置找到: https://github.com/AMZN-Olex/O3DEBookCode2111/tree/ch11_prefabs
以下是如何从组件生成预制件的完整示例。我们将构建一个组件,该组件将具有一个可在 Editor 中填充的字段,并在激活实体时生成该字段的一个实例。
例 11.4.MySpawnerComponent (我的生成器组件)
SpawnAllEntities 的 API 需要两个额外的概念才能理解。
- AzFramework::Spawnable 是由 SpawnAllEntities 生成的产品。
- 但是,它需要用 AzFramework::EntitySpawnTicket 包装。此票证还将保存对将由 AzFramework::SpawnableEntitiesInterface 的 spawn 方法生成的实体的引用。当票证超出范围时,这些实体将被停用并销毁。
考虑到这一点,以下是从可生成对象生成实体的步骤:
- 创建可生成对象的资产。我们将从 Editor 中分配它。
AZ::Data::Asset<AzFramework::Spawnable> m_spawnableAsset;
- 通过将 spawnable 传递到票证中来创建票证。
AzFramework::EntitySpawnTicket m_ticket;
m_ticket = AzFramework::EntitySpawnTicket(m_spawnableAsset);4
现在,如果我们将此票证传递给 SpawnAllEntities,它将从该 spawn able 中生成实体。但有一个问题,这些实体将在哪里生成?除非我们指定位置,否则它们将相对于原点生成。SpawnAllEntities 没有指定指定位置的方法,那么我们该怎么做呢?答案在 AzFramework::SpawnAllEntitiesOptionalArgs 中。它提供了一种在通过 member m_preInsertionCallback 激活实体之前修改实体的方法。下面介绍如何将根实体放置在所需的位置。
例 11.5.在某个位置生成实体
using namespace AzFramework;
auto preSpawnCallback = [world](
EntitySpawnTicket::Id /*ticketId*/,
SpawnableEntityContainerView view)
{
const AZ::Entity* e = *view.begin();
if (auto* tc = e->FindComponent<TransformComponent>())
{
tc->SetWorldTM(world);
}
};
SpawnAllEntitiesOptionalArgs optionalArgs;
optionalArgs.m_preInsertionCallback = AZStd::move(preSpawnCallback);4
AzFramework::SpawnableEntityContainerView 包含即将生成和激活的实体。在激活实体之前调用 Callback m_preInsertionCallback。
- 使用此配置,我们已准备好请求要生成的实体。
AzFramework::SpawnableEntitiesInterface::Get()->
SpawnAllEntities(m_ticket, AZStd::move(optionalArgs));
注意:实体可能不会立即生成,因此请确保您的代码已准备好在下一个或稍后的游戏 tick 接收回调和实体。
提示:如果您的预制件是使用包含所有其他实体的实体构建的,就像我们在第 3 章 实体和组件简介中所做的那样,则您只需分配根实体的位置。其他子实体将相对于其父实体的 transform 定位自己。
小结
注意:您可以在 GitHub 上找到本章的代码和资源: https://github.com/AMZN-Olex/O3DEBookCode2111/tree/ch11_prefabs
例 11.6.带有 Complex_Object.prefab 的 MySpawnerComponent
MySpawnerComponent 将 AZ::Data::Asset AzFramework::Spawnable 反映到编辑器。将其分配为 Complex_Object.spawnable。这是完整的源代码。
例 11.7. MySpawnerComponent.h
#pragma once
#include <AzCore/Component/Component.h>
#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
namespace MyProject
{
// An example of spawning prefab from C++.
class MySpawnerComponent : public AZ::Component
{
public:
AZ_COMPONENT(MySpawnerComponent,
"{F75160EB-CB82-4A41-8AB0-68AD43B9625B}");
// AZ::Component overrides
void Activate() override;
void Deactivate() override {}
// Provide runtime reflection, if any
static void Reflect(AZ::ReflectContext* reflection);
private:
AZ::Data::Asset<AzFramework::Spawnable> m_spawnableAsset;
AzFramework::EntitySpawnTicket m_ticket;
};
}
例 11.8. MySpawnerComponent.cpp
#include "MySpawnerComponent.h"
#include <AzCore/Asset/AssetSerializer.h>
#include <AzCore/Serialization/EditContext.h>
using namespace MyProject;
void MySpawnerComponent::Activate()
{
using namespace AzFramework;
AZ::Transform world = GetEntity()->GetTransform()->GetWorldTM();
m_ticket = EntitySpawnTicket(m_spawnableAsset);
auto cb = [world](
EntitySpawnTicket::Id /*ticketId*/,
SpawnableEntityContainerView view)
{
const AZ::Entity* e = *view.begin();
if (auto* tc = e->FindComponent<TransformComponent>())
{
tc->SetWorldTM(world);
}
};
if (m_ticket.IsValid())
{
SpawnAllEntitiesOptionalArgs optionalArgs;
optionalArgs.m_preInsertionCallback = AZStd::move(cb);
SpawnableEntitiesInterface::Get()->SpawnAllEntities(
m_ticket, AZStd::move(optionalArgs));
}
}
void MySpawnerComponent::Reflect(AZ::ReflectContext* reflection)
{
auto sc = azrtti_cast<AZ::SerializeContext*>(reflection);
if (!sc) return;
sc->Class<MySpawnerComponent, Component>()
->Field("Prefab", &MySpawnerComponent::m_spawnableAsset)
->Version(1);
AZ::EditContext* ec = sc->GetEditContext();
if (!ec) return;
using namespace AZ::Edit::Attributes;
ec->Class<MySpawnerComponent>("My Spawner Component",
"[An example of spawning prefab from C++]")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AppearsInAddComponentMenu, AZ_CRC("Game"))
->Attribute(Category, "My Project")
->DataElement(nullptr, &MySpawnerComponent::m_spawnableAsset,
"Prefab",
"Spawn this prefab once when this entity activates")
;
}