Unreal

Usage guide of the Unreal plugin GripForgeKits: export a game as an Unreal project (gripforge game-export --target unreal: the prebuilt libraries libgfkits and libgfpresent are fetched and checked, the plugin compiled at the first opening; --capi and --plugin offline), AGfGameMode, UGfGameComponent in your own level (models by slot, input, camera, events, HUD rows), every call of UGfGameHost (game, step, inputs, entities, events, hooks from C++ and Blueprint, saves, presentation), the axes, animation layers, what the Unreal view does not draw yet, limits.

Source: module-kits:wrappers/unreal/usage/en.mdView Markdown source

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

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:

LibraryWhat 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:

    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 <libraries folder> and --plugin <GripForgeKits folder>. 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/<slot>) 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:

PropertyWhat
GamePath, PresentPaththe game file and the presentation file of the export, relative to Content/
bAutoStartstart at BeginPlay (else StartGame())
bDrawEntities, EntityClassone AGfEntityActor per entity (subclass it to draw an entity your way)
bForwardInputkeys, mouse buttons and the gamepad of the first player controller to the kits, by their web codes
bFollowPlayerthe view on the entity tagged player
Modelsthe model of each slot you bind yourself (the others come from models.json)
bPlaySoundthe sound of the game (audio.core)
bShowInterfacethe interface of the game (ui.webui: HUD and menus) over the view
WatchPatternthe 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:

CallWhat
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/<game>/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):

Host->TapHook(TEXT("combat.damage"), [](const TSharedPtr<FJsonObject>& Payload, const FString&) -> TSharedPtr<FJsonObject> {
    if (Payload->GetStringField(TEXT("targetTeam")) != TEXT("enemy")) return nullptr;
    auto Set = MakeShared<FJsonObject>(); Set->SetNumberField(TEXT("multiply"), 2.0);
    auto Out = MakeShared<FJsonObject>(); 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.<n>, 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 <kit>).

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<version>); 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).