Ke hana ʻana i kahi pāʻani Pūnaewele .io Multiplayer
Hoʻokuʻu ʻia i ka makahiki 2015 Agar.io lilo i kupuna no kekahi ano hou pāʻani .ioi ulu a kaulana mai ia manawa. Ua ʻike pilikino wau i ka piʻi ʻana o ka kaulana o nā pāʻani .io: i nā makahiki ʻekolu i hala iho nei, ua loaʻa iaʻu hana a kūʻai aku i ʻelua pāʻani o kēia ʻano..
Inā ʻaʻole ʻoe i lohe mua i kēia mau pāʻani, he mau pāʻani pūnaewele Multiplayer manuahi kēia i maʻalahi ke pāʻani (ʻaʻole koi ʻia kahi moʻokāki). Kūʻē lākou i nā mea pāʻani kūʻē he nui ma ke kahua hoʻokahi. Nā pāʻani .io kaulana ʻē aʻe: ʻO Slither.io и Diep.io.
Ma kēia pou, e ʻimi mākou pehea hana i .io pāʻani mai ka wā kahiko. No kēia mea, lawa ka ʻike o Javascript: pono ʻoe e hoʻomaopopo i nā mea like syntax ES6, huaʻōlelo this и olelo pomaikai. ʻOiai ʻaʻole ʻoe ʻike pono iā Javascript, hiki iā ʻoe ke hoʻomaopopo i ka hapa nui o ka pou.
laʻana pāʻani .io
No ke kōkua aʻo ʻana, e nānā mākou laʻana pāʻani .io. E ho'āʻo e pāʻani!
He mea maʻalahi ka pāʻani: ke hoʻomalu nei ʻoe i kahi moku ma kahi kahua kahi e loaʻa ai nā mea pāʻani ʻē aʻe. Hoʻopau koke kāu moku i nā projectiles a hoʻāʻo ʻoe e pā i nā mea pāʻani ʻē aʻe me ka pale ʻana i kā lākou projectiles.
1. Hōʻike pōkole pōkole o ka papahana
Manaʻo wau kiʻi kumu kumu laʻana pāʻani i hiki iā ʻoe ke hahai mai iaʻu.
Hoʻohana ka laʻana i kēia:
ka hoike ʻo ia ka mea kaulana loa ʻo Node.js pūnaewele puni honua e hoʻokele nei i ka kikowaena pūnaewele o ka pāʻani.
kumu.io - he waihona waihona no ka hoʻololi ʻana i ka ʻikepili ma waena o kahi polokalamu kele pūnaewele a me kahi kikowaena.
Pūnaewele Pūnaewele - manakia module. Hiki iā ʻoe ke heluhelu e pili ana i ke kumu e hoʻohana ai i ka Webpack. maanei.
ʻO nā mea a pau i loko o kahi waihona public/ e waiho ʻia e ke kikowaena. IN public/assets/ aia nā kiʻi i hoʻohana ʻia e kā mākou papahana.
src /
Aia nā code kumu a pau i loko o ka waihona src/. Nā poʻo inoa client/ и server/ olelo no lakou iho a shared/ Loaʻa i kahi faila mau i lawe ʻia e ka mea kūʻai aku a me ke kikowaena.
2. Nā hoʻonohonoho hui/papahana
E like me ka mea i ʻōlelo ʻia ma luna, hoʻohana mākou i ka mana module e kūkulu i ka papahana. Pūnaewele Pūnaewele. E nānā i kā mākou hoʻonohonoho Webpack:
src/client/index.js ʻo ia ka helu komo o ka mea kūʻai Javascript (JS). E hoʻomaka ana ʻo Webpack mai ʻaneʻi a huli hou i nā faila i lawe ʻia mai.
E loaʻa i ka papa kuhikuhi ka puka JS o kā mākou Webpack kūkulu dist/. E kapa wau i kēia faila i kā mākou js pūʻolo.
Hoʻohana mākou 'o Babel, a me ka hoʻonohonoho pono @babel/preset-env e unuhi i kā mākou JS code no nā polokalamu kele kahiko.
Ke hoʻohana nei mākou i kahi plugin e unuhi i nā CSS āpau i kuhikuhi ʻia e nā faila JS a hoʻohui iā lākou i kahi hoʻokahi. E kapa aku au iā mākou pūʻolo css.
Ua ʻike paha ʻoe i nā inoa faila ʻē aʻe '[name].[contenthash].ext'. Loaʻa iā lākou hoʻololi inoa faila Pūnaewele: [name] e hoʻololi ʻia me ka inoa o ka helu helu (i kā mākou hihia, kēia game), aii [contenthash] e hoʻololi ʻia me ka hash o ka waihona. Hana mākou ia hoʻonui i ka papahana no ka hashing - hiki iā ʻoe ke haʻi aku i nā mākaʻikaʻi e hūnā i kā mākou pūʻolo JS no ka pau ʻole, no ka mea inā hoʻololi kekahi pūʻolo, a laila hoʻololi pū kona inoa faila (hoʻololi contenthash). ʻO ka hopena hope ka inoa o ka faila ʻike game.dbeee76e91a97d0c7207.js.
waihona webpack.common.js - ʻO kēia ka waihona hoʻonohonoho kumu a mākou e hoʻokomo ai i ka hoʻomohala a hoʻopau i nā hoʻonohonoho papahana. Eia kekahi laʻana hoʻonohonoho hoʻomohala:
No ka pono, hoʻohana mākou i ka hana hoʻomohala webpack.dev.js, a hoʻololi i webpack.prod.jse hoʻonui i ka nui o ka pūʻolo i ka wā e kau ai i ka hana.
Hoʻonohonoho kūloko
Manaʻo wau e kau i ka papahana ma kāu mīkini kūloko i hiki iā ʻoe ke hahai i nā ʻanuʻu i helu ʻia ma kēia pou. He maʻalahi ka hoʻonohonoho: ʻo ka mua, pono ka ʻōnaehana wahi и NPM. A laila pono ʻoe e hana
$ git clone https://github.com/vzhou842/example-.io-game.git
$ cd example-.io-game
$ npm install
a ua mākaukau ʻoe e hele! No ka hoʻomaka ʻana i ke kikowaena hoʻomohala, holo wale
$ npm run develop
a hele i ka polokalamu kele pūnaewele localhost: 3000. E kūkulu hou ke kikowaena hoʻomohala i nā pūʻolo JS a me CSS i ka wā e loli ai nā code - e hōʻoluʻolu wale i ka ʻaoʻao e ʻike i nā loli āpau!
3. Nā Manaʻo Hoʻokomo Mea Kūʻai
E iho kākou i ke code game pono'ī. Pono mua mākou i kahi ʻaoʻao index.html, i ka wā e kipa aku ai i ka pūnaewele, e hoʻouka mua ka polokalamu kele pūnaewele. E maʻalahi loa kā mākou ʻaoʻao:
index.html
He laʻana .io pāʻani PALI
Ua maʻalahi iki kēia hiʻohiʻona code no ka akaka, a e hana like wau me ka nui o nā hiʻohiʻona post. Hiki ke nānā 'ia ke code piha ma Github.
Aia iā mākou:
ʻElemu canvas HTML5 (<canvas>) a mākou e hoʻohana ai e hoʻolilo i ka pāʻani.
<link> e hoʻohui i kā mākou pūʻolo CSS.
<script> e hoʻohui i kā mākou pūʻolo Javascript.
Papa kuhikuhi nui me ka mea hoʻohana <input> a me ke pihi PLAY (<button>).
Ma hope o ka hoʻouka ʻana i ka ʻaoʻao home, e hoʻomaka ka polokalamu kele e hoʻokō i ka code Javascript, e hoʻomaka ana mai ka waihona JS helu komo: src/client/index.js.
He paʻakikī paha kēia, akā ʻaʻole nui ka hana ma aneʻi:
Ke lawe mai nei i kekahi mau faila JS ʻē aʻe.
CSS import (no laila ʻike ʻo Webpack e hoʻokomo iā lākou i kā mākou pūʻolo CSS).
Hoʻollo connect() e hoʻokumu i kahi pilina me ke kikowaena a holo downloadAssets() e kiʻi i nā kiʻi pono e hoʻolilo i ka pāʻani.
Ma hope o ka pau ʻana o ka pae 3 hōʻike ʻia ka papa kuhikuhi nui (playMenu).
Hoʻonohonoho i ka mea hoʻohana no ke kaomi ʻana i ke pihi "PLAY". Ke paʻi ʻia ke pihi, hoʻomaka ke code i ka pāʻani a haʻi i ke kikowaena ua mākaukau mākou e pāʻani.
ʻO ka "ʻiʻo" nui o kā mākou mea kūʻai aku-server logic aia i kēlā mau faila i lawe ʻia e ka faila index.js. I kēia manawa e noʻonoʻo mākou iā lākou a pau i ka hoʻonohonoho.
4. Hoʻololi i ka ʻikepili o ka mea kūʻai aku
Ma kēia pāʻani, hoʻohana mākou i kahi hale waihona puke kaulana e kamaʻilio me ke kikowaena kumu.io. Loaʻa iā Socket.io ke kākoʻo maoli Pūnaewele Pūnaewele, i kūpono no ke kamaʻilio ʻelua ala: hiki iā mākou ke hoʻouna i nā leka i ke kikowaena и hiki i ke kikowaena ke hoʻouna i nā leka iā mākou ma ka pilina like.
E loaʻa iā mākou hoʻokahi faila src/client/networking.jsnana e malama nā mea a pau kamaʻilio me ke kikowaena:
ʻAʻole paʻakikī loa ka hoʻokō ʻana i ka hoʻokele waiwai! ʻO ka manaʻo nui ka mālama ʻana i kahi mea assets, e hoʻopaʻa i ke kī o ka filename i ka waiwai o ka mea Image. Ke hoʻouka ʻia ka punawai, mālama mākou i loko o kahi mea assets no ka hiki wawe i keia mua aku. I ka manawa hea e ʻae ʻia ai kēlā me kēia kumuwaiwai e hoʻoiho (ʻo ia hoʻi, nā mea a pau nā kumuwaiwai), ʻae mākou downloadPromise.
Ma hope o ka hoʻoiho ʻana i nā kumuwaiwai, hiki iā ʻoe ke hoʻomaka i ka hana. E like me ka mea i ʻōlelo ʻia ma mua, e huki i kahi ʻaoʻao pūnaewele, hoʻohana mākou HTML5 Canvas (<canvas>). He maʻalahi kā mākou pāʻani, no laila pono mākou e huki i kēia mau mea:
Ke kumu
Moku mea pāʻani
ʻO nā mea pāʻani ʻē aʻe i ka pāʻani
Nā nui
Eia nā ʻāpana koʻikoʻi src/client/render.js, ka mea e hāʻawi pololei i nā mea ʻehā i helu ʻia ma luna nei:
render.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);
}
Hoʻopōkole ʻia kēia code no ka maopopo.
render() ʻO ia ka hana nui o kēia faila. startRendering() и stopRendering() e hoʻomalu i ka hoʻāla ʻana o ka loop render ma 60 FPS.
Nā hoʻokō paʻa o nā hana kōkua hoʻohālikelike hoʻokahi (e.g. renderBullet()) ʻaʻole ia he mea nui, akā eia kekahi laʻana maʻalahi:
render.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 hoʻomaopopo ke hoʻohana nei mākou i ke ʻano getAsset(), i ike mua ia ma asset.js!
Inā makemake ʻoe e aʻo e pili ana i nā mea kōkua rendering ʻē aʻe, a laila e heluhelu i ke koena. src/client/render.js.
6. Hoʻokomo mea kūʻai
ʻO ka manawa kēia e hana ai i kahi pāʻani hiki ke pāʻani! E maʻalahi loa ka hoʻolālā mana: e hoʻololi i ke ala o ka neʻe ʻana, hiki iā ʻoe ke hoʻohana i ka ʻiole (ma ke kamepiula) a i ʻole e pā i ka pale (ma kahi polokalamu kelepona). No ka hoʻokō ʻana i kēia, e kākau inoa mākou Nā Hoʻolohe Hanana no nā hanana ʻiole a pā.
E mālama i kēia mau mea a pau src/client/input.js:
komo.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() ʻO nā mea hoʻolohe hanana e kāhea ana updateDirection() (mai networking.js) ke loaʻa kahi hanana hoʻokomo (no ka laʻana, ke neʻe ʻia ka ʻiole). updateDirection() mālama i ka leka uila me ke kikowaena, nāna e mālama i ka hanana hoʻokomo a hōʻano hou i ke kūlana pāʻani e like me ia.
7. Kūlana Kūlana
ʻO kēia māhele ka mea paʻakikī loa ma ka hapa mua o ka pou. Mai hoʻonāwaliwali inā ʻaʻole ʻoe maopopo i ka manawa mua āu i heluhelu ai! Hiki iā ʻoe ke lele a hoʻi hou i hope.
ʻO ka ʻāpana hope o ka puzzle e pono ai e hoʻopiha i ka code client/server moku'āina. E hoʻomanaʻo i ka snippet code mai ka ʻāpana Client Rendering?
render.js
import { getCurrentState } from './state';
function render() {
const { me, others, bullets } = getCurrentState();
// Do the rendering
// ...
}
getCurrentState() hiki iā ia ke hāʻawi iā mākou i ke kūlana o kēia manawa o ka pāʻani i ka mea kūʻai aku i kēlā me kēia manawa ma muli o nā mea hou i loaʻa mai ke kikowaena. Eia kekahi laʻana o ka hōʻano hou pāʻani i hiki i ke kikowaena ke hoʻouna:
Loaʻa i kēlā me kēia pāʻani he ʻelima mau kahua like:
t: Hōʻailona manawa kikowaena e hōʻike ana i ka wā i hana ʻia ai kēia hōʻano hou.
me: ʻIke e pili ana i ka mea pāʻani e loaʻa ana i kēia hōʻano hou.
kekahi: He pūʻulu ʻike e pili ana i nā mea pāʻani ʻē aʻe e komo ana i ka pāʻani hoʻokahi.
nā hua'ōlelo: he mau ʻike e pili ana i nā projectiles i ka pāʻani.
alakaʻi: ʻIkepili papa alakaʻi o kēia manawa. Ma kēia pou, ʻaʻole mākou e noʻonoʻo iā lākou.
7.1 Kūlana naive mea kūʻai aku
Hoʻokō naive getCurrentState() hiki ke hoʻihoʻi pololei i ka ʻikepili o ka mea hou loa i loaʻa i ka pāʻani.
naive-state.js
let lastGameUpdate = null;
// Handle a newly received game update.
export function processGameUpdate(update) {
lastGameUpdate = update;
}
export function getCurrentState() {
return lastGameUpdate;
}
Maikaʻi a maopopo! Akā inā he mea maʻalahi ia. ʻO kekahi o nā kumu he pilikia kēia hoʻokō: e kaupalena ana i ka helu o ka hoʻolilo ʻana i ka uaki kikowaena.
Pākuʻi kiʻi: helu o nā papa (ʻo ia hoʻi render()) no kekona, a i ʻole FPS. Hoʻoikaika pinepine nā pāʻani e hoʻokō ma kahi o 60 FPS.
Ka helu kuhi: ʻO ke alapine o ka hoʻouna ʻana o ke kikowaena i nā mea hou pāʻani i nā mea kūʻai aku. ʻOi aku ka haʻahaʻa ma mua o ka helu kiʻi. I kā mākou pāʻani, holo ke kikowaena ma ke alapine o 30 mau pōʻai i kēlā me kēia kekona.
Inā hāʻawi wale mākou i ka mea hou o ka pāʻani, a laila ʻaʻole e hele ka FPS ma luna o 30, no ka mea ʻAʻole loaʻa iā mākou ma mua o 30 mau mea hou i kēlā me kēia kekona mai ke kikowaena. ʻOiai inā mākou e kāhea render() 60 mau manawa i kēlā me kēia kekona, a laila ʻo ka hapalua o kēia mau kelepona e unuhi hou i ka mea like, ʻaʻohe hana maoli. ʻO kekahi pilikia me ka hoʻokō naive ʻo ia pili i ka lohi. Me ka wikiwiki pūnaewele maikaʻi loa, e loaʻa i ka mea kūʻai kahi hōʻano pāʻani i kēlā me kēia 33ms (30 mau kekona):
ʻO ka mea pōʻino, ʻaʻohe mea kūpono. ʻO kahi kiʻi ʻoi aku ka ʻoiaʻiʻo:
ʻO ka hoʻokō naive ʻo ia ka hihia maikaʻi loa i ka wā e pili ana i ka latency. Inā loaʻa kahi hōʻano pāʻani me ka lohi o 50ms, a laila nā hale kūʻai he 50ms hou aʻe no ka mea e hāʻawi mau ana ia i ke kūlana pāʻani mai ka hoʻohou mua. Hiki iā ʻoe ke noʻonoʻo i ka hōʻoluʻolu ʻole o kēia no ka mea pāʻani: ʻo ka hoʻopaʻa ʻana i ka pāʻani e hoʻoneʻe ʻia ka pāʻani a paʻa ʻole.
7.2 Hoʻomaikaʻi ʻia ke kūlana o ka mea kūʻai aku
E hoʻomaikaʻi mākou i ka hoʻokō naive. ʻO ka mua, hoʻohana mākou ka hoʻopaneʻe ʻana no 100 ms. ʻO ia ke ʻano o ke kūlana "i kēia manawa" o ka mea kūʻai aku e lohi mau ma hope o ka mokuʻāina o ka pāʻani ma ke kikowaena e 100ms. No ka laʻana, inā ʻo ka manawa ma ke kikowaena 150, a laila e hāʻawi ka mea kūʻai aku i ka mokuʻāina i noho ai ke kikowaena i kēlā manawa 50:
Hāʻawi kēia iā mākou i kahi buffer 100ms e ola i nā manawa hou o ka pāʻani hiki ʻole.
E mau ana ka uku no keia hoʻokomo lag no 100 ms. He mōhai liʻiliʻi kēia no ka pāʻani maʻalahi - ʻaʻole ʻike ka hapa nui o nā mea pāʻani (ʻoi aku nā mea pāʻani maʻamau) i kēia lohi. ʻOi aku ka maʻalahi o ka poʻe e hoʻololi i ka latency 100ms mau ma mua o ka pāʻani me kahi latency hiki ʻole ke ʻike ʻia.
Hiki iā mākou ke hoʻohana i kekahi ʻenehana i kapa ʻia wānana ʻaoʻao kūʻai, he hana maikaʻi ia no ka hōʻemi ʻana i ka latency i ʻike ʻia, akā ʻaʻole e uhi ʻia ma kēia pou.
ʻO kekahi hoʻomaikaʻi hou a mākou e hoʻohana nei linear interpolation. Ma muli o ka hoʻokuʻu ʻana i ka lag, ʻoi aku ka liʻiliʻi ma mua o ka manawa o ka mea kūʻai aku. Ke kāhea ʻia getCurrentState(), hiki iā mākou ke hoʻokō linear interpolation ma waena o nā hoʻolaha pāʻani ma mua a ma hope o ka manawa o kēia manawa i ka mea kūʻai:
Hoʻopau kēia i ka pilikia frame rate: hiki iā mākou ke hāʻawi i nā kiʻi kūʻokoʻa i kēlā me kēia frame rate a mākou e makemake ai!
7.3 Ka hoʻokō ʻana i ke kūlana mea kūʻai aku i hoʻonui ʻia
Laʻana hoʻokō ma src/client/state.js hoʻohana i ka render lag a me linear interpolation, akā ʻaʻole no ka lōʻihi. E ʻoki i ke code i ʻelua ʻāpana. Eia ka mea mua:
state.js, māhele 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;
}
ʻO ka hana mua e ʻike i ka mea currentServerTime(). E like me kā mākou i ʻike ai ma mua, ʻo kēlā me kēia pāʻani pāʻani e loaʻa kahi kikowaena manawa. Makemake mākou e hoʻohana i ka render latency e hāʻawi i ke kiʻi 100ms ma hope o ke kikowaena, akā ʻaʻole mākou e ʻike i ka manawa o kēia manawa ma ke kikowaena, no ka mea ʻaʻole hiki iā mākou ke ʻike i ka lōʻihi o ka loaʻa ʻana o kekahi o nā mea hou iā mākou. ʻAʻole hiki ke ʻike ʻia ka Pūnaewele a hiki ke ʻano like ʻole kona wikiwiki!
No ka hoʻopau ʻana i kēia pilikia, hiki iā mākou ke hoʻohana i kahi hoʻohālikelike kūpono: mākou e hoʻohālike ua hiki koke mai ka mea hou mua. Inā he ʻoiaʻiʻo kēia, a laila ʻike mākou i ka manawa kikowaena i kēia manawa kūikawā! Mālama mākou i ka hōʻailona manawa o ke kikowaena firstServerTimestamp a mālama i kā mākou kūloko (mea kūʻai aku) timestamp i ka manawa like i loko gameStart.
E kali. ʻAʻole pono he manawa ma ke kikowaena = manawa ma ka mea kūʻai? No ke aha mākou e hoʻokaʻawale ai ma waena o "ka leka manawa kikowaena" a me ka "ka manawa manawaleʻa"? He nīnau nui kēia! ʻIke ʻia ʻaʻole like lākou. Date.now() e hoʻihoʻi i nā manawa like ʻole i ka mea kūʻai aku a me ke kikowaena, a hilinaʻi ia i nā kumu kūloko o kēia mau mīkini. Mai noʻonoʻo e like nā kaha manawa ma nā mīkini a pau.
I kēia manawa maopopo mākou i ka hana currentServerTime(): hoi mai ka hōʻailona manawa kikowaena o ka manawa hāʻawi i kēia manawa. I nā huaʻōlelo ʻē aʻe, ʻo kēia ka manawa o ke kikowaena o kēia manawa (firstServerTimestamp <+ (Date.now() - gameStart)) hōʻemi i ka hoʻopaneʻe ʻana (RENDER_DELAY).
I kēia manawa, e nānā kākou i ke ʻano o kā mākou lawelawe ʻana i nā mea hou pāʻani. I ka loaʻa ʻana mai ka server update, ua kapa ʻia processGameUpdate()a mālama mākou i ka mea hou i kahi array gameUpdates. A laila, e nānā i ka hoʻohana ʻana i ka hoʻomanaʻo, wehe mākou i nā mea hou kahiko ma mua hoʻonui kumuno ka mea, ʻaʻole pono mākou iā lākou.
He aha ka "hōʻano hou"? ʻO kēia ʻo ka mea hou mua i loaʻa iā mākou ma ka neʻe ʻana i hope mai ka manawa o ke kikowaena. E hoʻomanaʻo i kēia kiʻi?
ʻO ka hōʻano hou o ka pāʻani ma ka ʻaoʻao hema o "Client Render Time" ʻo ia ka waihona kumu.
He aha ka hoʻonui kumu i hoʻohana ʻia no? No ke aha e hiki ai iā mākou ke hoʻokuʻu i nā mea hou i ka baseline? No ka hoʻomaopopo ʻana i kēia, e ʻae ma ka hopena e noʻonoʻo i ka hoʻokō getCurrentState():
state.js, māhele 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),
};
}
}
Hana mākou i ʻekolu mau hihia:
base < 0 ʻo ia hoʻi, ʻaʻohe mea hou a hiki i ka manawa hāʻawi o kēia manawa (e ʻike i ka hoʻokō ma luna getBaseUpdate()). Hiki ke hana i kēia ma ka hoʻomaka ʻana o ka pāʻani ma muli o ka hana lag. I kēia hihia, hoʻohana mākou i ka mea hou loa i loaʻa.
base ʻo ia ka mea hou loa i loaʻa iā mākou. Ma muli paha o ka lohi o ka pūnaewele a i ʻole ka pili pūnaewele maikaʻi ʻole. I kēia hihia, ke hoʻohana nei mākou i ka mea hou loa i loaʻa iā mākou.
Loaʻa iā mākou kahi mea hou ma mua a ma hope o ka manawa hāʻawi i kēia manawa, no laila hiki iā mākou interpolate!
ʻO nā mea a pau i koe i loko state.js he hoʻokō ʻia o ka hoʻopili laina laina maʻalahi (akā ʻoluʻolu). Inā makemake ʻoe e ʻimi iā ʻoe iho, a laila wehe state.js maluna o Github.
Mahele 2. Kahua hope
Ma kēia ʻāpana, e nānā mākou i ka Node.js backend nāna e hoʻomalu i kā mākou laʻana pāʻani .io.
1. Lae komo komo
No ka mālamaʻana i ka pūnaewele pūnaewele, e hoʻohana mākou i kahi pūnaewele pūnaewele kaulana no Node.js i kapaʻia ka hoike. E hoʻonohonoho ʻia e kā mākou kikowaena kikowaena helu waihona src/server/server.js:
server.js hapa 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}`);
E hoʻomanaʻo i ka hapa mua a mākou i kūkākūkā ai iā Webpack? ʻO kēia kahi e hoʻohana ai mākou i kā mākou hoʻonohonoho Webpack. E hoʻohana mākou iā lākou ma nā ala ʻelua:
Hoʻohana webpack-dev-waena e hana hou i kā mākou mau pūʻolo hoʻomohala, a i ʻole
waihona hoʻololi statically dist/, kahi e kākau ai ʻo Webpack i kā mākou mau faila ma hope o ke kūkulu ʻana.
ʻO kekahi hana nui server.js ʻo ka hoʻonohonoho ʻana i ke kikowaena kumu.ioe pili wale ana i ke kikowaena Express:
Ma hope o ka hoʻokumu ʻana i kahi pilina socket.io i ke kikowaena, hoʻonohonoho mākou i nā mea lawelawe hanana no ke kumu hou. Mālama nā mea lawelawe i nā memo i loaʻa mai nā mea kūʻai mai ma ka hāʻawi ʻana i kahi mea hoʻokahi game:
server.js hapa 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);
}
Ke hana nei mākou i kahi pāʻani .io, no laila pono mākou i hoʻokahi kope Game ("Paʻani") - pāʻani nā mea pāʻani a pau ma ke kahua hoʻokahi! Ma ka ʻāpana aʻe e ʻike mākou i ka hana ʻana o kēia papa Game.
2. Nā kikowaena pāʻani
Papa Game loaʻa i ka loina koʻikoʻi ma ka ʻaoʻao kikowaena. ʻElua mau hana nui: hoʻokele pāʻani и pāʻani hoʻohālike.
E hoʻomaka kākou me ka hana mua, ka hoʻokele pāʻani.
game.js hapa 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);
}
}
// ...
}
Ma kēia pāʻani, e ʻike mākou i nā mea pāʻani ma ke kahua id kā lākou socket.io socket (inā huikau ʻoe, a laila e hoʻi i server.js). Hāʻawi ʻo Socket.io i kēlā me kēia kumu i kahi kū hoʻokahi idno laila ʻaʻole pono mākou e hopohopo no ia mea. E kāhea wau iā ia ID mea pāʻani.
Me kēlā noʻonoʻo, e ʻimi kākou i nā ʻano hoʻololi i loko o kahi papa Game:
sockets he mea e hoʻopaʻa ai i ka mea pāʻani ID i ke kumu i pili me ka mea pāʻani. Hiki iā mākou ke komo i nā kumu e kā lākou mau mea pāʻani i ka manawa mau.
players He mea ia e hoʻopaʻa ai i ka mea pāʻani i ke code> mea pāʻani
bullets he laha o na mea Bullet, ʻaʻohe ʻano o ka hoʻonohonoho. lastUpdateTime ʻo ia ka hōʻailona manawa o ka manawa hope i hōʻano hou ʻia ka pāʻani. E ʻike mākou pehea e hoʻohana koke ʻia ai. shouldSendUpdate he mea hoololi kokua. E ʻike koke mākou i kona hoʻohana ʻana.
Nā Palapala addPlayer(), removePlayer() и handleInput() ʻAʻole pono e wehewehe, hoʻohana ʻia lākou i loko server.js. Inā pono ʻoe e hōʻano hou i kou hoʻomanaʻo, e hoʻi i kahi kiʻekiʻe iki.
Laina hope constructor() hoʻomaka pōʻai hou nā pāʻani (me ka pinepine o 60 mau mea hou/s):
game.js hapa 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;
}
}
// ...
}
Palapala update() aia paha ka ʻāpana koʻikoʻi o ka loina ʻaoʻao server. Eia kāna hana, ma ke ʻano:
E helu ana i ka lōʻihi dt ua hala mai ka hope update().
Hoʻohou i kēlā me kēia projectile a luku iā lākou inā pono. E ʻike mākou i ka hoʻokō ʻana o kēia hana ma hope. I kēia manawa, ua lawa iā mākou ke ʻike i kēlā bullet.update()hoʻi trueinā e luku ʻia ka pahu pahu (heʻe ʻo ia i waho o ke kahua pāʻani).
Hoʻohou i kēlā me kēia mea pāʻani a hoʻoulu i kahi projectile inā pono. E ʻike pū mākou i kēia hoʻokō ma hope − player.update()hiki ke hoihoi i kekahi mea Bullet.
E nānā no ka hui ʻana ma waena o nā projectiles a me nā mea pāʻani me applyCollisions(), ka mea e hoʻihoʻi mai i ke ʻano o nā projectiles i pā i nā mea pāʻani. No kēlā me kēia projectile i hoʻi mai, hoʻonui mākou i nā helu o ka mea pāʻani nāna i puhi iā ia (hoʻohana player.onDealtDamage()) a laila e wehe i ka projectile mai ka array bullets.
Hoʻomaopopo a luku i nā mea pāʻani i pepehi ʻia.
Hoʻouna i kahi hōʻano pāʻani i nā mea pāʻani a pau kēlā me kēia kekona manawa i kaheaia update(). Kōkua kēia iā mākou e mālama i ka mea hoʻololi kōkua i ʻōlelo ʻia ma luna. shouldSendUpdate. No ka mea update() i kāhea ʻia 60 mau manawa / s, hoʻouna mākou i nā mea hou pāʻani 30 mau manawa / s. Pela, alapine o ka uaki ʻO ka uaki server he 30 mau hola / s (ua kamaʻilio mākou e pili ana i nā helu uaki ma ka hapa mua).
No ke aha e hoʻouna ai i nā mea hou pāʻani wale nō ma o ka manawa ? E mālama i ke kahawai. ʻO 30 mau pāʻani i kēlā me kēia kekona he nui!
No ke aha e kelepona ʻole ai update() 30 manawa i kekona? No ka hoʻomaikaʻi ʻana i ka simulation pāʻani. ʻO ka mea i kapa pinepine ʻia update(), ʻoi aku ka pololei o ka simulation pāʻani. Akā, mai lawe nui i ka nui o nā pilikia. update(), no ka mea, he hana pili kālā kēia - ua lawa ka 60 kekona.
ʻO ke koena o ka papa Game aia nā ʻano kōkua i hoʻohana ʻia ma update():
getLeaderboard() maʻalahi loa - hoʻokaʻawale i nā mea pāʻani ma ka helu, lawe i ka ʻelima kiʻekiʻe, a hoʻihoʻi i ka inoa inoa a me ka helu no kēlā me kēia.
createUpdate() hoʻohana ʻia i update() e hana i nā mea hou pāʻani i hāʻawi ʻia i nā mea pāʻani. ʻO kāna hana nui ke kāhea ʻana i nā ala serializeForUpdate()hoʻokō ʻia no nā papa Player и Bullet. E hoʻomaopopo i ka hāʻawi wale ʻana i ka ʻikepili i kēlā me kēia mea pāʻani e pili ana kokoke loa nā mea pāʻani a me nā projectiles - ʻaʻohe pono e hoʻouna i ka ʻike e pili ana i nā mea pāʻani i mamao loa mai ka mea pāʻani!
3. Nā mea pāʻani ma ke kikowaena
I loko o kā mākou pāʻani, ua like loa nā projectiles a me nā mea pāʻani: he mau mea pāʻani holoʻokoʻa lākou. No ka hoʻohana ʻana i kēia like ma waena o nā mea pāʻani a me nā projectiles, e hoʻomaka kākou ma ka hoʻokō ʻana i kahi papa kumu Object:
ʻAʻohe mea paʻakikī e hana nei. E lilo kēia papa i wahi heleuma maikaʻi no ka hoʻonui. E ʻike kākou pehea ka papa Bullet hoʻohana Object:
bullet.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;
}
}
Ka hoʻokō Bullet pōkole loa! Ua hoʻohui mākou i Object nā mea hoʻonui wale nō:
Ke hoʻohana nei i kahi pūʻolo pōkole no ka hanauna maalea id pahuhana.
Hoʻohui i kahi kahua parentIDi hiki iā ʻoe ke hahai i ka mea pāʻani nāna i hana i kēia projectile.
Hoʻohui i kahi waiwai hoʻihoʻi i update(), ua like ia me trueinā aia ka projectile ma waho o ke kahua (e hoʻomanaʻo mākou i kamaʻilio e pili ana i kēia ma ka pauku hope?).
E neʻe kākou i ka Player:
mea pāʻani.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,
};
}
}
ʻOi aku ka paʻakikī o nā mea pāʻani ma mua o nā projectiles, no laila pono e mālama ʻia kekahi mau māla i kēia papa. ʻO kāna ʻano hana update() he nui nā hana, ʻo ia hoʻi, e hoʻihoʻi i ka projectile hou i hana ʻia inā ʻaʻohe mea i koe fireCooldown (hoʻomanaʻo mākou i kamaʻilio e pili ana i kēia ma ka pauku mua?). Hoʻonui ia i ke ʻano serializeForUpdate(), no ka mea pono mākou e hoʻokomo i nā māla hou no ka mea pāʻani i ka hoʻonui pāʻani.
Loaʻa i kahi papa kumu Object - he hana koʻikoʻi e pale i ka hana hou ʻana i ke code. No ka laʻana, ʻaʻohe papa Object pono ka hoʻokō like ʻana o kēlā me kēia mea pāʻani distanceTo(), a ʻo ka hoʻopaʻa ʻana i kēia mau hoʻokō āpau ma nā faila he mea pōʻino. He mea nui kēia no nā papahana nui.i ka nui o ka hoonui ana Object ke ulu nei na papa.
4. ʻIke hoʻokuʻi
ʻO ka mea wale nō i koe iā mākou, ʻo ia ka ʻike i ka wā e pā ai nā projectiles i nā mea pāʻani! E hoʻomanaʻo i kēia ʻāpana code mai ke ʻano update() ma ka papa Game:
pāʻani.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),
);
// ...
}
}
Pono mākou e hoʻokō i ke ʻano applyCollisions(), ka mea e hoʻihoʻi i nā projectiles a pau i pā i nā mea pāʻani. ʻO ka mea pōmaikaʻi, ʻaʻole paʻakikī ke hana no ka mea
He mau pōʻai nā mea kuʻi a pau, a ʻo ia ke ʻano maʻalahi loa e hoʻokō i ka ʻike ʻana.
Loaʻa iā mākou kahi ala distanceTo(), a mākou i hoʻokō ai i ka pauku mua ma ka papa Object.
Eia ke ʻano o kā mākou hoʻokō ʻana i ka ʻike hui ʻana:
collisions.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;
}
Hoʻokumu ʻia kēia ʻike kuʻi maʻalahi ma ka ʻoiaʻiʻo hui 'elua mau poai ke emi ka mamao ma waena o ko laua mau kikowaena ma mua o ka huina o ko laua radii. Eia ke ano o ka loihi o na kikowaena o na poai elua me ka huina o ko laua radii.
Aia kekahi mau mea hou aʻe e noʻonoʻo ai ma aneʻi:
ʻAʻole pono e pā ka projectile i ka mea pāʻani nāna ia i hana. Hiki ke hoʻokō ʻia kēia ma ka hoʻohālikelike ʻana bullet.parentID с player.id.
Hoʻokahi wale nō paʻi o ka projectile i ka hihia palena o nā mea pāʻani he nui i ka manawa like. E hoʻoponopono mākou i kēia pilikia me ka hoʻohana ʻana i ka mea hoʻohana break: i ka loa'a 'ana o ka mea hookani pila me ka projectile, ho'opau mākou i ka huli 'ana a ne'e aku i ka projectile a'e.
Ka hopena
ʻo ia wale nō! Ua uhi mākou i nā mea āpau e pono ai ʻoe e ʻike e hana i kahi pāʻani pūnaewele .io. He aha ka hope? E kūkulu i kāu pāʻani .io ponoʻī!
Loaʻa nā kumu hoʻohālike āpau a kau ʻia ma Github.