Architecture
Table of Contents
- 1. Overview
- 2. Engine Architecture (ECS)
- 3. Server Architecture
- 4. Client Architecture
- 5. Network Protocol Integration
- 6. Client Components
- 7. Client Systems
- 8. Platform Abstraction
1. Overview
R-TYPE J.A.M.E.S. is built upon a Client-Server architecture using a custom Entity Component System (ECS) engine. The system is designed to be modular, performant, and network-efficient, adhering to the Authoritative Server model.
The engine features backend-agnostic abstractions for input, graphics, and platform events, making it portable and suitable for reuse in other game projects.
High-Level Architecture
graph TD
subgraph Client ["Client Application (Single Thread)"]
C_Main[Main Event Loop]
C_ASIO[ASIO Async I/O]
C_Render[SFML Rendering]
C_Input[Input Handling]
C_Main --> C_ASIO
C_Main --> C_Render
C_Main --> C_Input
end
subgraph Server ["Server Application (Single Thread)"]
S_Net["Network Class (ASIO)"]
S_Logic[Game Logic / Tick]
S_ECS[ECS Registry]
S_Net -->|Inputs| S_Logic
S_Logic -->|Snapshots| S_Net
S_Logic --> S_ECS
end
C_ASIO <==>|UDP / TCP| S_Net
2. Engine Architecture (ECS)
The core of the game logic is driven by a custom ECS library located in the engine/ directory. This library is header-only (mostly) and provides the foundation for both the Client and Server.
Core Concepts
- Registry (
registry): The central manager that handles entities, components, and systems. It mapsEntityIDs to their component data. - Entity: A simple unique identifier (
size_t). It has no data itself but serves as a key to retrieve components. - Component: Plain Old Data (POD) structs (e.g.,
Position,Velocity,Health). Stored in contiguous memory usingSparseArray. - System: Logic functions that iterate over specific combinations of components to update the game state (e.g.,
MovementSystem,CollisionSystem). - Sparse Array: A memory-efficient container that stores components packed tightly, allowing for cache-friendly iteration.
ECS Data Flow
classDiagram
class Registry {
+spawn_entity()
+kill_entity()
+register_component()
+add_system()
+run_systems()
}
class SparseArray~Component~ {
+insert()
+remove()
+operator[]
}
class System {
<<Function>>
+operator()(Registry)
}
Registry *-- "many" SparseArray
Registry o-- "many" System
3. Server Architecture
The server is the Authority. It maintains the "true" state of the game and broadcasts it to clients. It is designed to be robust and efficient.
Threading Model
The server operates on a single thread and uses ASIO async functions for asynchronous capabilities. ASIO handles the threading part internally, allowing the server to handle network operations and game logic concurrently without manual thread management.
-
Network Operations:
- Handles all incoming UDP/TCP traffic using
Asioasync callbacks. - Deserializes packets directly upon receipt.
- Sends outgoing packets (Snapshots) asynchronously.
- Handles all incoming UDP/TCP traffic using
-
Game Logic:
- Runs the main Game Loop.
- Processes inputs received via network callbacks.
- Runs ECS Systems (Physics, Collision, AI).
- Generates World Snapshots and schedules them for sending.
Server Loop
sequenceDiagram
participant Server as Server (ASIO Loop)
participant Client
loop Every Tick
Client->>Server: Send Inputs (Async Receive)
Server->>Server: Process Inputs
Server->>Server: Update Physics (ECS)
Server->>Server: Check Collisions
Server->>Client: Broadcast Snapshot (Async Send)
end
4. Client Architecture
The client is responsible for rendering the game state and capturing user input. It uses SFML for graphics and audio.
Event Loop Model
The client operates on a single thread using ASIO async I/O:
- Main Event Loop:
- Handles the Window and Event Polling (SFML requirement).
- Listens for Server Snapshots (UDP) and Events (TCP) via ASIO async callbacks.
- Updates the local "Network State" (the target state to interpolate towards).
- Sends Player Inputs to the server via ASIO async send.
- Interpolates entities between the last known state and the current Network State.
- Renders the scene.
Rendering & Interpolation
To ensure smooth gameplay despite network latency, the client does not just "teleport" entities to the server's position. Instead, it uses Snapshot Interpolation:
- Visual State: The position currently drawn on screen.
- Target State: The latest position received from the server.
- Logic:
VisualPos = Lerp(CurrentPos, TargetPos, DeltaTime * SmoothingFactor)
Client Loop
graph LR
subgraph Input
Keyboard -->|Capture| MainLoop
end
subgraph Network
ServerPacket -->|Async Receive| ASIO_Callback
ASIO_Callback -->|Update Target| GameState
MainLoop -->|Async Send Input| ASIO
end
subgraph Rendering
GameState -->|Interpolate| MainLoop
MainLoop -->|Draw| Screen
end
5. Network Protocol Integration
The architecture strictly follows the binary protocol defined in protocol.md.
- TCP: Used for reliable session events (Connect, Disconnect, Game Start).
- UDP: Used for high-frequency gameplay data (Inputs, Snapshots).
- Packet Handling:
- TickId: Used to order UDP packets and discard old data.
- Serialization: Custom binary serializer ensures strict byte alignment (Little Endian).
Data Synchronization Strategy
- Client Input: Sent as a bitmask (Held Keys) every tick.
- Server Validation: Server applies input to the entity's velocity/state.
- Server Snapshot: Server sends the full list of visible entities (ID, Type, Position, Angle).
- Client Correction: Client updates its local entities to match the server's snapshot, spawning or destroying entities as needed.
6. Client Components
The client ECS uses a rich set of components representing rendering, movement, gameplay, collision, and network state.
Below is the complete list used by the client.
📦 Rendering & Transform Components
| Component | Purpose |
|---|---|
| Transform | Position, rotation, scale, origin & custom origins |
| Drawable | Sprite path, texture, z-index rendering order |
| AnimatedSprite | Frame-based animation support (frame duration, looping) |
| ParallaxLayer | Represents parallax scrolling layers |
🎮 Movement & Control Components
| Component | Purpose |
|---|---|
| Velocity | Entity movement velocity & acceleration |
| Controllable | Marks a player-controllable entity |
| InputState | Player inputs (up, down, shoot…) |
🧱 Collision & Physics Components
| Component | Purpose |
|---|---|
| HitBox | Collision rectangle for AABB collisions |
| Solid | Indicates blocking entities or platforms |
| Lifetime | Automatically despawns entities after X seconds |
| DespawnOnExit | Removes offscreen entities (projectiles, enemies) |
🧩 Gameplay Components
| Component | Purpose |
|---|---|
| PlayerTag | Identifies a player entity |
| EnemyTag | Identifies an enemy and its type |
| Projectile | Stores damage, speed, and shooter ID |
| Health | HP, max HP, invincibility frames |
| StatsGame | Player score & gameplay stats |
| Weapon | Fire rate, projectile type, cooldown |
| PowerUp | Temporary player boosts |
| StateMachine | For enemy/boss AI behavior |
🌐 Networking Components
| Component | Purpose |
|---|---|
| NetworkId | Unique server-synced ID |
| InterpolatedPosition | Target position for snapshot interpolation |
7. Client Systems
All systems listed here run every frame and operate on specific component combinations.
✔ MovementSystem
Updates entity positions using Velocity and Transform.
✔ AnimationSystem
Advances frames for all AnimatedSprite components.
✔ CollisionDetectionSystem
Detects collisions using AABB checks
→ Sends CollisionEvent via EventBus.
✔ DrawableSystem
Loads textures, updates sprite properties, sorts by z-index, and draws them.
✔ PlayfieldLimitSystem
Prevents the player from leaving the visible screen area.
✔ DeltaTimeSystem
Updates delta time each frame via the GameWorld timing utility.
✔ AudioSystem
Plays sounds on events (shooting, impact, explosion).
✔ LifetimeSystem
Destroys entities whose Lifetime counters reach zero.
✔ DespawnOffscreenSystem
Removes projectiles and enemies that leave the playfield.
✔ StateMachineSystem
Executes AI logic for enemies and boss patterns.
✔ WeaponSystem
Handles fire rate, cooldown, and projectile spawning.
✔ ParallaxSystem
Scrolls background layers at different speeds for depth effect.
✔ HealthSystem
Applies damage, handles enemy/player death events.
✔ InputSystem
Processes player input and updates controllable entities.
✔ ProjectileSystem
Manages projectile spawning, movement, and despawning logic.
🔔 Event Bus (Collision & Gameplay Events)
The Event Bus provides a decoupled system for communication between systems.
Example: Collision Handling
game_world.event_bus_.Subscribe<CollisionEvent>(
[](const CollisionEvent &event, int) {
auto &players = event.game_world_.registry_.GetComponents<Com::PlayerTag>();
if (players.has(event.entity_a_) || players.has(event.entity_b_)) {
// Handle player collision logic (damage, knockback, invincibility...)
}
}
);
This system is used for:
- Player taking damage
- Enemy death events
- Projectile collisions
8. Platform Abstraction
The engine provides backend-agnostic abstractions to decouple game logic from specific platform libraries (SFML, SDL, etc.).
OS Event System
The OS Event Abstraction Layer handles window and input events without exposing the underlying backend:
// Engine defines the interface and event types
Engine::Platform::OSEvent event;
while (game_world.event_source_->Poll(event)) {
if (event.type == Engine::Platform::OSEventType::Closed) {
game_world.window_.close();
}
}
Key Components:
Engine::Platform::OSEvent- Backend-agnostic event structureIPlatformEventSource- Abstract polling interfaceSFMLEventSource- SFML concrete implementation
Benefits:
- Engine has zero SFML dependencies in core code
- Future backends (SDL, GLFW) can be added via plugins
- Improved testability with mock event sources
For detailed information, see OS Event Abstraction Layer.
Input Abstraction
Game actions are decoupled from physical keys through an abstraction layer:
// Engine provides generic input primitives
Engine::Input::Key, Engine::Input::MouseButton
// Game defines its own actions
Game::Action::MoveUp, Game::Action::Shoot
// Mappings configured at game layer
input_manager.BindKey(Engine::Input::Key::W, Game::Action::MoveUp);
Graphics Abstraction
Engine graphics types are backend-independent:
// Engine types (not SFML types)
Engine::Graphics::Color
Engine::Graphics::Vector2f
// Adapters convert at the boundary
sf::Color sfml_color = Adapters::ToSFMLColor(engine_color);
This architecture enables:
- Portability - Engine can run on different platforms/backends
- Reusability - Core engine can be used in other games
- Testability - Mock implementations for unit testing
- Plugin Support - Dynamic backend loading (future)
- Power-up pickups
- Boss AI triggers