aio3d-core
TypeScript icon, indicating that this package has built-in type declarations

0.0.8 • Public • Published

AIO3D Engine Core Package

A modular Entity Component System (ECS) game engine for building 3D web applications and games.

Overview

The AIO3D Engine Core Package provides a robust Entity Component System (ECS) architecture for building 3D applications and games. It seamlessly integrates with Three.js for rendering and implements a modular design to promote clean, maintainable code.

Note: This package is distributed in minified form, but the public API remains fully accessible as documented here.

Key Features

  • Entity Component System (ECS) - Clean separation of data and behavior
  • Three.js Integration - First-class support for Three.js rendering
  • Prefab System - Data-driven entity creation
  • Level Management - Structured level transitions
  • Event System - Decoupled communication between systems
  • DOM Integration - Seamless DOM and WebGL integration
  • Component Factory System - Flexible component instantiation
  • Physics System - Rapier-powered 3D physics with collision detection

Installation

npm install aio3d-core

Public API

ECS Framework

The foundation of the engine:

  • World: Manages entities and systems, provides the event bus
  • Entity: Container for components with unique ID
  • Component: Base class for all component types
  • System: Base class for all system logic

Components

Built-in components include:

  • TransformComponent: Position, rotation, scale data
  • MeshComponent: 3D visual representation using Three.js
  • CameraComponent: Camera properties and configuration
  • PersistentComponent: Marks entity to survive level transitions
  • DOMComponent: Connects to DOM elements
  • OrbitControlComponent: Orbit camera controls
  • ModelComponent: 3D model data with animation support
  • AnimationControllerComponent: Animation state management for models
  • RigidBodyComponent: Physics body properties (dynamic, static, kinematic)
  • ColliderComponent: Physics shape and collision properties

Systems

Built-in systems include:

  • SceneSystem: Manages Three.js scene and renderer setup
  • RenderSystem: Handles rendering the scene
  • MeshRegistrationSystem: Adds meshes to the scene
  • WindowSystem: Handles window events and resizing
  • InputSystem: Manages keyboard, mouse, and touch inputs
  • OrbitCameraSystem: Implements orbit camera controls
  • ModelSystem: Manages 3D model loading and animation setup
  • ModelRegistrationSystem: Registers models with the scene
  • AnimationControllerSystem: Controls model animation states and transitions
  • PhysicsSystem: Manages Rapier physics simulation and synchronization

Usage Examples

Basic Setup

import {
  World,
  Entity,
  SceneSystem,
  RenderSystem,
  ComponentTypes,
  prefabRegistry,
  PrefabService,
} from "aio3d-core";

// Create world
const world = new World();

// Add core systems
world.addSystem(new SceneSystem());
world.addSystem(new RenderSystem());

// Create entity from prefab
const prefabService = new PrefabService(world, factoryRegistry);
const cubeEntity = prefabService.createEntityFromPrefab("CubePrefab");
world.addEntity(cubeEntity);

// Game loop
function gameLoop(time) {
  const deltaTime = time - lastTime;
  lastTime = time;

  // Update all systems
  world.update(deltaTime / 1000);

  requestAnimationFrame(gameLoop);
}

requestAnimationFrame(gameLoop);

Prefab System

Template-based entity creation:

// Define prefab
const cubePrefab = {
  name: "CubePrefab",
  components: [
    {
      type: ComponentTypes.TRANSFORM,
      data: { position: new THREE.Vector3(0, 1, 0) },
    },
    {
      type: ComponentTypes.MESH,
      data: {
        geometryType: "BoxGeometry",
        geometryArgs: [1, 1, 1],
        materialType: "MeshStandardMaterial",
        materialArgs: { color: 0x3080ff },
      },
    },
  ],
};

// Register prefab
prefabRegistry.registerPrefab(cubePrefab);

// Create entity from prefab
const entity = prefabService.createEntityFromPrefab("CubePrefab");

Level Management

Structure game into distinct levels:

// Register level
LevelRegistry.getInstance().registerLevel(
  "MAIN_LEVEL",
  (container, world, prefabService, levelService) =>
    new MainLevel(container, world, prefabService, levelService)
);

// Change level
levelService.changeLevel("MAIN_LEVEL");

Event System

Communication between systems:

// Subscribe to event
world.eventBus.on("entityCreatedFromPrefab", handleEntityCreated);

// Emit event
world.eventBus.emit("input_action", { type: "JUMP", value: 1 });

// Unsubscribe
world.eventBus.off("entityCreatedFromPrefab", handleEntityCreated);

Physics System

Creating physics-enabled entities:

// Create an entity with physics
const box = new Entity();

// Add transform component (position/rotation will be managed by physics)
const transform = new TransformComponent();
transform.position.set(0, 5, 0); // Initial position before physics takes over
box.addComponent(ComponentTypes.TRANSFORM, transform);

// Add mesh component (visual representation)
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0xff0000 });
const meshComp = new MeshComponent(geometry, material);
box.addComponent(ComponentTypes.MESH, meshComp);

// Add rigid body component (physics properties)
box.addComponent(ComponentTypes.RIGID_BODY, {
  bodyType: "dynamic", // Options: "dynamic", "static", "kinematicPosition", "kinematicVelocity"
  mass: 1.0,
  gravityScale: 1.0,
  linearDamping: 0.01,
  angularDamping: 0.05,
});

// Add collider component (collision shape)
box.addComponent(ComponentTypes.COLLIDER, {
  shape: "box", // Options: "box", "sphere", "capsule"
  size: [1, 1, 1], // Dimensions based on shape type
  isSensor: false, // true for trigger volumes
  restitution: 0.3, // Bounciness (0-1)
  friction: 0.8, // Surface friction
});

// Add to world
world.addEntity(box);

// Apply physics forces via events
world.eventBus.emit("physics_apply_impulse", {
  entityId: box.id,
  impulse: { x: 0, y: -5, z: 0 },
});

// Apply rotational forces
world.eventBus.emit("physics_apply_torque_impulse", {
  entityId: box.id,
  torque: { x: 0, y: 1, z: 0 },
});

// Listen for collision events
world.eventBus.on("physics_collision_start", (event) => {
  console.log(`Collision between entities ${event.bodyA} and ${event.bodyB}`);
});

Animation System

Manage model animations with state-based controllers:

// Create animation controller component
const animController = new AnimationControllerComponent();

// Map states to animation names
animController.states.set("idle", "Idle");
animController.states.set("walk", "Walk");
animController.states.set("run", "Run");

// Support model swapping for different animations
animController.useModelSwapping = true;
animController.modelUrlMap.set("idle", "assets/models/character/idle.glb");
animController.modelUrlMap.set("walk", "assets/models/character/walk.glb");
animController.modelUrlMap.set("run", "assets/models/character/run.glb");

// Change animation state
world.eventBus.emit("animation_state_change", {
  entityId: entity.id,
  state: "walk",
  loop: true,
});

Best Practices

Component Design

  • Components should be pure data containers
  • Implement validate() for constraint enforcement
  • Implement cleanup() for resource disposal
export class CustomComponent extends Component {
  public readonly type = ComponentTypes.CUSTOM;
  public value: number;

  constructor(data?: { value?: number }) {
    super();
    this.value = data?.value ?? 0;
  }

  public validate(): void {
    // Enforce constraints
    this.value = Math.max(0, Math.min(100, this.value));
  }

  public cleanup(): void {
    // Dispose resources
    // e.g., this.texture?.dispose();
  }
}

System Implementation

export class CustomSystem extends System {
  private boundHandler: (event: any) => void;

  constructor() {
    super();
    this.boundHandler = this.handleEvent.bind(this);
  }

  public initialize(world: World): void {
    this.world = world;
    world.eventBus.on("someEvent", this.boundHandler);
  }

  public update(world: World, deltaTime: number): void {
    try {
      const entities = world.queryComponents([
        ComponentTypes.TRANSFORM,
        ComponentTypes.CUSTOM,
      ]);

      for (const entity of entities) {
        const transform = entity.getComponent<TransformComponent>(
          ComponentTypes.TRANSFORM
        );
        const custom = entity.getComponent<CustomComponent>(
          ComponentTypes.CUSTOM
        );

        if (!transform || !custom) continue;

        // Process entity
      }
    } catch (error) {
      console.error("Error in CustomSystem update:", error);
    }
  }

  private handleEvent(data: any): void {
    // Handle event
  }

  public cleanup(): void {
    if (this.world?.eventBus) {
      this.world.eventBus.off("someEvent", this.boundHandler);
    }
    this.world = null;
  }
}

Physics Best Practices

  1. Component Setup

    • Keep TransformComponent and MeshComponent synchronized with the physics simulation
    • Set initial positions in TransformComponent before adding the entity to the world
    • Understand that physics will take control of positioning after initialization
  2. Performance Optimization

    • Use appropriate collider shapes (prefer primitive shapes like box/sphere over complex ones)
    • Adjust physics parameters based on simulation needs:
      • Higher gravityScale for faster falling
      • Lower linearDamping for more persistent motion
      • Lower angularDamping to allow rotation
  3. Physics Events

    • Use the event system for physics interactions:
      • physics_apply_impulse for immediate force application
      • physics_apply_torque_impulse for rotational forces
      • Listen for physics_collision_start/end for gameplay logic
  4. TransformComponent Synchronization

    • During initialization: TransformComponent → RigidBody (one-time)
    • During gameplay: RigidBody → TransformComponent (every frame)
    • Manual updates to TransformComponent won't affect physics simulation after initialization

Working With Obfuscated Code

Since this package is distributed in obfuscated form, the following tips will help you use it effectively:

  1. Use TypeScript: TypeScript definitions are provided and unobfuscated, giving you autocomplete and type checking
  2. Error Handling: Add proper try/catch blocks when working with the API
  3. Console Debugging: Enable verbose logging with loggingService.setGlobalLevel(LogLevel.DEBUG);
  4. Sample Reference: Refer to the game-template package for implementation examples

WebGL Management Best Practices

  • Initialize WebGL context asynchronously
  • Handle context loss and restoration using provided events
  • Always check renderer state before performing operations
  • Properly dispose Three.js resources using cleanup methods

Examples

For complete implementation examples, check out the game-template package which demonstrates practical usage of all core engine features.

Support

If you encounter issues when using the API:

  1. Verify your implementation against the examples in game-template
  2. Enable debug logging to get more diagnostic information
  3. Check for updated documentation or package versions

License

MIT

Local Development with Dependent Packages

If you are developing aio3d-core concurrently with a package that depends on it locally (like game-template within this monorepo), you'll need to link the packages.

  1. Linking: The monorepo root includes a script npm run link:setup. Run this once after npm install in the root. This script uses npm link to make your local aio3d-core build available to other local packages (like game-template) as if it were installed normally.
  2. Build Watching: You need to continuously build aio3d-core as you make changes. Run npm run watch:core (or npm run dev:core) in a separate terminal from the root directory. This watches the core source files and rebuilds the output in packages/core/dist, which is what the linked dependents will use.
  3. TypeScript Paths: Dependent packages (like game-template) use TypeScript's paths in their tsconfig.json to point directly to the aio3d-core source (../core/src). This enables better IDE integration (like Go to Definition) and allows tools like Vite to leverage the source code for faster development builds (HMR).

This combination ensures that dependent packages can resolve aio3d-core correctly both at build/runtime (via the linked built artifacts) and during development (via TypeScript paths to the source).

Readme

Keywords

Package Sidebar

Install

npm i aio3d-core

Weekly Downloads

17

Version

0.0.8

License

MIT

Unpacked Size

2.43 MB

Total Files

71

Last publish

Collaborators

  • chongdashu