“Can you add multiplayer?” might be the most frequently asked question indie developers receive — and the most frequently underestimated in terms of engineering effort. Multiplayer transforms every system in your game. Input handling, game state management, UI, testing, deployment — everything gets more complex.
That said, multiplayer done well can transform a good game into a memorable one. And for retro-style 2D games, the technical challenges are more manageable than for 3D titles with complex physics and high-fidelity animation.
At Relish Games, we approach multiplayer pragmatically. This guide covers the fundamentals of networking for 2D games, with honest assessments of the tradeoffs.
Networking Architectures
Client-Server
One machine (the server) is the authority on game state. All other machines (clients) send inputs and receive state updates.
Advantages:
- Cheat prevention — the server validates all actions
- Consistent game state — one source of truth
- Scalable — adding players doesn’t increase peer connections
Disadvantages:
- Server hosting costs money
- Single point of failure
- All players experience at least server-trip latency
Best for: Competitive games, games with more than 4 players, games where fairness is critical.
Peer-to-Peer
All machines communicate directly. Each peer is responsible for its own entities.
Advantages:
- No server hosting costs
- Lower latency between peers (direct connection)
- Simpler initial implementation
Disadvantages:
- Cheat prevention is harder
- Connection count grows quadratically with player count
- NAT traversal is a real headache
- Desyncs are harder to resolve
Best for: Cooperative games, local/LAN games, 2-player experiences, games where trust between players is assumed.
Rollback Netcode
A specific technique primarily used in fighting games and fast-action games. Each client runs the simulation independently, predicting the opponent’s input. When actual input arrives and doesn’t match the prediction, the game “rolls back” to the last confirmed state and re-simulates.
Advantages:
- Feels responsive even with moderate latency
- Input lag is minimised (actions happen immediately on the local machine)
Disadvantages:
- Complex to implement correctly
- Requires deterministic game simulation
- Visual rollbacks are noticeable at high latency
Best for: 1v1 or small-player-count action games where input responsiveness is paramount.
State Synchronisation
The core networking problem: keeping all players’ views of the game consistent.
Full State Sync
Send the complete game state every update. Simple but bandwidth-heavy.
Works for: Simple games with small state (card games, turn-based games, puzzle games).
Doesn’t work for: Games with hundreds of entities or frequent updates.
Delta Compression
Send only what changed since the last update. Dramatically reduces bandwidth.
Implementation: Each state update includes only modified entity properties. Clients apply deltas to their local copy of the game state.
Gotcha: If a delta packet is lost, the client’s state diverges. Include periodic full-state snapshots to recover.
Input Synchronisation
Instead of synchronising state, synchronise inputs. Each client runs the same simulation with the same inputs and arrives at the same state.
Requirement: Perfectly deterministic simulation. Same inputs must produce exactly the same state on every machine. This means no floating-point inconsistencies, no unordered iterations, no system-dependent randomness.
For 2D games with simpler physics, deterministic simulation is achievable. Fixed-point math or careful floating-point handling, combined with a seeded PRNG (as we covered in procedural content generation), provides the foundation.
Latency Compensation
The fundamental reality of networked games: information takes time to travel. Players are always seeing a slightly outdated view of the game.
Client-Side Prediction
The client simulates the player’s own actions immediately without waiting for server confirmation. If the server agrees, the player never noticed the latency. If the server disagrees, the client corrects to match the server.
For 2D games: Movement prediction is straightforward — apply the same movement logic on client and server. Shooting prediction is trickier — did the shot hit based on where the target was on the shooter’s screen, or where it actually was on the server?
Entity Interpolation
Other players’ positions arrive in discrete updates. Between updates, interpolate smoothly:
Render position = Previous position + (Current position - Previous position) × interpolation_factor
This adds a small amount of visual latency (one update interval) but eliminates the jerky movement that raw network updates produce.
Input Delay
The simplest latency compensation: add a few frames of input delay. The player’s inputs are buffered briefly, giving enough time for network round-trips to complete before the input needs to be applied.
Tradeoff: Feels slightly less responsive but eliminates rollbacks and corrections. Many successful fighting games use 2–3 frames of input delay.
Practical Considerations for 2D Games
2D Simplifies Some Things
- Fewer entities: 2D games typically have fewer simultaneous entities than 3D, reducing bandwidth requirements
- Simpler physics: 2D collision and movement are more easily made deterministic
- Lower data per entity: Position is 2 floats instead of 3, rotation is 1 float instead of a quaternion
- Sprite state is compact: Current animation frame and facing direction are small values
2D Doesn’t Simplify Everything
- Input responsiveness still matters — 2D action games need tight controls regardless of networking
- Visual desyncs are more obvious — in a pixel-precise environment, entities that jitter or teleport are immediately visible
- Game design adaptation — some single-player mechanics don’t translate to multiplayer (pause, time manipulation, camera that follows one player)
Implementation Path
Step 1: Make Your Game State Serialisable
Before writing any networking code, ensure your game state can be captured and restored:
struct GameState {
EntityState entities[MAX_ENTITIES];
int entityCount;
int frameNumber;
uint32_t checksum;
};
If you can save and load complete game states, you can sync them over a network.
Step 2: Add Input Abstraction
Decouple input from game logic:
struct PlayerInput {
bool moveUp, moveDown, moveLeft, moveRight;
bool fire;
int aimX, aimY;
int frameNumber;
};
Game logic should consume PlayerInput structs, not raw hardware input. This lets you feed network-received inputs into the same logic.
Step 3: Start with Local Split-Screen
Implement two-player local play first. This validates your input abstraction and state management without any networking complexity.
Step 4: Add Networking
Choose a networking library:
- ENet: Lightweight, reliable UDP library. Good for games.
- SteamNetworkingSockets: Free, handles NAT traversal, relay servers included
- Custom UDP: Maximum control, maximum work
Start with a simple architecture (lock-step or client-server with full state sync) and optimise later.
Step 5: Playtest on Real Networks
Local testing hides latency problems. Test with real network conditions — or simulate them with artificial delay and packet loss.
When Multiplayer Is (and Isn’t) Worth It
Worth It
- Your game’s core loop is inherently social (cooperative puzzles, competitive combat)
- Your target audience expects multiplayer (fighting games, party games)
- Multiplayer extends the game’s lifespan significantly
- You have the engineering capacity to maintain it post-launch
Not Worth It
- Multiplayer is a checkbox feature, not a design pillar
- Your game is primarily a single-player narrative experience
- You can’t commit to maintaining servers or matchmaking infrastructure
- The engineering cost would delay a game that’s great as single-player
Be honest about your capacity. A single-player game that ships is better than a multiplayer game that doesn’t.
What We’d Recommend
- Design for multiplayer from the start if it’s a core feature — retrofitting is painful
- Start with cooperative rather than competitive — sync requirements are more forgiving
- Use an existing networking library — don’t write your own UDP layer
- Test with real latency early — don’t wait until launch to discover problems
- Provide good single-player even if multiplayer is the focus — players need to play when friends aren’t online
Networking is complex, but for 2D games, the scope is manageable if you’re methodical about it.
Discuss multiplayer implementation with other developers in our community forum, or explore our projects page to see how we approach game architecture.