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:
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])) }
}
}
idis a reverse-domain id. A project's manifest names it inplugins, and a plugin markedrequiredrefuses to load without a capability of that id.componentsare the bindings for the components the capability adds. They are registered before the scene decodes, so a document can name them.loadreads what the systems need from the project's files and puts it intoSceneContent, under aSceneContentKeythe capability declares. It runs once per load, before the physics world exists. ThrowIllegalArgumentExceptionfor a scene the capability cannot run; the load is refused with the scene's path.planadds the systems a scene uses, withplan.fixedfor the fixed step andplan.framefor each rendered frame.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, implementsAutoCloseableand is closed when the scene stops.
Run it
Pass the capability when the project loads:
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
- Scene documents for how components are registered and decoded.
- Scene DSL for writing a game's scenes and systems directly in Kotlin.
- The
awake:project:runtimeREADME for what each of Core's capabilities runs.