Ti tu silẹ ni ọdun 2015 Agar.io di progenitor ti a titun oriṣi awọn ere .ioeyiti o ti dagba ni olokiki lati igba naa. Mo ti ni iriri tikalararẹ igbega ni olokiki ti awọn ere .io: ni ọdun mẹta sẹhin, Mo ni ṣẹda ati ta awọn ere meji ti oriṣi yii..
Ni ọran ti o ko tii gbọ ti awọn ere wọnyi tẹlẹ, iwọnyi jẹ awọn ere wẹẹbu elere pupọ ọfẹ ti o rọrun lati mu ṣiṣẹ (ko si akọọlẹ ti o nilo). Won maa koju ọpọlọpọ awọn titako awọn ẹrọ orin ni kanna arena. Awọn ere .io olokiki miiran: Slither.io и Diep.io.
Ninu ifiweranṣẹ yii, a yoo ṣawari bii ṣẹda .io ere lati ibere. Fun eyi, imọ nikan ti Javascript yoo to: o nilo lati ni oye awọn nkan bii sintasi ES6, koko this и Awọn ileri. Paapaa ti imọ rẹ ti Javascript ko ba pe, o tun le loye pupọ julọ ifiweranṣẹ naa.
.io ere apẹẹrẹ
Fun iranlọwọ ẹkọ, a yoo tọka si .io ere apẹẹrẹ. Gbiyanju lati mu ṣiṣẹ!
Ere naa rọrun pupọ: o ṣakoso ọkọ oju-omi kekere kan ni gbagede nibiti awọn oṣere miiran wa. Ọkọ oju omi rẹ ṣe ina awọn ohun-ọṣọ laifọwọyi ati pe o gbiyanju lati kọlu awọn oṣere miiran lakoko ti o yago fun awọn iṣẹ akanṣe wọn.
Ohun gbogbo ninu folda public/ yoo wa ni statically silẹ nipasẹ olupin. IN public/assets/ ni awọn aworan ti a lo nipasẹ iṣẹ akanṣe wa.
src /
Gbogbo koodu orisun wa ninu folda src/. Awọn akọle client/ и server/ sọ fun ara wọn ati shared/ ni faili ibakan ti o jẹ agbewọle nipasẹ alabara ati olupin.
2. Assemblies / ise agbese eto
Gẹgẹbi a ti sọ loke, a lo oluṣakoso module lati kọ iṣẹ naa. Oju-iwe wẹẹbu. Jẹ ki a wo iṣeto Webpack wa:
src/client/index.js jẹ aaye titẹsi ti alabara Javascript (JS). Apo wẹẹbu yoo bẹrẹ lati ibi ati wa leralera fun awọn faili miiran ti a ko wọle.
Ijade JS ti apamọ wẹẹbu wa yoo wa ninu itọsọna naa dist/. Emi yoo pe faili yii wa js package.
A lo Babel, ati ni pato iṣeto ni @babel/tito-env si gbigbe koodu JS wa fun awọn aṣawakiri agbalagba.
A nlo ohun itanna kan lati yọ gbogbo CSS ti a tọka si nipasẹ awọn faili JS ki o si darapọ wọn ni aye kan. Emi yoo pe e ni tiwa css package.
O le ti ṣe akiyesi awọn orukọ faili package ajeji '[name].[contenthash].ext'. Wọn ninu filename substitutions Apo apamọ wẹẹbu: [name] yoo rọpo pẹlu orukọ aaye titẹ sii (ninu ọran wa, eyi game), ṣugbọn [contenthash] yoo rọpo pẹlu hash ti akoonu faili naa. A ṣe si je ki ise agbese fun hashing - o le sọ fun awọn aṣawakiri lati kaṣe awọn idii JS wa titilai, nitori Ti package ba yipada, lẹhinna orukọ faili tun yipada (ayipada contenthash). Abajade ikẹhin yoo jẹ orukọ faili wiwo game.dbeee76e91a97d0c7207.js.
Ọna webpack.common.js jẹ faili iṣeto ipilẹ ti a gbe wọle sinu idagbasoke ati awọn atunto iṣẹ akanṣe. Eyi ni iṣeto idagbasoke apẹẹrẹ:
Fun ṣiṣe, a lo ninu ilana idagbasoke webpack.dev.js, o si yipada si webpack.prod.jslati mu iwọn package pọ si nigbati o ba nlo si iṣelọpọ.
Eto agbegbe
Mo ṣeduro fifi sori ẹrọ akanṣe lori ẹrọ agbegbe kan ki o le tẹle awọn igbesẹ ti a ṣe akojọ si ni ifiweranṣẹ yii. Eto naa rọrun: akọkọ, eto naa gbọdọ ti fi sii ipade и NPM. Nigbamii o nilo lati ṣe
$ git clone https://github.com/vzhou842/example-.io-game.git
$ cd example-.io-game
$ npm install
ati pe o ti ṣetan lati lọ! Lati bẹrẹ olupin idagbasoke, kan ṣiṣẹ
$ npm run develop
ki o si lọ si ẹrọ aṣawakiri wẹẹbu Agbegbe: 3000. Olupin idagbasoke yoo tun awọn idii JS ati CSS ṣe laifọwọyi bi koodu ṣe yipada - kan sọ oju-iwe naa sọ lati rii gbogbo awọn ayipada!
3. Awọn aaye titẹ sii alabara
Jẹ ki a sọkalẹ lọ si koodu ere funrararẹ. Ni akọkọ a nilo oju-iwe kan index.html, nigbati o ba n ṣabẹwo si aaye naa, ẹrọ aṣawakiri yoo kọkọ ṣajọpọ rẹ. Oju-iwe wa yoo rọrun pupọ:
index.html
Ohun apẹẹrẹ .io game ERE
Apeere koodu yii ti jẹ irọrun diẹ fun mimọ, ati pe Emi yoo ṣe kanna pẹlu ọpọlọpọ awọn apẹẹrẹ ifiweranṣẹ miiran. Awọn koodu ni kikun le nigbagbogbo wa ni bojuwo ni Github.
Eyi le dun idiju, ṣugbọn ko si ohun ti n lọ pupọ nibi:
Gbigbe awọn faili JS lọpọlọpọ miiran wọle.
Gbe wọle CSS (nitorina Webpack mọ lati fi wọn sinu package CSS wa).
Запуск connect() lati fi idi kan asopọ pẹlu olupin ati ṣiṣe awọn downloadAssets() lati ṣe igbasilẹ awọn aworan ti o nilo lati ṣe ere naa.
Lẹhin ipari ipele 3 akojọ aṣayan akọkọ ti han (playMenu).
Ṣiṣeto olutọju fun titẹ bọtini "PLAY". Nigbati awọn bọtini ti wa ni e, initializes awọn koodu si awọn olupin ti a ba wa setan lati mu.
“Eran” akọkọ ti oye-ọrọ olupin-alabara wa wa ninu awọn faili wọnyẹn ti faili naa ko wọle index.js. Bayi a yoo ro gbogbo wọn ni ibere.
4. Paṣipaarọ data onibara
Ninu ere yii, a lo ile-ikawe olokiki kan lati ṣe ibasọrọ pẹlu olupin naa iho.io. Socket.io ni atilẹyin abinibi ayelujara iho, eyiti o baamu daradara fun ibaraẹnisọrọ ọna meji: a le fi awọn ifiranṣẹ ranṣẹ si olupin naa и olupin le fi awọn ifiranṣẹ ranṣẹ si wa lori asopọ kanna.
A yoo ni faili kan src/client/networking.jsti yoo toju gbogbo eniyan ibaraẹnisọrọ pẹlu olupin:
Isakoso awọn oluşewadi kii ṣe pe o nira lati ṣe! Ero akọkọ ni lati tọju ohun kan assets, eyi ti yoo so bọtini orukọ faili mọ iye ohun naa Image. Nigbati awọn oluşewadi ti wa ni ti kojọpọ, a fipamọ sinu ohun kan assets fun awọn ọna wiwọle ni ojo iwaju. Nigbawo ni awọn orisun kọọkan yoo gba laaye lati ṣe igbasilẹ (iyẹn ni, gbogbo awọn orisun), a gba laaye downloadPromise.
Lẹhin igbasilẹ awọn orisun, o le bẹrẹ ṣiṣe. Gẹgẹbi a ti sọ tẹlẹ, lati fa lori oju-iwe wẹẹbu, a lo HTML5 kanfasi (<canvas>). Ere wa rọrun pupọ, nitorinaa a nilo lati fa atẹle wọnyi:
Abẹlẹ
Ọkọ ẹrọ orin
Miiran awọn ẹrọ orin ni awọn ere
Ikarahun
Eyi ni awọn snippets pataki src/client/render.js, eyi ti o ṣe deede awọn ohun mẹrin ti a ṣe akojọ loke:
mú.js
import { getAsset } from './assets';
import { getCurrentState } from './state';
const Constants = require('../shared/constants');
const { PLAYER_RADIUS, PLAYER_MAX_HP, BULLET_RADIUS, MAP_SIZE } = Constants;
// Get the canvas graphics context
const canvas = document.getElementById('game-canvas');
const context = canvas.getContext('2d');
// Make the canvas fullscreen
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
function render() {
const { me, others, bullets } = getCurrentState();
if (!me) {
return;
}
// Draw background
renderBackground(me.x, me.y);
// Draw all bullets
bullets.forEach(renderBullet.bind(null, me));
// Draw all players
renderPlayer(me, me);
others.forEach(renderPlayer.bind(null, me));
}
// ... Helper functions here excluded
let renderInterval = null;
export function startRendering() {
renderInterval = setInterval(render, 1000 / 60);
}
export function stopRendering() {
clearInterval(renderInterval);
}
Yi koodu ti wa ni tun kuru fun wípé.
render() jẹ iṣẹ akọkọ ti faili yii. startRendering() и stopRendering() ṣakoso imuṣiṣẹ ti lupu mu ṣiṣẹ ni 60 FPS.
Awọn imuṣẹ ni pato ti awọn iṣẹ oluranlọwọ onisọtọ (fun apẹẹrẹ renderBullet()) kii ṣe pataki, ṣugbọn eyi ni apẹẹrẹ ti o rọrun kan:
mú.js
function renderBullet(me, bullet) {
const { x, y } = bullet;
context.drawImage(
getAsset('bullet.svg'),
canvas.width / 2 + x - me.x - BULLET_RADIUS,
canvas.height / 2 + y - me.y - BULLET_RADIUS,
BULLET_RADIUS * 2,
BULLET_RADIUS * 2,
);
}
Ṣe akiyesi pe a nlo ọna naa getAsset(), eyi ti a ti ri tẹlẹ ninu asset.js!
Ti o ba nifẹ lati kọ ẹkọ nipa awọn oluranlọwọ ti n ṣe iranlọwọ, lẹhinna ka iyoku. src / onibara / render.js.
6. input ose
O to akoko lati ṣe ere kan playable! Ilana iṣakoso yoo rọrun pupọ: lati yi itọsọna gbigbe pada, o le lo Asin (lori kọnputa) tabi fọwọkan iboju (lori ẹrọ alagbeka). Lati ṣe eyi, a yoo forukọsilẹ Iṣẹlẹ Awọn olutẹtisi fun Asin ati Fọwọkan iṣẹlẹ.
Yoo toju gbogbo eyi src/client/input.js:
igbewọle.js
import { updateDirection } from './networking';
function onMouseInput(e) {
handleInput(e.clientX, e.clientY);
}
function onTouchInput(e) {
const touch = e.touches[0];
handleInput(touch.clientX, touch.clientY);
}
function handleInput(x, y) {
const dir = Math.atan2(x - window.innerWidth / 2, window.innerHeight / 2 - y);
updateDirection(dir);
}
export function startCapturingInput() {
window.addEventListener('mousemove', onMouseInput);
window.addEventListener('touchmove', onTouchInput);
}
export function stopCapturingInput() {
window.removeEventListener('mousemove', onMouseInput);
window.removeEventListener('touchmove', onTouchInput);
}
onMouseInput() и onTouchInput() jẹ Awọn olutẹtisi Iṣẹlẹ ti o pe updateDirection() (lati networking.js) nigbati iṣẹlẹ titẹ sii ba waye (fun apẹẹrẹ, nigbati a ba gbe eku). updateDirection() n kapa fifiranṣẹ pẹlu olupin, eyiti o mu iṣẹlẹ titẹ sii ati ṣe imudojuiwọn ipo ere ni ibamu.
7. Onibara Ipo
Abala yii ni o nira julọ ni apakan akọkọ ti ifiweranṣẹ naa. Maṣe rẹwẹsi ti o ko ba loye rẹ ni igba akọkọ ti o ka! O le paapaa foju rẹ ki o pada si ọdọ rẹ nigbamii.
Awọn ti o kẹhin nkan ti awọn adojuru nilo lati pari awọn ose/koodu olupin jẹ ipinle. Ranti snippet koodu lati apakan Rendering Client?
mú.js
import { getCurrentState } from './state';
function render() {
const { me, others, bullets } = getCurrentState();
// Do the rendering
// ...
}
getCurrentState() yẹ ki o ni anfani lati fun wa ni awọn ti isiyi ipo ti awọn ere ni ose ni eyikeyi ojuami ni akoko da lori awọn imudojuiwọn gba lati olupin. Eyi ni apẹẹrẹ ti imudojuiwọn ere ti olupin le firanṣẹ:
t: Server timestamp nfihan igba ti imudojuiwọn yi da.
me: Alaye nipa ẹrọ orin gbigba imudojuiwọn yii.
awọn miran: Ohun orun ti alaye nipa miiran awọn ẹrọ orin kopa ninu kanna game.
awako: orun ti alaye nipa projectiles ni awọn ere.
adari: Lọwọlọwọ leaderboard data. Ninu ifiweranṣẹ yii, a kii yoo gbero wọn.
7.1 Naive ni ose ipinle
Naive imuse getCurrentState() le nikan taara pada awọn data ti awọn julọ laipe gba game imudojuiwọn.
òfo-ipinlẹ.js
let lastGameUpdate = null;
// Handle a newly received game update.
export function processGameUpdate(update) {
lastGameUpdate = update;
}
export function getCurrentState() {
return lastGameUpdate;
}
O dara ati kedere! Ṣugbọn ti o ba jẹ pe o rọrun. Ọkan ninu awọn idi ti imuse yii jẹ iṣoro: o se idinwo awọn Rendering fireemu oṣuwọn to olupin aago oṣuwọn.
Iwọn fireemu: nọmba awọn fireemu (ie awọn ipe render()) fun iṣẹju-aaya, tabi FPS. Awọn ere nigbagbogbo ngbiyanju lati ṣaṣeyọri o kere ju 60 FPS.
Oṣuwọn ami si: Awọn igbohunsafẹfẹ ni eyi ti awọn olupin rán game awọn imudojuiwọn to ibara. Nigbagbogbo o kere ju iwọn fireemu lọ. Ninu ere wa, olupin naa n ṣiṣẹ ni igbohunsafẹfẹ ti awọn iyipo 30 fun iṣẹju kan.
Ti a ba kan mu imudojuiwọn tuntun ti ere naa, lẹhinna FPS kii yoo ni pataki ju 30 lọ, nitori a ko gba diẹ sii ju awọn imudojuiwọn 30 fun iṣẹju kan lati olupin naa. Paapa ti a ba pe render() Awọn akoko 60 fun iṣẹju kan, lẹhinna idaji awọn ipe wọnyi yoo kan tun ṣe ohun kanna, ni pataki ko ṣe nkankan. Iṣoro miiran pẹlu imuse ti o rọrun ni pe o prone to idaduro. Pẹlu iyara Intanẹẹti pipe, alabara yoo gba imudojuiwọn ere ni deede ni gbogbo 33ms (30 fun iṣẹju kan):
Laanu, ko si ohun ti o pe. Aworan ti o daju diẹ sii yoo jẹ:
Imuse ti o rọrun jẹ iṣe ọran ti o buru julọ nigbati o ba de lairi. Ti imudojuiwọn ere ba gba pẹlu idaduro 50ms, lẹhinna onibara ibùso afikun 50ms nitori pe o tun n funni ni ipo ere lati imudojuiwọn iṣaaju. O le fojuinu bawo ni eyi ṣe korọrun fun ẹrọ orin: braking lainidii yoo jẹ ki ere naa rilara ati riru.
7.2 Dara si ni ose ipinle
A yoo ṣe diẹ ninu awọn ilọsiwaju si imuse rọrun. Ni akọkọ, a lo idaduro Rendering fun 100 ms. Eyi tumọ si pe ipo “lọwọlọwọ” ti alabara nigbagbogbo yoo dinku lẹhin ipo ere lori olupin nipasẹ 100ms. Fun apẹẹrẹ, ti akoko lori olupin ba jẹ 150, lẹhinna alabara yoo funni ni ipo ti olupin naa wa ni akoko naa 50:
Eyi fun wa ni ifipamọ 100ms lati ye awọn akoko imudojuiwọn ere ti a ko sọ asọtẹlẹ:
Isanwo fun eyi yoo wa titi lailai aisun igbewọle fun 100 ms. Eyi jẹ irubọ kekere fun imuṣere oriire - pupọ julọ awọn oṣere (paapaa awọn oṣere lasan) kii yoo paapaa ṣe akiyesi idaduro yii. O rọrun pupọ fun eniyan lati ṣatunṣe si aiduro 100ms igbagbogbo ju ti o jẹ lati mu ṣiṣẹ pẹlu airi airotẹlẹ.
A tun le lo ilana miiran ti a npe ni ose-ẹgbẹ asọtẹlẹ, eyi ti o ṣe iṣẹ ti o dara lati dinku idinku ti a ti fiyesi, ṣugbọn kii yoo bo ni ipo yii.
Ilọsiwaju miiran ti a nlo ni laini interpolation. Nitori aisun Rendering, a nigbagbogbo jẹ o kere ju imudojuiwọn kan ṣaaju akoko lọwọlọwọ ni alabara. Nigbati a npe ni getCurrentState(), a le ṣiṣẹ laini interpolation laarin awọn imudojuiwọn ere ṣaaju ati lẹhin akoko lọwọlọwọ ni alabara:
Eyi yanju ọran oṣuwọn fireemu: a le ṣe awọn fireemu alailẹgbẹ ni oṣuwọn fireemu eyikeyi ti a fẹ!
7.3 Ṣiṣe imudara ipo alabara
Apẹẹrẹ imuse ni src/client/state.js nlo mejeeji jigbe aisun ati laini interpolation, sugbon ko fun gun. Jẹ ki a fọ koodu naa si awọn ẹya meji. Eyi ni akọkọ:
state.js apa 1
const RENDER_DELAY = 100;
const gameUpdates = [];
let gameStart = 0;
let firstServerTimestamp = 0;
export function initState() {
gameStart = 0;
firstServerTimestamp = 0;
}
export function processGameUpdate(update) {
if (!firstServerTimestamp) {
firstServerTimestamp = update.t;
gameStart = Date.now();
}
gameUpdates.push(update);
// Keep only one game update before the current server time
const base = getBaseUpdate();
if (base > 0) {
gameUpdates.splice(0, base);
}
}
function currentServerTime() {
return firstServerTimestamp + (Date.now() - gameStart) - RENDER_DELAY;
}
// Returns the index of the base update, the first game update before
// current server time, or -1 if N/A.
function getBaseUpdate() {
const serverTime = currentServerTime();
for (let i = gameUpdates.length - 1; i >= 0; i--) {
if (gameUpdates[i].t <= serverTime) {
return i;
}
}
return -1;
}
Ni igba akọkọ ti Igbese ni lati ro ero ohun ti currentServerTime(). Gẹgẹbi a ti rii ni iṣaaju, gbogbo imudojuiwọn ere pẹlu akoko olupin olupin kan. A fẹ lati lo lairi lati mu aworan naa 100ms lẹhin olupin, ṣugbọn a kii yoo mọ akoko lọwọlọwọ lori olupin naa, nitori a ko le mọ bi o gun ti o gba fun eyikeyi ninu awọn imudojuiwọn lati gba si wa. Intanẹẹti jẹ aisọtẹlẹ ati iyara rẹ le yatọ pupọ!
Lati wa ni ayika isoro yi, a le lo a reasonable isunmọ: a dibọn akọkọ imudojuiwọn de lesekese. Ti eyi ba jẹ otitọ, lẹhinna a yoo mọ akoko olupin ni akoko pataki yii! A tọju awọn timestamp olupin sinu firstServerTimestamp ki o si pa wa mọ agbegbe (onibara) timestamp ni akoko kanna ni gameStart.
Oh duro. Ṣe ko yẹ ki o jẹ akoko olupin = akoko alabara? Kilode ti a fi ṣe iyatọ laarin "tamp olupin" ati "timestamp onibara"? Eyi jẹ ibeere nla! O wa ni pe wọn kii ṣe ohun kanna. Date.now() yoo da pada orisirisi timestamps ni ose ati olupin, ati awọn ti o da lori awọn okunfa agbegbe si awọn wọnyi ero. Maṣe ro pe awọn akoko akoko yoo jẹ kanna lori gbogbo awọn ẹrọ.
Bayi a loye ohun ti o ṣe currentServerTime(): o pada timestamp olupin ti akoko imupada lọwọlọwọ. Ni awọn ọrọ miiran, eyi ni akoko lọwọlọwọ olupin (firstServerTimestamp <+ (Date.now() - gameStart)) iyokuro idaduro idaduro (RENDER_DELAY).
Bayi jẹ ki ká wo ni bi a ti mu awọn imudojuiwọn ere. Nigbati o ba gba lati ọdọ olupin imudojuiwọn, o pe processGameUpdate()ati pe a fipamọ imudojuiwọn tuntun si opo kan gameUpdates. Lẹhinna, lati ṣayẹwo lilo iranti, a yọ gbogbo awọn imudojuiwọn atijọ kuro ṣaaju ipilẹ imudojuiwọnnitori a ko nilo wọn mọ.
Kini "imudojuiwọn ipilẹ"? Eyi imudojuiwọn akọkọ ti a rii nipa gbigbe sẹhin lati akoko lọwọlọwọ olupin naa. Ranti aworan atọka yii?
Imudojuiwọn ere taara si apa osi ti “Aago Tunṣe Onibara” jẹ imudojuiwọn ipilẹ.
Kini imudojuiwọn ipilẹ ti a lo fun? Kini idi ti a le fi awọn imudojuiwọn silẹ si ipilẹ? Lati ro ero eyi, jẹ ki a nipari ro imuse getCurrentState():
state.js apa 2
export function getCurrentState() {
if (!firstServerTimestamp) {
return {};
}
const base = getBaseUpdate();
const serverTime = currentServerTime();
// If base is the most recent update we have, use its state.
// Else, interpolate between its state and the state of (base + 1).
if (base < 0) {
return gameUpdates[gameUpdates.length - 1];
} else if (base === gameUpdates.length - 1) {
return gameUpdates[base];
} else {
const baseUpdate = gameUpdates[base];
const next = gameUpdates[base + 1];
const r = (serverTime - baseUpdate.t) / (next.t - baseUpdate.t);
return {
me: interpolateObject(baseUpdate.me, next.me, r),
others: interpolateObjectArray(baseUpdate.others, next.others, r),
bullets: interpolateObjectArray(baseUpdate.bullets, next.bullets, r),
};
}
}
A ṣe itọju awọn ọran mẹta:
base < 0 tumọ si pe ko si awọn imudojuiwọn titi di akoko ṣiṣe lọwọlọwọ (wo imuse loke getBaseUpdate()). Eleyi le ṣẹlẹ ọtun ni awọn ibere ti awọn ere nitori lati Rendering aisun. Ni idi eyi, a lo imudojuiwọn tuntun ti o gba.
base jẹ imudojuiwọn to ṣẹṣẹ julọ ti a ni. Eyi le ṣẹlẹ nitori airi nẹtiwọki tabi asopọ intanẹẹti ti ko dara. Ni idi eyi, a tun nlo imudojuiwọn tuntun ti a ni.
A ni imudojuiwọn mejeeji ṣaaju ati lẹhin akoko ṣiṣe lọwọlọwọ, nitorinaa a le interpolate!
Gbogbo nkan ti o ku ninu state.js jẹ imuse ti interpolation laini ti o rọrun (ṣugbọn alaidun) mathimatiki. Ti o ba fẹ lati ṣawari rẹ funrararẹ, lẹhinna ṣii state.js on Github.
Apá 2. Backend server
Ni apakan yii, a yoo wo ẹhin Node.js ti o ṣakoso wa .io ere apẹẹrẹ.
1. Server titẹsi Point
Lati ṣakoso olupin wẹẹbu, a yoo lo ilana wẹẹbu olokiki fun Node.js ti a pe han. Yoo jẹ tunto nipasẹ faili aaye titẹsi olupin wa src/server/server.js:
server.js apakan 1
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackConfig = require('../../webpack.dev.js');
// Setup an Express server
const app = express();
app.use(express.static('public'));
if (process.env.NODE_ENV === 'development') {
// Setup Webpack for development
const compiler = webpack(webpackConfig);
app.use(webpackDevMiddleware(compiler));
} else {
// Static serve the dist/ folder in production
app.use(express.static('dist'));
}
// Listen on port
const port = process.env.PORT || 3000;
const server = app.listen(port);
console.log(`Server listening on port ${port}`);
Ranti pe ni apakan akọkọ ti a sọrọ lori Webpack? Eyi ni ibi ti a yoo lo awọn atunto Webpack wa. A yoo lo wọn ni awọn ọna meji:
Lẹhin ti iṣeto ni ifijišẹ socket.io asopọ si olupin naa, a ṣeto awọn olutọju iṣẹlẹ fun iho tuntun naa. Awọn oluṣakoso iṣẹlẹ mu awọn ifiranṣẹ ti a gba lati ọdọ awọn alabara lọwọ nipasẹ fifisilẹ si ohun kan ṣoṣo game:
server.js apakan 3
const Game = require('./game');
// ...
// Setup the Game
const game = new Game();
function joinGame(username) {
game.addPlayer(this, username);
}
function handleInput(dir) {
game.handleInput(this, dir);
}
function onDisconnect() {
game.removePlayer(this);
}
A n ṣẹda ere .io, nitorinaa a nilo ẹda kan nikan Game ("Ere") - gbogbo awọn oṣere ṣere ni aaye kanna! Ni apakan ti o tẹle, a yoo rii bi kilasi yii ṣe n ṣiṣẹ. Game.
2. game apèsè
Класс Game ni awọn pataki kannaa lori olupin ẹgbẹ. O ni awọn iṣẹ akọkọ meji: player isakoso и ere kikopa.
Jẹ ká bẹrẹ pẹlu awọn akọkọ-ṣiṣe, player isakoso.
game.js apakan 1
const Constants = require('../shared/constants');
const Player = require('./player');
class Game {
constructor() {
this.sockets = {};
this.players = {};
this.bullets = [];
this.lastUpdateTime = Date.now();
this.shouldSendUpdate = false;
setInterval(this.update.bind(this), 1000 / 60);
}
addPlayer(socket, username) {
this.sockets[socket.id] = socket;
// Generate a position to start this player at.
const x = Constants.MAP_SIZE * (0.25 + Math.random() * 0.5);
const y = Constants.MAP_SIZE * (0.25 + Math.random() * 0.5);
this.players[socket.id] = new Player(socket.id, username, x, y);
}
removePlayer(socket) {
delete this.sockets[socket.id];
delete this.players[socket.id];
}
handleInput(socket, dir) {
if (this.players[socket.id]) {
this.players[socket.id].setDirection(dir);
}
}
// ...
}
Ninu ere yii, a yoo ṣe idanimọ awọn oṣere nipasẹ aaye id socket.io socket wọn (ti o ba ni idamu, lẹhinna pada si server.js). Socket.io funrararẹ n fun iho kọọkan ni alailẹgbẹ idnitorina a ko nilo lati ṣe aniyan nipa iyẹn. Emi yoo pe e ID ẹrọ orin.
Pẹlu iyẹn ni lokan, jẹ ki a ṣawari awọn oniyipada apẹẹrẹ ni kilasi kan Game:
sockets jẹ ohun ti o sopọ ID ẹrọ orin si iho ti o ni nkan ṣe pẹlu ẹrọ orin. O gba wa laaye lati wọle si awọn iho nipasẹ awọn ID ẹrọ orin wọn ni akoko igbagbogbo.
players jẹ ohun kan ti o sopọ ID ẹrọ orin si koodu>Ohun ẹrọ orin
bullets jẹ ẹya orun ti ohun Bullet, eyi ti o ni ko si definite ibere. lastUpdateTime ni timestamp ti o kẹhin akoko awọn ere ti a imudojuiwọn. A yoo rii bi o ṣe nlo laipẹ. shouldSendUpdate jẹ oluranlọwọ oniyipada. A yoo tun rii lilo rẹ laipẹ.
Awọn ọna addPlayer(), removePlayer() и handleInput() ko si ye lati se alaye, ti won ti wa ni lo ninu server.js. Ti o ba nilo lati sọ iranti rẹ sọtun, lọ sẹhin diẹ ga julọ.
Laini ikẹhin constructor() bẹrẹ soke imudojuiwọn ọmọ awọn ere (pẹlu igbohunsafẹfẹ ti awọn imudojuiwọn 60 / s):
game.js apakan 2
const Constants = require('../shared/constants');
const applyCollisions = require('./collisions');
class Game {
// ...
update() {
// Calculate time elapsed
const now = Date.now();
const dt = (now - this.lastUpdateTime) / 1000;
this.lastUpdateTime = now;
// Update each bullet
const bulletsToRemove = [];
this.bullets.forEach(bullet => {
if (bullet.update(dt)) {
// Destroy this bullet
bulletsToRemove.push(bullet);
}
});
this.bullets = this.bullets.filter(
bullet => !bulletsToRemove.includes(bullet),
);
// Update each player
Object.keys(this.sockets).forEach(playerID => {
const player = this.players[playerID];
const newBullet = player.update(dt);
if (newBullet) {
this.bullets.push(newBullet);
}
});
// Apply collisions, give players score for hitting bullets
const destroyedBullets = applyCollisions(
Object.values(this.players),
this.bullets,
);
destroyedBullets.forEach(b => {
if (this.players[b.parentID]) {
this.players[b.parentID].onDealtDamage();
}
});
this.bullets = this.bullets.filter(
bullet => !destroyedBullets.includes(bullet),
);
// Check if any players are dead
Object.keys(this.sockets).forEach(playerID => {
const socket = this.sockets[playerID];
const player = this.players[playerID];
if (player.hp <= 0) {
socket.emit(Constants.MSG_TYPES.GAME_OVER);
this.removePlayer(socket);
}
});
// Send a game update to each player every other time
if (this.shouldSendUpdate) {
const leaderboard = this.getLeaderboard();
Object.keys(this.sockets).forEach(playerID => {
const socket = this.sockets[playerID];
const player = this.players[playerID];
socket.emit(
Constants.MSG_TYPES.GAME_UPDATE,
this.createUpdate(player, leaderboard),
);
});
this.shouldSendUpdate = false;
} else {
this.shouldSendUpdate = true;
}
}
// ...
}
Ọna update() ni boya nkan pataki julọ ti ọgbọn ẹgbẹ olupin. Eyi ni ohun ti o ṣe, ni ibere:
Iṣiro ohun ti akoko ti o jẹ dt koja niwon awọn ti o kẹhin update().
Ntu kọọkan projectile ati run wọn ti o ba wulo. A yoo rii imuse ti iṣẹ yii nigbamii. Fun bayi o ti to fun wa lati mọ iyẹn bullet.update()pada trueti o ba ti projectile yẹ ki o run (o jade kuro ni gbagede).
Mu kọọkan player ati spawns a projectile ti o ba wulo. A yoo tun rii imuse yii nigbamii - player.update()le da ohun kan pada Bullet.
Sọwedowo fun collisions laarin projectiles ati awọn ẹrọ orin pẹlu applyCollisions(), eyi ti o pada ohun orun ti projectiles ti o lu awọn ẹrọ orin. Fun kọọkan projectile pada, a mu awọn ojuami ti awọn ẹrọ orin ti o kuro lenu ise (lilo player.onDealtDamage()) ati ki o si yọ awọn projectile lati orun bullets.
Notifies ati ki o run gbogbo awọn ẹrọ orin pa.
Fi imudojuiwọn ere ranṣẹ si gbogbo awọn oṣere gbogbo iṣẹju igba nigba ti a npe ni update(). Eyi ṣe iranlọwọ fun wa lati tọju oniyipada oniranlọwọ ti a mẹnuba loke. shouldSendUpdate. Bi update() ti a npe ni 60 igba / s, a fi game awọn imudojuiwọn 30 igba / s. Bayi, aago igbohunsafẹfẹ Aago olupin jẹ awọn aago 30 / s (a sọrọ nipa awọn oṣuwọn aago ni apakan akọkọ).
Kini idi ti o fi awọn imudojuiwọn ere ranṣẹ nikan nipasẹ akoko ? Lati fi ikanni pamọ. Awọn imudojuiwọn ere 30 fun iṣẹju keji jẹ pupọ!
Idi ti ko kan pe update() 30 igba fun keji? Lati mu kikopa ere dara si. Awọn diẹ igba ti a npe ni update(), deede diẹ sii kikopa ere yoo jẹ. Ṣugbọn maṣe gbe lọ pẹlu nọmba awọn italaya. update(), nitori eyi jẹ iṣẹ-ṣiṣe ti o gbowolori ni iṣiro - 60 fun iṣẹju kan ti to.
Awọn iyokù ti awọn kilasi Game ni awọn ọna oluranlọwọ ti a lo ninu update():
getLeaderboard() lẹwa o rọrun - o lẹsẹsẹ awọn ẹrọ orin nipa Dimegilio, gba awọn oke marun, ati ki o pada awọn orukọ olumulo ati Dimegilio fun kọọkan.
createUpdate() lo ninu update() lati ṣẹda awọn imudojuiwọn ere ti o pin si awọn ẹrọ orin. Awọn oniwe-akọkọ-ṣiṣe ni lati pe awọn ọna serializeForUpdate()muse fun awọn kilasi Player и Bullet. Akiyesi pe o nikan koja data si kọọkan player nipa sunmọ julọ awọn oṣere ati awọn iṣẹ akanṣe - ko si iwulo lati atagba alaye nipa awọn nkan ere ti o jinna si ẹrọ orin naa!
3. Ere ohun lori olupin
Ninu ere wa, awọn ẹrọ akanṣe ati awọn oṣere jọra gaan: wọn jẹ áljẹbrà, yika, awọn nkan ere gbigbe. Lati lo anfani ti ibajọra yii laarin awọn oṣere ati awọn iṣẹ akanṣe, jẹ ki a bẹrẹ nipasẹ imuse kilasi mimọ Object:
Ko si ohun idiju ti o ṣẹlẹ nibi. Kilasi yii yoo jẹ aaye oran ti o dara fun itẹsiwaju naa. Jẹ ká wo bi awọn kilasi Bullet awọn lilo Object:
ọta ibọn.js
const shortid = require('shortid');
const ObjectClass = require('./object');
const Constants = require('../shared/constants');
class Bullet extends ObjectClass {
constructor(parentID, x, y, dir) {
super(shortid(), x, y, dir, Constants.BULLET_SPEED);
this.parentID = parentID;
}
// Returns true if the bullet should be destroyed
update(dt) {
super.update(dt);
return this.x < 0 || this.x > Constants.MAP_SIZE || this.y < 0 || this.y > Constants.MAP_SIZE;
}
}
Imuse Bullet kukuru pupọ! A ti fi kun si Object nikan awọn amugbooro wọnyi:
Lilo package kan shortid fun ID iran id projectile.
Fifi aaye kan kun parentIDki o le orin ẹrọ orin ti o da yi projectile.
Fifi iye pada si update(), eyiti o dọgba si trueti o ba ti projectile ni ita arene (ranti a ti sọrọ nipa yi ni awọn ti o kẹhin apakan?).
Jẹ ki a lọ si Player:
elere.js
const ObjectClass = require('./object');
const Bullet = require('./bullet');
const Constants = require('../shared/constants');
class Player extends ObjectClass {
constructor(id, username, x, y) {
super(id, x, y, Math.random() * 2 * Math.PI, Constants.PLAYER_SPEED);
this.username = username;
this.hp = Constants.PLAYER_MAX_HP;
this.fireCooldown = 0;
this.score = 0;
}
// Returns a newly created bullet, or null.
update(dt) {
super.update(dt);
// Update score
this.score += dt * Constants.SCORE_PER_SECOND;
// Make sure the player stays in bounds
this.x = Math.max(0, Math.min(Constants.MAP_SIZE, this.x));
this.y = Math.max(0, Math.min(Constants.MAP_SIZE, this.y));
// Fire a bullet, if needed
this.fireCooldown -= dt;
if (this.fireCooldown <= 0) {
this.fireCooldown += Constants.PLAYER_FIRE_COOLDOWN;
return new Bullet(this.id, this.x, this.y, this.direction);
}
return null;
}
takeBulletDamage() {
this.hp -= Constants.BULLET_DAMAGE;
}
onDealtDamage() {
this.score += Constants.SCORE_BULLET_HIT;
}
serializeForUpdate() {
return {
...(super.serializeForUpdate()),
direction: this.direction,
hp: this.hp,
};
}
}
Awọn oṣere jẹ eka sii ju awọn iṣẹ akanṣe, nitorinaa awọn aaye diẹ diẹ sii yẹ ki o wa ni ipamọ ni kilasi yii. Ọna rẹ update() ṣe kan pupo ti ise, ni pato, pada awọn rinle da projectile ti o ba ti nibẹ ni o wa kò osi fireCooldown (ranti a ti sọrọ nipa eyi ni apakan ti tẹlẹ?). Bakannaa o gbooro ọna naa serializeForUpdate(), nitori a nilo lati ni awọn aaye afikun fun ẹrọ orin ni imudojuiwọn ere.
Nini kilasi mimọ Object - ohun pataki igbese lati yago fun tun koodu. Fun apẹẹrẹ, ko si kilasi Object gbogbo ohun ere gbọdọ ni imuse kanna distanceTo(), ati daakọ-sọ gbogbo awọn imuse wọnyi kọja awọn faili lọpọlọpọ yoo jẹ alaburuku. Eyi di pataki paapaa fun awọn iṣẹ akanṣe nla.nigbati awọn nọmba ti jù Object awọn kilasi ti wa ni dagba.
4. Iwari ijamba
Awọn nikan ohun osi fun a da nigbati awọn projectiles lu awọn ẹrọ orin! Ranti nkan koodu yii lati ọna naa update() ninu kilasi Game:
ere.js
const applyCollisions = require('./collisions');
class Game {
// ...
update() {
// ...
// Apply collisions, give players score for hitting bullets
const destroyedBullets = applyCollisions(
Object.values(this.players),
this.bullets,
);
destroyedBullets.forEach(b => {
if (this.players[b.parentID]) {
this.players[b.parentID].onDealtDamage();
}
});
this.bullets = this.bullets.filter(
bullet => !destroyedBullets.includes(bullet),
);
// ...
}
}
A nilo lati lo ọna naa applyCollisions(), eyi ti o pada gbogbo awọn projectiles ti o lu awọn ẹrọ orin. Ni Oriire, kii ṣe pe o nira lati ṣe nitori
Gbogbo awọn nkan ikọlu jẹ awọn iyika, eyiti o jẹ apẹrẹ ti o rọrun julọ lati ṣe iwari ijamba.
A ti ni ọna kan tẹlẹ distanceTo(), eyiti a ṣe ni apakan ti tẹlẹ ninu kilasi naa Object.
Eyi ni ohun ti imuse wiwa ikọlu wa dabi:
ijamba.js
const Constants = require('../shared/constants');
// Returns an array of bullets to be destroyed.
function applyCollisions(players, bullets) {
const destroyedBullets = [];
for (let i = 0; i < bullets.length; i++) {
// Look for a player (who didn't create the bullet) to collide each bullet with.
// As soon as we find one, break out of the loop to prevent double counting a bullet.
for (let j = 0; j < players.length; j++) {
const bullet = bullets[i];
const player = players[j];
if (
bullet.parentID !== player.id &&
player.distanceTo(bullet) <= Constants.PLAYER_RADIUS + Constants.BULLET_RADIUS
) {
destroyedBullets.push(bullet);
player.takeBulletDamage();
break;
}
}
}
return destroyedBullets;
}
Wiwa ijamba ti o rọrun yii da lori otitọ pe awọn iyika meji kọlu ti aaye laarin awọn ile-iṣẹ wọn kere ju iye awọn radi wọn lọ. Eyi ni ọran nibiti aaye laarin awọn ile-iṣẹ ti awọn iyika meji jẹ deede deede si apao awọn radii wọn:
Awọn abala tọkọtaya diẹ sii wa lati ronu nibi:
Awọn projectile gbọdọ ko lu awọn ẹrọ orin ti o da o. Eyi le ṣee ṣe nipa ifiwera bullet.parentID с player.id.
Awọn projectile gbọdọ nikan lu ni kete ti ni awọn idiwon nla ti ọpọ awọn ẹrọ orin colliding ni akoko kanna. A yoo yanju iṣoro yii nipa lilo oniṣẹ break: ni kete ti ẹrọ orin colliding pẹlu awọn projectile ti wa ni ri, a da awọn àwárí ati ki o gbe lori si tókàn projectile.
ipari
Gbogbo ẹ niyẹn! A ti bo ohun gbogbo ti o nilo lati mọ lati ṣẹda ere wẹẹbu .io kan. Kini atẹle? Kọ ere .io tirẹ!
Gbogbo koodu ayẹwo wa ni ṣiṣi orisun ati firanṣẹ lori Github.