# Using the Game Kits from Unreal (`UGfGameHost`, `UGfGameComponent`) Play a GripForge game inside an Unreal Engine project: the plugin **GripForgeKits** runs the Game Kits and tells your level what happened; `UGfGameComponent` shows what the kits ask. Readable offline: `gripforge docs unreal`, or the MCP tool `gripforge_docs` (topic `unreal`). ## What it is for The plugin runs the same Rust logic of the Game Kits as the web player, the desktop player, Godot and Unity — movement, combat, inventory, missions, scenes, saves — from the same game file, through two native libraries: | Library | What it holds | |---|---| | `libgfkits` (`gfkits.dll`, `libgfkits.so`, `libgfkits.dylib`) | the kit host and the logic of every kit, behind a C interface (`gfkits.h`) | | `libgfpresent` (`gfpresent.dll`, …) | the presentation modules of the kits | No rule of a kit is written in C++ or Blueprint: the plugin loads the libraries at run time (`FPlatformProcess::GetDllHandle`, nothing linked) and maps what they say to Unreal objects. You change the rules by tapping hooks, adding registry entries and calling services — never by rewriting a kit. ## Getting started 1. **Install the CLI** (Node.js 22.19 or later): `curl -fsSL https://gripforge.ai/cli/install.sh | sh`, then `gripforge login`. 2. **Export your game** as an Unreal project: ```sh gripforge game-export gkp_... ./my-unreal-game --target unreal ``` The CLI downloads the web export of the game, fetches the libraries of its own version (`--platforms linux-x86_64,windows-x86_64,macos-universal`), checks each file against the sha256 it carries and writes the project. Offline: `--capi ` and `--plugin `. A web export you already have: `gripforge game-export ./my-unreal-game --target unreal --from-web ./my-web-export`. 3. **Open `GripForgeGame.uproject`** with Unreal Engine 5.3 or later. The plugin ships as C++ sources: the first opening compiles it (Visual Studio on Windows, Xcode on macOS, the engine's own clang on Linux). 4. **Press Play.** `AGfGameMode` (set in `Config/DefaultEngine.ini`) starts the game in the engine's empty map: one actor per entity, the view on the player. 5. **The models are bound by themselves.** The models of the game are glTF files under `Content/GripForge/assets`. When the editor opens the project, the plugin imports them with Interchange (`/Game/GripForge/Models/`) and writes `Content/GripForge/models.json`: each slot gets its Skeletal or Static Mesh and its clips, named as the animations of the file. Without a window: `UnrealEditor-Cmd GripForgeGame.uproject -run=GfImportModels [-force]`. To draw a slot your way, fill **Models** on the `UGfGameComponent` (per slot `hero` or `hero:red`: a mesh, its clips by name, an offset): what you bind wins. A slot with no model at all is drawn as a box the size of the entity — never an invisible entity. ## In your own level Drop any actor in the level and add the component **GripForge Game** (`UGfGameComponent`). Its properties: | Property | What | |---|---| | `GamePath`, `PresentPath` | the game file and the presentation file of the export, relative to `Content/` | | `bAutoStart` | start at BeginPlay (else `StartGame()`) | | `bDrawEntities`, `EntityClass` | one `AGfEntityActor` per entity (subclass it to draw an entity your way) | | `bForwardInput` | keys, mouse buttons and the gamepad of the first player controller to the kits, by their web codes | | `bFollowPlayer` | the view on the entity tagged `player` | | `Models` | the model of each slot you bind yourself (the others come from `models.json`) | | `bPlaySound` | the sound of the game (`audio.core`) | | `bShowInterface` | the interface of the game (`ui.webui`: HUD and menus) over the view | | `WatchPattern` | the events given to `OnGameEvent` (`*`, `combat.*`…) | Events: `OnSnapshot` (each frame), `OnHud` (the HUD rows of the declared vars, when they change), `OnProblem` (why the game or its presentation did not start, named: `kit_library_missing: …`). `Host` is the `UGfGameHost`. ## `UGfGameHost` The same calls as the Godot and Unity hosts, one for one, over `gfkits.h`: | Call | What | |---|---| | `LoadGame(path)`, `SetGame(json)`, `SetOption(key, json)`, `RegisterEntry(collection, json)`, `StartGame()`, `StopGame()` | the game | | `Step(dt)`, `StepTicks(n)`, `Tick()`, `Elapsed()` | the clock (fixed ticks inside; `UGfGameComponent` steps from its `TickComponent` with the real frame dt, never from physics) | | `TickInfo()`, `TickAlpha()`, `TickAdjust(json)` | the game tick (`hz`, interpolation `alpha`); a live `{"hz":20}` change | | `Apply(opJson)`, `CallService(service, method, argsJson)` | an operation of the kit host, a service of a kit | | `Key(code, down)`, `Axis(action, v)`, `Button(action, down)`, `Gamepad(buttons, axes)`, `InputSpecs()` | input | | `BindComponent(id, component)`, `EntityRows()`, `Column(id, name)`, `Entity(id)`, `Snapshot()`, `State(path)`, `Config(kit)`, `Registry(collection)` | entities and state | | `Watch(pattern)` + `OnGameEvent` (Blueprint) / `OnEvent` (C++) | events | | `Hooks()`, `TapHook(hook, fn, opts)` (C++), `TapHookEvent` (Blueprint), `UntapHook(id)` | hooks | | `Save()`, `LoadSave(json)`, `Store()`, `bPersistStore` (`Saved/GripForge//store.json`) | saves | | `PresentNative/Kit/Start/Frame/Snapshot/Commands/Result/Command/Info`, `HudRows()` | presentation | | `HostRequests()`, `AudioTake()`, `AnimLayerWeights(json)` | window, sound, animation layers | | `UGfGameHost::FindFor(worldContext)` | the running host of a world, for an adapter component | The axes: the kits speak metres, Y up, right handed; Unreal centimetres, Z up, left handed. A point (x, y, z) of the game is (−z, x, y) × 100 in Unreal, a yaw θ is −θ (`GfSpace::ToUnreal`, `YawToUnreal`, `QuatToUnreal`). Change a rule from C++ (a hook of combat.core): ```cpp Host->TapHook(TEXT("combat.damage"), [](const TSharedPtr& Payload, const FString&) -> TSharedPtr { if (Payload->GetStringField(TEXT("targetTeam")) != TEXT("enemy")) return nullptr; auto Set = MakeShared(); Set->SetNumberField(TEXT("multiply"), 2.0); auto Out = MakeShared(); Out->SetObjectField(TEXT("set"), Set); return Out; }, TEXT("{\"id\":\"double_hits\"}")); ``` ## Animation `AGfEntityActor` plays the clip of the entity's state (`model.anim`) by the rules every host shares (the clip named like the state, then one alike, then idle; one-shots play once and hold; 0.12 s cross-fade) with `UGfAnimInstance` — no Animation Blueprint needed. An action on part of the body over the locomotion (`model.animLayer` over `model.animBase`: an attack while running) plays on the bones its layer takes: which bones and how much come from the Rust of the Bevy player (`gfk_anim_layer_weights`), then the two poses are blended per bone (the layered blend per bone of an AnimGraph, fed at run time). ## What the view draws - **The presentation modules of the kits** (`libgfpresent`) run every frame; their commands are drawn by `UGfViewCommands`: meshes, textures, standard materials (dynamic instances of the glTF materials of Unreal's Interchange plugin: lit or unlit, opaque, masked or blended, one or two sided), nodes hung on the world, on an entity, on one of its bones or on the camera, instances, models of slots, lights (lumens, lux), the sun and the ambient light of the scene, ground rings, the camera a module imposes, the looks of entities (tint, glow, scale, hidden), what a module claims (the entity's stand-in box is then not drawn). - **The interface** of the game (`ui.webui`: the HUD and the menus of the kits, the same page as on the web) is run by its module in an embedded browser and shown over the view (`UGfWebUI`); every key and click goes to it first, what it takes never reaches the game. Its browser engine comes with the export (`--bundle-web-engine`) or is installed in the cache of the user; a missing one is named in a band at the top of the window, never hidden. No HUD is made natively beside it (`OnHud` still gives the rows to a Blueprint that wants them). - **The sound** of `audio.core` is played (`UGfAudioOutput`): its base sounds rendered by the library, the files of the game decoded by it (WAV, Ogg Vorbis, MP3, FLAC), each voice a `UAudioComponent` at its place for a spatial one. - **Levels of detail and display optimisation** (kits render.lod and render.optimize, installed and on in every new game): the levels baked into the game copies of the models become, at import, the LODs of the slot's static mesh, with the screen sizes of the model's passport (by default level 1 under 60 % of the view's height, level 2 under 30 %, level 3 under 10 %): Unreal's own LOD selection switches them, by the size on the screen only. A level fixed by the game (`lod.level.`, `lod.off`, `render.lod.forceEntity`) is forced on the component. Every copy shares the model's static mesh (a `ModelInstances` set is one instanced component); what is off camera is culled by Unreal and stays loaded; `alwaysShown` is never culled, `instancing: false` gives an entity a mesh of its own. Both kits appear in the debug panel with Unreal's counters (`render.extra.optimize`). - **Shadows** (kit render.shadows, a module apart, on by default): the shadow mode of each mesh comes from the passport of its model (`kits.shadows`: `auto`, `enabled`, `disabled`, `dynamic`, `cached`, `blob`, `baked`; a character or a vehicle is `enabled`, a model that says nothing gets its mode from its size: under 0.3 m `disabled`, under 1.5 m `dynamic`, else `enabled`) and is written into the game copy by the build. `UGfRenderShadows` sets the `CastShadow` of every mesh component; the mode a file declares is read at import (`models.json`, `shadow`) because the glTF is not there at run time. A component the game set to cast none is left alone. A `dynamic` shadow is cut while the frames are late (under 50 per second for two seconds) and given back after a calm while; with `shadowFallback: "blob"` the cut shadow becomes a blob (a decal with a radial texture; not verified in an Unreal project yet). `cached` and `baked` stay `enabled` (Virtual Shadow Maps cache by themselves) and are counted under their own names. The counters (`shadows`, `shadowDisabled`, `shadowDynamic`, `shadowEnabled`, `shadowBlobs`, `shadowCut`, `shadowCuts`) join `render.extra.optimize` in the debug panel, the same keys on every engine. What it does not draw yet is answered `Refused` to the module with its name (the module degrades by itself) and counted in `Refused()`: module UI trees (`UiTree`), particles, trails and decals, bones and IK driven by a module, post passes and WGSL materials (their standard base is drawn), named nodes of a model (`NodeBind`). The catalogue says it on the `unreal` target of each kit (`gripforge kit api `). ## Limits and versions Unreal Engine 5.3 or later (checked on 5.8); Windows x64, Linux x64, macOS (universal library). The plugin version is the release of the libraries (`gfkits-v`); interface 1.5 or later of `gfkits.h` (the plugin and the libraries of one release go together). Errors of the library: `kit_library_missing`, `kit_library_unreadable`, `kit_library_too_old`, `kit_library_version` (`GFK_LIBRARY` names another library file).