When people hear the word architecture, they often think it's something only large companies need to worry about.
I used to think the same.
But once you start working on a project that you'll be maintaining for months—or even years—you quickly realize that writing code is only half the job. The other half is organizing that code so it remains easy to understand, extend, and maintain.
That's what software architecture is all about.
Although I'll use VEngine as an example throughout this article, these ideas apply to almost every large software project, not just game engines.
A good architecture divides an application into smaller systems, where each system has a single responsibility.
Instead of one class trying to do everything, responsibilities are separated into independent components.
For example:
A Window system manages the application window.
A Renderer system draws graphics.
An Input system handles keyboard and mouse events.
An Audio system plays sounds.
Each system has one job.
The application simply coordinates these systems to build the final program.
This makes the project much easier to understand and maintain as it grows.
Right now, VEngine is still very small.
Its architecture looks like this:
Application
/ \
/ \
Window Renderer
\ /
\ /
Raylib
Although simple, this diagram already demonstrates an important design principle.
The Application class does not create windows directly.
It does not render graphics.
It does not call Raylib functions itself.
Instead, it coordinates the different systems.
You can think of the Application class as a manager.
It tells each system when to perform its work, but it doesn't perform that work itself.
The Window system is responsible for everything related to the application window.
Window
│
├── Create()
├── Destroy()
├── Running()
└── SetTargetFPS()
Every window-related Raylib call belongs here.
If something goes wrong with the window, I know exactly where to look.
The Renderer has a completely different responsibility.
Renderer
│
├── BeginFrame()
├── EndFrame()
├── Clear()
├── DrawCircle()
├── DrawRectangle()
└── DrawText()
Everything here is related to drawing.
Instead of calling Raylib directly, the rest of the engine communicates with the Renderer.
The Renderer then communicates with Raylib.
If the engine owns all these systems, where does the actual game logic go?
That's where Sandbox comes in.
Sandbox is not an engine system like Window or Renderer.
It is the user's application.
It inherits from Application, allowing the engine to provide all the infrastructure while the developer focuses entirely on the game.
For example, this is where the developer writes code such as:
Moving the player
Updating enemies
Drawing game objects
Handling game rules
The game doesn't worry about creating windows or managing the render loop.
The engine already takes care of those responsibilities.
The overall flow looks like this:
Sandbox
│
▼
Application
│
▼
Window / Renderer / Input
│
▼
Raylib
Notice that the flow always moves downward.
The Sandbox talks to the engine.
The engine talks to its systems.
The systems talk to Raylib.
The Sandbox never communicates with Raylib directly.
You can think of VEngine as three separate layers.
USER
────────────────────────────────
Sandbox
▲
│ inherits
│
────────────────────────────────
ENGINE
Application
/ | \
/ | \
Window Renderer Input
\ | /
────────────────────────────────
BACKEND
Raylib
Each layer has its own responsibility.
This is where game developers write their games.
This layer provides reusable systems like Window, Renderer, and Input.
Raylib is simply the graphics backend used by the engine.
Because Raylib stays hidden behind the engine, the user never depends on it directly.
At first, this architecture might seem like extra work.
Why not just call Raylib directly?
The answer is flexibility.
Imagine that one day I decide to replace Raylib with another graphics library, such as SDL, GLFW + OpenGL, or Vulkan.
If every part of my game called Raylib directly, I would have to rewrite code throughout the entire project.
With this architecture, only the systems that depend on Raylib need to change.
The Sandbox remains exactly the same.
Similarly, if I discover a bug in window management, I know it must be inside the Window system.
If rendering behaves incorrectly, I immediately look at the Renderer.
Each responsibility has a single home.
VEngine is still in its early stages, but I believe building a solid architecture first will make every future feature easier to implement.
Today there are only a few systems.
Tomorrow there might be rendering pipelines, physics, audio, scenes, asset management, scripting, and much more.
The architecture doesn't make the engine more powerful.
It makes the engine easier to grow.
Github: VEngine