Sea, pirates - Jeu en ligne 3D dans le navigateur

Salutations aux utilisateurs de Habr et aux lecteurs occasionnels. C'est l'histoire du développement d'un jeu en ligne multijoueur basé sur un navigateur avec des graphismes 3D low-poly et une physique 2D simple.



Il y a beaucoup de mini-jeux 2D basĂ©s sur navigateur derriĂšre, mais un tel projet est nouveau pour moi. Dans gamedev, rĂ©soudre des problĂšmes que vous n'avez pas encore rencontrĂ©s peut ĂȘtre passionnant et intĂ©ressant. L'essentiel est de ne pas se coincer avec des piĂšces de meulage et de dĂ©marrer un jeu de travail tant qu'il y a une envie et une motivation, alors ne perdons pas de temps et commençons Ă  dĂ©velopper!





Le jeu en quelques mots



Survival Fight est le seul mode de jeu pour le moment. Batailles de 2 Ă  6 navires sans renaissance, oĂč le dernier joueur survivant est considĂ©rĂ© comme le gagnant et reçoit x3 points et or.



ContrÎles d'arcade : boutons W, A, D ou flÚches pour se déplacer, barre d'espace pour tirer sur les navires ennemis. Vous n'avez pas besoin de viser, vous ne pouvez pas manquer, les dégùts dépendent du caractÚre aléatoire et de l'angle du tir. Des dégùts plus importants sont accompagnés d'une médaille «droit sur cible».



Nous gagnons de l'or en prenant les premiÚres places dans le classement des joueurs en 24 heures et en 7 jours (réinitialisé à 00h00, heure de Moscou) et en accomplissant des tùches quotidiennes (l'une des trois est émise pour une journée, à son tour). Il y a aussi de l'or pour les batailles, mais moins.



Dépenser de l'ormettre des voiles noires sur votre navire pendant 24 heures. Les plans pour ajouter la possibilité de réveiller le Kraken, qui emportera le fond de n'importe quel navire ennemi ses tentacules géants :)



PVP ou zassal lĂąche? Une fonctionnalitĂ© que je voulais implĂ©menter avant mĂȘme de choisir un thĂšme pirate est la possibilitĂ© de se battre avec des amis en quelques clics. Sans inscription et gestes inutiles, vous pouvez envoyer un lien d'invitation Ă  vos amis et attendre qu'ils entrent dans le jeu en utilisant le lien: une salle privĂ©e qui peut ĂȘtre ouverte pour tout le monde est crĂ©Ă©e automatiquement lorsque quelqu'un suit le lien, Ă  condition que «l'auteur» du lien n'en ait pas dĂ©marrĂ© un autre bataille.



Pile technologique



Three.js est l'une des bibliothÚques les plus populaires pour travailler avec la 3D dans le navigateur avec une bonne documentation et de nombreux exemples différents. De plus, j'ai déjà utilisé Three.js - le choix est évident.



L'absence de moteur de jeu est due au manque d'expĂ©rience pertinente et au dĂ©sir d'apprendre quelque chose sans lequel tout fonctionne bien quand mĂȘme :)



Node.js parce que c'est simple, rapide et pratique, mĂȘme si je n'avais aucune expĂ©rience directement dans Node.js. J'ai considĂ©rĂ© Java comme une alternative, j'ai menĂ© quelques expĂ©riences locales, y compris avec des sockets Web, mais je n'ai pas osĂ© savoir s'il Ă©tait difficile d'exĂ©cuter Java sur un VPS. Une autre option - Go, sa syntaxe me dĂ©courage - n'a pas avancĂ© dans son Ă©tude d'un iota.



Pour les sockets Web, utilisez le module ws dans Node.js.



PHP et MySQLchoix moins Ă©vident, mais le critĂšre est toujours le mĂȘme - rapidement et facilement, car il y a de l'expĂ©rience dans ces technologies.



Cela se passe comme







suit : PHP est nĂ©cessaire avant tout pour renvoyer les pages Web au client et pour les requĂȘtes AJAX rares, mais pour la plupart, le client communique toujours avec le serveur de jeu sur Node.js via des sockets Web.



Je ne voulais pas du tout lier le serveur de jeu Ă  la base de donnĂ©es, donc tout passe par PHP. À mon avis, il y a des avantages ici, mĂȘme si je ne suis pas sĂ»r qu'ils soient importants. Par exemple, puisque les donnĂ©es prĂȘtes Ă  l'emploi sous la forme requise arrivent Ă  Node.js, Node.js ne perd pas de temps Ă  traiter et Ă  effectuer des requĂȘtes supplĂ©mentaires dans la base de donnĂ©es, mais traite des choses plus importantes - il «digĂšre» les actions des joueurs et change l'Ă©tat du monde du jeu dans les salles.



ModĂšle d'abord



Le développement a commencé par une chose simple et la plus importante - un certain modÚle du monde du jeu, décrivant les batailles navales du point de vue du serveur. Le canevas 2D simple est idéal pour l'affichage schématique du modÚle à l'écran.







Au dĂ©part, j'ai dĂ©fini la physique normale du "verlet" et pris en compte les diffĂ©rentes rĂ©sistances au mouvement du navire dans des directions diffĂ©rentes par rapport Ă  la direction de la coque. Mais en me souciant des performances du serveur, j'ai remplacĂ© la physique normale par la plus simple, oĂč les contours du navire ne restaient que dans le visuel, alors que physiquement les navires sont des objets ronds qui n'ont mĂȘme pas d'inertie. Au lieu de l'inertie, l'accĂ©lĂ©ration vers l'avant est limitĂ©e.



Les tirs et les coups sont réduits à de simples opérations avec les vecteurs de la direction du navire et de la direction du tir. Il n'y a pas d'obus ici. Si le produit scalaire des vecteurs normalisés correspond aux valeurs acceptables en tenant compte de la distance à la cible, alors il y aura un coup et un coup si le joueur appuie sur le bouton.



Le JavaScript cÎté client pour le rendu du modÚle du monde du jeu, la gestion du mouvement des navires et des tirs, j'ai porté sur le serveur Node.js presque inchangé.



Serveur de jeu



Le serveur WebSocket Node.js se compose de seulement 3 scripts:



  • main.js - le script principal qui reçoit les messages WS des joueurs, crĂ©e des salles et fait tourner les engrenages de cette machine
  • room.js - un script responsable du gameplay dans la salle: mise Ă  jour du monde du jeu, envoi de mises Ă  jour aux joueurs dans la salle
  • funcs.js - comprend une classe pour travailler avec des vecteurs, quelques fonctions d'assistance et une classe qui implĂ©mente une liste doublement liĂ©e


Au fur et à mesure que le développement progressait, de nouvelles classes ont été ajoutées - presque toutes sont directement liées au gameplay et se retrouvent dans le fichier room.js. Parfois, il est pratique de travailler avec des classes séparément (dans des fichiers séparés), mais l'option tout en un n'est pas non plus mauvaise, tant qu'il n'y a pas trop de classes (il est pratique de faire défiler vers le haut et de se souvenir des paramÚtres pris par une méthode d'une autre classe).



La liste actuelle des classes de serveurs de jeu:



  • WaitRoom - la piĂšce oĂč les joueurs attendent le dĂ©but de la bataille, elle a sa propre mĂ©thode de tick qui envoie ses mises Ă  jour et commence la crĂ©ation de la salle de jeu lorsque plus de la moitiĂ© des joueurs sont prĂȘts pour la bataille
  • Room — , : /, ,
  • Player — «» :
  • Ship — : , , ,
  • PhysicsEngine — ,
  • PhysicsBody —


Room
let upd = {p: [], t: this.gamet};
let t = Date.now();
let dt = t - this.lt;
let nalive = 0;

for (let i in this.players) {
	this.players[i].tick(t, dt);
}

this.physics.run(dt);

for (let i in this.players) {
	upd.p.push(this.players[i].getUpd());
}

this.chronology.addLast(clone(upd));
if (this.chronology.n > 30) this.chronology.remFirst();

let updjson = JSON.stringify(upd);

for (let i in this.players) {
	let pl = this.players[i];
	if (pl.ship.health > 0) nalive++;
	if (pl.deadLeave) continue;
	pl.cl.ws.send(updjson);
}

this.lt = t;
this.gamet += dt;

if (nalive <= 1) return false;
return true;




En plus des cours, il existe des fonctions telles que l'obtention de donnĂ©es utilisateur, la mise Ă  jour d'une tĂąche quotidienne, l'obtention d'une rĂ©compense, l'achat d'un skin. Ces fonctions envoient essentiellement des requĂȘtes https Ă  PHP, qui exĂ©cute une ou plusieurs requĂȘtes MySQL et renvoie le rĂ©sultat.



Retards du réseau



La compensation de latence du rĂ©seau est une partie importante du dĂ©veloppement de jeux en ligne. Sur ce sujet, j'ai relu Ă  plusieurs reprises une sĂ©rie d'articles ici sur HabrĂ© . Dans le cas d'une bataille de voiliers, la compensation du retard peut ĂȘtre simple, mais vous devez encore faire des compromis.



L'interpolation est constamment effectuée sur le client - le calcul de l'état du monde du jeu entre deux moments dans le temps, dont les données ont déjà été obtenues. Il y a une petite marge de temps, ce qui réduit la probabilité de sauts soudains, et avec des retards importants du réseau et l'absence de nouvelles données, l'interpolation est remplacée par une extrapolation. L'extrapolation ne donne pas des résultats trÚs corrects, mais elle est bon marché pour le processeur et ne dépend pas de la façon dont le mouvement des navires est implémenté sur le serveur, et bien sûr, elle peut parfois sauver la situation.



Pour rĂ©soudre le problĂšme des retards, beaucoup dĂ©pend du jeu et de son rythme. Je sacrifie une rĂ©ponse rapide aux actions du joueur au profit d'une animation fluide et d'une correspondance exacte de l'image avec l'Ă©tat du monde du jeu Ă  un moment donnĂ©. La seule exception est qu'une salve de canon est jouĂ©e immĂ©diatement en appuyant sur un bouton. Le reste peut ĂȘtre attribuĂ© aux lois de l'univers et au surplus de rhum de l'Ă©quipage du navire :)



L'extrémité avant



Malheureusement, il n'y a pas de structure ou de hiĂ©rarchie claire des classes et des mĂ©thodes. Tout JS est divisĂ© en objets avec leurs propres fonctions, qui dans un sens sont Ă©gales. Presque tous mes projets prĂ©cĂ©dents Ă©taient plus logiques que celui-ci. C'est en partie parce que le premier objectif Ă©tait de dĂ©boguer le modĂšle du monde du jeu sur le serveur et l'interaction rĂ©seau sans prĂȘter attention Ă  l'interface et Ă  la composante visuelle du jeu. Quand est venu le temps d'ajouter la 3D, je l'ai littĂ©ralement ajoutĂ©e Ă  la version de test existante, en gros, j'ai remplacĂ© la fonction 2D drawShip par exactement la mĂȘme chose, mais en 3D, bien qu'Ă  l'amiable, cela valait la peine de rĂ©viser toute la structure et de prĂ©parer la base pour les changements futurs.



Navire 3D



Three.js prend en charge l'utilisation de modĂšles 3D prĂȘts Ă  l'emploi dans diffĂ©rents formats. J'ai choisi le format GLTF / GLB pour moi-mĂȘme, oĂč les textures et les animations peuvent ĂȘtre intĂ©grĂ©es, c.-Ă -d. le dĂ©veloppeur ne devrait pas se demander "avoir toutes les textures chargĂ©es?"



Je n'ai jamais eu affaire Ă  des Ă©diteurs 3D auparavant. L'Ă©tape logique a Ă©tĂ© de contacter un spĂ©cialiste d'un Ă©change indĂ©pendant avec pour mission de crĂ©er un modĂšle 3D d'un voilier avec une animation intĂ©grĂ©e d'une salve de canon. Mais je n'ai pas pu rĂ©sister seul Ă  de petits changements dans le modĂšle spĂ©cialisĂ© fini et j'ai fini par crĂ©er mon modĂšle Ă  partir de zĂ©ro dans Blender. CrĂ©er un modĂšle low-poly avec presque aucune texture est simple, difficile sans un modĂšle prĂȘt Ă  l'emploi d'un spĂ©cialiste pour Ă©tudier dans un Ă©diteur 3D ce qui est nĂ©cessaire pour une tĂąche spĂ©cifique (au moins moralement :).







Shaders au dieu des shaders



La principale raison pour laquelle j'ai besoin de mes shaders est la possibilité de manipuler la géométrie d'un objet sur la carte vidéo pendant le rendu, ce qui a de bonnes performances. Three.js vous permet non seulement de créer vos propres shaders, mais peut également prendre en charge une partie du travail.



Le mĂ©canisme ou la mĂ©thode que j'ai utilisĂ© lors de la crĂ©ation d'un systĂšme de particules pour animer des dommages Ă  un navire, une surface d'eau dynamique ou un fond marin statique est le mĂȘme: le ShaderMaterial spĂ©cial fournit une interface simplifiĂ©e pour l'utilisation de son shader (son code GLSL), BufferGeometry vous permet de crĂ©er une gĂ©omĂ©trie Ă  partir de donnĂ©es arbitraires ...



Un espace vide, une structure de code qui me convenait Ă  copier, complĂ©ter et modifier pour crĂ©er mon objet 3D de la mĂȘme maniĂšre:



Afficher le code
let vs = `
	attribute vec4 color;
	varying vec4 vColor;

	void main(){
		vColor = color;
		gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
		// gl_PointSize = 5.0; // for particles
	}
`;
let fs = `
	uniform float opacity;
	varying vec4 vColor;

	void main() {
		gl_FragColor = vec4(vColor.xyz, vColor.w * opacity);
	}
`;

let material = new THREE.ShaderMaterial( {
	uniforms: {
		opacity: {value: 0.5}
	},
	vertexShader: vs,
	fragmentShader: fs,
	transparent: true
});

let geometry = new THREE.BufferGeometry();

//let indices = [];
let vertices = [];
let colors = [];

/* ... */

//geometry.setIndex( indices );
geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( colors, 4 ) );

let mesh = new THREE.Mesh(geometry, material);




Dommages aux navires



Les animations de dĂ©gĂąts des vaisseaux sont des particules en mouvement qui changent de taille et de couleur, dont le comportement est dĂ©terminĂ© par leurs attributs et le code du shader GLSL. La gĂ©nĂ©ration de particules (gĂ©omĂ©trie et matĂ©riau) se fait Ă  l'avance, puis pour chaque navire sa propre instance (Mesh) de particules endommagĂ©es est crĂ©Ă©e (la gĂ©omĂ©trie est commune Ă  tous, le matĂ©riau est clonĂ©). Il existe un certain nombre d'attributs de particules, mais le shader crĂ©Ă© implĂ©mente simultanĂ©ment de grands nuages ​​de poussiĂšre se dĂ©plaçant lentement, des dĂ©bris volants rapidement et des particules de feu, dont l'activitĂ© dĂ©pend du degrĂ© de dommage au navire.







Mer



La mer est également implémentée en utilisant ShaderMaterial. Chaque sommet se déplace dans les 3 directions le long d'une sinusoïde, formant des ondes aléatoires. Les attributs définissent les amplitudes pour chaque direction de mouvement et la phase de la sinusoïde.



Pour diversifier les couleurs sur l'eau et rendre le jeu plus intéressant et plus agréable à regarder, il a été décidé d'ajouter le fond et les ßles. La couleur du fond dépend de la hauteur / profondeur et brille à travers la surface de l'eau créant des zones sombres et claires.



Le fond marin est crĂ©Ă© Ă  partir d'une carte de hauteur, qui a Ă©tĂ© crĂ©Ă©e en 2 Ă©tapes: d'abord, le fond sans Ăźles a Ă©tĂ© crĂ©Ă© dans un Ă©diteur graphique (dans mon cas, les outils Ă©taient rendus -> nuages ​​et flou gaussien), puis les Ăźles ont Ă©tĂ© ajoutĂ©es dans un ordre alĂ©atoire Ă  l'aide de Canvas JS en ligne sur jsFiddle dessiner un cercle et brouiller. Certaines Ăźles sont basses, Ă  travers elles, vous pouvez tirer sur des adversaires, d'autres ont une certaine hauteur, les tirs ne les traversent pas. En plus de la carte de hauteur elle-mĂȘme, je reçois en sortie des donnĂ©es au format json sur les Ăźles (leur position et leur taille) pour la physique sur le serveur.







Et aprĂšs?



Il existe de nombreux plans pour le dĂ©veloppement du jeu. Les principaux sont les nouveaux modes de jeu. Les plus petits - crĂ©ez des ombres / reflets sur l'eau, en tenant compte des limitations de performances de WebGL et JS. J'ai dĂ©jĂ  Ă©voquĂ© l'opportunitĂ© de rĂ©veiller le Kraken :) L'unification des joueurs en salles en fonction de leur expĂ©rience accumulĂ©e n'a pas encore Ă©tĂ© mise en Ɠuvre. Une amĂ©lioration Ă©vidente, mais pas trop prioritaire, consiste Ă  crĂ©er plusieurs cartes des fonds marins et des Ăźles et de choisir l'une d'entre elles au hasard pour une nouvelle bataille.



Vous pouvez crĂ©er beaucoup d'effets visuels en dessinant Ă  plusieurs reprises la scĂšne "en mĂ©moire" puis en combinant toutes les donnĂ©es en une seule image (en fait, cela peut ĂȘtre appelĂ© post-traitement), mais ma main ne se lĂšve pas pour augmenter la charge sur le client de cette maniĂšre, car le client est toujours un navigateur plutĂŽt qu'une application native. Peut-ĂȘtre qu'un jour je dĂ©ciderai de cette Ă©tape.



Il y a aussi des questions auxquelles j'ai maintenant du mal à répondre: combien de joueurs en ligne un serveur virtuel bon marché peut-il supporter, s'il sera possible de collecter au moins un certain nombre de joueurs intéressés et comment le faire.



ƒuf de Pñques



Qui n'aime pas se souvenir des vieux jeux informatiques qui ont donnĂ© tant d'Ă©motions? J'adore rejouer le jeu Corsairs 2 (alias Sea Dogs 2) encore et encore jusqu'Ă  prĂ©sent. Je n'ai pas pu m'empĂȘcher d'ajouter un secret Ă  mon jeu et qui rappelle explicitement et indirectement "Corsairs 2". Je ne rĂ©vĂ©lerai pas toutes les cartes, mais je donnerai un indice: mon Ɠuf de PĂąques est un certain objet que vous pouvez trouver en explorant la mer (vous n'avez pas besoin de naviguer loin sur la mer sans fin, l'objet est raisonnable, mais la probabilitĂ© de le trouver n'est pas Ă©levĂ©e). L'Ɠuf de PĂąques rĂ©pare complĂštement le navire endommagĂ©.



Qu'est-il arrivé



Vidéo minute (test à partir de 2 appareils):





Lien vers le jeu: https://sailfire.pw



Il y a aussi un formulaire de contact, des messages me sont envoyés par télégramme: https://sailfire.pw/feedback/

Liens pour ceux qui souhaitent se tenir au courant des actualités et mises à jour: VK Public , chaßne Telegram



All Articles