Getting Started

Usage

Learn the basics of creating games with Viber3D using an ECS, plus optional physics and batched rendering.

This guide walks you through the basic flow of creating interactive experiences in Viber3D. We'll cover:

  1. Introduction to ECS
  2. Setup & Project Structure
  3. Basic ECS Workflow
  4. Rendering with React & Three.js
  5. Optional Features
  6. Example Flow & Next Steps

1. Introduction to ECS

Viber3D uses an ECS (Entity-Component-System) approach, provided by koota. This decouples data (traits/components) from logic (systems). For example:

  • Traits hold data such as position, velocity, or health.
  • Entities are just IDs that can own traits.
  • Systems run each frame (or on demand), processing entities that match specific traits (e.g., anything that has Position and Velocity).

Why ECS?

  1. Modularity: Quickly add or remove features by combining traits.
  2. Performance: Large numbers of entities can be handled efficiently.
  3. Maintainability: Clear separation of data, logic, and rendering.

2. Project Structure & Setup

A typical Viber3D project:

viber3d/
├── src
│   ├── assets/                 # 3D models, textures, images
│   ├── components/             # React components for rendering 3D objects/UI
│   ├── systems/                # ECS Systems for game logic updates
│   ├── traits/                 # ECS Traits (components) describing entity data
│   ├── utils/                  # Utility functions (math, sorting, spatial hashing)
│   ├── actions.ts              # Central actions to spawn or modify entities
│   ├── app.tsx                 # Main React component (root of your 3D scene)
│   ├── frameloop.ts             # Main ECS update loop
│   ├── index.css               # Global CSS or Tailwind CSS (if used)
│   ├── main.tsx                # React app root, renders <App />
│   ├── startup.tsx             # Startup logic (initial spawns, intervals)
│   └── world.ts                # Creates the ECS world with default traits
├── index.html                  # Basic HTML page with root div
├── package.json                # Project dependencies and scripts
└── tsconfig.json               # TypeScript configuration

Installation

Typically:

# or npm i ...
yarn add react react-dom three @react-three/fiber @react-three/drei koota

If you plan on using Jolt or another physics engine, install that as well:

yarn add jolt-physics

3. Basic ECS Workflow

a) Create a World

In src/ecs/index.ts:

import { createWorld } from 'koota'
import { Schedule } from 'directed'
import type { World } from 'koota'

export const world = createWorld()

// Build a system schedule
export const schedule = new Schedule<{ world: World; delta: number }>()
// Add systems in order
schedule.add(movementSystem)
schedule.add(collisionSystem, { after: movementSystem })
schedule.add(aiSystem, { after: collisionSystem })
schedule.build()

b) Define Traits (Data)

In src/ecs/traits.ts:

import { trait } from 'koota'
import { Vector3, Quaternion } from 'three'

// For positions, store a Vector3
export const Position = trait(() => new Vector3())

// For rotations, store a Quaternion
export const Rotation = trait(() => new Quaternion())

// Example: store numeric health
export const Health = trait({ amount: 100 })

// Tag trait (no data)
export const IsEnemy = trait()

// ...

c) Create Systems (Logic)

In src/ecs/systems.ts:

import { World } from 'koota'
import { Position, Rotation } from './traits'
import { Vector3 } from 'three'

export function movementSystem({ world, delta }: { world: World; delta: number }) {
  // For each entity with Position, move upward
  world.query(Position).updateEach(([pos]) => {
    pos.y += 1 * delta
  })
}

export function collisionSystem({ world }: { world: World }) {
  // Example collision logic ...
}

export function aiSystem({ world }: { world: World }) {
  // Example AI logic ...
}

Each system takes { world, delta }, or any additional context you want, and updates data accordingly.

d) Spawning Entities

Spawn new entities in a system, or via an action (a centralized place to modify ECS state):

import { createActions } from 'koota/react'
import { Position, IsEnemy, Health } from './traits'
import { Vector3 } from 'three'

export const exampleActions = createActions((world) => ({
  spawnEnemy() {
    return world.spawn(
      IsEnemy(),
      Position(new Vector3(0, 5, 0)),
      Health({ amount: 50 })
    )
  }
}))

4. Rendering with React & Three.js

a) Wrap Your App in a WorldProvider

In src/main.tsx:

import React from 'react'
import ReactDOM from 'react-dom/client'
import { WorldProvider } from 'koota/react'
import { world } from './ecs/index'
import App from './react-components/App'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <WorldProvider world={world}>
      <App />
    </WorldProvider>
  </React.StrictMode>
)

b) Connect ECS to the Render Loop

Using React Three Fiber, you can run ECS each frame via useFrame:

// SceneContainer.tsx
import { useFrame } from '@react-three/fiber'
import { useWorld } from 'koota/react'
import { schedule } from '../ecs/index'

export function SceneContainer() {
  const world = useWorld()

  useFrame((_, delta) => {
    // Step the ECS schedule with { world, delta }
    schedule.run({ world, delta })
  })

  return (
    <>
      {/* any objects, lights, environment, etc. */}
    </>
  )
}

c) Query & Render Entities

// EnemyRenderer.tsx
import { useQuery } from 'koota/react'
import { IsEnemy, Position } from '../ecs/traits'

export function EnemyRenderer() {
  const enemies = useQuery(IsEnemy, Position)

  return (
    <>
      {enemies.map((entity) => (
        <EnemyVisual key={entity.id()} entity={entity} />
      ))}
    </>
  )
}

// Basic visualization
function EnemyVisual({ entity }) {
  const position = entity.get(Position)
  if (!position) return null

  return (
    <mesh position={position}>
      <sphereGeometry args={[0.5, 16, 16]} />
      <meshStandardMaterial color="red" />
    </mesh>
  )
}

5. Optional Features: Physics & Batched Mesh

Physics

If you need real-time physics (e.g., collisions, rigid bodies), check out the Physics guide. It shows how to:

  • Load Jolt (or another physics engine)
  • Store bodies in ECS via a RigidBody trait
  • Write a system to step physics & sync transforms

Batched Mesh

If your scene has many objects (like hundreds or thousands of small items), read the Batched Mesh guide. This approach groups multiple ECS entities into one Three.js mesh under the hood, reducing draw calls and improving performance.


6. Example Flow & Next Steps

In a typical Viber3D project:

  1. Initialize ECS in ecs/index.ts.
  2. Define traits (ecs/traits.ts) and systems (ecs/systems.ts).
  3. Wrap your app with WorldProvider to make ECS accessible in React.
  4. Use @react-three/fiber to render your scene, calling schedule.run({ world, delta }) each frame.
  5. (Optional) Add advanced features:
    • Physics: If you need collisions and movement under gravity.
    • Batched Mesh: If you have large numbers of similar objects.

Finally, run your dev server to see it all in action:

npm run dev
# or
yarn dev

Next Steps:

That’s it for the Usage overview—happy building!