167 lines
4.1 KiB
Markdown
167 lines
4.1 KiB
Markdown
---
|
|
title: "Entities"
|
|
type: docs
|
|
---
|
|
|
|
# Entities and the World
|
|
|
|
Prosperon uses a **script + overrides** model for entities, similar to Source engine entities. An entity type is defined by a script (`.cm` module), and instances are records that override specific attributes.
|
|
|
|
## Entity Types
|
|
|
|
An entity type is a module that returns a prototype object:
|
|
|
|
```javascript
|
|
// entities/goblin.cm
|
|
var sprite = use('sprite')
|
|
|
|
return {
|
|
health: 100,
|
|
speed: 2,
|
|
image: "goblin.png",
|
|
|
|
init: function() {
|
|
this.sprite = sprite({
|
|
image: this.image,
|
|
pos: this.pos,
|
|
width: 32,
|
|
height: 32,
|
|
layer: 5
|
|
})
|
|
},
|
|
|
|
on_destroy: function() {
|
|
this.sprite.destroy()
|
|
}
|
|
}
|
|
```
|
|
|
|
The return value defines every valid field with a default. This prototype IS the schema — the editor and tooling can introspect it to know what fields exist and what their defaults are.
|
|
|
|
## Creating Entities
|
|
|
|
Use the `world` module to create entity instances:
|
|
|
|
```javascript
|
|
var world = use('world')
|
|
var goblin_proto = use('entities/goblin')
|
|
|
|
var goblin = world.add_entity(goblin_proto, {
|
|
pos: {x: 100, y: 200},
|
|
health: 50
|
|
})
|
|
```
|
|
|
|
The second argument overrides specific fields from the prototype. Unspecified fields keep their defaults.
|
|
|
|
## Entity Lifecycle
|
|
|
|
Entities have two lifecycle hooks:
|
|
|
|
- `init()` — called when the entity is created (after overrides are applied)
|
|
- `on_destroy()` — called when the entity is removed from the world
|
|
|
|
```javascript
|
|
world.destroy_entity(goblin)
|
|
```
|
|
|
|
## Levels as Data
|
|
|
|
A level is a collection of entity instances stored as JSON — each entry is a script path plus attribute overrides:
|
|
|
|
```json
|
|
[
|
|
{"script": "entities/goblin", "pos": {"x": 100, "y": 200}, "health": 50},
|
|
{"script": "entities/goblin", "pos": {"x": 300, "y": 200}},
|
|
{"script": "entities/tree", "pos": {"x": 200, "y": 150}},
|
|
{"script": "entities/player_spawn", "pos": {"x": 50, "y": 50}}
|
|
]
|
|
```
|
|
|
|
The world loads this file, creates each entity from its prototype with the overrides applied. Since levels are JSON, they're easy to diff in source control and simple to generate from an editor.
|
|
|
|
## Composition via Modules
|
|
|
|
Entity behavior comes from composition, not inheritance. A goblin that patrols and has health uses separate modules:
|
|
|
|
```javascript
|
|
// entities/patrol_goblin.cm
|
|
var health = use('systems/health')
|
|
var patrol = use('systems/patrol')
|
|
|
|
return {
|
|
health: 100,
|
|
patrol_speed: 1.5,
|
|
patrol_points: [],
|
|
|
|
init: function() {
|
|
health.attach(this)
|
|
patrol.attach(this, this.patrol_points)
|
|
}
|
|
}
|
|
```
|
|
|
|
Each system module provides behavior that operates on the entity's data. No class trees, no diamond inheritance — just modules that read and write fields.
|
|
|
|
## Querying Entities
|
|
|
|
The world module provides several ways to find and iterate entities:
|
|
|
|
```javascript
|
|
var world = use('world')
|
|
|
|
// Iterate all entities
|
|
world.each(function(entity) {
|
|
log.console(entity)
|
|
})
|
|
|
|
// Filter entities by predicate — returns array
|
|
var enemies = world.query(function(e) { return e.team == 'enemy' })
|
|
|
|
// Find first matching entity
|
|
var player = world.find(function(e) { return e.is_player })
|
|
|
|
// Get entity count
|
|
var n = world.count()
|
|
```
|
|
|
|
## Updating Entities
|
|
|
|
Call `world.update(dt)` each frame to tick all entities that have an `update(dt)` method:
|
|
|
|
```javascript
|
|
// In your core.start() update callback:
|
|
core.start({
|
|
update: function(dt) {
|
|
world.update(dt)
|
|
}
|
|
})
|
|
```
|
|
|
|
## Loading Levels
|
|
|
|
Load a level from a JSON array of entity definitions:
|
|
|
|
```javascript
|
|
world.load_level([
|
|
{"script": "entities/goblin", "pos": {"x": 100, "y": 200}},
|
|
{"script": "entities/tree", "pos": {"x": 300, "y": 100}}
|
|
])
|
|
```
|
|
|
|
Each entry's `script` field is loaded via `use()` as the prototype. All other fields are applied as overrides.
|
|
|
|
## Clearing the World
|
|
|
|
Remove and destroy all entities:
|
|
|
|
```javascript
|
|
world.clear()
|
|
```
|
|
|
|
## Override Rules
|
|
|
|
Overrides replace fields at the top level. If the prototype has `pos: {x: 0, y: 0}` and the override has `pos: {x: 50, y: 100}`, the entire `pos` is replaced. This is predictable and avoids ambiguity about partial nested merges.
|
|
|
|
Overrides should be pure data — field values only. No expressions, no conditionals, no logic. The script defines behavior; the override only tweaks data.
|