组件
组件 = 数据
组件是定义世界结构的基础,封装了系统变异的状态。
在设计一个世界中的组件时,必须仔细考虑所创建的抽象概念,并始终牢记可组合性。
组件是结构体
组件在 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_zero
和 is_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
, Health
和 Position
组件,而 "哥布林 "将拥有 Health
和 Position
组件。这样,我们就不必为每种实体类型创建 Health
和 Position
组件了。
因此,一个系统会像是这样:
#[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