-
Notifications
You must be signed in to change notification settings - Fork 0
Archetypes
Archetypes provide a means of describing an entity's structural blueprint through composition.
That is, a document declaring
- what components the entity has,
- what states it can enter,
- what children it has,
- what logic runs concurrently or conditionally with that entity,
- etc.
A good way to view archetypes is; a means of describing a particular piece of data or logic that can be attached to an entity arbitrarily.
Archetypes are generally considered standalone structural elements that can be extended and freely reconfigured by a developer, however, they can also be utilized to provide concrete behaviors, such as states, or as a side-effect of attaching a component.
Archetypes and their contents are used to build a factory data structure, used to instantiate multiple instances of a particular entity type.
This means that archetypes themselves are treated as primitive building blocks that are not uniquely instantiated on their own, but are instead instantiated indirectly as part of this greater factory structure. As such, processed archetypes are able to be mutated as the entity's factory definition is built, allowing for unique combinations that can change the original meaning or behavior of a particular element.
For example, you could have one archetype that is responsible for controlling which threads or actions execute when specific events occur, but not provide concrete definitions for anything. You could then, for various entity configurations, use composition to import different implementations that suit the particular entity's needs, without the original archetype knowing anything about the different types of entities, or their implementations.
An instantiation can be performed with any archetype, and in doing so, a shared factory data structure is created, reducing overhead on repeated executions. It is common to use a dedicated archetype to be invoked for a particular game object implementation. In this pattern, this dedicated archetype would be responsible for aggregating other archetypes in a centralized location, separating what design elements are considered composite vs. what is considered concrete.
An entity is considered partially instantiated prior to attachment of its environment-dependent components (i.e. indirection). Attachment and evaluation of these components is normally delayed until all other derived entities (such as children) are themselves instantiated. This allows the developer to reference elements of other entities and components during the instantiation phase.
Paths are used by archetypes in several places, but are especially useful for imports and external resources.
Paths are relative, taking into account several working directories established by the instantiating environment. These working directories are, in order of priority:
- The Instantiated Archetype's Directory
- A Context-Specific Shared Directory
- The Service's Archetype Root Path
- The Engine's Archetype Root Path
Imports allow the user to specify the name of another archetype to be merged in-place into the current entity, allowing for straight-forward composition.
Imports are processed first-to-last, meaning the latest import always takes precedence in the case of a conflict. e.g. if two archetypes contain definitions for the same component, the component definition from the archetype imported last will be used.
With that in mind, however, imports are the first elements processed in an archetype. This means that if the archetype performing the import, for example, overrides the definition of a component, that archetype's definition will take precedence over its imported definitions.
Although archetypes are generally JSON files, the .json suffix should be avoided when specifying archetypes by name.
Import declarations may be performed using string or array syntax. e.g.
"import": "some_archetype_name"
"import": ["first_archetype", "second_archetype"]
NOTE:
Cyclic imports may produce undefined behavior. Be sure to only ever establish one-way relationships between archetypes.
e.g. Assuming archetypeAimports archetypeB, which in turn imports archetypeC,
Cshall never importAorB, andBshall never importA
Alternative keys allowed: imports, modules, merge
A component is a (reflected) data type used to attach a particular property, behavior, or other attribute to an entity.
Components can be initialized with the same general-purpose object construction rules used throughout the engine.
An entry for a component can either appear at top-level within the archetype object itself, or as an entry in an explicit components field of the archetype object.
Example using components as top-level members of an archetype:
"CollisionComponent":
{
"shape:CollisionShapeDescription":
{
"primitive": "CollisionShapePrimitive::Capsule",
"radius": 1.5,
"height": 6.25
}
},
"TransformComponent": {}The explicit components field may be an array, object, or string.
If the type is an object, each key corresponds to the name of a component, whereas the value follows standard object construction syntax.
Example using dedicated components field as an object:
"components":
{
"AlignmentComponent" : "child(player_model)"
}If the type is an array, each element is treated as the name of the component, and the component itself will be default constructed, if applicable:
"components": ["TransformComponent"]If the type is a string, the value will be treated as the name of the component, and the component itself will be default constructed, if applicable:
"components": "MotionComponent"If both the components field, and top-level component definitions exist in the same archetype, the contents of the components field shall be processed first.
Alternative keys allowed: component
For more details on components, please view the dedicated article on the subject.
A state is a configuration of an entity that can be activated on demand.
States can be thought of as a (temporary) change of the entity's archetype.
By default, an entity is 'stateless' -- that is to say, unless explicitly set using the default_state field, an entity will not activate any of its states, despite being capable of activation.
This can be useful, as we can make the assumption that an entity is in a 'dormant' or 'inactive' state, until an initial state-change operation takes place. While in this dormant state, all other configurations of the archetype are applied, as if the entity had no state definitions at all.
States can be defined using the top-level states field of the archetype. This states field may be an object, array, or string.
If the states field is an object, each entry's key is treated as the name of the state.
The entry's value may be either an embedded JSON object, containing the state definition:
"states":
{
"my_state":
{
"add": ["MotionComponent"]
}
}or another string, providing an import path to the state definition:
"states":
{
"my_state": "path/to/a_state"
}This essentially provides a name/alias to the state indicated by the import path.
If the states field is an array, each element will be enumerated, where string values will be treated as state import paths, and object values will be treated as embedded state definitions. In the case of embedded object values, the object's name field may be used to explicitly define the state's name.
"states":
[
"path/to/my_state",
{
"name": "other_state_name",
"add":
{
"NameComponent": "Some Name"
}
}
]If the states field is a string, the value will be used as an import path to the desired definition.
"states": "path/to/my_state"If a state is imported via an anonymous string value, the state's name may be deduced via the import path.
e.g. "path/to/my_state" will adopt the name my_state.
Alternative keys allowed: state
For more details on their inner workings, please view the dedicated article on states.
Archetypes provide optional support for threads, which allow for logic to be tied to an instantiated entity.
Top-level threads begin execution immediately, although control-flow is largely left up to the archetype's implementer. These threads are tied to the archetype itself and will not terminate until either script execution completes, termination is explicitly requested, or the originating entity is destroyed.
This means that if the entity's state changes, these archetype threads will not automatically terminate.
Threads can be defined using the top-level threads field of the archetype. This field can be either an array, string, or object.
For details on how to declare and define threads, please view the dedicated article on the subject.
Alternative keys allowed: do, execute
Children are statically listed entities to be constructed alongside the entity utilizing the archetype.
For example, an entity (factory, composed of archetypes) could feature various components and logic used to implement a 'player' object's functionality, but not care about how that 'player' is represented in a scene.
This entity could remain relatively self-contained, by leveraging a child entity, whose purpose is to encapsulate the visual representation of the 'player' object through use of a ModelComponent and AnimationComponent.
Children can be declared using the top-level children field of an archetype.
The children field can be either a string or an array.
In the case of a string, the value is used as a path to a definition (archetype) to be used to instantiate a child entity:
"children": "path/to/my_child"If the children field is an array, each element acts as a path to a child entity, instantiated in order of appearance:
"children":
[
"first_child",
"second_child",
"subdirectory/third_child"
]If an entity has children, that entity will become partially instantiated while its children are being created, only completing once every child is fully instantiated, recursively. For more details, please view the instantiation section.
Archetypes have optional support for attaching 3D model data to an entity via the model field.
If specified, this field must contain a string value representing a path to the desired model data:
"model": "path/to/my_model.xyz"This resource path is similar to import paths as it takes into account the active directory of the archetype file.
For more details, please view the paths section above.
As noted in the states section, the default_state field may be used to specify a string value containing the name of a state the entity will immediately activate upon creation:
"default_state": "player_common"If this field is absent, the entity will assume an inactive state, awaiting activation from an external source.