Web Engine Docs
Preparing documentation
Use the search bar to quickly find any topic
Preparing documentation
Use the search bar to quickly find any topic
Learn how to define, register, and serialize custom ECS components with editor integration.
Components are pure data containers in Web Engine's ECS architecture. This guide covers creating custom components, registering schemas with the ComponentRegistry, and integrating with the editor UI.
Components are defined using bitECS and registered with ComponentRegistry for editor metadata:
import { defineComponent, Types } from "bitecs";import { ComponentRegistry } from "@web-engine-dev/core";// 1. Define the bitECS componentexport const Health = defineComponent({current: Types.f32,max: Types.f32,});// 2. Register schema with editor metadataComponentRegistry.register("Health", {name: "Health",description: "Entity health and damage tracking",category: "Gameplay",component: Health,props: {current: { type: "float", default: 100, min: 0 },max: { type: "float", default: 100, min: 0 },},});
Components are stored in TypedArrays for cache efficiency
Full TypeScript support with schema validation
Automatic UI generation from property schemas
Components are automatically serialized to scene JSON
interface ComponentSchema {name: string; // Component identifierdescription?: string; // Human-readable descriptioncategory?: string; // Editor category (Physics, Rendering, etc.)component?: ComponentType; // bitECS component definitionversion?: number; // Schema version for migrationsprops: Record<string, PropSchema>; // Property definitionscanDisable?: boolean; // Whether component can be disabled}
ComponentRegistry supports a wide range of property types for different use cases:
type PropType =| "float" // Single floating-point number| "int" // Integer number| "string" // Text string| "boolean" // True/false checkbox| "vec2" // 2D vector (x, y)| "vec3" // 3D vector (x, y, z)| "vec4" // 4D vector (x, y, z, w)| "vec2int" // 2D integer vector| "vec3int" // 3D integer vector| "color" // RGB/RGBA color picker| "quaternion" // Rotation quaternion| "matrix3" // 3x3 matrix| "matrix4" // 4x4 matrix| "curve" // Animation curve| "enum" // Dropdown selection| "asset" // Asset reference (legacy)| "animation_clip" // Animation clip reference| "entity" // Entity reference| "array" // Array of values| "assetRef" // UUID-based asset reference| "labels" // Tag/label editor| "renderLayers" // Render layer bitmask| "tags"; // Tag bitmask
ComponentRegistry.register("Movement", {name: "Movement",props: {speed: {type: "float",default: 5.0,min: 0,max: 100,step: 0.1, // Slider step sizelabel: "Speed", // Display label},jumpCount: {type: "int",default: 2,min: 1,max: 5,},},});
props: {position: {type: "vec3",default: [0, 0, 0],label: "Position",},velocity: {type: "vec3",default: [0, 0, 0],},color: {type: "color",default: [1, 1, 1, 1], // RGBA},rotation: {type: "quaternion",default: [0, 0, 0, 1], // x, y, z, w},}
props: {bodyType: {type: "enum",options: ["0", "1", "2"],labels: ["Dynamic", "Static", "Kinematic"],default: "0",},quality: {type: "enum",options: ["low", "medium", "high", "ultra"],default: "medium",},}
props: {model: {type: "assetRef",acceptTypes: ["model"], // Only accept model assetsshowPreview: true, // Show thumbnail in editorallowClear: true, // Allow clearing the referencelabel: "Model Asset",},texture: {type: "assetRef",acceptTypes: ["texture"],showPreview: true,},sounds: {type: "assetRef",acceptTypes: ["audio"],},}
props: {waypoints: {type: "array",items: { type: "vec3" }, // Array of vec3maxItems: 10,minItems: 2,defaultItem: [0, 0, 0],reorderable: true, // Enable drag-to-reordercollapsible: true, // Enable collapse/expand},damageMultipliers: {type: "array",items: { type: "float", min: 0, max: 10 },maxItems: 5,},}
props: {tags: {type: "labels",suggestions: ["enemy", "player", "npc", "boss"],maxLabels: 5,label: "Entity Tags",},layers: {type: "renderLayers", // 32-bit bitmask for layersdefault: 1, // Layer 0 enabled},}
import { defineComponent, Types } from "bitecs";import { ComponentRegistry } from "@web-engine-dev/core";// 1. Define bitECS component structureexport const Inventory = defineComponent({capacity: Types.ui16,gold: Types.ui32,// Note: Arrays and complex types need custom serialization});// 2. Register schema with ComponentRegistryComponentRegistry.register("Inventory", {name: "Inventory",description: "Player inventory system",category: "Gameplay",component: Inventory,version: 1,canDisable: true,props: {capacity: {type: "int",default: 20,min: 1,max: 100,label: "Max Items",},gold: {type: "int",default: 0,min: 0,label: "Gold Amount",},items: {type: "array",items: {type: "assetRef",acceptTypes: ["prefab"],},maxItems: 20,reorderable: true,collapsible: true,label: "Items",},},});
import { ComponentRegistry } from "@web-engine-dev/core";// Register a new componentComponentRegistry.register("MyComponent", {name: "MyComponent",description: "Does something cool",category: "Custom",props: {value: { type: "float", default: 0 },},});// Check if component existsif (ComponentRegistry.has("MyComponent")) {console.log("Component registered");}// Get component schemaconst schema = ComponentRegistry.get("MyComponent");console.log(schema?.description);// Get all componentsconst allComponents = ComponentRegistry.getAll();// Get components by categoryconst physicsComponents = Array.from(allComponents.values()).filter(c => c.category === "Physics");// Validate component dataconst validationResult = ComponentRegistry.validateData("MyComponent", {value: 42,});if (!validationResult.valid) {validationResult.errors.forEach(err => {console.error(`${err.property}: ${err.message}`);});}
Components are automatically serialized to/from scene JSON. The serialization system uses the component schema to convert between ECS data and JSON:
// Scene JSON format{"entities": [{"id": "entity-1","name": "Player","components": {"Transform": {"position": [0, 1, 0],"rotation": [0, 0, 0, 1],"scale": [1, 1, 1]},"Health": {"current": 100,"max": 100},"Inventory": {"capacity": 20,"gold": 150,"items": ["item-uuid-1","item-uuid-2"]}}}]}// Components are deserialized on scene load// and serialized on scene save automatically
Organize components into categories for the editor UI:
// Validation happens automatically during registrationComponentRegistry.register("MyComponent", {name: "MyComponent",props: {speed: {type: "float",min: 0,max: 100,required: true, // Field is required},target: {type: "entity",required: false, // Field is optional},},});// Manual validationconst result = ComponentRegistry.validateData("MyComponent", {speed: 150, // Out of range});if (!result.valid) {// [{ property: "speed", message: "Value must be <= 100", code: "MAX" }]console.error(result.errors);}
Component Design
canDisable: false for critical components like Transformimport { defineComponent, Types } from "bitecs";import { ComponentRegistry } from "@web-engine-dev/core";export const Weapon = defineComponent({damage: Types.f32,fireRate: Types.f32,ammoCount: Types.ui16,maxAmmo: Types.ui16,reloadTime: Types.f32,// ... more fields});ComponentRegistry.register("Weapon", {name: "Weapon",description: "Weapon system with ammo and fire rate",category: "Gameplay",component: Weapon,version: 2,props: {damage: {type: "float",default: 25,min: 0,max: 1000,step: 5,label: "Damage per Hit",},fireRate: {type: "float",default: 10,min: 0.1,max: 60,step: 0.5,label: "Rounds per Minute",},ammoCount: {type: "int",default: 30,min: 0,label: "Current Ammo",},maxAmmo: {type: "int",default: 30,min: 1,max: 200,label: "Magazine Size",},reloadTime: {type: "float",default: 2.0,min: 0.1,max: 10,step: 0.1,label: "Reload Time (s)",},weaponModel: {type: "assetRef",acceptTypes: ["model"],showPreview: true,label: "Weapon Model",},fireSound: {type: "assetRef",acceptTypes: ["audio"],label: "Fire Sound",},muzzleFlash: {type: "assetRef",acceptTypes: ["prefab"],label: "Muzzle Flash Effect",},damageTypes: {type: "labels",suggestions: ["kinetic", "explosive", "energy", "poison"],maxLabels: 3,label: "Damage Types",},},});