2

Je développe un jeu multijoueur sur navigateur, dans lequel chaque client interpole (linéaire) les trames d'entités envoyées par le serveur. Il ne semble pas trop mal à un haut débit (> 30fps), mais commence à scintiller à des fréquences inférieures (< 30fps) et gèle et saute et très faible framerates (< 10fps). Je veux réduire le framerate, et je sais que c'est possible (voir Brutal.io qui envoie des mises à jour à 10fps).Jeu par navigateur multijoueur: interpolation linéaire provoquant jitter et saut

Ceci est l'algorithme de base que je suis en utilisant:

  • Le serveur envoie des mises à jour à un framerate (par exemple, 10fps)
  • Le client rend le jeu à un framerate (par exemple, 60fps)
  • le client ne met pas à jour les entités à l'écran pour correspondre directement les données du serveur en place: ce regarderait nerveux
  • au lieu de cela, il utilise une fonction d'interpolation linéaire pour lisser les images entre les mises à jour du serveur
  • Avec l'exemple de 10: 60fps, le client viderait 6 cadres entre les deux pour créer une animation fluide
  • Elle le fait en mesurant le delta (différence) entre les mises à jour du serveur, et aussi avec des cadres de rendu client
  • Il a ensuite obtient un multiplicateur par delta client plongée par delta du serveur
  • Ensuite, il appelle la fonction d'interpolation linéaire, en utilisant la position de l'écran, la position du serveur, et le multiplicateur, pour générer une nouvelle position de l'écran

Cet extrait ne contenir un code spécifique, mais devrait être assez bon pour démontrer la vue d'ensemble de base (voir les commentaires dans le code pour info):

var serverDelta = 1; // Setting up a variable to store the time between server updates 

// Called when the server sends an update (aiming for 10fps) 
function onServerUpdate(message) { 
    serverDelta = Date.now() - lastServerFrame; 
} 

// Called when the client renders (could be as high as 60fps) 
var onClientRender() { 
    var clientDelta = Date.now() - lastUpdateFrame; 

    // Describes the multiplier used for the linear interpolation function 
    var lerpMult = clientDelta/serverDelta; 
    if (lerpMult > 1) { // Making sure that the screen position doesn't go beyond the server position 
     lerpMult = 1; 
    } 
    lastUpdateFrame = Date.now(); 

    ... 

    // For each entity 
    // ($x,$y) is position sent by server, (x,y) is current position on screen 
    entity.x = linearInterpolate(entity.x, entity.$x, lerpMult/2); 
    entity.y = linearInterpolate(entity.y, entity.$y, lerpMult/2); 
} 

function linearInterpolate(a, b, f) { 
    return (a * (1 - f)) + (b * f); 
}; 

Comme indiqué ci-dessus, cela crée une gigue et un saut dans le mouvement. Y a-t-il quelque chose que je fais de mal? Comment pourrais-je rendre cette motion fluide?

+0

J'essaie de comprendre - comment savez-vous ce que le cadre du dernier serveur est (dans l'interpolation) avant qu'il arrive? En d'autres termes, comment interpoler entre une valeur et une autre valeur que vous ne connaissez pas, sans provoquer de décalage? – clabe45

+0

Hmm ok, alors dites-vous que la façon dont je le fais l'interpolant en utilisant un delta de la trame avant, avec les valeurs actuelles? La solution serait-elle de stocker un "delta du dernier serveur" et de l'utiliser? –

+0

@ clabe45 Désolé, j'ai oublié de vous mentionner. Voir ci-dessus ^^^. –

Répondre

2

L'interpolation doit se faire entre deux états de serveur. Vous pouvez conserver un historique des derniers états du serveur X reçus sur le client. Chaque état du serveur représente un cadre spécifique.

Par exemple, supposons que votre client a gardé les états de serveur suivants et leurs cadres:

state[0] = {frame: 0, ... }; 
state[1] = {frame: 10, ... }; 
state[2] = {frame: 20, ... }; 

Si le client est maintenant le rendu image 15, il doit interpoler les positions à mi-chemin entre state[1] et state[2]. La formule est

// prev=1, next=2 
let interpolatePercent = (clientFrame - serverState[prev].frame)/serverUpdateRate; 
entity.x = interpolatePercent * (serverState[next].entity.x - serverState[prev].entity.x) + serverState[prev].entity.x; 

Dans votre code, lerpMult peut bien être supérieur à 1, auquel cas vous faites maintenant extrapolation au lieu d'interpolation qui est beaucoup plus difficile.

vous pouvez également également jeter un oeil à la bibliothèque open source qui met en oeuvre une interpolation (et extrapolation) pour les jeux multijoueurs navigateur: https://github.com/lance-gg/lance