How to Define Prefabs: Aggregate

This guide explains how to define a [PrefabData] that encapsulates other [PrefabData].

If you intend to include a [Component] that has not yet got a corresponding [PrefabData], please use an appropriate guide from the [available guides][bk_prefab_prelude] to create its [PrefabData] first.

Steps

  1. Ensure your crate has the following dependencies:

    [dependencies]
    amethyst = ".." # Minimum version 0.10
    serde = { version = "1.0", features = ["derive"] }
    
  2. Import the following items:

    use amethyst::{
        assets::{PrefabData, ProgressCounter},
        derive::PrefabData,
        ecs::Entity,
        Error,
    };
    use serde::{Deserialize, Serialize};
    
  3. Define the aggregate prefab data type.

    In these examples, Named, Position, and Weapon all derive [PrefabData].

    extern crate amethyst;
    extern crate serde;
    use amethyst::{
        assets::{PrefabData, ProgressCounter},
        core::Named,
        derive::PrefabData,
        ecs::{
            storage::DenseVecStorage,
            Component, Entity, WriteStorage,
        },
        prelude::*,
        Error,
    };
    use serde::{Deserialize, Serialize};
    
    #[derive(Clone, Copy, Component, Debug, Default, Deserialize, Serialize, PrefabData)]
    #[prefab(Component)]
    #[serde(deny_unknown_fields)]
    pub struct Position(pub f32, pub f32, pub f32);
    
    /// **Note:** All fields must be specified in the prefab. If a field is
    /// not specified, then the prefab will fail to load.
    #[derive(Deserialize, Serialize, PrefabData)]
    #[serde(deny_unknown_fields)]
    pub struct Player {
        name: Named,
        position: Position,
    }
    

    If you want to mix different types of entities within a single prefab then you must define an enum that implements PrefabData. Each variant is treated in the same way as PrefabData structs.

    extern crate amethyst;
    extern crate serde;
    use amethyst::{
        assets::{PrefabData, ProgressCounter},
        core::Named,
        derive::PrefabData,
        ecs::{
            storage::{DenseVecStorage, VecStorage},
            Component, Entity, WriteStorage,
        },
        prelude::*,
        Error,
    };
    use serde::{Deserialize, Serialize};
    
    #[derive(Clone, Copy, Component, Debug, Default, Deserialize, Serialize, PrefabData)]
    #[prefab(Component)]
    #[serde(deny_unknown_fields)]
    pub struct Position(pub f32, pub f32, pub f32);
    
    #[derive(Clone, Copy, Component, Debug, Deserialize, Serialize, PrefabData)]
    #[prefab(Component)]
    #[storage(VecStorage)]
    pub enum Weapon {
        Axe,
        Sword,
    }
    
    /// All fields implement `PrefabData`.
    ///
    /// **Note:** If a field is of type `Option<_>` and not specified in the prefab, it will default
    /// to `None`.
    #[derive(Debug, Deserialize, Serialize, PrefabData)]
    #[serde(deny_unknown_fields)]
    pub enum CustomPrefabData {
        Player {
            name: Named,
            position: Option<Position>,
        },
        Weapon {
            weapon_type: Weapon,
            position: Option<Position>,
        },
    }
    
    

    Note: There is an important limitation when building PrefabDatas, particularly enum PrefabDatas. No two fields in the PrefabData or in any nested PrefabDatas under it can access the same Component unless all accesses are reads. This is still true even if the fields appear in different variants of an enum. This means that the following PrefabData will fail at runtime when loaded:

    extern crate amethyst;
    extern crate serde;
    use amethyst::{
        assets::{PrefabData, ProgressCounter},
        core::Named,
        derive::PrefabData,
        ecs::{
            storage::{DenseVecStorage, VecStorage},
            Component, Entity, WriteStorage,
        },
        prelude::*,
        renderer::sprite::prefab::SpriteScenePrefab,
        Error,
    };
    use serde::{Deserialize, Serialize};
    
    #[derive(Clone, Copy, Component, Debug, Default, Deserialize, Serialize, PrefabData)]
    #[prefab(Component)]
    #[serde(deny_unknown_fields)]
    pub struct SpecialPower;
    
    #[derive(Debug, Deserialize, Serialize, PrefabData)]
    #[serde(deny_unknown_fields)]
    pub enum CustomPrefabData {
        MundaneCreature {
            sprite: SpriteScenePrefab,
        },
        MagicalCreature {
            special_power: SpecialPower,
            sprite: SpriteScenePrefab,
        },
    }
    
    

    The problem is that both the SpriteScenePrefabs need to write to Transform and several other common Components. Because Amythest's underlyng ECS system determines what resources are accessed based on static types it can't determine that only one of the SpriteScenePrefabs will be accessed at a time and it attempts a double mutable borrow which fails. The solution is to define the PrefabData hierarchically so each component only appears once:

    extern crate amethyst;
    extern crate serde;
    use amethyst::{
        assets::{PrefabData, ProgressCounter},
        core::Named,
        derive::PrefabData,
        ecs::{
            storage::{DenseVecStorage, VecStorage},
            Component, Entity, WriteStorage,
        },
        prelude::*,
        renderer::sprite::prefab::SpriteScenePrefab,
        Error,
    };
    use serde::{Deserialize, Serialize};
    
    #[derive(Clone, Copy, Component, Debug, Default, Deserialize, Serialize, PrefabData)]
    #[prefab(Component)]
    #[serde(deny_unknown_fields)]
    pub struct SpecialPower;
    
    #[derive(Debug, Deserialize, Serialize, PrefabData)]
    #[serde(deny_unknown_fields)]
    pub enum CreatureDetailsPrefab {
        MundaneCreature {
        },
        MagicalCreature {
            special_power: SpecialPower,
        },
    }
    #[derive(Debug, Deserialize, Serialize, PrefabData)]
    #[serde(deny_unknown_fields)]
    pub struct CustomPrefabData {
        sprite: SpriteScenePrefab,
        creature_details: CreatureDetailsPrefab,
    }
    
    

    The [PrefabData][api_pf_derive] derive implements the [PrefabData] trait for the type. The generated code will handle invoking the appropriate [PrefabData] methods when loading and attaching components to an entity. Note: This differs from the simple component [PrefabData] derive implementation – there is no #[prefab(Component)] attribute.

    The [#[serde(default)]] attribute allows fields to not be specified in the prefab, and the fields' default value will be used. If this attribute is not present, then all fields must be specified in the prefab.

    Finally, the [#[serde(deny_unknown_fields)]] ensures that deserialization produces an error if it encounters an unknown field. This will help expose mistakes in the prefab file, such as when there is a typo.

  4. Now the type can be used in a prefab.

    • struct prefab data:

      #![enable(implicit_some)]
      Prefab(
          entities: [
              PrefabEntity(
                  data: Player(
                      name: Named(name: "Zero"),
                      position: Position(1.0, 2.0, 3.0),
                  ),
              ),
          ],
      )
      
    • enum prefab data:

      #![enable(implicit_some)]
      Prefab(
          entities: [
              // Player
              PrefabEntity(
                  data: Player(
                      name: Named(name: "Zero"),
                      position: Position(1.0, 2.0, 3.0),
                  ),
              ),
              // Weapon
              PrefabEntity(
                  parent: 0,
                  data: Weapon(
                      weapon_type: Sword,
                      position: Position(4.0, 5.0, 6.0),
                  ),
              ),
          ],
      )
      

To see this in a complete example, run the [prefab_custom example][repo_prefab_custom] or the [prefab_multi example][repo_prefab_multi] from the Amethyst repository:

cargo run --example prefab_custom # superset prefab
cargo run --example prefab_multi # object prefab

[#[serde(default)]]: https://serde.rs/container-attrs.html#default [#[serde(deny_unknown_fields)]]: https://serde.rs/container-attrs.html#deny_unknown_fields [Component]: https://docs.amethyst.rs/stable/specs/trait.Component.html [Prefab]: https://docs.amethyst.rs/stable/amethyst_assets/struct.Prefab.html [PrefabData]: https://docs.amethyst.rs/stable/amethyst_assets/trait.PrefabData.html#impl-PrefabData%3C%27a%3E [api_pf_derive]: https://docs.amethyst.rs/stable/amethyst_derive/derive.PrefabData.html [bk_prefab_prelude]: how_to_define_prefabs_prelude.html [repo_prefab_custom]: https://github.com/amethyst/amethyst/tree/master/examples/prefab_custom [repo_prefab_multi]: https://github.com/amethyst/amethyst/tree/master/examples/prefab_multi