Develop
Develop
Select your platform

Systems in Spatial SDK

Updated: Mar 4, 2026

Overview

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.

What systems do

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

How systems work

Every system follows the same fundamental pattern:
  1. Query: Find entities with the components you need.
  2. Process: Read component data and apply your logic.
  3. 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:
  1. Query discovers new entities with changed(Mesh.id) and required components.
  2. Process uses delta timing for frame-rate-independent animation.
  3. 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.

Key design principles

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.

Next steps

Learn how to create custom systems and how queries work:
Did you find this page helpful?