Skip to content

KKE Showcase (kke_demo)

A walkable course where every spot shows one part of the engine. You run an animated character around a 60 x 60 m yard: push and shoot crates, break glass, wood and stone, vault fences, climb blocks, hang from ledges, run along a wall, wade through a pool where props float on waves, and watch lava melt a block. Up to four people can play on one machine in split screen, and others can join over the network. Near each station the HUD tells you what to try there.

This is the player-facing tour in docs/SHOWCASE.md. This README is the developer deep-dive: how each station and system is built, and why. It is the starting point for any third-person action game: a platformer, a parkour or traversal game, a physics playground, a co-op or online game with a character you walk around.

Vaulting and climbing on the parkour lane

Other pictures from this demo: ledge-hang.webp, parkour.webp, crate-rain.webp (the stress test) and split-screen.webp, all in website/static/media/.

Run it

The executable is kke_demo (add_executable(kke_demo ...) in CMakeLists.txt). The root CMakeLists.txt only adds this folder when KKE_ENABLE_JOLT is on, which is the default.

cmake --preset default && cmake --build build
cd build/bin && ./kke_demo
KKE_SKIP_INTRO=1 ./kke_demo      # no logo intro (needed when headless)

Build options change what you get:

Option Default Without it
KKE_ENABLE_JOLT ON The demo is not built at all
KKE_ENABLE_FEMFX OFF (ON in the everything preset) No breaking yard, no FEMFX balls when you shoot, the stress test's impact phase fires Jolt iron blocks instead
ENGINE_ENABLE_LUA ON No scripts/ folder, no ScriptModule (no toys, no break-the-targets)
KKE_ENABLE_NET ON No NetModule: offline only
KKE_ENABLE_VOICE ON (needs NET) No voice chat

So cmake --preset everything is the way to see the breaking yard.

Environment variables for scripted runs (screenshots, checks). All of these are read in ShowcaseModule::init unless noted:

Variable What it does
KKE_SKIP_INTRO=1 No logo intro (engine)
KKE_START_AT=x,y,z[,yaw] Start there, facing yaw degrees (the camera too)
KKE_DEMO_AUTOPILOT=1 The character runs the parkour lane (or a scene's trail) by itself and logs each vault and climb
KKE_DEMO_HANG=1 Jump at the 3 m wall, hang, shimmy round its end, jump off. Loops every 10 s
KKE_DEMO_TRICKS=1 Wall run and wall jump, pillar leaps, the leap up to the beam. Loops every 13.5 s
KKE_DEMO_WORLD=1 Opens the world map for a few seconds, then visits every zone in turn (5 s each) and logs the ground there
KKE_DEMO_ITEMS=1 At the supply table: picks up what's in reach, opens the bag, equips an axe, a rifle, a helmet, a canteen and a lantern, closes it and turns the camera to see them worn. Logs the bag and the weight. =2 leaves the bag open (drive it with keys or the mouse; each place and drop is logged)
KKE_DEMO_CARRY=1 Spawns a crate, lifts it, carries it a few steps and throws it, and logs where it is. Loops every 7 s
KKE_DEMO_GUNS=1 At the firing range: takes the guns off the bench, knocks down plates with the rifle, breaks the glass, the plank and a stone wall, sets off the red barrels, switches to the pistol, throws a grenade. Logs every hit and blast
KKE_DEMO_DRIVE=1 At the race track: gets in the red hatch and drives a lap of the oval (logs its progress and the lap time), then flies the plane from the airfield round four waypoints and dives it into the runway (the crash). =2 skips the car
KKE_DEMO_NATURE=1 In the nature park: takes the axe, fells a tree, splits a log, picks three flowers, then walks a trail through the snow field and looks back at it
KKE_DEMO_PARKOUR=1 In the parkour park: runs up the 3.6 m wall and climbs onto it, vaults over a thin wall from a hang, shimmies round the corner of the shimmy wall and leaps the gap, then runs the rooftops. Logs every move. =2 starts at the hang vault, =3 the shimmy, =4 the rooftops
KKE_DEMO_BRIDGE=1 Drops an iron ball on the yard glass, then rolls one into a crate, and logs how far the crates moved (FEMFX builds)
KKE_BRIDGE=0 Turns the FEMFX-Jolt bridge off, to compare
KKE_SPLIT=2..4 Start with that many local players
KKE_OVERHEAD=1 An overhead picture-in-picture view of player 1
KKE_MAIN_MENU=pause Opens the pause menu 1.5 s after start (the game shell's switch; KKE_MAIN_MENU=0 skips the title)
KKE_SCENE=town_block Start in the Synty scene whose path contains this text
KKE_COURSE_ART=0 Leaves the Synty art off the course
KKE_CHARACTER=SK_Character_... Play as a Synty character, retargeted
KKE_LEFT_HANDED=1 Mirror the keyboard bindings across the G/H line
KKE_STRESS_TEST=1 Runs the 36 s stress test at start and quits when done
KKE_BENCH_DIR=dir Where the stress report goes (default benchmark/)
KKE_NET=host / KKE_NET=join:ADDRESS Host or join (read by NetModule)
KKE_DEMO_ONLINE=1 Run it on a host and a guest: the host spawns a barrel; the guest asks for a crate, walks to it, lifts it, carries it, throws it and clears. Both log what they see (online demo (host), online: player 1 picked up body ...)
KKE_VOICE=off No microphone (read by VoiceModule)
KKE_SCRIPTS_DIR=<repo>/games/showcase/scripts Run the Lua scripts from the source folder, so edits reload live
KKE_ANIMATIONS_DIR, KKE_SCENES_DIR, KKE_ASSETS_DIR / KKE_SYNTY_DIR, KKE_COURSE_DIR Where to find the animations, scenes, Synty packs and the course art

Controls

Player 1. The bindings come from InputModule::defineCharacterActions (InputModule.cpp) plus four actions the showcase adds in ShowcaseModule::init (reset, pickup, panels and zoom.pad, the d-pad's held zoom) and the spawn menu's (spawn.menu, and spawn.up, spawn.down, spawn.pick, spawn.close while its list is open: Spawner.cpp). init also moves the ping off the d-pad down and push-to-talk off B, which the showcase uses for other things (BUG-065). Everything except Esc is rebindable and saved in input.json.

Action Keyboard and mouse Controller
Move W A S D Left stick
Look Mouse (click the view first to capture it) Right stick
Sprint Left Shift (hold) L3 (toggle)
Walk Left Alt (hold) Tilt the stick a little (analog speed); no button
Jump, vault, climb, grab a ledge Space A
Crouch, or let go of a ledge C (toggle) B (toggle)
Shoot (or throw what you hold); with a gun in your right hand, fire it (the rifle is automatic), with a grenade, throw it Left click (hold for 4 shots a second) RT
Aim down the sights (the camera comes in over the shoulder, a crosshair shows) Right mouse (hold) LT (hold)
Push what you look at E Y
Pick up / put down the crate, barrel or ball in front of you; an item goes in your bag. Next to a car or the plane: get in (and out) F X
Bag (inventory and equipment) Tab or I View
World map: the zones, travel there M the pause menu's World map
Spawn menu: crates, barrels, balls, a tower, a ragdoll dummy; clear, reset the world G RB
First / third person V R3
Camera distance Mouse wheel (while captured) D-pad up / down (hold)
Reset the world (also in the spawn menu) R none (spawn menu)
Pause menu: settings, button remapping, quit Esc Start
Engine panels (ImGui, developer tools) F1 none
Ping the surroundings (hear the walls) Q D-pad left
Push to talk (voice builds) P (B is a toy) LB

On a ledge: A/D (or the stick) shimmies along it and round corners, Space climbs up, back plus Space jumps off, C lets go. Jump with left or right held leaps to the next edge. Running fast beside a wall and jumping starts a wall run; jump again to kick off.

The pause menu is the one every KKE game shares (kke::GameShellModule, GAME_SHELL.md): the mouse, the arrows and Enter, or the d-pad / left stick and A; B, Esc or Start closes it. The HUD's corner hint shows the pause button of the device in use.

Players 2 to 4 (split screen) get a controller each: move, look, sprint, walk, crouch, jump, shoot (RT), push (Y), zoom (d-pad) and the pause menu (Start or View / Back) all work from it; shots and pushes go along that player's own view.

The Lua scripts add their own actions:

Script Action Keyboard Controller
toys.lua Build a crate tower / throw a ball / clear H / B / N (or hold B) none: the spawn menu (RB) has towers and balls
targets.lua (from games/first_lua_game) Start break-the-targets T D-pad right

How it plays

There is no win or lose: it is a playground. You spawn at (0, 0, 6). The stations, with their positions from Layout.h:

Station Where What to try
Crate pile (4, -3) 15 crates in a pyramid, plus three big light boxes at z = 10. Push (E) or shoot
Breaking yard (14, -6) A glass pane on two supports with three small crates under it, a wooden plank bridge, a stone wall. Shoot them (FEMFX builds only)
Parkour lane x = 20, z 28 down to 5 A 1 m fence and a 0.6 m wall to vault, a 1.5 m block to climb, a 2.1 m ledge only a sprint reaches, a 3 m wall at x = 16 to hang from
Trick course x = 26 A 12 m wall to run along (z 11 to 23), three thin pillars (tops 2.9, 3.2, 3.5 m) to leap between, a thin wall under a beam to leap up to
Lava (0, 20) Lava pours onto a block that melts: ice, then wax, then aluminium. Push a crate in
Pool (-18, 20) Vault the 1 m wall and wade in (no running). Crates, planks and a raft float, a steel block sinks
Moving platform (-14, 6) Stand on it: it carries you side to side and up and down
Low roof (10, 6) 1.2 m clearance: crouch to get under
Stairs, ramp, steep slope, pillars around (-8, -4), (8, -10), (-14 to -6.5, -14) 0.25 m steps, a 24 degree ramp you walk, a 55 degree slope you slide off, pillars that push the camera in
Supply table (-4.5, 2.5) One of every item: pick them up, open the bag, equip them

The open world

The yard is the middle of an open world 1.4 km across (World.cpp). Each yard wall has a gate; outside, the ground rolls on to a ring of hills, with a mountain to the north west. Roads run from the gates to the zones (their table is layout::kZones in Layout.h); a post at each gate has a board in each zone's colour, and every zone flies a 14 m flag in its colour so you can find it from far away. The HUD names the zone you are in.

Zone Where Road
Parkour park (0, -160) north gate
Firing range (170, -150) north gate, then right
Airfield (380, 40), runway x 185 to 575 east gate
Race track (0, 255) south gate
Nature park (-230, 40) west gate
Snow field (-300, -280), 30 m up the mountain west gate, then the dirt trail up

M (or the pause menu's World map) opens the map: the zones, the roads and you (the yellow dot points the way you face). Choose a zone with up and down (or the mouse) and A, Enter or a click takes you there.

The terrain is one mesh with a vertex every 5 m and one static Jolt mesh body; groundHeight(x, z) reads the same grid, so what later rounds put out there sits on it. Roads are cut level across so cars sit flat. The camera's far plane is 1500 m and the mood's fog hides the edge.

The pause menu (Esc / Start) has Resume, World map, Reset the world, 1 to 4 players, Engine panels (developer builds), Settings, Controls, Main menu and Quit. Offline it pauses the game; online the others keep playing. The KKE Showcase ImGui window (always shown) has the controls, a Scenes list to visit the Synty levels, and panels for movement, performance and the stress test, character, lighting, lava, split screen and camera. F1 shows the other modules' panels too.

The firing range

North of the yard (zone 2, Range.cpp, the guns in Guns.cpp). The bench holds a rifle, a pistol, rifle and pistol rounds and two boxes of grenades; picking a gun or a tool up with an empty right hand puts it straight in that hand. Down range, from left to right:

Lane What is there
Plates 10 steel plates on plinths at 15, 28 and 45 m. A hit knocks one down; the HUD counts "Plates down"
Breakables (FEMFX) A glass pane, a plank on two blocks, a stone wall at 14 m and a bigger one at 23 m. Bullets break them for real; each piece is its own body
Barrels and crates 5 red barrels, a crate pyramid and two ragdoll dummies. A shot barrel burns for a moment and blows up, setting off the barrels near it

How a shot works: the crosshair point is a ray from the camera; the shot leaves the muzzle towards it with a little spread (0.25 degrees aimed, 1.6 from the hip). Jolt (kke::physicsRaycast) and FEMFX each find their nearest hit and the nearer wins. A Jolt hit gets an impulse and an effect by material (sparks on steel, chips on wood and stone); a FEMFX hit gets a small fast iron slug spawned just short of it, so the break is FEMFX's own fracture, not a fake one. Magazines refill from the bag's rounds when empty (30 rifle, 12 pistol); no rounds left is a click and a toast.

Grenades and barrels call explode: kke::physicsBlast pushes everything in both physics engines away (7 m), FEMFX walls crack, barrels and grenades in reach go up after it, and there is a fireball, smoke, a shockwave ring, a point-light flash, camera shake and a synthesized boom. Fuses count in physics time (fixedUpdate), so a slow machine does not blow a grenade up in mid-air. Every sound is synthesized at startup (buildGuns): no audio files. The effects are the built-in kke::ParticleLibrary ones, drawn in renderTranslucent; their shaders are copied by this game's CMakeLists.

While aiming, the character turns with the camera and walks; the arms are placed by IK (aimHands: rifle to the shoulder with the left hand on the fore-end, pistol out in both hands, a grenade raised to throw).

Cars and the plane

Vehicles.cpp. Walk up to a car or the plane and press pickup (F / X) to get in; the same button gets you out once you have stopped. The camera chases what you ride and swings in behind it when you stop turning it; the wheel or the d-pad moves it out (V: from the seat).

Keyboard Controller
Car: steer, gas, brake / reverse A D, W (or Shift), S (or Ctrl) Left stick, RT, LT
Car: handbrake, back on its wheels Space, R A, none
Plane: stick (forward: nose down), roll W S, A D Left stick
Plane: throttle up / down, wheel brakes Shift / Ctrl, Space RT / LT, A

The race track (zone 5): an oval 585 m round (two 120 m straights and two bends of 55 m radius), kerbs on the bends, a start gantry, a slalom of cones on the far straight and a jump beside the car park. Three cars wait in the car park, each its own kke::VehicleDesc on Jolt's vehicle physics (VEHICLES.md): a light front-drive hatch, a rear-drive coupe and a heavy all-wheel-drive truck that sits higher. Go once round the oval on the track and the HUD gives the lap time (physics time, so it is the same on a slow PC).

The airfield (zone 4): a stunt plane at the west end of the runway. It flies on the Flying demo's flight model (games/flying_demo/Flight.h, compiled into this game): lift, drag, stalls and the nose that comes up by itself at take-off speed, so full throttle and a straight run is a take-off. A kinematic Jolt body follows it, so it pushes crates and people out of the way. Land on the runway or any flat field; a hard landing, a hillside or anything built is a crash: an explosion (the guns' explode), a wreck, and a new plane on the runway four seconds later.

Each machine drives its own cars and plane: they aren't shared online yet (see "Online" below). The engine note is kke::EngineSound, synthesized from the rpm and the throttle like the racing demo's.

The parkour park

Parkour.cpp. Zone 2 on the map, up the road north of the yard: seven sections, each built to show one move, and the HUD names the one you are in. Nothing in it is marked for the movement; Locomotion reads every box the same way (MOVEMENT.md).

Section What's there The move
Vault field fences (1.0, 1.1 m), low walls, a 1.2 m box, two fences close together run and jump: a vault; sprinting, a speed vault that keeps the run
Wall climb walls of 3, 3.6 and 4.2 m, a painted run-up to each run at one and jump: you run up it and catch the top; jump again to climb on
Hang vault thin walls of 2.6 and 3 m hang from the top, jump: over and down the far side
Shimmy wall a thin 3 m wall round an outside corner, a 1 m gap, a 3.5 m piece hang, shimmy hand over hand round the corner, leap the gap
Ledge leaps five thin pillars, tops 2.8 to 4 m hang, jump with left or right to leap to the next
Wall run two 4 m walls, 3.5 m apart sprint beside one, jump: wall run; jump again to kick across
Rooftops seven roofs 3.5 to 5.5 m, gaps of 2 to 2.5 m, a rail and a box on top up the steps, sprint and jump the gaps; a roof too high is caught by the edge

The wall climb and the hang vault are Locomotion moves (this round); the shimmy's hands and feet are IK steps here (Animation): no UAL clip has a hang or a shimmy.

The nature park and the snow field

Nature.cpp. The nature park (zone 6 on the map, west of the yard): about 85 trees round a meadow, undergrowth, and grass that sways in gusts of wind and bends away from your legs. With POLYGON Nature installed the trees, stumps and plants are Synty's (each tree an instance turned about its foot every frame, which is the sway); without it the trees are built from shapes (round, pine and birch) and look the same from a distance (KKE_NATURE_ART=0 shows those even with the pack). The grass blades are always this file's own: a blade or two every half metre within 20 m of you, rebuilt each frame, no shadows. Leaves drift down while you are in the forest.

Take the axe from the chopping block at the gate (or the supply table) and fire swings it (both hands on the handle, by IK). Four hits fell a tree: it tips away from you, slow at first then fast (a rod falling over its foot), crashes down with a shake, and after a moment lies as two to four logs (props: carry them, throw them, or swing at one to split it into firewood for the bag). The stump stays. Flowers in the meadow (poppies, buttercups, cornflowers) are picked with pickup straight into the bag. Reset the world and the forest grows back.

The snow field (zone 7, up the mountain): a layer of snow 22 cm deep that keeps every footprint and tyre track. It is a grid of how far each point is pressed (quarter-metre cells, 116 m across), drawn as 64 chunks of which only the pressed ones are rebuilt (at most six a frame). Feet stamp an oval every stride, left then right; a car's wheels stamp where they touch. Snow falls round the camera there. Reset the world for fresh snow.

Spawning and carrying

RB (G) opens the spawn menu on the right while the game keeps running: up and down to choose (d-pad, left stick, W/S, arrows, or the mouse), A, Space, Enter or a click spawns it in front of you, and the list stays open so you can spawn more; B, RB, G or Backspace closes it. It has a wooden crate, a small crate, a big light box, an iron crate (too heavy to lift), a barrel, a rubber ball, a crate tower and a ragdoll dummy, then Clear what I spawned and Reset the world (what you spawned goes, the course's crates go back, you're back at the start).

X (F) picks up the crate, barrel or ball in front of you, up to 60 kg: it rides in front of your chest in both hands and stays a physics body, so it bumps into walls and other crates instead of going through them (snag it on something and you let go). X (F) puts it down, RT (click) throws it where you look. Climbing, vaulting or hanging drops it. Heavier things you push (Y, E).

Online it all works the same for everyone. The host makes what anyone spawns, so every machine sees the same crate in the same place, and the host's physics moves it. Lift something on a guest and the host carries it in front of you; everyone sees it ride along and fly when you throw it. Clear what I spawned clears everyone's; Reset the world (host) resets everyone. What stays on each machine: the firing range's dummies, the forest (trees, logs, flowers), the snow's footprints, and the cars and plane.

Items, the bag and equipment

The supply table by the start (-4.5, 2.5) has one of everything: a wood axe, a hunting rifle, a pistol and their rounds, a first-aid kit, a lantern, a helmet, a canteen, firewood, apples and flowers. Walk up and X (F) puts the one in front of you in your bag; the HUD says what it is.

Tab, I or View opens the bag over the game: a 10 x 6 grid where every item takes its cells (the rifle 4 x 1, the axe 1 x 3), with what you wear and hold on the left and the details of the item under the cursor (description, weight, size, where it equips) on the right. Move with the arrows, WASD, the d-pad, the stick or the mouse. A, Space or a click takes an item and places it again: on a cell (the cells turn green where it fits, red where it doesn't), on a stack of the same kind (they merge), or on an equipment slot. R or Y turns what you're moving, E, X or a right-click equips or takes off, Q or RB drops it in front of you, T or R3 sorts the bag biggest first. B, View, Tab or Esc closes it.

What you equip is drawn on the character (kke::Equipment): the axe or the rifle in the right hand or across the back, the pistol or the canteen on a hip, the lantern in the left hand, the helmet on the head, the fingers closed round what a hand holds. Carrying a crate stows the hands' things. The weight shows under the grid: past half of 30 kg you get slower, near the limit you can't sprint. The kinds of item are data, data/items.json (name, description, size, weight, stack, slots); Items.cpp makes their meshes.

How it works

Startup and the frame

main.cpp builds the kke::Application (1280 x 720, mood clear_day, far plane 1500 m: the open world is 1.4 km across) and adds modules in this order:

  1. SettingsModule("settings.json"): first, so the resource budget (threads, frame caps) is set before physics starts its workers.
  2. InputModule("input.json"): actions and bindings.
  3. RigidBodyModule: Jolt collision, rigid bodies, character controllers.
  4. NetModule (NET builds), game id "kke_demo": added before the showcase "so a join is under way when the crates spawn".
  5. ModelModule (the animated character), UiModule (RmlUi), AudioModule, VoiceModule (voice builds), SoundVisualizerModule (on by default here).
  6. PhysicsModule and PhysicsBridgeModule (FEMFX builds). The FEMFX ground plane is not drawn: the course has its own floor.
  7. kke_showcase::ShowcaseModule: the demo itself.
  8. ScriptModule("scripts") (Lua builds), DebugControlModule, StatsModule.

Every module except the showcase goes into a panels list whose ImGui windows start hidden; setEnginePanels hands the list to the showcase so F1 can show them. Modules are initialised in dependency order: ShowcaseModule::dependencies() requires RigidBodyModule, InputModule and ModelModule, and optionally wants UiModule and NetModule, so those are ready when its init runs.

ShowcaseModule::init wires the net callbacks, defines the extra input actions, builds the level (buildLevel, dressCourse, spawnCrates, spawnBreakables, setupPlayer), builds the HUD, finds the Synty scenes, then reads the environment variables above.

Each frame the engine calls, in this order:

  • frameStart: follows the game shell's menu (open or closed) and lets go of the mouse when it opens. It runs even while the game is paused.
  • fixedUpdate (60 Hz): the lava station, the moving platform and the pool's buoyancy.
  • update: stress test, crate batch, lava mesh, HUD, crouch, input, the scripted demos, Locomotion::update, animation, network state, other players' avatars, the model transform and IK, the camera, the split screen players, and the sun from the Lighting panel.
  • prepass, render, renderShadow: level, pool, lava, scene grounds, the crate batch, the platform, and a box stand-in when there is no character model.
  • renderUi: the ImGui window (skipped while the pause menu is open).

The code is split by topic, all methods of the one ShowcaseModule class declared in ShowcaseModule.h: ShowcaseModule.cpp (level, player, animation, networking, ImGui), Stations.cpp (pool, lava basin, course art), LavaStation.cpp (its own class), SplitScreen.cpp, StressTest.cpp and Hud.cpp.

The layout

Layout.h holds every station's position as constexpr values in kke_showcase::layout (kYard, kCratePile, kPoolCenter, kLavaCenter, kLaneX, kTrickX, kPlatform, kLowRoof). The level builder, the stress test (which aims at the yard) and the HUD (which decides which station you are at) all read it, so moving a station is one edit. course_art.scene.json uses the same world coordinates.

The level

The course is boxes made in code, with no asset files. Two helpers do all of it:

  • appendBox(matrix, half, color, v, idx) (anonymous namespace in ShowcaseModule.cpp) appends the 24 vertices and 36 indices of a box to a vertex list. Top faces keep the colour, sides get 85 %, the bottom 60 %, "so shape reads better without textures". It checks each face's winding against its normal and flips the triangles if needed.
  • addStaticBox(Box, v, idx) appends the box to the mesh and also adds a matching static Jolt body (RigidWorld::Motion::Static, material 1, "stone" for impact sounds).
void ShowcaseModule::addStaticBox(const Box& b, std::vector<kke::Vertex>& v, std::vector<uint32_t>& i) {
    glm::mat4 m = glm::rotate(glm::translate(glm::mat4(1.0f), b.center), glm::radians(b.yaw), glm::vec3(0, 1, 0));
    appendBox(m, b.half, b.color, v, i);
    kke::RigidWorld::BodyDesc d;
    d.shape = kke::RigidWorld::Shape::Box;
    d.motion = kke::RigidWorld::Motion::Static;
    ...
    m_rigid->world().add(d);
}

buildLevel calls it for the floor (60 x 60 m, top at y = 0), four 3 m perimeter walls, eight 0.25 m stair steps up to a 2 m platform, pillars and the low roof, then buildParkourLane, buildTrickCourse, buildPool and buildLava. The tilted ramp (-24 degrees round Z) and the too-steep slope (55 degrees round X) are added by hand because they are rotated round another axis than Y. Everything ends in one DynamicMeshRenderer (m_level) uploaded once: the whole static course is one draw call.

The parkour lane is "not marked up": there is no "vault here" tag. Heights are chosen so kke::Locomotion's sensors classify each piece by shape: a 1 m fence and 0.6 m wall are vaults, a 1.5 m block is a climb, the 2.1 m ledge is only reachable with the sprint sensor, and the 3 m wall is too high to climb so you hang from it. See MOVEMENT.md.

Crates and the moving platform

spawnCrates removes any old crates and adds, in this order: the 15-crate pyramid (0.6 m, density 250, "wood"), three big light boxes (1.2 m, density 60), the pool floaters (spawnPoolFloaters), and three small crates under the yard's glass. Each is a Crate { body, half, cube } where cube picks one of four colours. The order matters for networking (below).

Crates are not drawn one by one. batchCrates rebuilds one mesh from every body's current transform each frame and uploads it: "one draw (and one shadow draw) instead of two per crate". The comment above it measures about 0.1 ms for 300 crates on one core.

The platform is a kinematic body. fixedUpdate moves it with moveKinematic along

glm::vec3 p(-14.0f + 5.0f * std::sin(m_platformTime * 0.5f),
            0.6f + 1.2f * (0.5f + 0.5f * std::sin(m_platformTime * 0.35f)), 6.0f);

moveKinematic gives Jolt the velocity, so a character standing on it is carried. It is drawn with m_cubes[3] scaled to its half extents.

The player

setupPlayer adds a Jolt character (RigidWorld::addCharacter) at m_spawn and wraps it in a kke::Locomotion, the movement layer that does turning, air control, vault, climb, hang, leap and wall run.

readActions turns the input map into flags: move (2D), sprint, walk, crouch, and a queued jump. The "game" input context is switched off while typing in ImGui or while the menu is open. Then update builds a Locomotion::Input:

in.move = m_rig.forward() * m_moveInput.y + m_rig.right() * m_moveInput.x;
in.move.y = 0.0f;
if (glm::length(in.move) > 1e-3f) in.move = glm::normalize(in.move) * std::min(1.0f, glm::length(m_moveInput));
...
in.fast = m_sprint && !m_crouch && !wading;
in.slow = m_walk || wading;
in.goUp = m_jumpQueued;

Movement is camera-relative, and the stick's length is kept so a small tilt walks. goUp means "go up": whether that is a jump, a vault, a climb or a ledge grab is Locomotion's decision from what its sensors see.

Crouch shrinks the capsule from 1.8 m to 1.0 m with setCharacterHeight, which fails if there is no headroom, so standing up waits until you are out from under the roof (the panel shows "No room to stand up"). While hanging, crouch means "let go" instead. Inside the pool (inPool) sprint is off and walk is forced. Falling below y = -20 teleports you back to the spawn.

The camera

A kke::CameraRig in third person (m_rig). Mouse look goes through addLook scaled by m_mouseSensitivity (0.12 degrees per pixel), the stick through look.rate times m_stickSpeed (200 degrees per second, 70 % of that vertically). The pivot height eases between 1.5 m and 0.85 m when crouched.

m_rig.update takes a ray cast callback that uses Jolt, so the spring arm pulls in when a wall or pillar is between the camera and the character. During a wall run the shoulder offset swaps to the open side, because "over the wall shoulder the spring arm would pull in to the back of the head". In first person the body faces where you look and the model is hidden.

Animation

The character is Quaternius' Universal Animation Library mannequin. setupPlayer looks for assets/animations/UAL1_Standard.fbx (with kke::findAssetFolder, or KKE_ANIMATIONS_DIR). If it is missing, the log says so and the character is a blue box (m_capsule). If UAL2.fbx is next to it, setupPlayer loads it as CPU data only, keeps just five clips (SafetyVault, ClimbUp_1m, ClimbUp_2m, WallRun_L_Loop, WallRun_R_Loop) and drops its meshes.

buildAnimator creates a kke::AnimationSet and removes the pelvis "lift" from the traversal clips (removeLift): Locomotion already moves the capsule up the wall, so the clip must not lift the body a second time.

addAnimatorStates defines the same states in the same order on every animator (ours, split-screen players', network players'), so the state indices m_stMove, m_stJump and so on mean the same everywhere:

  • move: a blend state of Idle (0), Walk (1.6 m/s), Jog (3.6 m/s) and Sprint (6.2 m/s). These are "the animations' own foot speeds, which are also kke::Locomotion's default walk/run/sprint/crouch speeds, so feet don't slide". The parameter is the measured ground speed.
  • crouch: Crouch_Idle (0) and Crouch_Fwd (1.4 m/s).
  • jump, fall, land: clip states.
  • vault, climb_up, hang, climb_over: stand-in poses from the standard set, picked by name with fallbacks (pick({"Vault", "Jump_Loop"})), because volume 1 has no vault, climb or hang clips.
  • vault_clip, climb_low, climb_high: the real UAL2 clips when present. animate calls setProgress(Locomotion::traversalProgress()) so the clip is posed by how far through the move the capsule is: the hands meet the edge whatever the timing.
  • wall_run_l, wall_run_r: UAL2 loops, or the sprint.
  • The wall climb (a Leap with wallClimbing()) plays the first 30% of climb_high by progress: the spring and the reach. The hang vault (a Vault with hangVaulting()) plays climb_high's pull-up, then the second half of vault_clip.

animate(Animator&, MotionInfo&, dt) is a switch on Locomotion::State that picks the state and cross-fade time. A climb up a wall under 1.6 m (kClimbHighFrom) uses the 1 m clip, higher uses the 2 m clip. A landing from more than 0.6 m plays land. MotionInfo is a small struct so the same function drives our player (from Locomotion) and remote players (from network state).

Other characters. The Character panel (or KKE_CHARACTER) lists every skinned SK_Character* in the Synty catalog. useCharacter loads it, matches its bones to the mannequin's by name (kke::matchBones), and retargets the UAL clips onto it (kke::retargetAnimations); the log says how many bones matched. m_modelYaw turns the model so it faces -Z (Synty characters face +Z).

IK and footsteps

applyIk runs after the animator, on the pose, before it is written to the model's bone locals:

  • Feet. A kke::FootPlacer casts a ray down from each foot (Jolt, 1.2 m, only surfaces with normal.y above 0.5) and plants the foot on stairs and slopes, lowering the hips if needed. Its weight eases to 1 on the ground and to 0 in the air.
  • Footsteps. kke::CharacterFootsteps plays step sounds when the placed feet actually touch down (AUDIO.md).
  • Hands. While hanging, at the end of a leap, and in the first part of a climb (progress under 0.7) or vault (under 0.45), kke::solveTwoBone puts each hand on the edge, shoulder width apart, with the elbow pole pushed out and down. The grip point is Locomotion::hangEdge() or the last obstacle's top.
  • Shimmy. Hanging and moving along the edge, the hands go hand over hand: each is planted while the body moves past it (60% of a 0.36 m stride), then lifted 7 cm and moved on, the leading hand first. The braced feet step along the wall in between (m_shimmyDist).
  • Wall climb steps. Running up a wall, the feet plant two steps on it, left then right, each held where it first touched while the body rises.

Both can be switched off in the Character panel.

Shooting and pushing

shoot spawns a FEMFX iron sphere (radius 0.15 m, density 7800, 22 m/s) 1 m in front of the camera, sends a shot event online, then calls forcePush. Without FEMFX spawnBall does nothing, so shooting only pushes. forcePush casts a 30 m Jolt ray from the camera and, if it hits a crate, applies an impulse of 60 along the view at the hit point. Fire repeats every 0.25 s while held. The click that captures the mouse is swallowed (m_swallowFire) so it is not a shot.

The breaking yard (FEMFX builds)

spawnBreakables builds three FEMFX objects with spawnPatternedBox:

Piece Size (m) Material Fracture pattern
Glass pane 2.1 x 0.05 x 1.2 glass: density 2500, stiffness 7e7, breaks at 1e5 Radial, from the centre
Plank 2.4 x 0.12 x 0.3 wood: 600, 1e7, 1.5e5 Splinters
Stone wall 1.4 x 1.0 x 0.25 stone: 2500, 3e7, 1e5 Voronoi

The supports under the pane and plank are unbreakable FEMFX blocks (fractureStressThreshold = 1e12) and also Jolt static boxes so the character collides with them. The bridge is told to ignore those Jolt boxes: "FEMFX mustn't see a second one in the same place".

PhysicsBridgeModule mirrors Jolt bodies near moving FEMFX pieces into FEMFX and feeds the pushes back into Jolt, so falling shards knock the three crates under the glass (PHYSICS_BRIDGE.md). KKE_DEMO_BRIDGE=1 (updateBridgeDemo) measures it: at 3 s it drops a ball on the glass, at 7 s logs how far each crate moved and rolls a ball into the pyramid's first crate, at 10 s logs how far that crate went.

The pool

buildPool (Stations.cpp) adds four 1 m stone walls round an 8 x 8 m basin and a tile floor. The water is a kke::OceanRenderer grid (40 cells, 20 cm each) drawing m_poolWaves, a kke::OceanWaves with a light breeze (setWind(1.5f, 0.6f, 0.3f)) and sea level 0.75 m.

floatBodies runs every fixed step. It asks Jolt for the bodies in the basin (bodiesInBox) and, for each dynamic one inside it, calls kke::boxBuoyancy with a WaterSurface callback that reads the same waves the renderer draws. The result is a list of forces at sample points, applied as impulses (force * dt). "So what bobs matches what you see."

The floaters differ only in density: pine 450 rides high, oak 750 sits low, a 300 raft stays flat, a 500 plank, and steel 7800 sinks.

The lava station

buildLava adds a low basalt basin (0.45 m walls, 2.8 m inside) and a spout arm, then creates a LavaStation (LavaStation.h, LavaStation.cpp). It has three parts:

  • The lava: a kke::ParticleFluid of at most 700 particles, radius 4 cm, poured at 50 a second from 1.5 m up at 1200 C. Two substeps per fixed step "keep it from tunnelling" at the ~6 m/s the drop reaches. Below 550 C it turns to dark crust (solidifyTemperature).
  • The block: a kke::MeltVolume, a 0.5 m cube of 2.5 cm voxels (24 x 24 x 24). Heat from the lava melts voxels into liquid particles of the block's own material. Three presets from melt_demo cycle: ice, wax, aluminium (which glows as it heats).
  • Colliders: two callbacks on the fluid. One is the block's signed distance. The other finds the nearest dynamic Jolt box (signed distance to an oriented box, boxDistance) or character capsule in the basin, so crates and the player push the lava aside.

When the block is under 3 % or the particle budget is full, it waits 4 s (kNextAfter) and starts the next block (next). The Lava panel has a "Next block" button.

Drawing: particles are coloured by material and temperature in prepass. With one view, kke::FluidSurfaceRenderer draws a smooth screen-space surface. In split screen it draws SphereImpostorRenderer spheres instead, because the smooth surface is "made for the one main view".

Two cost rules. The station only simulates while a camera is within 18 m (lavaWatched, checks every split-screen view): "a station nobody looks at costs nothing". And the lava runs locally on every machine, not over the network: "what it looks like isn't gameplay state".

Course art and scenes

dressCourse loads course_art.scene.json (copied to bin/course/) on the course itself at origin 0. It is a normal kke.scene file (SceneFile.h): 50 entries (8 of them grids) placing 37 POLYGON Nature and POLYGON Town assets, 108 instances in all. Collision per object: 26 "box", 2 "mesh" (the rock piles), 22 "none" (mountains, trees beyond the walls, grass). It stays out of the paths the stations need.

It is all or nothing: before loading, every object is looked up in the asset catalog, and if any is missing the log says which pack is needed and nothing is placed. The comment: "half the art would look like a bug".

findScenes lists scenes/*.scene.json (found by walking up from the working folder, or KKE_SCENES_DIR). The Scenes panel's Go button calls visitScene, which loads the scene the first time at its own origin 200 m along X per scene, adds a flat ground box if the file has one, teleports you to its spawn, and copies its sun, ambient and up to two point lights into the lighting. Missing assets are counted in the status line. "Back to the course" returns you.

HUD and pause menu

Hud.cpp uses RmlUi through UiModule. buildHud creates one data model, "demo", and binds HudState fields to it (move, speed, trick, station, station_text, menu_hint, station_live, panels, online, players). The document, ui/showcase_hud.rml, shares theme.rcss from games/rmlui_demo/ui/.

updateHud runs every frame. It turns the Locomotion state and speed into a word (Standing, Walking over 0.2 m/s, Running over 2.2, Sprinting over 4.8, Vaulting, Hanging or Shimmying, and so on), then checks which station box your feet are in and picks its title and hint. The lava adds a live line ("Wax block, 40% left"). A small set lambda only marks a variable dirty when its text changed, so RmlUi does not re-lay-out every frame. menu_hint is the corner's menu prompt, promptText("{shell.pause} menu"): the Esc keycap on the keyboard, the Start glyph on a controller.

Hints are written with action names in braces, such as "Push them {interact} or shoot them over {fire}.", and passed through InputModule::promptText, which swaps each {action} for the button glyph of the device in use (INPUT.md).

The pause menu is kke::GameShellModule's (GAME_SHELL.md). buildPauseRows adds the showcase's rows to it: Back to the start, the number of players on this screen, and the engine panels in developer builds. The shell pauses the Application only when not online, opens on Esc, Start or View from any local player, and Esc is a raw key there too, "so you can never lock yourself out".

Split screen

SplitScreen.cpp. setLocalPlayers(count) adds or removes LocalPlayers: each has its own Jolt character, Locomotion, CameraRig, Camera, model instance and Animator. It calls InputModule::setPlayers, which copies player 1's actions and bindings into each new player's map.

assignControllers runs every frame: connected gamepads, in the order they were plugged in, go to players 2, 3, 4. Player 1 gets every other device (keyboard, mouse, and any spare controller). A player without a controller runs the parkour lane by itself, starting 2.5 s after the one before, so split screen can be tried with no controllers at all.

updateLocalPlayers steps each player's Locomotion and camera, animates them with the same animate function (no IK), then fills Application::views(): kke::splitScreen(players, sideBySide) gives the rectangles, player 1 uses the engine camera. With m_overhead a camera 14 m above player 1 is added as a picture-in-picture (kke::pictureInPicture). Up to kke::kMaxViews (4) views are drawn; each module's render runs once per view.

Players 2 to 4 also shoot, push (along their own camera: shoot(p.camera), forcePush(p.camera)), zoom, reset themselves (X) and open or close the pause menu (the game shell reads every player's map). The Lua toys and the ping still read player 1's map only.

Networking (NET builds)

NetModule (NETWORKING.md) does the transport, host or join, LAN list and input replay. The showcase decides what is shared:

  • Bodies. replicateBodies registers the platform as network id 0 and the crates as 1, 2, 3... "The same bodies in the same order on every machine." The host simulates them; clients follow. That is why a client's fixedUpdate returns before moving the platform or floating the pool bodies.
  • Breakables. The three yard pieces are registered with replicateBreakable, so when the host's glass breaks, clients break along the same borders.
  • Your player. sendNetState fills a NetPlayerState (position, velocity, yaw, Locomotion state, speed, progress, an aux value that means obstacle height, wall side or fall height depending on the state, and a crouch flag). A jump of more than 3 m sets kPlayerTeleported. With input replay (stepNetPlayer), the host moves your character from your inputs and onCorrection teleports you if you drift.
  • Other players. updateAvatars spawns a model and animator per remote player and feeds animate a MotionInfo rebuilt from their state (a jump is a Ground to Air change going up faster than 1 m/s).
  • Events. Shots, pushes and resets are game events serialised with serialize template functions over kke::net::WriteStream / ReadStream (one function per message for both directions). The host relays shots to everyone. A client's push is applied locally and sent to the host, which ignores it if the crate is more than 2 m from the pushed point ("a stale or made-up push does nothing"). A client's reset asks the host. The event structs live in NetEvents.h.
  • Spawned things (Online.cpp). A guest's spawn menu sends the row and the spot to the host (SpawnRowEvent); the host checks the spot is within 8 m of that player, builds it with spawnRowAt and shares each prop with NetModule::spawn (kind 0x5301, a PropDesc with the shape, size, colour, material and rotation). Every machine's spawn listener builds the same prop and bindSpawnedBody ties it to the host's body, which then replicates like the crates. despawn removes it everywhere when it's cleared or the oldest goes (the 60-prop cap).
  • Carrying. A guest's pick up sends CarryEvent (net id, yaw); the host checks it is within 3 m and adds a RemoteCarry, then stepRemoteCarries steers that body to holdPointFor the guest's replicated position and facing, the same maths as your own carry (steerHeld). Put down and throw send their velocity. The guest's copy only lets go past 4 m, lifting too (the host's own limit is 2.5 m), since the copy arrives a little late.

The stress test

StressTest.cpp. The Performance panel button, or KKE_STRESS_TEST=1 (which quits at the end). The same script on every machine "so reports compare":

Time (s) Phase What runs
0 to 2 warm-up not recorded (teleport, first pipelines)
2 to 12 walk autopilot on the parkour lane: animation, IK, shadows
12 to 24 crates 300 crates rain at 30 a second over (0, -2), watched from 9 m
24 to 36 impacts 4 shots a second: FEMFX iron balls at the glass, plank and wall, or without FEMFX, Jolt iron blocks into the crate area

startStressTest saves the resource budget and vsync, then runs uncapped ("this measures the machine, not the player's cap"). A fixed random seed makes the rain the same every run. stressShoot computes a ballistic velocity that lands on the target at 18 m/s horizontal. Every frame a kke::FrameStats::Frame records frame, GPU and physics time and body count. finishStressTest removes the rain, restores the settings, and writes a kke::BenchmarkReport (stress_<time>_<host>.txt and .json) with the system, the settings, the list of running modules and the peak memory. See BENCHMARKS.md.

The Lua scripts

ScriptModule runs every .lua in bin/scripts/ and reloads a file when it changes. CMake copies two sets there:

  • scripts/toys.lua: a small "Garry's Mod-style" script. It defines three actions with input.define, and in a Think hook builds a 12-crate metal tower in front of the camera (physics.box), throws a rubber ball (physics.sphere) or removes what it made. A Contact hook counts hits faster than 6 m/s. Save the file while the game runs and the old toys are replaced, not duplicated.
  • targets.lua and targets_hud.rml from games/first_lua_game/scripts/: break-the-targets, a whole small game in one Lua file (see that game's README).

The copy in bin/scripts/ is what runs. Set KKE_SCRIPTS_DIR to the source folder to edit live. API: SCRIPTING.md.

Design decisions

  • The course is boxes made in code. It needs no asset pack, so it runs in CI and on a fresh clone. Synty art is an optional layer on top (course_art.scene.json), loaded only when every asset is present.
  • Station positions live in one header. The commit that added Layout.h moved them there "so the level, the stress test and the HUD agree".
  • Nothing on the parkour lane is marked up. kke::Locomotion reads the shapes with its sensors. A game built on this works on any level geometry, including Synty scenes (the autopilot runs their trails), without tagging every ledge.
  • Blend-space speeds equal Locomotion's speeds. Idle/walk/jog/sprint sit at 0, 1.6, 3.6 and 6.2 m/s, the clips' foot speeds and Locomotion's defaults, and the blend parameter is the measured speed, so feet do not slide, even in turns.
  • Traversal clips are posed by progress with the lift removed. The comments say it: "Locomotion moves the capsule either way, the clips only provide the pose", and posing by progress means "hands meet the edge whatever the timing".
  • Stand-in poses plus hand IK when clips are missing. Volume 1 has no vault or climb clips, so the closest poses are used and IK puts the hands on the edge. Real clips are picked up by name when a pack has them. The demo never requires the optional UAL2 file.
  • One animator setup function for every character. addAnimatorStates and animate(MotionInfo) serve our player, split-screen players and network players, so all three look the same and the state indices match.
  • All crates in one batched mesh. One draw and one shadow draw instead of two per crate. The comment on batchCrates: "~0.1 ms for 300 crates on one core; far cheaper than 600 draws".
  • The lava costs nothing when nobody looks. It simulates only within 18 m of a camera, uses 700 particles ("a quarter of melt_demo's: one station of many", citing OPTIMIZATION.md rule 5) and coarser 4 cm particles.
  • Lava is local, crates are the host's. The lava's look is not gameplay state, so each machine runs its own. Crates, the platform and breakables are shared because players push and break them.
  • The pool's physics reads the same waves it draws. Buoyancy samples OceanWaves::height and velocity at the same time value as the renderer, so floating props match the surface.
  • Course art is all or nothing. "Half the art would look like a bug."
  • Esc is fixed, not rebindable. It always frees the mouse and opens the menu, "so you can never lock yourself out" by rebinding.
  • The menu only pauses offline. The game shell's rule for every game: online the others play on; only an offline game stops.
  • Split screen works without controllers. Extra players with no pad run the lane on their own "so split screen can be tried alone".
  • The stress test is scripted and uncapped. A fixed script and seed so reports compare between machines, and no vsync or frame cap so the numbers are the machine's, not the cap's. Settings are restored after.

Tuning

What Where Effect
kWalkSpeed, kJogSpeed, kSprintSpeed, kCrouchSpeed (1.6, 3.6, 6.2, 1.4) ShowcaseModule.cpp top Blend points; change with Locomotion::Settings speeds or feet slide
kClimbHighFrom (1.6 m) ShowcaseModule.cpp Walls at least this high use the 2 m climb clip
Turn rate, sprint turn rate, speed in turns, air steering, vault clearance, climb time Movement panel (Locomotion::Settings) Live feel of the character
m_mouseSensitivity (0.12 deg/px), m_stickSpeed (200 deg/s) ShowcaseModule.h, Controls panel Look speed
Arm length (1.5 to 10), shoulder (0.45), lag, field of view Camera panel (CameraRig::settings) Third-person camera
Capsule heights 1.8 / 1.0 m, pivot 1.5 / 0.85 m ShowcaseModule::update Standing and crouched size and camera height
Fire cooldown 0.25 s, push impulse 60, ray 30 m readActions, forcePush Shooting and pushing
Ball: radius 0.15, density 7800, speed 22 spawnBall How hard shots hit the breakables
Breakable materials and fractureStressThreshold spawnBreakables Lower breaks easier
kPoolHalf 4 x 4, kPoolWall 1, kPoolWater 0.75, wind Stations.cpp Pool size, depth, wave size
Floater densities spawnPoolFloaters Under 1000 floats; lower rides higher
kLavaWatchDistance (18 m) Stations.cpp How close a camera must be for the lava to run
kMaxParticles 700, kRadius 0.04, kPourRate 50, kNextAfter 4 LavaStation.cpp Lava amount, detail and cost
Melt presets (melting point, heat capacity, melt rate, conduction) kPresets in LavaStation.cpp How fast each block melts
kLaneGap 2.5 s, kLaneMaxTime 25 s SplitScreen.cpp Controller-less runners
kPhaseEnd, kRainCrates 300, kRainPerSecond 30, kShotsPerSecond 4 StressTest.cpp Stress test load (changing them makes old reports incomparable)
Sun direction, height, strength, colour, ambient Lighting panel Light 0 each frame
Performance: use everything, background cap, render scale, worker threads Performance panel (SettingsModule) Saved to settings.json

Engine features it uses

Feature Header / module Doc
Module system, views, pause kke/Application.h, kke/Module.h HISTORY.md
Jolt bodies, character controller, ray casts kke/RigidWorld.h, RigidBodyModule
Character movement and parkour kke/Locomotion.h MOVEMENT.md
Camera spring arm kke/CameraRig.h
Animation blending, retargeting, IK kke/Animator.h, kke/AnimRig.h, ModelModule cookbook/animation.md
Footstep sounds kke/Footsteps.h, AudioModule AUDIO.md
Input actions, controllers, prompts kke/InputMap.h, InputModule INPUT.md
RmlUi HUD and menu UiModule
FEMFX breakables PhysicsModule, kke/FracturePattern.h
FEMFX-Jolt bridge PhysicsBridgeModule, kke/PhysicsBridge.h PHYSICS_BRIDGE.md
Buoyancy and waves kke/Buoyancy.h, kke/Ocean.h, kke/OceanRenderer.h
Particle liquid and melting kke/ParticleFluid.h, kke/MeltVolume.h, kke/FluidSurface.h, kke/SphereImpostors.h
Split screen and picture-in-picture kke/Viewports.h
Networking, input replay, voice NetModule, VoiceModule, kke/net/ NETWORKING.md
Scene files and the asset catalog kke/SceneFile.h, kke/SceneLoader.h, kke/AssetCatalog.h SCENES.md
Frame stats and reports kke/FrameStats.h, kke/BenchmarkReport.h BENCHMARKS.md
Resource budget kke/ResourceGovernor.h, SettingsModule
Lua scripting ScriptModule SCRIPTING.md
Moods (sky, fog, ambience) Application::setMood MOODS.md

Assets

The course itself needs no asset pack. What it loads:

  • Character and animations (optional, the character is a box without them): Quaternius' Universal Animation Library, CC0. assets/animations/UAL1_Standard.fbx is in the repository. assets/animations/UAL2.fbx (volume 2) is not; put it next to the first for the real vault, climb and wall-run clips. Without it the log says "vault and climb use stand-in poses".
  • Synty course art (optional): POLYGON Nature and POLYGON Town, in the git-ignored assets/synty/. The full list of 37 assets and counts is in SCENES.md. If any one is missing the course stays plain and the log names the missing asset and packs.
  • Synty scenes (optional, Scenes panel): scenes/farm.scene.json (POLYGON Farm), scenes/forest_trail.scene.json (POLYGON Nature), scenes/town_block.scene.json (POLYGON Town). Missing assets are skipped and counted in the status line.
  • Synty characters (optional): any skinned SK_Character* from an installed pack, from the Character panel.
  • Fonts: Noto Sans Regular, Bold, Italic and Noto Color Emoji (SIL Open Font License 1.1, assets/fonts/).
  • UI theme: games/rmlui_demo/ui/theme.rcss.
  • Sky and ambience: the clear_day mood (MOODS.md).
  • Licences for everything: DEPENDENCIES.md.

Synty packs are never committed. Get them with tools/fetch_assets.sh (see SCENES.md).

Make a game like this

  1. Script game or C++ game? For a Lua game run tools/new_game my_game: it copies games/template/ (with its own PlayerModule), names it and adds it to the build. Read skills/make-a-game first. For a C++ game like this one, copy games/showcase/ to games/my_game/, rename the executable in CMakeLists.txt and the namespace, and add add_subdirectory(games/my_game) to the root CMakeLists.txt.
  2. Keep main.cpp's module order. Settings first, input, physics, net before your module, then models and UI. Drop what you do not need (the stress test, split screen, the pool) by deleting the file and its calls.
  3. Put your level's positions in a Layout.h before you build anything, so the level, HUD and any scripted tests read one place.
  4. Build the level. Start with addStaticBox for blockout, then move to a scene file (SCENES.md) and kke::loadScene when you have art, like dressCourse and visitScene do.
  5. Keep the player chain: input map to Locomotion::Input (camera relative), Locomotion::update, animate, applyIk, CameraRig. Tune movement in the Movement panel, then copy the numbers into Locomotion::Settings.
  6. Match the blend-space speeds to Locomotion's speeds if you change either, or the feet slide.
  7. Write your HUD in RmlUi with one data model, and write hints with {action} names through promptText so the right buttons show.
  8. For multiplayer, decide what is shared. Register shared bodies in the same order everywhere (replicateBody), send one-off actions as events, and let the host check them. Keep effects that are only looks local.
  9. Give every cost a switch. Simulate stations only when watched, batch many small objects into one mesh.
  10. Check it: KKE_SKIP_INTRO=1 ./my_game, then read the log for warnings. For a Lua game, tools/check_game. Read MOVEMENT.md, NETWORKING.md and INPUT.md next.

Pitfalls the code shows:

  • Add NetModule before your module, or a join is not under way when you spawn shared bodies.
  • A client must not simulate shared bodies (fixedUpdate returns early for clients).
  • A static Jolt body and a FEMFX object in the same place need PhysicsBridgeModule::ignore, or FEMFX sees the object twice.
  • The pause menu must be driven from frameStart: update does not run while paused.
  • Screen-space effects (the fluid surface) are drawn for one view; give split screen a per-view fallback.
  • The mouse-capture click must not also count as a shot.
  • defineCharacterActions binds keys you may use for something else (Q and the d-pad down ping, B talks). clearBindings the ones that clash, then bind them where they fit, before commitDefaults.

Files

File What is in it
main.cpp The Application, mood, and the modules in order
ShowcaseModule.h The one module class: every method and all state, grouped by topic
ShowcaseModule.cpp Input actions, the level, crates, breakables, player, animation, IK, shooting, networking, rendering, the ImGui window
Stations.cpp The pool (walls, waves, floaters, buoyancy), the lava basin, lavaWatched, dressCourse
LavaStation.h, LavaStation.cpp The lava: fluid, melting block, colliders, presets, drawing
SplitScreen.cpp Local players 2-4, controller assignment, views, the overhead view
StressTest.cpp The 36 s stress test and its report
Guns.cpp Guns, grenades, explosions: firing, hits, synthesized sounds, effects, aiming arms
Online.cpp, NetEvents.h Spawned props and carrying online: the host makes what guests spawn, carries what they lift; the game's event messages; the KKE_DEMO_ONLINE run
Parkour.cpp The parkour park's seven sections, the HUD's name for each, the KKE_DEMO_PARKOUR run
Nature.cpp The nature park (trees, undergrowth, grass, wind), chopping trees and splitting logs, picking flowers, the snow field and its footprints, the KKE_DEMO_NATURE run
Vehicles.cpp The race track (oval, kerbs, cones, jump), three cars, the plane and its crash, getting in and out, the chase camera, lap times, the engine sound, the KKE_DEMO_DRIVE run
Range.cpp The firing range: bench, plates, barrels, crates, dummies, FEMFX glass and walls, the KKE_DEMO_GUNS run
Hud.cpp RmlUi data model, HUD updates, the pause menu's rows
Layout.h Station positions
course_art.scene.json Synty art placed around the stations
ui/showcase_hud.rml The HUD: movement state, station hint
scripts/toys.lua Lua toys: tower, ball, clear, hit counter
game.json Manifest (copied to marketplace/showcase/)
CMakeLists.txt The executable, shaders, fonts, UI, scripts and course art it copies