A guided tour from "open a window" to "spawn a physics-enabled actor with
a 3D-positioned audio loop". Every snippet is copy-pasteable; later lessons assume the previous ones were followed.
Suggested order
- Compile / Link / Run — get a binary running.
- Subsystem Map — understand the subsystems.
- Walk the four lessons below.
Prerequisites
- A working build (
./compile.sh) — see Compile / Link / Run.
- An
assets/ folder under the repo root (the engine resolves every relative path against this location at runtime).
Lesson 1 — window, main loop, log
The smallest possible game using the engine. Subclass mnd::Application, hand it to the singleton, and call mnd::Engine::Run().
{
public:
{
return true;
}
void Update(
float dt)
override
{
RequestClose();
}
};
{
engine.SetApplication(new HelloGame());
engine.Init(1280, 720);
engine.Run();
engine.Destroy();
return 0;
}
#define LOG_INFO(fmt,...)
Umbrella include for the entire Engine library.
Interface that every runnable game or lab must implement.
virtual bool Init()=0
Called once by Engine::Init() after the GL context is ready.
virtual void Update(float deltaTime)=0
Called every frame by Engine::Run() with the elapsed time.
virtual void RegisterTypes()=0
virtual void Destroy()=0
Called by Engine::Destroy() before the GL context is torn down. Release GPU resources (meshes,...
static Engine & GetInstance()
Returns the single Engine instance (created on first call).
What you get for free:
- A 1280x720 GLFW window with an OpenGL 3.3 core context.
- The Editor overlay (toggle with
F1).
- The mnd::Log macros writing colour-coded output to the terminal.
Lesson 2 — a textured cube on screen
Move past the cleared screen by submitting a draw command per frame.
bool HelloGame::Init() override
{
m_mesh = std::make_unique<mnd::Mesh>();
m_mesh->Upload(mnd::Builder::Cube(1.0F));
m_shader = std::make_shared<mnd::ShaderProgram>();
m_shader->LoadFromFiles("shaders/lit.vert", "shaders/lit.frag");
m_material = std::make_shared<mnd::Material>(m_shader);
m_material->SetTexture("uAlbedo", textures.Load("textures/crate.png"));
return true;
}
void HelloGame::Update(float dt) override
{
.mesh = m_mesh.get(),
.transform = glm::translate(glm::mat4(1.0F), {0.0F, 0.0F, -3.0F}),
});
}
std::shared_ptr< Material > material
RenderQueue & GetRenderQueue()
Returns the per-frame command queue that batches and issues draw calls.
void Submit(const RenderCommand &command)
Add a draw command to the queue for this frame.
Asset paths are interpreted by mnd::FileSystem against the ASSETS_DIR that CMake bakes in at configure time, so "textures/crate.png" resolves to assets/textures/crate.png inside the repo.
Lesson 3 — Scene, GameObjects, Components
Doing everything from Update() works for one mesh; for a real world you want a tree of objects with attached behaviours. That's mnd::Scene + mnd::GameObject + mnd::Component.
bool HelloGame::Init() override
{
auto *cam = scene.CreateGameObject("MainCamera");
cam->SetPosition({0.0F, 1.6F, 4.0F});
auto *light = scene.CreateGameObject("Sun");
light->SetPosition({2.0F, 4.0F, 2.0F});
auto *cube = scene.CreateGameObject("Crate");
cube->SetPosition({0.0F, 0.5F, 0.0F});
mc->
SetMesh(mnd::Builder::Cube(1.0F));
mc->SetMaterial(m_material);
return true;
}
Marks its GameObject as the 3D audio listener.
Perspective camera — provides view and projection matrices each frame.
Scene * GetScene()
Returns the currently active scene, or nullptr if none is set.
Point light that contributes position + colour to the render pass.
void SetColor(const vec3 &color)
Set the light's emission colour.
Makes a GameObject renderable by submitting its mesh each frame.
void SetMesh(const std::shared_ptr< Mesh > &mesh)
Update() no longer touches the render queue — mnd::MeshComponent does it for you. To make the cube spin, write your own component:
{
public:
void Update(
float dt)
override
{
t = glm::rotate(t, dt, glm::vec3(0.0F, 1.0F, 0.0F));
}
};
void HelloGame::RegisterTypes() override
{
mnd::Component::Register<SpinComponent>();
}
cube->AddComponent<SpinComponent>();
virtual void Update(f32 deltaTime)=0
void SetRotation(const quat &rot)
const quat & GetRotation() const
#define COMPONENT(ComponentClass)
The COMPONENT(...) macro generates the type-id glue mnd::Scene needs to deserialize and inspect components in the Editor.
Lesson 4 — physics, audio, input
Round things off with rigid bodies, sound, and a player controller.
auto *crate = scene.CreateGameObject("Crate");
crate->SetPosition({0.0F, 5.0F, 0.0F});
body->SetCollider(mnd::Collider::Box({0.5F, 0.5F, 0.5F}));
body->SetMass(2.0F);
audio->Play("hum", true);
Plays one or more named audio clips, attached to a GameObject.
void RegisterAudio(const std::string &name, std::shared_ptr< Audio > &clip)
Add a clip under name; later calls reference it by that key.
static std::shared_ptr< Audio > Load(const std::string &path)
Decode an audio file from disk and return a shared instance.
Component bridge between a GameObject and the physics world.
The mnd::PhysicsComponent attaches a Bullet rigid body to the GameObject and keeps the transform synchronised every frame. mnd::AudioComponent forwards the GameObject's world position to every clip, so the hum pans correctly as you walk around. mnd::PlayerControllerComponent reads mnd::InputManager and produces walk + look behaviour with mouse-look clamped to ±89°.
Where to next
- Skim the topic tree (the Topics menu in the navbar) to see every group at a glance.
- Open
engine/source/Monad.h — the umbrella header lists every public type with a one-line description.
- The
assets/ directory in the repo has reference shaders, textures, and a few .scene.json files you can load via mnd::Scene::LoadFromFile().