Building rpgCore: Cross-Language Architecture for Multi-Genre Games
rpgCore taught me that architectural clarity is worth more than raw performance when prototyping complex systems. Here’s how we bridged Python, C#, and Rust into a cohesive game engine.
The Problem Statement
Most game engines are monolithic. Godot is great at rendering, but scripting in C# alone feels limiting. I wanted to explore: what if the engine was polyglot by design?
- Python for high-level game logic (fast to prototype, mature libraries)
- C#/Godot for rendering and visual debugging
- Rust for performance-critical calculations
The catch: making three languages cooperate without spaghetti code.
Entity-Component-System Architecture
ECS is the right abstraction here. Instead of:
class Player(GameObject):
def move(self):
pass
def take_damage(self):
pass
def interact(self):
pass
We define entities as pure data:
@dataclass
class Entity:
id: str
components: Dict[str, Component]
And behavior emerges from systems operating on components:
class MovementSystem:
def update(self, entities: List[Entity]):
for entity in entities:
if entity.has(Transform) and entity.has(Velocity):
transform = entity.get(Transform)
velocity = entity.get(Velocity)
transform.position += velocity.value * dt
Why this matters: A player-controlled character needs Transform, Velocity, Input, Sprite, Health. An NPC needs Transform, Velocity, Sprite, Health, AI. A projectile needs Transform, Velocity, Sprite, Damage. Zero code duplication—just different component combinations.
Cross-Language Communication
Python core ↔️ C# frontend via socket IPC. Protocol Buffers for serialization.
Python server:
async def game_loop():
while running:
# Update physics, AI, game state
await world.update(dt)
# Serialize entity state
message = serialize_world_state(world)
# Send to Godot
await socket.send(message)
C# client (Godot):
while (socket.connected) {
byte[] data = socket.Receive();
var state = WorldState.Parser.ParseFrom(data);
// Update visual representation
UpdateVisuals(state);
}
This separation is powerful:
- Change Python game logic without restarting Godot editor
- Godot dev can tweak visuals independently
- Easy to run game server headless (no rendering)
Testing & 100% Pass Rate
Every system has unit tests. Game logic lives in pure Python:
def test_movement():
world = World()
entity = world.create_entity()
entity.add(Transform(position=(0, 0)))
entity.add(Velocity(vector=(1, 0)))
movement_system = MovementSystem()
movement_system.update([entity], dt=1.0)
assert entity.get(Transform).position == (1, 0)
No Godot, no rendering, no I/O. Pure logic. 31 tests, 31 passing.
This matters. I can refactor with confidence. The tests guarantee I haven’t broken core mechanics.
Lessons Learned
1. Architecture first, performance later. Socket IPC has overhead. But the clarity of separating C# rendering from Python logic pays dividends. We’re not simulating 1000 entities or running at 4K—the game runs silky smooth at 60 FPS.
2. Constraints breed creativity. “Each language must do one job well” forced us to think clearly. Python isn’t trying to render. C# isn’t doing AI training. Result: code is easier to reason about.
3. Tests compound in value. The 31 tests are not overhead. They’re insurance. As complexity grew, they prevented catastrophic regressions. By the end, I was confident shipping without fear.
4. Documentation is not optional. BUILD_PIPELINE.md is 50 lines of “here’s exactly how to build this.” DELIVERABLES.md lists every component and system. This matters for future me (or a team) picking up the code.
The Takeaway
This architecture doesn’t scale to AAA game development (100+ engineers). But for prototyping ambitious game ideas with high code quality? It’s hard to beat.
ECS decouples everything. Cross-language boundaries enforce clean abstractions. Testing Python logic in isolation removes a huge source of bugs.
Would I use this for a production shipped game? Maybe not—the overhead might matter at scale. But for proving that clean architecture works in game development? Absolutely.