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
- Creation via
world.spawn(...)
- Updates (via systems or direct calls to
entity.set(...)
) - 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.