A hand-rolled Vulkan playground in modern C++17 — spawn on a procedurally generated island in an endless ocean, build with a live Bullet physics simulation, and survive as the day/night sky turns.
No engine. No asset pipeline. No textures. Just Vulkan, GLM, a physics engine, and a very countable pile of cubes.
You spawn onto a single seeded island — a coastal peak rising out of a calm, animated sea. The terrain is a true survival-start: mountains, beaches and plateaus all on one landmass, with nothing but open ocean in every other direction. The day/night clock drives the sun, moon, stars and lighting, so the island changes character as hours pass.
Drop cubes at the point you're aiming at, stack and shove them, walk into them (your body is an invisible kinematic capsule) — then pick one up, hold R to spin it around in your hand, and throw it as hard as you like. Every cube is simulated with mass, friction and restitution, so piles tumble, bounce and topple naturally. Carrying hugs the block to walls and floors instead of driving it through them.
| 🖥️ Renderer from scratch | Instance/device/swapchain setup, resize handling, render-views + D32 depth, mailbox present mode (FIFO fallback), two-frames-in-flight ring buffers with persistent staging — no per-frame vkQueueWaitIdle, no CPU-side reallocation |
| 🏝️ Survival island | One seeded island on an endless ocean: a forced origin cell with a random-radius coastal profile and an offset mountain apex, so you never spawn on the mountaintop. Lattice-cell groundwork keeps adding more islands a one-line change away |
| 🌊 Animated ocean | Translucent, fresnel-lit, sun-glinting water plane that streams with the camera and hugs coastlines, exactly at sea level so land never sinks |
| 🗺️ Streaming terrain | The island (visual mesh 160 m / 96×96, collision mesh 160 m / 64×64) and the water re-center around the player as they move, staying perfectly in sync so gameplay matches the view — the endless ocean feels endless without ever loading more |
| 🎲 Deterministic worlds | Seedable noise — --seed N reproduces the exact same world, otherwise every run is unique |
| ☀️ Day/night sky | Real-time clock (240 s day) with a gradient sky, sun disc + halo, moon, twinkling stars, sunset corona, and CPU-driven ambient that the shared lighting model reads |
| 📦 Physics cubes | Up to 32 simulated cubes: place by raycast aim, knock them, stack them, carry them, throw them, or delete them |
| 🎛️ Carry system | Point-to-point constraint to a grab anchor in front of the camera; a forward raycast shortens the hold when a wall or floor is in the way, so the block rests against surfaces instead of jittering, and can be spun in hand (hold R + mouse) |
| ⬜ Block outlines | 8 px-wide glowing outlines on the carried cube and whatever you're aiming at, drawn through depth-biased line primitives with device line-width support |
| 🏋️ Player collider | An invisible kinematic capsule tracks the camera, so you can physically shove the cubes you've placed |
| 🏃 Responsive camera | Asymptotic accel/friction, look inertia, head-tilt on turns, speed-based FOV kick, terrain-following eye height (rides slopes, drops off cliffs), jump and gravity |
| 🪟 Animated menus | Breathing title glow, staggered fade-ins, hover glow/trail on buttons, and a fully styled settings screen — no pixel-font hacks |
| ⚙️ Persistent settings | Mouse sensitivity, FOV and every movement key are rebindable in-game and saved to ~/.config/ragdoll/settings.conf |
| 🧭 Wayland-friendly | Native Wayland app-id through GLFW, first-class Linux citizen |
| Input | Action |
|---|---|
W A S D |
Move |
Mouse |
Look (pointer captured in-game) |
Left click |
Place a cube at the aim point (re-captures the pointer if released) |
Right click |
Remove the cube under the crosshair, or drop + remove the held one |
E |
Pick up the cube you're looking at / drop the one you're holding |
Q |
Throw the cube you're holding |
R |
Rotate the held cube with the mouse (hold; releases R stops the spin but keeps holding) |
Y |
Respawn |
Esc |
Release the pointer (click to re-capture) |
Space |
Jump |
Left Ctrl |
Sprint |
Left Shift |
Sneak |
Movement keys, jump/interact/drop, plus mouse sensitivity and FOV are all rebindable in the Settings menu and persist to disk.
The in-game Settings screen (from the main menu) exposes:
- Video — mouse sensitivity and field of view sliders.
- Controls — rebind any of the nine movement/action keys by clicking the binding and pressing a new key (Esc cancels).
- Reset to Defaults — restores everything.
Values are saved as a plain key = value file to
~/.config/ragdoll/settings.conf (or $XDG_CONFIG_HOME/ragdoll/settings.conf
when set).
sudo dnf install -y gcc-c++ cmake glfw-devel glm-devel vulkan-headers vulkan-loader-devel glslc bullet-develImGui is vendored as a git submodule — don't forget
git submodule update --init --recursive.
git submodule update --init --recursive
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
./build/ragdollglslc compiles the GLSL shaders to SPIR-V during the build, and the .spv
files are copied next to the binary automatically.
Reproduce a specific world round-trip:
./build/ragdoll --seed 1337CMakeLists.txt C++17 build; glslc->SPIR-V; Bullet + ImGui linking
shaders/shader.{vert,frag} Procedural island terrain (layers + distance fog)
shaders/mesh.{vert,frag} Instanced cube pipeline
shaders/outline.{vert,frag} Flat-color line pipeline for block outlines
shaders/sky.{vert,frag} Fullscreen sky triangle: gradient, sun, moon, stars
shaders/water.{vert,frag} Animated translucent sea plane
src/main.cpp GLFW window, input, game loop, menu/settings/HUD UI, item logic
src/Camera.hpp/.cpp FPS movement, look, tilt, FOV kick, jump/gravity, terrain-follow
src/Settings.hpp/.cpp Persistent settings: load/save + human-readable key names
src/Terrain.hpp/.cpp Seedable island elevation + climate sampling shared by render & physics
src/VulkanRenderer.hpp/.cpp Vulkan core: swapchain, pipelines, ring-buffered instancing, sky, water, imgui hooks
src/PhysicsWorld.hpp/.cpp btDiscreteDynamicsWorld wrapper (fixed 1/120 substeps)
third_party/imgui Dear ImGui submodule (1.93.0 WIP)
Feel constants live in src/Camera.hpp; item/player constants in src/main.cpp;
the island terrain sampling model in src/Terrain.cpp (sea level is
kSeaLevel = -2.0 in Terrain.hpp).
ACCEL_RATE/FRICTION_RATE— body weight (acceleration vs. coasting)LOOK_SMOOTH_RATE— how much the view lags behind the mouseTILT_MAX_DEG/TILT_GAIN— head lean into turnsFOV_BOOST/FOV_SPRINT_EXTRA/FOV_SNEAK_REDUCE— FOV kick intensityEYE_HEIGHT/GRAVITY/JUMP_SPEED/STEP_HEIGHT— jump feel, eye clamp, and how steep a slope the camera still rides as "ground"MOVE_SPEED/SPRINT_SPEED/SNEAK_SPEED— top movement speedskItemSize/kItemMass/kPickupRange/kHoldDist/kHoldDistMin/kThrowSpeed/kMaxItems— cube geometry, carry feel, and throw power
The physics clock steps at a fixed 1/120 s with at most four substeps per
frame, so cubes stay stable even under frame hitches.
- Per-object distance + frustum culling tied to the fog distance.
- More item shapes (spheres, planks, cylinders) and a deletion/undo shortcut.
- Screenshot capture and in-game audio.
- Persist placed cubes so a build survives a restart.
- In-game help overlay bound to the rebindable controls.
MIT — go build something weird with it.