|
SleakEngine 1.0.0
C++23 multi-backend game engine
|
Your First Scene showed the pieces in isolation. This tutorial wires them into a loop you can lose and win: walk a first-person body around a floor, collect five cubes, watch a counter on the HUD, and switch to a results scene when the last one is gone.
Everything here builds on the project layout from Getting Started: a Game class deriving from Sleak::GameBase, scenes deriving from Sleak::Scene, and a Client executable that copies assets next to the binary.
| Piece | Engine type |
|---|---|
| Ground and pickups | Sleak::GameObject primitives plus Sleak::ColliderComponent |
| Player body | Sleak::Camera with Sleak::FirstPersonController and a rigidbody |
| Lighting | Sleak::DirectionalLight and the scene's Sleak::LightManager |
| Input | Sleak::EventDispatcher handlers for key press and release |
| Pickup detection | Sleak::Physics::PhysicsWorld::OverlapSphere |
| HUD | Sleak::UI immediate-mode calls from Update |
| Screen change | Sleak::GameBase scene swap after Application::WaitGPUIdle |
The scene holds three kinds of state: the objects it needs to find again later, the handler ids it must unregister, and the gameplay counters.
Two layer bits keep the pickup query from picking up the floor:
ColliderComponent starts at layer 0xFFFFFFFF, which matches every mask. Assign explicit bits to anything you intend to query for, or every query returns everything.
Static level geometry is a primitive with an AABB collider and a Static rigidbody. The collider shape is in local space, so scale the transform and size the shape to match.
The camera carries the controller, the collider, and the rigidbody. Attach the components first, then AddObject, because that call is what registers the collider with the scene's Sleak::Physics::PhysicsWorld.
One key light with shadows and a small ambient term is enough to read the scene. Lights are GameObjects, so they go in through AddObject and the scene registers them with the Sleak::LightManager for you.
Pickups are triggers: they belong to the broadphase so queries find them, but they generate no physics response, so the player walks straight through one instead of bouncing off it.
Sleak::GameObject::CreateCube, CreatePlane, CreateSphere, CreateCapsule, CreateCylinder, and CreateTorus build the mesh and transform for you, which is what you want while the level is still made of boxes. Swap in Sleak::ModelLoader::Load when real art arrives.
Key state arrives as events, not as a queryable snapshot, so a held key is something you track yourself: set a flag on KeyPressedEvent, clear it on KeyReleasedEvent, and read the flag during Update.
if_key_down(KEY__LEFT) expands to a test on a local named e, so keep that parameter name. Use if_key_press for edge-triggered actions: it additionally rejects OS auto-repeat, which is what stops a held R from resetting the run sixty times a second.
Apply the flags where the rest of your per-frame work happens:
A Kinematic body moves under your control and pushes dynamic bodies without being pushed back, which is exactly the behavior a platform wants.
The engine resolves collisions inside PhysicsWorld::Step; it does not call back into your scene when two shapes touch. Gameplay reactions are queries you run yourself, and the query API is fast enough to run every frame because it goes through the broadphase tree.
CollisionPair::b is the collider the query found; a stays null for overlap queries, since there is no second collider involved. DestroyObject queues the object and the scene frees it at a safe point later in the frame, which is why it is safe to call while iterating query results. Raycast, SphereSweep, and OverlapAABB take the same optional layerMask and are the right tools for interaction prompts, footstep checks, and blast radii.
Sleak::UI is immediate mode. Call it every frame from your update; there is no widget tree to keep in sync.
Widgets return true on the frame they change, so the standard shape is if (UI::Button("Restart")) Restart();. Never include imgui.h from game code: Dear ImGui is a private dependency of the engine target, and UI/UI.hpp is where a missing wrapper belongs.
Ending the run means handing control back to the game object, because the scene registry lives on Sleak::GameBase, not on any single scene.
WaitGPUIdle before the swap is not optional. The GPU is still reading buffers and images owned by scene objects, and freeing them underneath an in-flight frame crashes the driver. The same rule applies to RemoveScene, which unloads and deletes the scene along with every object it owns.
The scene side just asks the game to do it:
Handlers outlive the scene unless you remove them, and the next dispatched event then calls into freed memory. Unregister in OnDeactivate, the destructor, or both.
Objects need no such care: the scene owns everything passed to AddObject and destroys all of it on unload.