Vue 3 Integration
Status
SageCanvas, useSageEngine, useSageEntity, and useSageEntities work against 0.10. useSageAction still listens on a bus actions no longer travel on and has not been rebuilt.
Installation
npm install @skewedaspect/sage-vue@skewedaspect/sage-vue has peer dependencies on vue (3.x) and @skewedaspect/sage.
SageCanvas
SageCanvas renders the canvas, calls createGameEngine, and provides the engine to child components through Vue's dependency injection.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
entityClasses | Readonly<Record<string, GameEntityType>> | {} | The entity class table the simulation spawns from |
options | SageOptions | {} | Engine configuration passed to createGameEngine() |
engine | GameEngine | none | A pre-created engine. When given, the component neither creates nor disposes one. |
Events
| Event | Payload | Description |
|---|---|---|
engine-ready | GameEngine | The engine has initialized |
engine-error | unknown | Engine creation failed |
Slot props
| Slot prop | Type | Description |
|---|---|---|
loading | boolean | true until the engine finishes initializing |
error | unknown | The error if initialization failed, otherwise null |
engine | GameEngine | null | The engine once ready |
Usage
<template>
<SageCanvas :entity-classes="entityClasses" :options="engineOptions" @engine-ready="onReady">
<template #default="{ loading, error }">
<div v-if="loading" class="loading">Initializing engine...</div>
<div v-else-if="error" class="error">Failed to start: {{ error }}</div>
<GameHud v-else />
</template>
</SageCanvas>
</template>
<!--------------------------------------------------------------------------------------------------------------------->
<style lang="scss" scoped>
.loading, .error {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 1.5rem;
}
</style>
<!--------------------------------------------------------------------------------------------------------------------->
<script setup lang="ts">
import { SageCanvas } from '@skewedaspect/sage-vue';
import type { GameEngine, SageOptions } from '@skewedaspect/sage';
// Components
import GameHud from './gameHud.vue';
// Entities
import { entityClasses } from './entities/index.ts';
const engineOptions : SageOptions = { logLevel: 'info' };
async function onReady(engine : GameEngine) : Promise<void>
{
await engine.managers.levelManager.activateLevel('arena');
await engine.start();
}
</script>
<!--------------------------------------------------------------------------------------------------------------------->The component's overlay div holds the slot content above the canvas. The overlay itself passes pointer events through to the canvas; every element inside it receives them, so HUD buttons and menus are interactive without extra CSS.
On unmount, SageCanvas stops the engine and disposes the BabylonJS engine, unless the engine came in through the engine prop. If the component unmounts while createGameEngine is still in flight, the engine that resolves afterwards is stopped and disposed rather than left running.
A ResizeObserver on the container calls engine.renderEngine.resize() when the container's size changes.
useSageEngine
Returns the engine ref and a computed library event bus. Every other composable builds on it.
import { useSageEngine } from '@skewedaspect/sage-vue';
const { engine, eventBus } = useSageEngine();
// engine is ShallowRef<GameEngine | null>
// eventBus is ComputedRef<GameEventBus | null>The ref starts as null and is set once the engine initializes. Calling it outside a <SageCanvas> subtree throws.
useSageEvent
Subscribes to the engine's library bus by pattern, with cleanup on unmount:
import { useSageEvent } from '@skewedaspect/sage-vue';
useSageEvent('level:*', (event) =>
{
console.log(event.type, event.payload);
});The library bus carries level, asset, trigger, and game events. Entity events and actions travel on the simulation bus, which this composable does not reach. See Events.
useSageTimer
Wraps engine.timer with cleanup on unmount. Timers requested before the engine boots are queued and started when it arrives.
import { useSageTimer } from '@skewedaspect/sage-vue';
const { delay, interval, cooldown } = useSageTimer();
const cancel = delay(3000, () => showTutorialHint());useSageEntity and useSageEntities
A component reads an entity through a view: its state, read-only and reactive, and its operations.
<script setup lang="ts">
import { useSageEntity } from '@skewedaspect/sage-vue';
import { PlayerEntity } from './entities/index.ts';
const props = defineProps<{ id : string }>();
const player = useSageEntity(PlayerEntity, props.id);
</script>
<template>
<div v-if="player.present">
Health: {{ player.state.health }}
<button @click="player.ops.heal(10)">Heal</button>
</div>
</template>The class both types the view and checks it, so an ID naming an entity of another class leaves the view absent, and a subclass counts. useSageEntities(entityClass, tag) answers with a view per entity of that class carrying a tag, in spawn order, each view the same object across refreshes so a v-for keyed by id reuses its components.
A view refreshes once per rendered frame and keeps refreshing while the simulation is paused, so a pause menu that changes something through an operation shows it. Writing to a view's state does not compile, and at runtime reaches only the view's own copy. The entity-behaviors example's playerStats.vue is written this way.
useSageAction
useSageAction(actionName) subscribes to action:<name> on the library bus. InputManager broadcasts actions on the simulation bus, so the ref never updates. It has not been rebuilt for 0.10.
