Core Concepts

Entities

Entities are the building blocks of your Viber3D game. They can have any number of traits that define their data/behavior.

Creating Entities

import { World, trait } from 'koota'
import * as THREE from 'three'

// Example traits
const Transform = trait({
  position: () => new THREE.Vector3(),
  rotation: () => new THREE.Euler()
})

const Health = trait({ amount: 100 })
const IsPlayer = trait() // tag trait, no data

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

// Spawn a player entity
const player = world.spawn(
  Transform(),
  Health({ amount: 100 }),
  IsPlayer()
)

Entity Lifecycle

  1. Creation via world.spawn(...)
  2. Updates (via systems or direct calls to entity.set(...))
  3. Removal when no longer needed (e.g., entity.destroy() or queries that remove entities conditionally)

Querying Entities

Koota’s query system lets you retrieve entities that have certain traits:

// Single entity (first match)
const player = world.queryFirst(IsPlayer)

// Multiple entities
const enemies = world.query(IsEnemy, Health)

Filtering

Use standard array filters or .filter() on the query results:

const lowHealthEnemies = world
  .query(IsEnemy, Health)
  .filter(([_, health]) => health.amount < 50)

Update in Bulk

updateEach() is a convenient way to iterate and immediately update trait data:

function damageSystem(world: World) {
  world.query(Health).updateEach(([health]) => {
    if (health.amount <= 0) {
      // handle death (destroy entity, spawn effect, etc.)
    }
  })
}

Removing Entities

// remove by reference
world.remove(entity)

// remove in a query
world.query(Health).removeIf(([health]) => health.amount <= 0)

// remove a single entity directly
entity.destroy()

Relationships

Koota supports relationships between entities with the relation() API. This is an alternative to storing entity references directly inside a normal trait. Relationships are powerful for parent-child hierarchies, inventory systems, or targeting logic.

import { relation } from 'koota'

const ChildOf = relation()
const parent = world.spawn()
const child = world.spawn(ChildOf(parent))

// Query all children of `parent`
const children = world.query(ChildOf(parent))

With Data

Relationships can carry extra fields:

const Contains = relation({ store: { amount: 0 } })

const inventory = world.spawn()
const gold = world.spawn()
inventory.add(Contains(gold))
inventory.set(Contains(gold), { amount: 10 })

Exclusive & Auto-Remove

const Targeting = relation({ exclusive: true })
// ensures only one target can be assigned at once

const ChildAutoRemove = relation({ autoRemoveTarget: true })
// destroying the parent will also destroy the child

Querying Relationships

import { Wildcard } from 'koota'

// Query all entities containing `gold`
const containers = world.query(Contains(gold))

// Query all relationships that target anything (`'*'`)
const anything = world.query(Contains('*'))

// Inverted or wildcard queries
const allRelatedToGold = world.query(Wildcard(gold))

Advanced Query Modifiers

Koota provides several modifiers for advanced use cases:

  • Not(...): Excludes entities that have certain traits.
  • Or(...): Finds entities matching any of the listed traits (logical OR).
  • Added/Removed/Changed: Track recent trait additions, removals, or changes since last query.

Example:

import { Not, Or, createAdded, createRemoved, createChanged } from 'koota'

// Exclude trait
const staticEntities = world.query(Transform, Not(Velocity))

// Logical OR
const movingOrVisible = world.query(Or(Velocity, Renderable))

// Track newly added
const Added = createAdded()
const newPositions = world.query(Added(Position))

// Track removed
const Removed = createRemoved()
const stoppedEntities = world.query(Removed(Velocity))

// Track changed
const Changed = createChanged()
const movedEntities = world.query(Changed(Position))

Common Patterns

Entity Templates

const enemyTemplate = [
  Transform(),
  Health({ amount: 50 }),
  IsEnemy()
]

function spawnEnemy(world: World) {
  return world.spawn(...enemyTemplate)
}

Direct References vs. Relationships

You can store entity references directly in a trait:

import { trait } from 'koota'

const TargetRef = trait({
  entity: null as Entity | null
})

world.spawn(IsProjectile(), TargetRef({ entity: enemy }))

However, for more complex parent-child or inventory logic, Relationships may be a better fit.


Next Steps

  • Learn more about Traits to define entity data.
  • Continue to Systems for processing queries each frame.
  • Check out Actions to see how to centralize entity creation and destruction.