Skip to content

@carrot/engine-vue ​

tsvue

Vue 3 composables for integrating the Carrot WebGL engine into Vue applications.

Installation ​

bash
npm install @carrot/engine-vue

Requires vue@^3.4.0 as a peer dependency.

Architecture / How It Works ​

This package provides a thin reactive layer between the Carrot engine and Vue 3's reactivity system. It exposes three composables:

  • useEngine - The primary entry point. Takes a canvas ref and engine configuration, creates a Game instance on mount, and tears it down on unmount. Exposes reactive fps and running state, plus start/stop controls. The Game instance is created inside onMounted (since it needs the DOM canvas element), and cleanup happens in onUnmounted.

  • useScene - Watches a Game ref and subscribes to scene switch events. Provides reactive access to the current Scene and its entity list, plus a switchTo function.

  • useInput - Watches an Engine ref and subscribes to input device change events. Tracks the active device category and whether a gamepad is connected.

All composables use shallowRef for engine objects (which are large, mutable class instances - deep reactivity would be wasteful and broken) and ref for primitive state like fps and running.

Lifecycle ​

onMounted → new Game(canvas) → autoStart? → game.start(scene)
                                              ↕
                                     useScene watches game
                                     useInput watches engine
onUnmounted → stop() → clearInterval(fpsInterval)

Dependencies ​

PackageRole
@carrot/engineCore WebGL engine (Engine class, Input system)
@carrot/engine-frameworkHigh-level framework (Game, Scene, Entity, SceneTemplate)
@carrot/signalsEvent system used by engine internals
@carrot/loggingLog interface for optional logging

Peer dependency: vue@^3.4.0

Build ​

bash
npm run build    # runs tsc

Output goes to dist/. The package ships ES modules with TypeScript declarations.


Usage Guide ​

Vue 3 composables for embedding and controlling the Carrot engine in your components.

Import ​

ts
import { useEngine, useScene, useInput } from '@carrot/engine-vue';

Common Patterns ​

1. Basic engine setup ​

Mount the engine on a canvas element with auto-start (default behaviour):

vue
<script setup lang="ts">
import { ref } from 'vue';
import { useEngine } from '@carrot/engine-vue';

const canvasRef = ref<HTMLCanvasElement>();
const { fps, running } = useEngine({ canvas: canvasRef });
</script>

<template>
  <canvas ref="canvasRef" width="1280" height="720" />
  <p>FPS: {{ fps }} | Running: {{ running }}</p>
</template>

2. Manual start with a custom scene ​

Disable auto-start and launch with your own scene when ready:

vue
<script setup lang="ts">
import { ref } from 'vue';
import { useEngine } from '@carrot/engine-vue';
import { Scene } from '@carrot/engine-framework';

const canvasRef = ref<HTMLCanvasElement>();
const { start, stop, running } = useEngine({
  canvas: canvasRef,
  autoStart: false,
  assetBasePath: '/game-assets/',
});

const myScene = new Scene('Level1');

function onPlay() {
  start(myScene);
}
</script>

<template>
  <canvas ref="canvasRef" />
  <button v-if="!running" @click="onPlay">Start</button>
  <button v-else @click="stop">Stop</button>
</template>

3. Scene management ​

Use useScene to reactively track and switch scenes:

vue
<script setup lang="ts">
import { ref } from 'vue';
import { useEngine, useScene } from '@carrot/engine-vue';
import { Scene } from '@carrot/engine-framework';

const canvasRef = ref<HTMLCanvasElement>();
const { game } = useEngine({ canvas: canvasRef });
const { scene, entities, switchTo } = useScene(game);

const menuScene = new Scene('Menu');
const gameScene = new Scene('Game');

async function goToGame() {
  await switchTo(gameScene);
}
</script>

<template>
  <canvas ref="canvasRef" />
  <div>
    <p>Current scene: {{ scene?.name }}</p>
    <p>Entity count: {{ entities.length }}</p>
    <button @click="goToGame">Start Game</button>
  </div>
</template>

4. Input device tracking ​

React to controller/keyboard changes in the UI:

vue
<script setup lang="ts">
import { ref } from 'vue';
import { useEngine, useInput } from '@carrot/engine-vue';

const canvasRef = ref<HTMLCanvasElement>();
const { engine } = useEngine({ canvas: canvasRef });
const { isGamepadActive, activeDevice } = useInput(engine);
</script>

<template>
  <canvas ref="canvasRef" />
  <p v-if="isGamepadActive">Gamepad connected - press A to continue</p>
  <p v-else>Using {{ activeDevice }}</p>
</template>

5. Full integration ​

Combining all three composables:

vue
<script setup lang="ts">
import { ref } from 'vue';
import { useEngine, useScene, useInput } from '@carrot/engine-vue';
import { Scene } from '@carrot/engine-framework';

const canvasRef = ref<HTMLCanvasElement>();
const { game, engine, fps, running, stop } = useEngine({
  canvas: canvasRef,
  scene: new Scene('Main'),
});
const { scene, entities, switchTo } = useScene(game);
const { activeDevice } = useInput(engine);
</script>

<template>
  <canvas ref="canvasRef" />
  <div class="hud">
    <span>{{ fps }} FPS</span>
    <span>{{ activeDevice }}</span>
    <span>{{ entities.length }} entities</span>
    <button @click="stop">Quit</button>
  </div>
</template>

Carrot