I think there are 2 types of client-server games:
- pretty slow games (turn-based), where everything is controlled on the server.
- fast games (shooters, mmorpg), where server manage only global state, but some minor details are managed on the client.
I don’t have expirience with 2nd type, so everything I write below is for 1th type.
How would a server interaction look when it comes to updating a user object?
When you create an entity on the client, do you replicate it on the server and then sync for conflicts?
Everything is calculated on server. The same things also calculated on client, but server calculations are more important.
- Client send an event to the server. Event is something like “turn right”, “move forward”, “attack”, etc… At the same time, game client display required animation.
- If event is valid, server reply: “OK”
- If event is not valid, sync was lost, and server send full game state to the client.
- Game client also listen for server events, something like “opponent moved”, “opponent attach”, etc…
Does it involve server-side rendering or how do you make two clients see the same entity?
Rendering is only on client, but server keeps entire game state, and can send that state to client.
SQL or Mongo?
Probably Mongo will be better.
Would a nodejs server be sufficient for cross-platform?
Yup, just use JSON for data exchange between server and client.
Another great article: http://buildnewgames.com/real-time-multiplayer/
Example of API for simple turn-based game
Server has only one method “sync-state”
IN: {
"stateUid": "...",
"authorization": "...",
"events": [ ... ]
}
stateUid - unique state identifier. after each sync identifier should be incremented.
OUT: {
"stateUid": "...",
"serverTimestamp": ...,
"state": {
"gameState": "notPlaying|searchingForOpponent|myTurn|opponentTurn",
"playersOnline": ...,
"me": {
"name": "...",
"avatarUrl": "...",
"ratingPosition": ...,
"winCount": ...,
"looseCount": ...,
"robotType": 0 | 1 | 2 | 3
},
"opponent": {
"name": "...",
"avatarUrl": "...",
"ratingPosition": ...,
"winCount": ...,
"looseCount": ...,
"robotType": 1 | 2 | 3
},
"board": {
"nextTurnTimestamp": ...,
"me": {
"row": ...,
"col": ...,
"wallsLeft": ...,
"rowToReach": ...
},
"opponent": {
"row": ...,
"col": ...,
"wallsLeft": ...,
"rowToReach": ...
},
"walls": [
{ "row": ..., "col": ..., "vert": true|false },
...
]
},
},
"events": [ ... ]
}
“state” is sent only when stateUid on client differs from stateUid on server (or server detects invalid event).
Events from game to server
Find opponent event - when player searches for opponent:
{
"event": "findOpponent",
data: {
"robotType": "..."
}
}
Cancel find event - when player close “opponent search” popup:
{
"event": "cancelFind",
}
Make move event - when player make move:
{
"event": "makeMove",
data: {
"row": ...,
"col": ...
}
}
Place wall event - when player want to place wall:
{
"event": "placeWall",
data: {
"row": ...,
"col": ...,
"vert": true|false
}
}
Events from server to game
When the game is in “searchingForOpponent” state, and players online count is changed, server send following event:
{
"event": "playersOnlineUpdated",
"data": {
"playersOnline": ...
}
}
When opponent found:
{
"event": "opponentFound",
"data": {
"name": "...",
"avatarUrl": "...",
"ratingPosition": ...,
"winCount": ...,
"looseCount": ...,
"robotType": "..."
}
}
Next event server send immediately after “opponentFound”:
{
"event": "initialState",
"data": {
"gameState": "myTurn|opponentTurn",
"board": {
"nextTurnTimestamp": ...,
"me": {
"row": ...,
"col": ...,
"rowToReach": ...,
"wallsLeft": ...
},
"opponent": {
"row": ...,
"col": ...,
"rowToReach": ...,
"wallsLeft": ...
}
}
}
}
When player or opponent wins:
{
"event": "winOrLoose",
"data": {
"win": true|false,
"byTime": true|false,
"me": {
"ratingPosition": ...,
"winCount": ...,
"looseCount": ...
},
"opponent": {
"ratingPosition": ...,
"winCount": ...,
"looseCount": ...
}
}
}
When opponent moved:
{
"event": "opponentMoved",
"data": {
"row": ...,
"col": ...
}
}
When opponent placed wall:
{
"event": "opponentPlacedWall",
"data": {
"row": ...,
"col": ...,
"vert": true|false
}
}
Next turn event:
{
"event": "nextTurn",
"data": {
"gameState": "myTurn|opponentTurn",
"nextTurnTimestamp": ...,
}
}