Project Postmortem

M3D

A solo-built 3D engine runtime with rendering, physics, scripting, scene loading, input, and directional audio.

Solo Developer C++ OpenGL SDL3 Jolt Physics Wren

Overview

M3D is a small 3D game engine I built in C++ as my capstone project for my bachelor's program. I made it to push myself further into engine architecture and to create a tool I could keep shaping around the kinds of games I want to make. It is not trying to replace Unity, Unreal, Godot, or any other commercial engine. The point was to understand the internal systems better by building as much of it as I reasonably could myself.

This ended up being the biggest and most complicated project I have ever completed. In practical terms, I built the foundation of a usable 3D engine from scratch: it can load scenes, render 3D models, simulate physics, play directional audio, process keyboard and mouse input, and run gameplay scripts.

Because this was a capstone project, M3D had to reach a presentable, working state by the end of the semester. That deadline forced me to prioritize the engine runtime over the editor. I still wanted the editor, and it is still part of the long-term plan, but a stable runtime mattered more for the final presentation. Cutting the editor was disappointing, but it let me ship a working engine on time instead of spreading the project too thin.

Engine Systems

The core of M3D is its entity component system. Every game object is represented by a GUID, and each entity stores its name, local transform, parent and child relationships, and attached components. Components are how the rest of the engine talks to game objects, which kept the different systems from becoming too tightly tangled together.

The scene and prefab system loads objects from JSON. A scene is a hierarchy of entities that gets loaded when the engine starts, while a prefab is a saved entity subtree that can be spawned during runtime. Both use the same basic format, so objects spawned from scripts behave the same way as objects that were already in the scene. It is not as nice as a visual editor, but it gave M3D a simple and consistent content pipeline.

The rendering system is a small forward renderer built with OpenGL 4.6 through GLAD. Mesh renderer components hold model references, and the renderer handles the lower-level work of setting camera matrices, submitting model transforms, caching loaded models, and drawing everything in the render queue. Models are loaded with ASSIMP and cached by file path, which kept repeated model usage from wasting memory or load time.

Physics is built on top of Jolt Physics. M3D supports rigidbodies, box colliders, sphere colliders, capsule colliders, compound collider shapes, collision layers, and trigger / collision events. The engine keeps a GUID mapping between physics bodies and ECS entities, so Jolt can do the simulation while the rest of the engine still works in terms of game objects. After each fixed physics step, dynamic object transforms are written back into the ECS.

The scripting system embeds Wren and was one of the most important parts of making M3D feel like an actual engine instead of just a tech demo. Scripts can implement lifecycle callbacks like on_start , on_update , and on_destroy , as well as collision and trigger callbacks. I exposed enough of the engine API for scripts to query input, move objects, spawn prefabs, destroy entities, attach components, apply physics forces, and play audio without recompiling the C++ code.

I also built a 3D audio system on top of SDL3's audio device management. Each frame, the audio system calculates panning and volume based on the source's position relative to the camera, mixes active sounds together, and keeps a small buffer ready so audio playback does not fall apart when the framerate wobbles.

Current State & Next Steps

By the end of the project, M3D could run small 3D demos using user-authored scenes and scripts. It could load custom models, simulate moving and colliding rigidbodies, play positional audio, respond to keyboard and mouse input, instantiate prefabs, and let scripts react to gameplay events.

The engine is in an early state, and I am actively working on finishing the parts that had to be cut for the capstone deadline. The biggest limitation right now is the missing editor. Hand-authoring scenes and prefabs in JSON works, but it is slow, and it makes iteration much more painful than it should be. The editor is the next major piece of the project, not something I have written off.

There are a few other major limitations too. M3D currently only supports one camera, so split-screen or multi-view games are basically out of reach. It also does not have controller support or skeletal animation yet. Those are all features on my planned roadmap, especially animation, because static meshes can only go so far before a game starts to feel lifeless.

Lessons Learned

The biggest lesson I learned from M3D is that everything takes longer than you think, especially integration. I could easily get individual systems working on their own, but making rendering, physics, scripting, audio, input, scene loading, and ECS behavior all cooperate was a completely different challenge. A lot of the hardest work happened in the spaces between systems.

I also learned how exhausting solo development can be on a larger project. When something broke, there was nobody else who knew the system well enough to immediately give me another perspective. I had to be the person who designed the system, found the bug, questioned the original design, and rewrote the broken parts. That taught me a lot, but it also made me appreciate code review and team development much more.

This project made engine architecture feel very real to me. Early decisions had ripple effects all over the codebase. Some decisions made development much easier later, like routing most system interaction through components. Other decisions were misinformed and forced larger rewrites once the engine got more complex. I came out of the project with a much better sense for why architecture matters, not in an abstract "clean code" way, but in the very practical sense that bad boundaries will eventually make you pay for them.

M3D is still growing, and it will not stay in this partially finished state. I am proud of what already works, and I am continuing to build on it until it becomes the small personal engine I originally wanted. It is the most challenging project I have completed, and it gave me a stronger foundation in rendering, physics, scripting, audio, engine runtime design, and the messy reality of making all those systems work together.