Version:

第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 需要两个额外的概念才能理解。

  1. AzFramework::Spawnable 是由 SpawnAllEntities 生成的产品。
  2. 但是,它需要用 AzFramework::EntitySpawnTicket 包装。此票证还将保存对将由 AzFramework::SpawnableEntitiesInterface 的 spawn 方法生成的实体的引用。当票证超出范围时,这些实体将被停用并销毁。

考虑到这一点,以下是从可生成对象生成实体的步骤:

  1. 创建可生成对象的资产。我们将从 Editor 中分配它。
   AZ::Data::Asset<AzFramework::Spawnable> m_spawnableAsset;
  1. 通过将 spawnable 传递到票证中来创建票证。
   AzFramework::EntitySpawnTicket m_ticket;
   m_ticket = AzFramework::EntitySpawnTicket(m_spawnableAsset);4
  1. 现在,如果我们将此票证传递给 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。

  1. 使用此配置,我们已准备好请求要生成的实体。
   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")
    ;
 }