Skip to content

Scene capabilities

A scene capability adds components to scene documents and the systems that run them, so a project made in AwakeKt Studio runs a game's own gameplay with no Kotlin of its own. Core's controls, physics, AI, particles and shader effects are capabilities too.

module: awake:project:runtime Vulkan WebGPU

loadProject and runProject run what a scene's components need. Each component's systems come from the capability that owns it. A game adds its own components, such as a grappling hook or a lighthouse beacon, by writing a capability and passing it in. A package can ship one for other games to use.

Write a capability

This one adds a lighthouse_beacon component, reads how far each beacon pulses from a file in the project, and runs a system that pulses every beacon:

Kotlin
private object BeaconCapability : SceneCapability {
    override val id = "com.example.harbor-town.beacon"
    override val components = listOf(BeaconBinding)

    val Pulse = SceneContentKey<Int>("beacon pulse")

    override suspend fun load(scene: SceneDocument, files: AssetSource, content: SceneContent.Builder) {
        if (scene.uses(SceneBeacon::class)) content[Pulse] = files.read(AssetPath("beacons/pulse.txt")).getOrThrow().decodeToString().toInt()
    }

    override fun plan(scene: SceneDocument, plan: SceneSystemPlan) {
        if (scene.uses(SceneBeacon::class)) plan.frame("beacons") { BeaconSystem(checkNotNull(it.content[Pulse])) }
    }
}
  • id is a reverse-domain id. A project's manifest names it in plugins, and a plugin marked required refuses to load without a capability of that id.
  • components are the bindings for the components the capability adds. They are registered before the scene decodes, so a document can name them.
  • load reads what the systems need from the project's files and puts it into SceneContent, under a SceneContentKey the capability declares. It runs once per load, before the physics world exists. Throw IllegalArgumentException for a scene the capability cannot run; the load is refused with the scene's path.
  • plan adds the systems a scene uses, with plan.fixed for the fixed step, plan.preAnimation for pose and snapshot updates run each rendered frame before world matrices are propagated for animation and cameras, and plan.frame for each rendered frame after that. scene.uses(type) says whether any node has a component. Each system needs a name of its own. A system that holds something to give back, such as GPU content, implements AutoCloseable and is closed when the scene stops. A system that must account for every fixed step reads services.fixedStep: the steps each frame ran and the time it dropped past the scene's cap. See Dropped time in a networked game.

Run it

Pass the capability when the project loads:

Kotlin
val project = loadProject(files(BEACON_SCENE), capabilities = listOf(BeaconCapability))
val game = app { scene("play") { runProject(project) } }

Core's capabilities run first, then yours, in the order you pass them. A host that runs a scene in a world of its own, as an editor's Play does, passes the same list to loadSceneContent and sceneSystemsFor instead.

A scene that names a component no capability registers is refused when it loads, naming the component, so a missing capability is found before anything plays. An editor that opens the scene without the game's code keeps such a component as data instead; see Scene documents.

What a capability is not

  • Not loaded at run time. Capabilities are linked when the game is built, like any dependency: Kotlin/Native and wasmJs cannot load code. Behaviour that must arrive with content, such as a project shared on the web, is data the engine interprets, like a shader document.
  • Not an editor plugin. A capability is code that ships in the game. An editor plugin only edits the data a capability reads, so a game plays the same in AwakeKt Studio as in an export. A package can ship both.

See also