Components
A component is state attached to an actor. You declare one by extending Component and
describing it with a decorator.
A complete component
Section titled “A complete component”import { Component, DefineComponent, DefineField } from 'dacha';
@DefineComponent({ name: 'Health',})export default class Health extends Component { @DefineField({ initialValue: 100, }) points!: number;
maxPoints = this.points;}That is the whole thing. Nothing else has to be registered anywhere.
Two rules are not visible in the code. The class has to be the default export, because
that is what the glob collects. And the name in
@DefineComponent is the name the configuration uses, so with the decorator you never assign
componentName yourself.
The decorator is optional. A class can set static componentName = 'Health' instead. The
game then finds it, but the editor does not show it. The editor lists only classes with a
decorator.
How a field gets its value
Section titled “How a field gets its value”The engine fills every field that has @DefineField. It takes the value from the
configuration. When the key is missing or the value is null, it uses the field’s
initializer, and then initialValue. A field without either stays undefined.
The ! after points tells TypeScript that the field gets a value without a constructor.
Fields fill in the order you declare them. That is why maxPoints = this.points sees the
value of points.
Write a constructor when a field needs additional initialization beyond its configured value.
Pass the configuration to super.
Without it, the decorated fields keep their initial values:
constructor(config: object) { super(config);
this.maxPoints = this.points * 2;}The constructor body runs after the fields are filled, so it can read and change them.
What survives a save
Section titled “What survives a save”points and maxPoints are both plain properties, and they behave completely differently.
The decorator is the whole difference.
points carries the decorator. You edit it in the inspector, and the editor writes it into
data/data.json. The engine copies the saved value into points when it builds the actor.
The value in the inspector is the value the component starts with.
maxPoints carries no decorator. It is not in the inspector, not in the file, and nothing
restores it.
So decorate what you want to change from the editor, and leave the rest as ordinary run-time state.
Changing the fields later
Section titled “Changing the fields later”Adding a field to a component that actors already use is safe. When the editor loads a project it compares the saved configuration against the current schemas, and it writes the field’s initial value wherever the value is missing. Existing actors pick up the new field with no work from you.
Renaming a field is not the same operation. The editor sees one name disappear and another appear. It fills the new name with its initial value and leaves the old key in the file, where nothing reads it. Values do not travel across a rename, so treat a rename as a delete plus an add.
A component is state, not logic
Section titled “A component is state, not logic”The engine never calls into a component. There is no update, and nothing in the loop
reaches a component except through the system or behavior that reads it.
Methods are fine. A helper that keeps two fields consistent, or a getter that derives a value from them, is still state with a convenient surface on it. What a component cannot be is a place for work that has to run on its own.
A component can look around from where it sits. this.actor is the actor the component is
attached to, and the engine sets it on attachment. getParentComponent() returns the same
component class from the parent actor, or undefined when the parent has none. Both are for
reading the hierarchy, not for running work of your own.
A component with no fields at all is still useful. It marks an actor, and a query finds the
actor by that mark. The engine’s own Behaviors is close to this, and a Player marker is
the kind you might write.
The work itself goes in a system when it is about a class of actors, or in a behavior when it is about one.
Using it
Section titled “Using it”Once the file exists, the component is available in the editor’s inspector for any actor, and in code by class:
import Health from '../components/health/health.component';
const health = actor.getComponent(Health);if (health) { health.points -= 10;}Generating one instead
Section titled “Generating one instead”The editor can write this file for you, with the decorator and the class already in place. See generating scripts.