# 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: | Library | What 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-.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: ```sh 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: ```sh 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: ```csharp 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 | Path | What | |---|---| | `Assets/StreamingAssets/gripforge/game.json` | The 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//` (the two libraries). | | `Assets/GameMods.cs`, `Packages/manifest.json`, `README.md`, `.gitignore` | Yours: an update of the export never rewrites them. | | `.gripforge/unity.lock.json` | What the export wrote, to update it safely. | The export holds no `.wasm`: the presentation modules are native code of `libgfpresent`. ## Exporting a game ```sh gripforge game-export [folder] --target unity [--platforms linux-x86_64,windows-x86_64,macos-universal] [--capi ] [--package ] [--webui-host ] [--bundle-web-engine | --webui-engine-when first-load|install] [--webui-engine-url ] [--check] [--force] [--json] gripforge game-export [folder] --target unity --from-web [same options] ``` - `--platforms` limits the libraries to the platforms named. - `--capi ` takes libraries you already have, offline: `/libgfkits.*` and `/libgfpresent.*` (the `capi/` folder of a release of the native libraries). `GRIPFORGE_UNITY_CAPI` does the same. - `--package ` 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`. | Field | Role | |---|---| | `host` | The `GfGameHost`; empty: the first one among the siblings and the children. | | `presentPath` | The presentation file, relative to StreamingAssets (`gripforge/present.json`). | | `drive` | Step the logic from here once per drawn frame (default on; it turns the host's `autostep` off). | | `provisionalHud` | Draw the provisional HUD where `ui.webui` shows no interface. | | `forwardInput` | Send the keyboard, the mouse and the gamepad to the game. | | `playSound` | Play the sound of the game (the output of `audio.core`). | | Member | Role | |---|---| | `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`, `snapshot` | The view of each entity, the camera, the snapshot of the frame. | | `started`, `framePresented` | Events. | **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 `, 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: | Option | What | |---|---| | `-gf-capture ` `-gf-frame ` | Writes a picture of frame `n` (default 120). | | `-gf-report ` | Writes `GfGameView.Report()` at that frame. | | `-gf-keys ` | Holds keys through the kit host between two frame numbers (web key codes: `KeyW@30-200`). | | `-gf-quit` | Leaves after the capture. | | `--webui-engine-dir=`, `--webui-engine-url=`, `--no-webui-engine-download` | Where 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`, arrays `List`, numbers `double`; `J.Obj(…)`, `J.Arr(…)`, `J.Str(…)`, `J.Num(…)` and `dict.Get(key)` read and build them. ### Properties | Field | Role | |---|---| | `gamePath` | A game file to load and start when the component starts (relative to StreamingAssets); empty: call `LoadGame` / `SetGame` and `StartGame` yourself. | | `autostep` | Step the game in `FixedUpdate` (default). Off: call `Step(delta)` yourself (`GfGameView` does). | | `persistStore` | Keep the local saves of the kits on disk (default on). | ### Loading and starting | Method | Role | |---|---| | `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 | Method | Role | |---|---| | `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 | Method | Role | |---|---| | `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 | Member | Role | |---|---| | `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 | Method | Role | |---|---| | `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 | Method | Role | |---|---| | `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 | Method | Role | |---|---| | `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 | Method | Role | |---|---| | `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: `/gripforge//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 | Member | Role | |---|---| | `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 | Name | When | |---|---| | `kit_library_missing` | `libgfkits` is not in `Packages/ai.gripforge.kits/Plugins//` (or beside the player). Export again for that platform. | | `kit_library_unreadable` | The file is not a library of this machine (another processor, damaged). | | `kit_library_version`, `kit_library_too_old` | The library is of another interface than the package. Export again: the CLI fetches the libraries that go with it. | | `bad_json`, `bad_project`, `bad_schema` | The game file is not one the library reads. | | `refused`, `not_found` | A kit refused an operation, or what was named does not exist: `LastError()` says why. | | `panicked` | The 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.