Systems
Systems are functions that modify a world. All game logic is implemented by systems.
An app executes each of it’s systems each step. Systems may be optionally configured to run in a specific order through ordering constraints and system groups. They may also be conditionally disabled (and enabled) with run criteria.
Systems are added to an app using the app’s addSystem
method.
let plantGrowthSystem = (world: j.World) => {
world.query(Plot).each((plot, plotWater) => {
world.query(Plant, j.ChildOf(plot)).each((plant, plantMass) => {
// (grow plant using plotWater)
})
})
}
app.addSystem(plantGrowthSystem)
By default, an app will execute it’s systems in the order they are added.
Systems are removed via the removeSystem
method.
app.removeSystem(plantGrowthSystem)
Ordering Constraints
The order in which an app executes systems can be configured using explicit ordering constraints. Ordering constraints are established using a constraint builder object passed to addSystem
’s optional second callback argument.
game
.addSystem(plantGrowthSystem, j.after(weatherSystem))
.addSystem(weatherSystem)
The after
constraint ensures a system will be run some time following a given system, while before
ensures a system is executed earlier in the pipeline.
Ordering constraints can be chained.
app.addSystem(pestSystem, j.after(weatherSystem).before(plantGrowthSystem))
Run Criteria
Systems can also be run conditionally based on the boolean result of a callback function. This predicate function is provided as the third argument to addSystem
.
let eachHundredthTick = (world: World) => {
return world.getResource(Clock).tick % 100 === 0
}
app.addSystem(plantGrowthSystem, j.after(weatherSystem), eachHundredthTick)
System Groups
Systems may be organized into groups. Javelin has six built-in groups:
enum Group {
Early,
EarlyUpdate,
Update,
LateUpdate,
Late,
}
Groups are run in the order they appear in the above enum. There are no rules around how built-in groups should be used, but here are some ideas:
Group.Early
can be used for detecting device input, processing incoming network messages, and any additional housekeeping that doesn’t touch the primary entities in your world.Group.EarlyUpdate
might be used for behaviors that have important implications for most entities in your game, like applying player input and updating a physics simulation.Group.Update
can be used for core game logic, like handling entity collision events, applying damage-over-time effects, spawning entities, etc.Group.LateUpdate
can be used to spawn and destroy entities because entity operations are deferred until the end of a step anyways.Group.Late
might be used to render the scene, send outgoing network messages, serialize game state, etc.
Systems are grouped using an app’s addSystemToGroup
method:
app.addSystemToGroup(j.Group.Late, renderPlotSystem)
Like addSystem
, addSystemToGroup
also accepts ordering constraints and run criteria through it’s third and fourth arguments.
app.addSystemToGroup(j.Group.Late, renderPlotSystem, _ =>
_.before(renderGrassSystem),
)
Custom system groups can be created with an app’s addGroup
method:
app.addGroup("plot_sim")
Like systems, system groups can be ordered and toggled using ordering constraints and run criteria, respectively.
app.addGroup(
"plot_sim",
j.before(j.Group.LateUpdate).after(j.Group.Update),
eachHundredthTick,
Initialization Systems
Javelin has a sixth built-in system group: Group.Init
. This group has run criteria and ordering constraints that ensure it is executed only once at the beginning of the app’s first step. Group.Init
is useful when performing one-off initialization logic, like loading a map or spawning a player.
Apps have a small convenience method for adding systems to Group.Init
: addInitSystem
.
app.addInitSystem(loadLevelSystem)
Of course, like each of the aformentioned system-related methods, addInitSystem
also accepts ordering constraints and run criteria.