组件

组件 = 数据

组件是定义世界结构的基础,封装了系统变异的状态。

在设计一个世界中的组件时,必须仔细考虑所创建的抽象概念,并始终牢记可组合性。

组件是结构体

组件在 Cairo 中被定义为结构体。它们可以包含任意数量的字段,但在 ECS 中,最好的做法是使用小的孤立组件。这样可以促进模块化和可组合性,使您可以在多个实体类型中重复使用组件。

#[derive(Component, Copy, Drop, Serde, SerdeLen)]
struct Moves {
    #[key]
    player: ContractAddress,
    remaining: u8,
}

#[key]属性

#[key] 属性向 Dojo 表明,该组件是由 player 字段索引的。您需要为每个组件定义一个键,因为这是您查询组件的方式。不过,您可以通过将多个字段定义为键来创建复合键。

#[derive(Component, Copy, Drop, Serde, SerdeLen)]
struct Resource {
    #[key]
    player: ContractAddress,
    #[key]
    location: ContractAddress,
    balance: u8,
}

在这种情况下,您就可以通过player和location字段来设置组件:

set!(
    ctx.world,
    (
        Resource {
            player: ctx.origin,
            location: 12,
            balance: 10
        },
    )
);

实现Trait

组件可以实现trait。这对于定义跨组件的通用功能非常有用。例如,您可能想定义一个实现了 PositionTrait trait的 Position 组件。该trait可定义 is_zerois_equal 等函数,这些函数可在访问组件时使用。

trait PositionTrait {
    fn is_zero(self: Position) -> bool;
    fn is_equal(self: Position, b: Position) -> bool;
}

impl PositionImpl of PositionTrait {
    fn is_zero(self: Position) -> bool {
        if self.x - self.y == 0 {
            return true;
        }
        false
    }

    fn is_equal(self: Position, b: Position) -> bool {
        self.x == b.x && self.y == b.y
    }
}

自定义设置的组件

假设我们需要一个地方来保存一个全局值,并能在将来灵活地修改它。例如,一个全局的战斗冷却参数(combat_cool_down)定义了一个实体准备再次攻击所需的持续时间。为此,我们可以制作一个组件,专门用于存储这个值,同时还允许通过分散式管理模式对其进行修改。

要创建这些组件,您可以按照通常的创建方法去创建。不过,在初始化它们时,请使用一个常量标识符,如 GAME_SETTINGS_ID。

const GAME_SETTINGS_ID: u32 = 9999999999999;

#[derive(Component, Copy, Drop, Serde, SerdeLen)]
struct GameSettings {
    #[key]
    game_settings_id: u32,
    combat_cool_down: u32,
}

类型

支持的组件类型:

  • u8
  • u16
  • u32
  • u64
  • u128
  • u256
  • ContractAddress

目前无法使用数组。

在实践中考虑模块性

下面是个具体的例子:人类和哥布林。虽然它们有着本质上的区别,但却有着共同的trait,比如都会拥有位置(position)和健康(health)。不过,人类拥有一个额外的组件。此外,我们还引入了 "计数器"(Counter)组件,它是用以统计人类和哥布林数量的独特机能。

#[derive(Component, Copy, Drop, Serde, SerdeLen)]
struct Potions {
    #[key]
    entity_id: u32,
    quantity: u8,
}

#[derive(Component, Copy, Drop, Serde, SerdeLen)]
struct Health {
    #[key]
    entity_id: u32,
    health: u8,
}

#[derive(Component, Copy, Drop, Serde, SerdeLen)]
struct Position {
    #[key]
    entity_id: u32,
    x: u32,
    y: u32
}

// Special counter component
#[derive(Component, Copy, Drop, Serde, SerdeLen)]
struct Counter {
    #[key]
    counter: u32,
    goblin_count: u32,
    human_count: u32,
}

因此,"人类 "将拥有 Potions, HealthPosition 组件,而 "哥布林 "将拥有 HealthPosition 组件。这样,我们就不必为每种实体类型创建 HealthPosition组件了。

因此,一个系统会像是这样:

#[system]
mod spawnHuman {
    use array::ArrayTrait;
    use box::BoxTrait;
    use traits::Into;
    use dojo::world::Context;

    use dojo_examples::components::Position;
    use dojo_examples::components::Health;
    use dojo_examples::components::Potions;
    use dojo_examples::components::Counter;

    // we can set the counter value as a const, then query it easily! This pattern is useful for settins.
    const COUNTER_ID: u32 = 9999999999999;

    fn execute(ctx: Context, entity_id: u32) {

        let counter = get!(ctx.world, COUNTER_ID, (Counter));

        let human_count = counter.human_count + 1;
        let goblin_count = counter.goblin_count + 1;

        // spawn a human
        set!(
            ctx.world,
            (
                Health {
                    entity_id: human_count, health: 100
                    }, 
                Position {
                    entity_id: human_count, x: position.x + 10, y: position.y + 10,
                    }, 
                Potions {
                    entity_id: human_count, quantity: 10
                    
                },
            )
        );

        // spawn a goblin
        set!(
            ctx.world,
            (
                Health {
                    entity_id: goblin_count, health: 100
                    }, 
                Position {
                    entity_id: goblin_count, x: position.x + 10, y: position.y + 10,
                    },
            )
        );

        // increment the counter
        set!(
            ctx.world,
            (
                Counter {
                    counter: COUNTER_ID, human_count: human_count, goblin_count: goblin_count
                },
            )
        );
        
        return ();
    }
}

完整示例见 Dojo Starter