Unity

Usage guide of the Unity package ai.gripforge.kits: getting started without Rust, export a game as a Unity project (gripforge game-export --target unity: the prebuilt libraries libgfkits and libgfpresent are fetched for Windows, macOS and Linux, checked and cached; --platforms, --capi and --package offline), the project and its first opening, building a player from the command line, what GfGameView draws of the kits and what it does not, the HUD of ui.webui, every method and event of GfGameHost (LoadGame, StartGame, Step, inputs, hooks, saves, Present*), errors, limits.

Source: docs/usage/unity.mdView Markdown source

Also read offline — CLI: grip-forge docs unityMCP tool: gripforge_docs { "topic": "unity" }

Using the Game Kits from Unity (GfGameHost) #

Play a GripForge game inside a Unity project: one component, GfGameHost, runs the Game Kits and tells your scene what happened; a second one, GfGameView, draws what the kits ask. Readable offline: gripforge docs unity, or the MCP tool gripforge_docs (topic unity).

What it is for #

The Unity package ai.gripforge.kits runs the same Rust logic of the Game Kits as the web player — movement, combat, inventory, missions, saves — from the same game file, through two native libraries:

LibraryWhat it holds
libgfkits (gfkits.dll, libgfkits.dylib)the kit host and the logic of every kit, behind a C interface (gfkits.h) the package calls by P/Invoke
libgfpresent (gfpresent.dll, libgfpresent.dylib)the presentation modules of the kits: what each kit draws, and the HUD and menus (ui.webui)

They are the same libraries the Godot addon and the desktop player load. No rule of a kit is written in C#: the package only calls the libraries and maps what they ask to Unity objects. gripforge game-export --target unity makes the whole Unity project from a game. You may change the rules from C# by tapping hooks or adding registry entries.

Getting started #

For a Unity user: no Rust, no compiler, no GripForge repository. The libraries are prebuilt for every platform; the CLI fetches them.

  1. Install the CLI (Node.js 22.19 or later): curl -fsSL https://gripforge.ai/cli/install.sh | sh on Linux and macOS, npm install -g https://gripforge.ai/cli/gripforge-cli-<version>.tgz on Windows (the current version is in https://gripforge.ai/cli/latest.json). Then gripforge login. An older CLI: gripforge update.
  2. Export your game as a Unity project:

    gripforge game-export gkp_... ./my-unity-game --target unity

    The CLI downloads the web export of the game, fetches the libraries of its own version for every platform (kept in a cache), checks each file against the sha256 it carries, and writes the project. --platforms linux-x86_64 (or windows-x86_64, macos-universal) fetches only what you need. A game you already exported for the web converts the same way: gripforge game-export ./my-unity-game --target unity --from-web ./my-web-export.

  3. Open the folder with Unity 6 (6000.0 or later) from the Unity Hub (Add project from disk). The first opening resolves the packages and sets the project up once: the Universal Render Pipeline, linear colours, both input systems, and the scene Assets/Scenes/Main.unity (a GfGameHost and its GfGameView). The menu GripForge → Set up the project does it again; it never changes what you already set.
  4. Run it: open Assets/Scenes/Main.unity and press Play. Or build a player from the command line:

    Unity -batchmode -quit -projectPath ./my-unity-game -buildTarget Linux64 \
      -executeMethod GripForge.Kits.Editor.GfBuild.Player -gfOut build/linux/game

    (Win64, OSXUniversal likewise, where your editor has their build support.)

  5. Change a rule in Assets/GameMods.cs (yours: an update never rewrites it) by tapping a hook of a kit:

    host.TapHook("combat.damage", (payload, hook) =>
        payload.Get("targetTeam") as string == "enemy" ? J.Obj("set", J.Obj("multiply", 2.0)) : null,
        J.Obj("id", "double_hits"));

Prerequisites #

  • Unity 6 (6000.0 or later), with the build support of the platforms you build for.
  • The packages the project names (fetched by Unity at the first opening): Universal Render Pipeline, glTFast (the models are read at run time), the Input System.
  • A 64-bit platform: Linux x86-64, Windows x86-64, macOS (one universal library for Apple silicon and Intel).

The project #

PathWhat
Assets/StreamingAssets/gripforge/game.jsonThe game: its project, its lockfile, its game schema. The same documents the web runs.
Assets/StreamingAssets/gripforge/present.json, modules/What draws the kits: their presentation modules (named by kit), the data they share, the models and images bound to the slots, the interface fragments.
Assets/StreamingAssets/gripforge/assets/The models (.glb), textures and sounds of the game, read at run time.
Packages/ai.gripforge.kits/The package: Runtime/ (C#), Editor/ (project set-up, command-line build), Plugins/<platform>/ (the two libraries).
Assets/GameMods.cs, Packages/manifest.json, README.md, .gitignoreYours: an update of the export never rewrites them.
.gripforge/unity.lock.jsonWhat the export wrote, to update it safely.

The export holds no .wasm: the presentation modules are native code of libgfpresent.

Exporting a game #

gripforge game-export <gkp_id> [folder] --target unity [--platforms linux-x86_64,windows-x86_64,macos-universal]
                      [--capi <libraries folder>] [--package <ai.gripforge.kits folder>] [--webui-host <folder>]
                      [--bundle-web-engine | --webui-engine-when first-load|install] [--webui-engine-url <base>]
                      [--check] [--force] [--json]
gripforge game-export [folder] --target unity --from-web <web export> [same options]
  • --platforms limits the libraries to the platforms named.
  • --capi <dir> takes libraries you already have, offline: <platform>/libgfkits.* and <platform>/libgfpresent.* (the capi/ folder of a release of the native libraries). GRIPFORGE_UNITY_CAPI does the same.
  • --package <dir> takes a copy of the package ai.gripforge.kits you already have (GRIPFORGE_UNITY_PACKAGE).
  • --check writes nothing and tells what would change; --json prints the report.
  • Updating: run the export again on the folder. The files the export manages (the game data, the package) are replaced; one you edited stops the update and is named (exit code 2) — --force replaces it. Your files are written once and kept.
  • --bundle-web-engine puts the browser engine of the interface in the project (webui-engine/): nothing is ever downloaded while the game plays.

Drawing the game: GfGameView #

GfGameView needs a GfGameHost (beside it or above it). Every drawn frame it steps the logic, asks the host for the frame of the presentation and applies it:

  • The entities. One object per entity of the game at its transform, interpolated between the two last ticks. The model bound to its slot is read from its .glb at run time (glTFast, the node names of the file kept) and sized by the rules of the web player (a character stands 1.8 m, model.placement…). An entity with no model is drawn by its stand-in (a capsule, a car body, a marker ring…), as the web player draws it.
  • The clips. The clip of the state the kit asks for (model.anim): the one the pack maps to the state, else one of the same name, else one alike, else idle. One-shot states play once; a change cross-fades in 0.12 s; clips advance with the time of the game.
  • The camera the kits describe in the camera.* state vars: top-down (perspective or orthographic), chase, first person; and what a module imposes (a pose, a mode, a shake).
  • The scene. The default look of the web player, then what the kits set over it: the sun, the fill (Unity's three-colour ambient light), the exposure, the background, the fog, the bloom and grading of world.lighting (a global volume, ACES tone curve).
  • What the kits draw themselves. The presentation modules of the kits run inside libgfkits; GfGameView applies their commands with Unity objects: meshes, textures, materials (the shader GripForge/Standard), nodes, instances, models of slots, lights, entity looks, effects, skeletons, sound.

The game lives under one object, GfWorld, mirrored on Z: the kits and the glTF files are right handed, Unity is left handed, and under that object the positions and rotations of the game are written as the kits give them. Things you place yourself in world space relate to the game through GfSpace.ToUnity / GfSpace.FromUnity.

FieldRole
hostThe GfGameHost; empty: the first one among the siblings and the children.
presentPathThe presentation file, relative to StreamingAssets (gripforge/present.json).
driveStep the logic from here once per drawn frame (default on; it turns the host's autostep off).
provisionalHudDraw the provisional HUD where ui.webui shows no interface.
forwardInputSend the keyboard, the mouse and the gamepad to the game.
playSoundPlay the sound of the game (the output of audio.core).
MemberRole
Report()What is drawn and what is not: { frames, entities, looks, nodes, meshes, materials, lights, modules, msPerFrame, commands: { name: times }, refused: { what: times }, refusedByModule, undrawn, unbound, standIns }.
HudData()Per kit that has a web interface: { layer, vars, views, shared } — the data ui.webui gives that kit's interface.
views, camera, snapshotThe view of each entity, the camera, the snapshot of the frame.
started, framePresentedEvents.

What is not drawn is not invented. A command GfGameView does not apply is answered Refused to its module (which may draw something simpler by itself) and counted in Report().refused. See Known limits.

Which kits run in Unity #

Every kit whose logic is in the native library runs in Unity with the rules of the web: the catalogue says it by itself (gripforge kit api <kit>, section Targets: unity: native (libgfkits)) and names what is still missing, never leaving it out — a view of a kit drawn on the web page only (to provide by a ui.webui fragment), a fragment not yet in the published release, or a part of the network core not available natively (WebRTC, the shared physics of net.physics). The export lists the same lacks as warnings. Before writing a mechanism of your own in C#, read the kit's Targets: if it says native (libgfkits), use the kit (its config, hooks and mods).

HUD and menus #

The interface of the kits is ui.webui on every engine: the fragments of the kits, the same page as the web, run in an embedded browser engine (never the browser of the machine) by the ui.webui module of libgfpresent. Its picture is shown over the game, and it gets every input event before the game: what the interface takes never reaches the game. A click on a button of a fragment becomes the command of its kit, as on the web.

  • The browser engine is installed once in a cache shared by every game of the user, the first time a game starts (the game plays meanwhile, a band shows the download) — or put in the project with --bundle-web-engine, or installed beforehand with gripforge webui-engine prepare.
  • No interface is never silent: without the module, the host page or the engine, the game runs and the named reason (webui-module-missing, webui-host-page-missing, webui-engine-no-archive…) is shown in a band at the top of the window.
  • Where the interface did not start, or for a game without fragments, a provisional HUD is drawn, marked as such.

Command line of a player #

A built player (or the editor) reads these options; nothing happens without them:

OptionWhat
-gf-capture <png> -gf-frame <n>Writes a picture of frame n (default 120).
-gf-report <json>Writes GfGameView.Report() at that frame.
-gf-keys <code@from-to,…>Holds keys through the kit host between two frame numbers (web key codes: KeyW@30-200).
-gf-quitLeaves after the capture.
--webui-engine-dir=<dir>, --webui-engine-url=<base>, --no-webui-engine-downloadWhere the browser engine of the interface comes from.

API reference #

GfGameHost (namespace GripForge.Kits) is a MonoBehaviour. Everything runs on the main thread. Values are the JSON of the kits as C# values: objects are Dictionary<string, object>, arrays List<object>, numbers double; J.Obj(…), J.Arr(…), J.Str(…), J.Num(…) and dict.Get(key) read and build them.

Properties #

FieldRole
gamePathA game file to load and start when the component starts (relative to StreamingAssets); empty: call LoadGame / SetGame and StartGame yourself.
autostepStep the game in FixedUpdate (default). Off: call Step(delta) yourself (GfGameView does).
persistStoreKeep the local saves of the kits on disk (default on).

Loading and starting #

MethodRole
LoadGame(path)Reads a game file ({ project, lock, seed?, physics?, schema?… }).
SetGame(dict)The game as a dictionary instead of a file.
SetOption(key, value)An option of the start: seed, fixedDt, physics, schema, saves, locale, now, tzOffset.
RegisterEntry(collection, entry)Adds an entry to a registry of the game (items, statuses…) before the start.
StartGame() / StopGame() / IsRunning()Mounts the kits and starts the game; ends it.
GfGameHost.LibraryInfo()What the library is: { abi, contracts, physics, kits }.

Time #

MethodRole
Step(delta)Advances the game by delta seconds (fixed ticks inside); answers the ticks played.
StepTicks(n)Plays exactly n fixed ticks (tests, replays).
Tick(), Elapsed()Fixed ticks played, seconds of game time.

Inputs #

MethodRole
Key(code, down)A key or mouse button by its web code (KeyW, Space, Mouse0): the bindings are the game's.
Axis(action, value), Button(action, down)An input action driven directly (an on-screen stick, your own input map).
Gamepad(buttons, axes)The pad in the standard order.
InputSpecs()The input actions the kits declared.

GfGameView forwards the keyboard, the mouse and the gamepad itself (forwardInput).

Entities #

MemberRole
entityAdded(id, entity), entityRemoved(id)Events: an entity appeared ({ id, kind, tags, components }) or left.
BindNode(id, transform), UnbindNode(id)A transform of yours that follows an entity (Unity's axes).
EntityRows()Every entity: id number, x, y, z, yaw (the axes of the game).
Column(id, name)The value of a column of the game schema in an entity's row (combat.health@1).
Entity(id), Snapshot()An entity (e12, or player); the whole game now.

State, config, registries #

MethodRole
State(path)A state var (score), every var when path is empty.
Config(kit)The config of an installed kit as the game runs it (after the mods).
Registry(collection)A registry or data collection as the kits use it.

Services, operations, events #

MethodRole
CallService(service, method, args)Calls a service of a kit: CallService("inventory.items", "give", J.Arr("herb", 1)).
Apply(op), ApplyJson(text)One operation of the kit host (emit, spawn, set, save…).
Watch(pattern) + gameEvent(topic, payload, event)The events whose topic matches (*, combat.*).

Hooks #

MethodRole
Hooks()The interceptable hooks the kits declared: their kit, mutable fields, cancellable.
TapHook(hook, tap, opts)tap(payload, hook) is called each time a kit decides the hook, and answers null (no change) or { "set": { field: value }, "cancel": true, "reason": "…" }. Only mutable fields may be set and only a cancellable hook cancelled; the rest is refused (event engine.hook.refused). opts: { "priority": -1000..1000, "id": "…" }. Answers the tap id. A tap must not call the host back.
UntapHook(id)Removes a tap.

Saves #

MethodRole
Save(), LoadSave(text)The game as a save (gripforge.save/1, the format of the web: a save moves between the two).
Store()The local save store of the kits ({ key: text }). The host keeps it on disk (persistStore, default on: <persistentDataPath>/gripforge/<game id>/store.json) and gives it back at the next start; off, persist it yourself and give it back with SetOption("saves", …).

Presentation #

PresentNative, PresentKit, PresentModule, PresentStart, PresentFrame, PresentSnapshot, PresentCommands, PresentResult, PresentCommand, PresentInfo, HostRequests, AudioTake and UiStart / UiFrame / UiInput / UiStop are what GfGameView calls each frame; a view of your own calls them in the same order (the order of a frame is the one of GfGameView.Update).

Status #

MemberRole
LastStatus(), LastError()The name of the status of the last call (ok, refused, not_found…) and the message of the last failure.
failed(status, message)Event: a call failed.
started(info), stepped(ticks)Events: the game started ({ kits, skipped, fixedDt, lockHash }); it advanced.

Errors #

NameWhen
kit_library_missinglibgfkits is not in Packages/ai.gripforge.kits/Plugins/<platform>/ (or beside the player). Export again for that platform.
kit_library_unreadableThe file is not a library of this machine (another processor, damaged).
kit_library_version, kit_library_too_oldThe library is of another interface than the package. Export again: the CLI fetches the libraries that go with it.
bad_json, bad_project, bad_schemaThe game file is not one the library reads.
refused, not_foundA kit refused an operation, or what was named does not exist: LastError() says why.
panickedThe logic stopped: the game is disabled.

A presentation module the library does not have is named in the log at start and its kit is not drawn by a module; the game still runs.

Known limits #

  • Shaders written by the modules. Unity compiles no shader while a game runs. A material a module shades with its own WGSL (MaterialWgsl: skies, water, roads) is drawn with its standard base material, and the module is told (Shader { ok: false }); a fullscreen pass of a module (PostPass) is not drawn.
  • The look is close to the web player's, not identical: Unity's tone curve, light falloff and shadows differ.
  • The picture of the interface is copied through memory each time it changes.
  • The chase camera does not cast the rays that keep it out of walls.
  • Everything else a module asks and the view does not draw is listed by GfGameView.Report().refused.

Versions #

The package carries the version of the native libraries it was written for (package.json), and checks the interface of libgfkits at start. Export again after updating the CLI: the package, the libraries and the game data are replaced together.