A Modern C# Game Framework: The Four-Layer Architecture
This article dives deep into the four-layer architecture of the MonoRpg framework, a scalable and maintainable design pattern that separates concerns for maximum code reusability.
As a game project grows, so does its complexity. Without a deliberate structure, a codebase can quickly devolve into “spaghetti code,” where game logic, rendering, and content are tangled together. This makes the code hard to test, difficult for new developers to understand, and almost impossible to reuse for future projects.
To solve this, I designed the MonoRpg framework around a clean, four-layer architecture. This design pattern enforces a strict separation of concerns, ensuring the codebase remains scalable, maintainable, and highly reusable.
The Four Layers Explained
The architecture consists of four distinct C# projects, with a strict, one-way dependency flow. A lower layer can never know about or reference a higher layer.
Layer 1: The Core Framework (MonoRPG.Framework)
This is the game-agnostic foundation of the entire stack.
- Purpose: To provide fundamental systems that could be used to build any type of 2D game, not just an RPG.
- Responsibilities:
- A rendering pipeline.
- A simple physics and collision system.
- An input abstraction layer.
- A screen management system.
- The base Entity Component System (ECS) infrastructure.
- Key Rule: This project has zero knowledge of RPG mechanics. It cannot reference any other layer.
Layer 2: The RPG Framework (MonoRPG.RpgFramework)
This layer builds upon the Core Framework to provide systems specifically for creating RPGs.
- Purpose: To provide reusable, generic RPG systems that are not tied to a specific game’s story or content.
- Responsibilities:
- LDtk level loading and integration.
- A flexible dialog system.
- NPC management infrastructure.
- A generic quest system.
- Key Rule: This project depends on
MonoRPG.Frameworkbut knows nothing about cards or TCG mechanics.
Layer 3: The TCG Framework (MonoRPG.TcgFramework)
This layer adds the specific mechanics required for a Trading Card Game.
- Purpose: To serve as a reusable foundation for any card-based RPG. It defines what a “card” is, how a “deck” works, and the rules of combat, but it doesn’t contain specific cards like “Fireball” or “Goblin.”
- Responsibilities:
- The core TCG combat engine (turns, mana, phases).
- Abstract card definitions and data structures.
- Deck management logic.
- UI components for card interaction.
- Key Rule: This project depends on
MonoRPG.RpgFrameworkbut contains no specific game content (assets, story, specific card data).
Layer 4: The Game (AetherDraw)
This is the final, concrete implementation of a specific game.
- Purpose: To contain all the unique content, rules, and logic that make the game unique. This layer is not reusable.
- Responsibilities:
- Specific card implementations (e.g., “Strike,” “Defend”).
- The game’s story, characters, and world data.
- Art assets, sound effects, and music.
- The main game entry point (
Program.cs).
- Key Rule: This project depends on all underlying frameworks to build the final experience.
The Dependency Flow: A One-Way Street
The power of this architecture lies in its strict, downward-only dependency flow:
Game (AetherDraw) → TCG Framework → RPG Framework → Core Framework
This is enforced by the C# project references. Because the MonoRPG.Framework project has no references to the other layers, I can be 100% certain that I haven’t accidentally mixed game-specific logic into my core rendering systems.
This structure allows for powerful extension through inheritance and composition. For example, the MonoRPG.RpgFramework layer can define a generic Enemy component, the MonoRPG.TcgFramework layer can extend it to CardBattlerEnemy, and finally, AetherDraw can implement the specific ForestTroll enemy.
The Benefits of a Layered Architecture
- Maximum Reusability: The most obvious benefit. Once AetherDraw is finished, I can package up the
MonoRPG.Framework,MonoRPG.RpgFramework, andMonoRPG.TcgFrameworkprojects and use them to kickstart my next TCG, saving months of work. - Improved Maintainability: This design makes it much easier to isolate and fix bugs. Is the problem with how a sprite is drawn? The bug is in the Core Framework. Is it an issue with a quest trigger? The bug is in the RPG Framework. Is it a card mechanic issue? It’s in the TCG Framework.
- Enhanced Scalability: The clean separation of concerns makes it easier to add new features without breaking existing ones. It also makes the codebase much easier for a new developer (or my future self) to understand.
- Faster Compilation: When I change game-specific code in
AetherDraw, only that small project needs to be recompiled, not the entire engine. This speeds up the development feedback loop.
Conclusion
A layered architecture is a powerful tool for managing complexity in game development. By enforcing a strict separation of concerns, it creates a codebase that is not only easier to build and maintain but also serves as a reusable foundation for all future projects. It’s a prime example of how a little architectural discipline upfront can pay massive dividends in the long run.