JeuWeb - Crée ton jeu par navigateur
Client-side prediction - Version imprimable

+- JeuWeb - Crée ton jeu par navigateur (https://jeuweb.org)
+-- Forum : Discussions, Aide, Ressources... (https://jeuweb.org/forumdisplay.php?fid=38)
+--- Forum : Programmation, infrastructure (https://jeuweb.org/forumdisplay.php?fid=51)
+--- Sujet : Client-side prediction (/showthread.php?tid=6238)

Pages : 1 2


Client-side prediction - Maks - 04-07-2012

Je voudrais vous faire part d'un petit compte rendu de mes recherches récentes sur l'architecture client-serveur d'un jeu en ligne. La façon la plus commune de procéder est d'envoyer un message au serveur lorsqu'un évènement est déclenché côté client (par exemple appuyer sur la flèche haut), afin que celui-ci traite l'information et renvoie une réponse à l'ensemble des clients y compris le client qui a déclenché l'évènement.
Cependant en cas de lag, le jeu se retrouve "freezé", votre personnage marche comme un robot, se teleporte ect. C'est le cas dans Counter Strike par exemple. Dans d'autres jeux, même lorsqu'il y a du lag, vous voyez votre personnage avancer normalement. C'est le cas dans Gears of War sur Xbox par exemple (bon c'est mon jeu Xbox préféré donc je le cite Big Grin). Lorsqu'il y a un décalage trop important, votre personnage se retrouve déporté en arrière, il retrouve son état de quelques secondes auparavant.
Ce système permet de cacher en partie le lag, même si bien sûr ça ne résout pas le problème. C'est moins frustrant pour le joueur de pouvoir encore faire quelque chose malgré le lag que se retrouver totalement bloqué. C'est se qu'on appelle la prédiction (coté client).

[Image: Lag_compensation.jpg]

Pour en discuter, je propose une implémentation pour NodeJS et Socket.IO (oui je sais encore ConfusediffleSmile.


###
# Client
# require jQuery, underscore
###

# [...]

$ ->
players = []
socket = io.connect '/?id=1'
client =
id: 1
x : 0
y : 0
hp: 100

# Récupération des joueurs depuis le serveur
socket.on 'sendPlayers', (pPlayers) ->
players.push player for player in JSON.parse pPlayers

client.move = (x, y) ->
# On fait la modif coté client tout de suite
# Sans la prédiction, on aurait attendu la réponse du server d'abord
client.x = x
client.y = y
# Animation...
# On avertit le server s'il s'agit du client
# => dans un cas réel, la méthode move() vient d'une classe Player
# => pour l'exemple j'ai abrégé en client.move()
socket.emit 'move', client if this is client

# Si le server nous avertir du déplacement d'un joueur
socket.on 'move', (pPlayer) ->
for player in players when player.id is pPlayer.id
player.move pPlayer.x, pPlayer.y

client.attack = (target) ->
# On fait la modif coté client tout de suite
# Sans la prédiction, on aurait attendu la réponse du server d'abord
target.hp -= 10
# Animation...
# On avertit le server s'il s'agit du client
# => dans un cas réel, la méthode attack() vient d'une classe Player
# => pour l'exemple j'ai abrégé en client.attack()
socket.emit 'attack', target if this is client

# Si le server nous avertit de l'attaque d'un joueur
socket.on 'attack', (pPlayer, pTarget) ->
for player in players when player.id is pPlayer.id
player.attack pTarget

# On vérifie si l'état du jeu côté client et le même que coté server
# Si ça n'est pas le cas, on rétablit l'état du jeu comme sur le server
# Note : pas optimisé car peut obliger à renvoyer beaucoup trop de données à intervalle régulier
# On pourrait stocker côté client l'état du jeu dans une pile puis restituer selon un timestamp ou un ID incrémenté au fur et à mesure des actions...
socket.on 'checkState', (pPlayers) ->
sameState = yes
sameState = _.isEqual(player, players[i]) and sameState for player, i in JSON.parse pPlayers
if sameState is no
players = []
players.push player for player in JSON.parse pPlayers


###
# Server
# require Socket.IO
###

# [...]
players = []
client = null
io = require('socket.io').listen 80

# Connexion à Socket.IO
io.sockets.on 'connection', (socket) ->

# On définit l'état du client
client =
id: socket.handshake.query.id #/?query=id
x : 0
y : 0
hp: 100
players.push client
# On envoie l'état des joueurs au client (pas optimisé si on a beaucoup de joueurs => /!\ bande passante)
socket.emit 'sendPlayers', JSON.stringify players # (JSON.stringify Array) => JSON

# Mise à jour coordonnées du joueur
socket.on 'move', (pPlayer) ->
# Vérifications préalables : assez d'énergie, client devant la cible, pas d'obstacle ect...
for player in players when player.id is pPlayer.id
# Note : on peut factoriser le code et partager les méthodes de classe entre le client et le serveur
player.x = pPlayer.x
player.y = pPlayer.y
# Note : broadcast envoie à tout le monde sauf le client
socket.broadcast.emit 'move', pPlayer

# Attaque d'un joueur
socket.on 'attack', (target) ->
# Vérifications préalables : assez d'énergie, client devant la cible, pas d'obstacle ect...
for player in players when player.id is target.id
player.hp -= 10
# Note : broadcast envoie à tout le monde sauf le client
socket.broadcast.emit 'attack', client, target

# Pour l'exemple, j'utilise un timer de 1 seconde
# Meilleure implémentation : faire la vérif à chaque fois qu'on reçoit un ping
setInterval ->
socket.emit 'checkState', JSON.stringify players # (JSON.stringify Array) => JSON
, 1000



RE: Client-side prediction - srm - 04-07-2012

La prédiction c'est bien, mais inutile pour notre cas, c'est utile pour les jeux qui nécessite beaucoup de rapidité, de l'ordre de la dizaine de milisecondes.




RE: Client-side prediction - Maks - 04-07-2012

Pour un jeu temps réel utilisant les websockets pourquoi ça serait inutile ?


RE: Client-side prediction - Ter Rowan - 04-07-2012

Mais quand est ce qu il y a correction ?

Genre le serveur n est pas d accord avec la position du client ?

La tel que je comprends, le joueur fait ce qu il veut sur son navigateur
On pourrait imaginer qu il déplace son perso autrement que par le code client du concepteur
(genre je me déplace deux fois plus vite que prévu)

De fait tu vas a un moment ou un autre mettre un controle pour valider le déplacement du perso cote serveur

Et la delta potentiel non ? Pourquoi broadcaster ?


RE: Client-side prediction - Maks - 04-07-2012

Citation :Mais quand est ce qu il y a correction ?

Genre le serveur n est pas d accord avec la position du client ?

Oui

Citation :1) La tel que je comprends, le joueur fait ce qu il veut sur son navigateur
2) On pourrait imaginer qu il déplace son perso autrement que par le code client du concepteur
(genre je me déplace deux fois plus vite que prévu)

1) Oui
2) Non, la vérification coté serveur existe toujours Smile

Citation :Et la delta potentiel non ? Pourquoi broadcaster ?

La delta ?
Le broadcast c'est pour prévenir les autres clients Wink


RE: Client-side prediction - t.bodeux - 04-07-2012

Citation :2) On pourrait imaginer qu il déplace son perso autrement que par le code client du concepteur
(genre je me déplace deux fois plus vite que prévu).

Si jamais c'est le cas, c'est que tu as une grosse faille au niveau de ton code. Toute action coté navigateur doit être vérifiée côté serveur avant d'être validée et devenir persistante (BDD). Ca parrait super logique mais en fait c'est le genre de faille qui arrive très vite :S


RE: Client-side prediction - atra27 - 04-07-2012

Qui parle de persistance?

La prédiction c'est:

J'appuye sur Z pour avancer,
Mon jeu local envoie la commande pour avancer au serveur
Mon jeu local joue l'anim est me fait avancer sur mon ecran
Le serveur check, et renvoie la position réelle du joueur
Mon jeu local fait une interpolation de sa version locale avec la version serveur-> on cache le lag au cours du temps.

Bon pour un jeu par navigateur, c'est pas loin d'être useless...


RE: Client-side prediction - Ter Rowan - 05-07-2012

Je vais reformuler

La tel que je vois le code j ai li pression que jamais le serveur n envoie la position du perso a son joueur (aux autres oui, a lui non)

Du coup je repose ma question : quand le serveur dit au joueur ou est son propre perso ?


RE: Client-side prediction - niahoo - 05-07-2012

SI tu veux des anim fluides, je pense que c'est loin d'être useless au contraire. Dans mon jeu on dirigera des petits vaisseaux, quand on cliquera sur un point de la carte pour que le vaisseau s'y rende, je ferai partir l'anim immédiatement en direction de ce point et le serveur renverra ensuite un point plus proche (si j'ai cliqué sur un obstacle), ou légèrement décalé.


RE: Client-side prediction - Sephi-Chan - 05-07-2012

Idem, je trouve ça très utile pour les performances ressenties de l'application. Smile