Master the art of building scalable, secure Move smart contracts with Dubheโs Entity Component System
Prerequisites: Basic understanding of blockchain concepts and programming. Complete the blockchain basics guide if youโre new to blockchain development.
Dubhe transforms traditional smart contract development by introducing Entity Component System (ECS) architecture to blockchain applications. Instead of monolithic contracts, you build modular, composable systems.
Traditional Approach
Dubhe ECS Approach
// Monolithic player contract - hard to extendmodule game::player { struct Player has key, store { id: UID, name: String, health: u64, level: u8, inventory: vector<Item>, position_x: u64, position_y: u64, experience: u64, // Adding new features requires modifying this struct } // All logic mixed together public fun create_player(...) { /* complex logic */ } public fun level_up(...) { /* more complex logic */ } public fun move_player(...) { /* even more logic */ } // Hard to test individual features}
// Modular components - easy to extendmodule game::components { struct HealthComponent has store, drop { current: u64, maximum: u64, } struct PositionComponent has store, drop { x: u64, y: u64, } struct ExperienceComponent has store, drop { current: u64, level: u8, }}// Focused systems - easy to testmodule game::systems { public entry fun level_up_system(world: &mut World, entity: u64) { // Clean, focused logic } public entry fun movement_system(world: &mut World, entity: u64, dx: u64, dy: u64) { // Single responsibility }}
Schema-First Development: Your schema definition automatically generates Move contracts, TypeScript types, and client SDK methods. This ensures type safety from blockchain to frontend.
// Auto-generated world.movemodule game::world { use sui::object::{Self, UID}; use sui::table::{Self, Table}; use sui::bag::{Self, Bag}; struct World has key { id: UID, entities: Table<u64, EntityData>, next_entity_id: u64, } struct EntityData has store { components: Bag, } // Auto-generated helper functions public fun spawn_entity(world: &mut World): u64 { ... } public fun add_component<T: store>(world: &mut World, entity: u64, component: T) { ... } public fun get_component<T: store>(world: &World, entity: u64): &T { ... } // ... more helper functions}
3
Implement Game Logic
Focus on the unique parts - Dubhe handles the boilerplate.
module game::combat_system { use game::world::{Self, World}; use game::components::{HealthComponent, PlayerComponent}; const EDamageExceedsHealth: u64 = 1; const ENotPlayerOwner: u64 = 2; public entry fun deal_damage( world: &mut World, target_entity: u64, damage_amount: u64, ctx: &TxContext ) { // Validate target exists assert!(world::has_component<HealthComponent>(world, target_entity), 0); // Apply damage let health = world::get_mut_component<HealthComponent>(world, target_entity); if (health.current > damage_amount) { health.current = health.current - damage_amount; } else { health.current = 0; // Add death marker for cleanup systems world::add_component(world, target_entity, DeadTag {}); // Emit death event event::emit(PlayerDeathEvent { entity: target_entity, killer: tx_context::sender(ctx), }); }; // Emit damage event for frontend event::emit(DamageDealtEvent { target: target_entity, damage: damage_amount, remaining_health: health.current, }); }}
4
Build and Test
# Build contractsdubhe build# Run unit testsdubhe test# Start local blockchain for integration testingdubhe node start# Deploy to local networkdubhe deploy --network local# Run integration testsdubhe test --integration
Components should be small, focused data structures with no behavior.
// โ Good: Small, focused componentsstruct HealthComponent has store, drop { current: u64, maximum: u64,}struct PositionComponent has store, drop { x: u64, y: u64,}struct VelocityComponent has store, drop { dx: u64, // velocity in x direction dy: u64, // velocity in y direction}// โ Bad: Large, unfocused componentstruct MegaPlayerComponent has store, drop { // Too many different concepts in one component health: u64, max_health: u64, position_x: u64, position_y: u64, velocity_x: u64, velocity_y: u64, level: u8, experience: u64, inventory_items: vector<u64>, equipment_weapon: u64, equipment_armor: u64, // ... this becomes unmaintainable}
Systems are functions that operate on entities with specific component combinations.
module game::movement_system { use game::world::{Self, World}; use game::components::{PositionComponent, VelocityComponent}; // System processes all entities with Position + Velocity public entry fun update_movement(world: &mut World) { // This would be generated by Dubhe's query system let moving_entities = world::query_entities_with<PositionComponent, VelocityComponent>(world); let i = 0; while (i < vector::length(&moving_entities)) { let entity = *vector::borrow(&moving_entities, i); // Get components let position = world::get_mut_component<PositionComponent>(world, entity); let velocity = world::get_component<VelocityComponent>(world, entity); // Apply physics position.x = position.x + velocity.dx; position.y = position.y + velocity.dy; // Boundary checking if (position.x > MAX_WORLD_X) position.x = MAX_WORLD_X; if (position.y > MAX_WORLD_Y) position.y = MAX_WORLD_Y; i = i + 1; }; } // System for player-initiated movement public entry fun move_entity( world: &mut World, entity: u64, new_x: u64, new_y: u64, ctx: &TxContext ) { // Validate ownership let player = world::get_component<PlayerComponent>(world, entity); assert!(player.owner == tx_context::sender(ctx), ENotOwner); // Validate position bounds assert!(new_x <= MAX_WORLD_X && new_y <= MAX_WORLD_Y, EInvalidPosition); // Update position let position = world::get_mut_component<PositionComponent>(world, entity); position.x = new_x; position.y = new_y; // Emit movement event event::emit(EntityMovedEvent { entity, old_x: position.x, old_y: position.y, new_x, new_y, }); }}
// AI states as componentsstruct IdleState has store, drop { idle_duration: u64,}struct PatrolState has store, drop { current_waypoint: u64, patrol_path: vector<u64>,}struct AttackState has store, drop { target_entity: u64, attack_start_time: u64,}// AI system processes entities based on their current statepublic entry fun ai_system(world: &mut World) { // Process idle entities let idle_entities = world::query_entities_with<IdleState>(world); process_idle_ai(world, idle_entities); // Process patrolling entities let patrol_entities = world::query_entities_with<PatrolState>(world); process_patrol_ai(world, patrol_entities); // Process attacking entities let attack_entities = world::query_entities_with<AttackState>(world); process_attack_ai(world, attack_entities);}
// Events for system coordinationstruct QuestCompletedEvent has copy, drop { player: u64, quest_id: u64, rewards: vector<u64>,}struct LevelUpEvent has copy, drop { player: u64, new_level: u8, skill_points: u8,}// Systems can react to eventspublic entry fun reward_system(world: &mut World) { // Process quest completion rewards // This would integrate with event listening in a real implementation}
// โ Good: Small, focused componentstruct PositionComponent has store, drop { x: u64, // 8 bytes y: u64, // 8 bytes} // Total: 16 bytes// โ Bad: Large component with unused fieldsstruct MegaComponent has store, drop { // Too much data in one component // Expensive to load when you only need position}
2
Batch Operations
Process multiple entities in single function calls when possible
3
Optimize Queries
Structure your component queries for maximum efficiency