Skip to main content

Architecture

Table of Contents

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 maps Entity IDs 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 using SparseArray.
  • 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 Asio async callbacks.
    • Deserializes packets directly upon receipt.
    • Sends outgoing packets (Snapshots) asynchronously.
  • 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

  1. Client Input: Sent as a bitmask (Held Keys) every tick.
  2. Server Validation: Server applies input to the entity's velocity/state.
  3. Server Snapshot: Server sends the full list of visible entities (ID, Type, Position, Angle).
  4. 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

ComponentPurpose
TransformPosition, rotation, scale, origin & custom origins
DrawableSprite path, texture, z-index rendering order
AnimatedSpriteFrame-based animation support (frame duration, looping)
ParallaxLayerRepresents parallax scrolling layers

🎮 Movement & Control Components

ComponentPurpose
VelocityEntity movement velocity & acceleration
ControllableMarks a player-controllable entity
InputStatePlayer inputs (up, down, shoot…)

🧱 Collision & Physics Components

ComponentPurpose
HitBoxCollision rectangle for AABB collisions
SolidIndicates blocking entities or platforms
LifetimeAutomatically despawns entities after X seconds
DespawnOnExitRemoves offscreen entities (projectiles, enemies)

🧩 Gameplay Components

ComponentPurpose
PlayerTagIdentifies a player entity
EnemyTagIdentifies an enemy and its type
ProjectileStores damage, speed, and shooter ID
HealthHP, max HP, invincibility frames
StatsGamePlayer score & gameplay stats
WeaponFire rate, projectile type, cooldown
PowerUpTemporary player boosts
StateMachineFor enemy/boss AI behavior

🌐 Networking Components

ComponentPurpose
NetworkIdUnique server-synced ID
InterpolatedPositionTarget 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 structure
  • IPlatformEventSource - Abstract polling interface
  • SFMLEventSource - 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