Skip to content
SiteEmail

Our Vue starter is a Vue 3 + Vite + TypeScript project, pre-configured to render correctly in the Gameface Player. Single-file components and the Composition API work the way they do on the web.

Vue uses a Virtual DOM, which adds more runtime overhead than SolidJS. That trade is worth making when your existing Vue experience saves more time than the overhead costs. The Recommended Tech Stack covers the full performance rationale.

We scaffold the project with create-gameface-app , passing the vue template so the CLI skips the framework prompt.

  1. Run the initializer and let it install the dependencies for you:

    Terminal window
    npm create gameface-app -- --template vue --default

    The CLI asks you to name your project, then scaffolds it and installs the dependencies.

  2. Move into the new folder and start Vite:

    Terminal window
    cd your-project-name
    npm run dev

    The dev server runs on port 3000.

  3. Point the Gameface Player at http://localhost:3000/. The sample UI loads, and Hot Module Replacement updates it every time you save a file.

The template opens on a sample entry screen that doubles as a working reference for your own views. It contains:

  • A parallax-style animated background.
  • An Achievements panel in the left column.
  • A PlayerStats panel in the top right.
  • An interactive CarouselMenu along the bottom center.
  • A START GAME action button.

The project also ships a gameface-models/Model.json file, which wires up the data-binding checks that the linter runs against your markup.

The template keeps styles flat and cache-friendly, which is what Gameface needs to hold a high frame rate. It layers a few approaches:

LayerApproach
Component layoutSCSS modules, one .module.scss per component
UtilitiesUnoCSS with presetGameface()
Inline stylesKept minimal, for genuinely dynamic values only. Static values become gf-prop--* classes through the vite-plugin-gameface-styles plugin
Responsive sizingrem units relative to the html font size, scaled from a 1920x1080 reference by the useResponsiveRootFontSize composable

The plugin and the preset work as a pair. vite-plugin-gameface-styles pulls static inline styles out of your markup and encodes them into gf-prop--* class names. presetGameface() is the UnoCSS preset that decodes those names back into real CSS, so the generated classes resolve to actual styles instead of nothing. The template wires both together in uno.config.ts. Dropping UnoCSS costs you the automatic class generation from inline styles, unless you write a preset of your own to replace it.

Vite is set up with the Sass modern-compiler, base: './', and cssCodeSplit: false so the output loads cleanly in the engine.

The template ships with the standard set of scripts:

CommandDescription
npm run devStart the dev server on port 3000
npm run buildRun vue-tsc and build the production bundle to dist/
npm run lintRun ESLint with eslint-plugin-gameface
npm run previewPreview the production build locally

Data-binding and mocked models are not part of the template, so this section covers wiring them up in Vue. The footer’s FPS and ping counters serve as the example: right now they read from a hardcoded systemStats object. The steps below move them onto a Gameface model instead. The same pattern covers any model the game exposes later.

The steps below only cover the Vue-specific mechanics. Mocking Data Models explains what a model is, and Data-Binding Basics covers the full binding syntax.

  1. Data-binding needs the engine global, which comes from cohtml.js. Copy cohtml.js and cohtml.d.ts out of your Gameface package (Player/Samples/uiresources/library) into src/data/, then import the library once in your entry point:

    src/main.ts
    import 'virtual:uno.css';
    import './styles/global.scss';
    import './data/cohtml';
    import App from './App.vue';
  2. Add one JSON file per model under gameface-models/. The file name becomes the model name, and only the shape matters, so the values are placeholders:

    gameface-models/SystemStats.json
    {
    "fps": 60,
    "ping": 12
    }

    eslint-plugin-gameface reads this directory and checks every binding expression against it. A typo such as {{SystemStats.fpsx}} fails npm run lint instead of silently rendering nothing.

  3. engine.createJSModel exposes the model as a global variable. Declare it next to the type so TypeScript knows it exists:

    src/data/types.ts
    export type SystemStats = {
    fps: number;
    ping: number;
    };
    declare global {
    var SystemStats: SystemStats;
    }
  4. Register the model once the engine is ready. The template already exports a systemStats object, so pass it straight in. The interval stands in for the game pushing new values, and every change needs updateWholeModel followed by synchronizeModels to reach the DOM:

    src/App.vue
    import { ref } from 'vue';
    import { onMounted, onUnmounted, ref } from 'vue';
    const selectedModeId = ref('multiplayer');
    useResponsiveRootFontSize();
    let intervalId: number | undefined;
    onMounted(() => {
    engine.whenReady.then(() => {
    engine.createJSModel('SystemStats', systemStats);
    engine.synchronizeModels();
    intervalId = window.setInterval(() => {
    SystemStats.fps = Math.round(Math.random() * 100);
    SystemStats.ping = Math.round(Math.random() * 100);
    engine.updateWholeModel(SystemStats);
    engine.synchronizeModels();
    }, 1000);
    });
    });
    onUnmounted(() => {
    window.clearInterval(intervalId);
    engine.unregisterModel(SystemStats);
    });
  5. Replace the interpolation with data-bind-value on the same elements. Vue only interpolates mustaches in text, never inside an attribute value, so write the expression as a plain static attribute and it reaches the engine exactly as typed:

    src/App.vue
    <footer :class="styles['app__footer']">
    <span :class="styles['app__stat']">FPS: {{ systemStats.fps }}</span>
    <span :class="styles['app__stat']">PING: {{ systemStats.ping }}ms</span>
    <span :class="styles['app__stat']">
    FPS: <span data-bind-value="{{SystemStats.fps}}" />
    </span>
    <span :class="styles['app__stat']">
    PING: <span data-bind-value="{{SystemStats.ping}}" />ms
    </span>
    </footer>

Run npm run dev and open the Player. The counters now change every second, driven by the model rather than by Vue.

Build the production bundle when you are ready to ship:

Terminal window
npm run build

Load the generated dist/index.html in the Gameface Player. The template sets base: './', so every asset resolves relative to that file and the build runs from any location.

The app mounts onto #app in index.html. Responsive font scaling runs in onMounted through the useResponsiveRootFontSize composable, which is typically early enough for Gameface’s first layout pass.

Reach for style: bindings only when a property genuinely has to change at runtime. Leave the static declarations to the style transformer, which compiles them into flat classes the engine can cache and reuse across elements.