XML-defined actors (marine, demons, projectiles, pickups, VFX) containing these attributes: collision shape, physics constants, AI sight radius/angle, camera eye height/FOV, weapon inventory, 8-directional sprite sets, sound bindings, and optional light emission. Every actor is the same Actor class pointing to a shared ActorDefinition looked up by name at spawn, and its behavior comes from definition flags.Â
Actors are referenced by generational handle (ActorHandle, a packed uid+index unsigned int) instead of raw pointers, so stale references to despawned actors fail safely.
AI and player share one Controller base class (AIController/PlayerController). A dev cheat (N) hot-swaps which actor a controller possesses, including taking over an enemy mid-game.Â
Data-driven weapons with attributes defining the weapon's refire time, damage range, impulse, projectile speed/cone, melee arc/range. One Weapon class branches on definition name instead of using per-weapon subclasses.Â
Maps are built from image files. Walls, floors, and ceilings are real extruded 3D geometry rather than raycasted pseudo-3D. Billboarded sprite actors live inside that geometry.
The day/night cycle drives gameplay as well as visuals: normalized time of day moves the sun's pitch/yaw, blends the sky across eight keyframed colors, and curves ambient light intensity. The same clock gates capture objectives, which are only capturable from 22:00 to 06:00 in-game time.
The objective mode has four corner rooms plus a center courtyard, each with independent capture percentage that fills while a player is inside and drains when they leave. Capture all four corners, then the courtyard, to win. Nightly demon respawns undo uncleared territory.
'ActorDefinition' is a flat bag of every property an actor might need: collision, physics, camera, AI, weapon inventory, visuals, and audio; each populated from its own XML sub-element:
Each 'AnimationGroup' (Walk/Attack/Hurt/Death) maps eight discrete facing vectors to frame ranges in a sprite sheet, and AnimationGroup::GetAnimationDefinitionBasedOnViewingDirection() picks the closest of the eight at render time from the actor's velocity/facing relative to the camera — the classic 8-direction billboard sprite technique, driven entirely by data rather than being wired up per monster in code.
Demon sprite sheet with first eight rows showing each 'pose' from a different direction
'WeaponDefinition' covers all three attack types with the same XML schema, switched by which attributes are present:
Weapon::Fire() branches on the weapon's name to call FirePistol() / FirePlasma() / melee logic, but the actual hitscan path is generic: it raycasts from the eye position along a randomized direction inside the weapon's cone against both world geometry and actors in one combined raycast, applies impulse and randomized damage to whatever it hit, and spawns a BloodSplatter or BulletHit actor at the impact point which itself just another XML-defined actor.
The level is drawn as a small PNG, where every pixel is one 1×1 tile of the world. At load time, each pixel's color is matched against tile types defined in XML. The engine then builds the walls, floors, lights and collision for the level from those tiles. Changing the layout only means repainting pixels, and adding a new kind of tile only means adding one line of XML. No code changes are needed for either.
Each tile type has an RGB key, says whether it's solid, sets its height, and picks the sprite-sheet cells for its walls or floor. Flags such as isGoal mark tiles the game treats specially.
The image loads flipped vertically, so pixel (0, 0) is the world origin and the level reads like a top-down view of the PNG. Pixels with zero alpha are left empty. Some tile types also add lights to the level: goal tiles get a colored spotlight, and brick walls and Light tiles get a point light. That way level designers place lighting by painting it into the image.
Solid tiles become four textured wall faces as tall as their tile height. Open tiles become a floor quad. Every tile goes into one vertex buffer and one index buffer, so the whole level draws in a single call.
Tiles are stored row by row, so the tile at any (x, y) is found directly from its index without searching. Actor collision only checks the eight tiles around an actor, pushing it out of solid ones. Hitscan weapons raycast against the same solid tiles. Actors are placed using the SpawnInfo entries from step 1.
Map image for the map to be generated
Generated map from the image
A 'Timer' tracks elapsed time as a fraction of a full in-game day; the sun's pitch and yaw are derived directly from that fraction rather than being simulated as an orbiting body:
The sky is a linear lerp between two of eight keyframed Rgba8 colors spanning midnight to midnight. Map::Render() scales the same timer fraction by the keyframe count, picks the bracketing pair, and gets the frame's clear color using the timer fraction as the lerp factor, which becomes the background via the renderer's clear screen function.