Systems in Spatial SDK
Updated: Mar 4, 2026
Systems contain the logic that brings your spatial applications to life. While
components store data about what entities are and have, systems define what entities do. They run every tick, finding entities with specific component combinations and processing their data to create behaviors like movement, animation, user interaction, and physics simulation.
Systems define what entities do by continuously processing entities that have specific component combinations:
- Movement systems read
Transform components to update positions - Rendering systems use
Mesh and Material components to draw 3D objects - Input systems handle
Grabbable components for user interaction
Every system follows the same fundamental pattern:
- Query: Find entities with the components you need.
- Process: Read component data and apply your logic.
- Update: Save changes back to the entities.
This pattern ensures data consistency and enables the ECS architecture’s performance benefits.
System and component processing lifecycle
Understanding the component lifecycle is crucial:
- Retrieve: Get components with
entity.getComponent<ComponentType>(). - Modify: Change component data directly.
- Persist: Save changes with
entity.setComponent(component).
Always retrieve the latest component state, modify it, and save it back to ensure thread safety and data consistency.
Components provide the data
First, entities need the right components to store behavioral data. Let’s use the same
UpAndDown component from the
Components documentation that stores animation parameters:
// An entity with both positional and animation data
val animatedCube = Entity.create(listOf(
Transform(Pose(Vector3(0f, 1.5f, -2f))), // Position in 3D space
Mesh(Uri.parse("apk:///assets/cube.glxf")), // Visual representation
UpAndDown(
amount = 0.1f, // Animation distance (matches XML default)
speed = 0.5f, // Animation speed (matches XML default)
offset = 0.0f, // Current animation offset
startPosition = Vector3(0f, 1.5f, -2f) // Base position
)
))
Components store what the entity has: position data (Transform), animation configuration (UpAndDown), and visual representation (Mesh).
Systems provide the behavior
Now create an UpAndDownSystem that processes the UpAndDown component data to animate entities smoothly up and down:
class UpAndDownSystem : SystemBase() {
private var lastTime = System.currentTimeMillis()
private var entities = mutableListOf<Entity>()
override fun execute() {
val currentTime = System.currentTimeMillis()
val dt = Math.min((currentTime - lastTime) / 1000f, 0.1f)
// Discover new entities with UpAndDown component
for (entity in Query.where { changed(Mesh.id) and has(UpAndDown.id, Transform.id) }.eval()) {
if (!entities.contains(entity)) {
val upAndDown = entity.getComponent<UpAndDown>()
upAndDown.startPosition = entity.getComponent<Transform>().transform.t
entity.setComponent(upAndDown)
entities += entity
}
}
// Animate all tracked entities
for (entity in entities) {
val transform = entity.getComponent<Transform>()
val upAndDown = entity.getComponent<UpAndDown>()
upAndDown.offset = (upAndDown.offset + upAndDown.speed * dt) % 1f
transform.transform.t.y =
upAndDown.startPosition.y - (sin(upAndDown.offset * Math.PI.toFloat()) * upAndDown.amount)
entity.setComponent(transform)
entity.setComponent(upAndDown)
}
lastTime = currentTime
}
}
This system demonstrates the core ECS pattern:
- Query discovers new entities with
changed(Mesh.id) and required components. - Process uses delta timing for frame-rate-independent animation.
- Update modifies components and saves changes back with
setComponent().
Registering systems and components
Before systems can process components, both must be registered with your application. This typically happens in your activity’s onCreate() method:
class MyActivity : AppSystemActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Register custom components first
componentManager.registerComponent<UpAndDown>(UpAndDown.Companion)
// Then register systems that use those components
systemManager.registerSystem(UpAndDownSystem())
}
}
Components must be registered before the systems that use them, as systems need to know about component types during initialization.
These principles help you create maintainable and performant systems:
- Single responsibility: Each system should handle one specific behavior or concern. The
UpAndDownSystem only handles vertical animation, not collision detection or user input. This makes systems easier to understand, test, and modify. - Use component data: Systems should be stateless and work only with component data. Instead of storing object positions inside a system, read and write
Transform components. This keeps data centralized and accessible to other systems. - Work with others: Systems coordinate by sharing component data, not by calling each other directly. This loose coupling makes systems more flexible and reusable.
These patterns make your spatial applications more modular and easier to extend with new behaviors.
Learn how to create custom systems and how queries work: