npx claudepluginhub dojoengine/bookThis skill is limited to using the following tools:
Create Dojo models that define your game's state using Entity Component System (ECS) patterns.
Generates Cairo Dojo system contracts for implementing game logic, modifying model state, handling player actions, and emitting events in Starknet games.
Provides Godot 4 GDScript patterns for architecture, signals, scenes, state machines, and optimization. Useful for building games, game systems, and best practices.
Provides architecture patterns and best practices for browser games using Three.js or Phaser. Guides event-driven systems, centralized GameState, project structure, and restart-safe designs.
Share bugs, ideas, or general feedback.
Create Dojo models that define your game's state using Entity Component System (ECS) patterns.
Generates Cairo model structs with:
#[dojo::model] attributeDrop, Serde)#[key])Interactive mode:
"Add a model for player positions"
I'll ask about:
Direct mode:
"Create a Position model with player as key and x, y coordinates"
In your model file (e.g., models.cairo):
use starknet::ContractAddress;
// For nested structs that aren't models
use dojo::meta::Introspect;
In systems that use models:
// Import your models
use my_project::models::{Player, Position, Inventory};
// Import Dojo storage traits
use dojo::model::{ModelStorage, ModelValueStorage};
Reading/Writing models in a system:
// Get world storage
let mut world = self.world_default();
// Read - provide all #[key] values
let player: Player = world.read_model(player_address);
// Write - model must contain all keys and data
world.write_model(@player);
Models are Cairo structs annotated with #[dojo::model].
They act as a key-value store where #[key] fields define the lookup key.
#[derive(Drop, Serde)]
#[dojo::model]
struct Moves {
#[key]
player: ContractAddress,
remaining: u8,
}
Required traits:
Drop - Cairo ownership systemSerde - Serialization for on-chain storageOptional traits:
Copy - Add when you need to copy values (for primitive types)Models keyed by player address:
#[derive(Drop, Serde)]
#[dojo::model]
struct Position {
#[key]
player: ContractAddress,
vec: Vec2,
}
#[derive(Drop, Copy, Serde, Introspect)]
struct Vec2 {
x: u32,
y: u32,
}
Custom nested structs must derive Introspect for Dojo to understand their structure.
Multiple keys for relationships (all keys must be provided when reading):
#[derive(Copy, Drop, Serde)]
#[dojo::model]
struct GameResource {
#[key]
player: ContractAddress,
#[key]
location: ContractAddress,
balance: u8,
}
Read with tuple of all keys:
let resource: GameResource = world.read_model((player, location));
Constant key for global settings:
const RESPAWN_DELAY: u128 = 9999999999999;
#[derive(Copy, Drop, Serde)]
#[dojo::model]
struct GameSetting {
#[key]
setting_id: u128,
setting_value: felt252,
}
// Usage
world.write_model(@GameSetting {
setting_id: RESPAWN_DELAY,
setting_value: (10 * 60).into()
});
Small, focused models that can be combined on entities:
#[derive(Copy, Drop, Serde)]
#[dojo::model]
struct Position {
#[key]
id: u32,
x: u32,
y: u32,
}
#[derive(Copy, Drop, Serde)]
#[dojo::model]
struct Health {
#[key]
id: u32,
health: u8,
}
// Human has Position + Health + Potions
// Orc has Position + Health (no Potions)
#[key] fieldGet the world storage in your system:
use dojo::model::{ModelStorage, ModelValueStorage};
let mut world = self.world(@"my_namespace");
world.write_model(@Position { player, vec: Vec2 { x: 0, y: 0 } });
let position: Position = world.read_model(player);
let resource: GameResource = world.read_model((player, location));
let entity_id = world.uuid();
world.write_model(@Health { id: entity_id, health: 100 });
u8, u16, u32, u64, u128, u256 - Unsigned integersfelt252 - Field elementsbool - BooleansContractAddress - Starknet addressesIntrospectIntrospectAfter creating models:
dojo-system skill to create systems that use your modelsdojo-test skill to test model read/write operationsdojo-config skill to configure permissions