Masewerawa ndi osavuta: mumawongolera sitima pabwalo ndi osewera ena. Sitima yanu imawotcha ma projectiles ndipo mumayesa kugunda osewera ena ndikupewa ma projectiles awo.
Zonse zili mufoda public/ idzatumizidwa ndi seva. MU public/assets/ ili ndi zithunzi zomwe zimagwiritsidwa ntchito ndi polojekiti yathu.
src /
Khodi yonse yoyambira ili mufoda src/. Maina audindo client/ ΠΈ server/ azilankhula okha ndi shared/ ili ndi fayilo yokhazikika yomwe idatumizidwa ndi kasitomala ndi seva.
2. Misonkhano / magawo a polojekiti
Monga tafotokozera pamwambapa, timagwiritsa ntchito ma module kuti timange projekiti Tsamba. Tiyeni tiwone masinthidwe athu a Webpack:
$ git clone https://github.com/vzhou842/example-.io-game.git
$ cd example-.io-game
$ npm install
ndipo mwakonzeka kupita! Kuti muyambe seva yachitukuko, ingothamangani
$ npm run develop
ndi kupita ku msakatuli wanu localhost: 3000. Seva yachitukuko imangopanganso ma phukusi a JS ndi CSS pomwe kusintha kwa ma code kumachitika - ingotsitsimutsani tsamba kuti muwone zosintha zonse!
Kukhazikitsa batani la "PLAY" dinani chowongolera. Batani likakanikiza, codeyo imayambitsa masewerawo ndikuuza seva kuti takonzeka kusewera.
"Nyama" yayikulu yamalingaliro athu a kasitomala-server ili m'mafayilo omwe adatumizidwa ndi fayilo index.js. Tsopano tiwona onse mwadongosolo.
4. Kusinthana kwa data ya kasitomala
Mu masewerawa timagwiritsa ntchito laibulale yodziwika bwino kuti tilankhule ndi seva alireza. Socket.io ili ndi chithandizo chokhazikika WebSoketi, omwe ali oyenerera kulankhulana kwa njira ziwiri: tikhoza kutumiza mauthenga ku seva ΠΈ seva imatha kutumiza mauthenga kwa ife pa intaneti yomweyo.
Tidzakhala ndi fayilo imodzi src/client/networking.jsamene adzasamalira aliyense kulumikizana ndi seva:
Kusintha kulikonse kwamasewera kumakhala ndi magawo asanu ofanana:
t: Chidindo chanthawi ya seva chosonyeza nthawi yomwe izi zidapangidwa.
me: Zambiri za wosewerayo akulandila izi.
ena: Zambiri za osewera ena omwe akuchita nawo masewera amodzi.
zipolopolo: Zambiri zokhudzana ndi projectiles pamasewerawa.
gulu lotsogolera: Zomwe zilipo panopa. Sitidzawaganizira mu positi iyi.
7.1 Kusazindikira kwa kasitomala
Kukhazikitsa kwa Naive getCurrentState() akhoza kungobweza mwachindunji deta kuchokera ku zosinthidwa zamasewera zomwe zalandiridwa posachedwa.
naive-state.js
let lastGameUpdate = null;
// Handle a newly received game update.
export function processGameUpdate(update) {
lastGameUpdate = update;
}
export function getCurrentState() {
return lastGameUpdate;
}
Zokongola komanso zomveka! Koma zikadakhala zosavuta. Chimodzi mwa zifukwa zomwe kukhazikitsa uku kumakhala kovuta: imachepetsa kuchuluka kwa chimango choperekera ku liwiro la wotchi ya seva.
Mtengo wa chimango: chiwerengero cha mafelemu (ie ma call render()) pa sekondi iliyonse, kapena FPS. Masewera nthawi zambiri amayesetsa kukwaniritsa 60 FPS.
Mtengo wa Tick: Mafupipafupi omwe seva imatumiza zosintha zamasewera kwa makasitomala. Nthawi zambiri imakhala yotsika kuposa mtengo wa chimango. M'masewera athu, seva imayendetsa nkhupakupa 30 pamphindikati.
Ngati tingopereka zosintha zaposachedwa, ndiye kuti FPS sichitha kupitilira 30 chifukwa sitilandila zosintha zopitilira 30 pamphindikati kuchokera pa seva. Ngakhale tiyitana render() 60 pa sekondi iliyonse, ndiye theka la mafoni awa amangojambulanso zomwezo, osachita kalikonse. Vuto lina ndi kukhazikitsa mosadziwa ndilokuti kutengera kuchedwa. Pa liwiro loyenera la intaneti, kasitomala alandila zosintha zamasewera ndendende ma 33 ms aliwonse (30 pamphindikati):
Kukhazikitsa kopanda nzeru ndizovuta kwambiri zikafika pa latency. Ngati zosintha zamasewera zikulandiridwa ndikuchedwa kwa 50ms, ndiye kasitomala amachedwa ndi 50ms owonjezera chifukwa ikuperekabe masewerawa kuchokera pazomwe zasintha. Mutha kulingalira momwe izi zimavutira kwa wosewera mpira: chifukwa cha kuchepa kwapang'onopang'ono, masewerawa amawoneka ngati osakhazikika komanso osakhazikika.
7.2 Chitukuko cha kasitomala
Tidzakonza zina pakukhazikitsa kopanda nzeru. Choyamba, timagwiritsa ntchito kupereka kuchedwa pa 100ms. Izi zikutanthauza kuti "panopa" kasitomala nthawi zonse amakhala 100ms kumbuyo kwamasewera pa seva. Mwachitsanzo, ngati nthawi ya seva ili 150, ndiye kasitomala adzapereka dziko lomwe seva inali nthawiyo 50:
Izi zimatipatsa chitetezo cha 100ms kuti tipulumuke nthawi yosayembekezereka ya zosintha zamasewera:
Mtengo wake udzakhala wamuyaya kulowa mkati pa 100ms. Uku ndi nsembe yaying'ono pamasewera osalala - osewera ambiri (makamaka wamba) sangazindikire kuchedwa uku. Ndikosavuta kuti anthu azolowere kuchedwa kwa 100ms kusiyana ndi kusewera ndi latency yosayembekezereka.
Titha kugwiritsa ntchito njira ina yotchedwa "Zolosera za mbali ya kasitomala", zomwe zimagwira ntchito yabwino yochepetsera kuchedwa kwachidziwitso, koma sizidzakambidwa m'nkhaniyi.
Kuwongolera kwina komwe timagwiritsa ntchito ndi kumasulira kwa mzere. Chifukwa cha kuchedwa kwapang'onopang'ono, nthawi zambiri timakhala ndikusintha kamodzi patsogolo pa nthawi yomwe kasitomala amalandila. Akaitanidwa getCurrentState(), tingakwaniritse kumasulira kwa mzere pakati pa zosintha zamasewera atangotsala pang'ono komanso pambuyo pa nthawi yapano mwa kasitomala:
Chitsanzo chokonzekera mu src/client/state.js amagwiritsa ntchito kuchedwa kwa kumasulira ndi kumasulira kwa mzere, koma izi sizitenga nthawi yayitali. Tiyeni tigawe kachidindo m'magawo awiri. Nayi yoyamba:
state.js, gawo 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;
}
Choyambirira chomwe muyenera kuchita ndikuzindikira zomwe akuchita currentServerTime(). Monga tawonera kale, zosintha zilizonse zamasewera zimakhala ndi sitampu yanthawi ya seva. Tikufuna kugwiritsa ntchito render latency kuti tipereke chithunzicho 100ms kuseri kwa seva, koma sitidzadziwa nthawi yomwe ilipo pa seva, chifukwa sitingadziwe kuti zinatenga nthawi yayitali bwanji kuti zosintha zina zifike kwa ife. Intaneti ndi yosadziΕ΅ika bwino ndipo liwiro lake limasiyana kwambiri!
Kuti tithane ndi vutoli, titha kugwiritsa ntchito kuyerekezera koyenera: ife tiyerekeze kuti zosintha zoyamba zafika nthawi yomweyo. Izi zikadakhala zoona, tikadadziwa nthawi ya seva panthawiyo! Timasunga sitampu yanthawi ya seva mkati firstServerTimestamp ndi kupulumutsa athu kwanuko (client) timestamp nthawi yomweyo mu gameStart.
O, dikirani miniti. Kodi sikuyenera kukhala nthawi pa seva = nthawi pa kasitomala? Chifukwa chiyani timasiyanitsa "chidindo chanthawi ya seva" ndi "chidindo chamakasitomala"? Ili ndi funso lalikulu! Zikuoneka kuti izi si chinthu chomwecho. Date.now() idzabwezeranso masitampu osiyanasiyana mu kasitomala ndi seva ndipo izi zimatengera zinthu zapafupi ndi makinawa. Musaganize kuti ma timestamp adzakhala ofanana pamakina onse.
Tsopano ife tikumvetsa chimene icho chimachita currentServerTime(): ikubwerera chidindo chanthawi ya seva chanthawi yoperekera. Mwanjira ina, iyi ndi nthawi ya seva yamakono (firstServerTimestamp <+ (Date.now() - gameStart)) kuchotsera kuchedwa (RENDER_DELAY).
Kodi "core update" ndi chiyani? Izi kusinthidwa koyamba komwe timapeza pobwerera m'mbuyo kuchokera pa nthawi ya seva yamakono. Mukukumbukira chithunzichi?
Zosintha zamasewera kumanzere kwa "Client Render Time" ndiye zosintha zoyambira.
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),
};
}
}
Timapereka milandu itatu:
base < 0 zikutanthauza kuti palibe zosintha mpaka nthawi yoperekera (onani kukhazikitsa pamwambapa getBaseUpdate()). Izi zitha kuchitika kumayambiriro kwamasewera chifukwa cha kuchedwa. Pankhaniyi, timagwiritsa ntchito zosinthidwa zaposachedwa kwambiri.
base ndiye zosintha zaposachedwa kwambiri zomwe tili nazo. Izi zitha kuchitika chifukwa chakuchedwa kwa netiweki kapena kusalumikizana bwino kwa intaneti. M'nkhani inonso timagwiritsa ntchito zosintha zaposachedwa zomwe tili nazo.
Tili ndi zosintha nthawi isanakwane komanso itatha, kuti tithe phatikiza!
Kuwongolera seva yapaintaneti tidzagwiritsa ntchito tsamba lodziwika bwino la Node.js lotchedwa kufotokoza. Idzakonzedwa ndi fayilo yathu yolowera pa seva src/server/server.js:
seva.js, gawo 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}`);
Kumbukirani kuti mu gawo loyamba tidakambirana Webpack? Apa ndipamene tidzagwiritsa ntchito masanjidwe athu a Webpack. Tidzawagwiritsa ntchito m'njira ziwiri:
Titakhazikitsa bwino kulumikizana kwa socket.io ndi seva, timakonza zosamalira zochitika pa socket yatsopano. Othandizira zochitika amakonza mauthenga omwe alandiridwa kuchokera kwa makasitomala powagawira ku chinthu cha singleton game:
seva.js, gawo 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);
}
Tikupanga masewera a .io, kotero tingofunika kope limodzi lokha Game ("Masewera") - osewera onse amasewera m'bwalo limodzi! Mu gawo lotsatira tiwona momwe kalasiyi imagwirira ntchito Game.
2. Ma seva amasewera
Kalasi Game ili ndi malingaliro ofunikira kwambiri pa seva. Lili ndi ntchito ziwiri zazikulu: kasamalidwe ka osewera ΠΈ masewera kayeseleledwe.
Tiyeni tiyambe ndi ntchito yoyamba - kuyang'anira osewera.
game.js, gawo 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);
}
}
// ...
}
Mu masewerowa tidziwa osewera ndi mabwalo id socket socket.io (ngati mwasokonezeka, bwererani ku server.js). Socket.io palokha imagawira soketi iliyonse kukhala yapadera id, kotero sitiyenera kuda nkhawa nazo. ndidzamuyitana Player ID.
sockets ndi chinthu chomwe chimamanga ID ya wosewera mpira ku socket yomwe imagwirizana ndi wosewera mpira. Zimatipatsa mwayi wopeza masiketi ndi ma ID awo osewera pakapita nthawi.
players ndi chinthu chomwe chimamanga ID ya player ku code> Player chinthu
createUpdate() ntchito mu update() kupanga zosintha zamasewera zomwe zimagawidwa kwa osewera. Ntchito yake yayikulu ndikuyitana njira serializeForUpdate(), zokhazikitsidwa pamakalasi Player ΠΈ Bullet. Dziwani kuti amangotumiza deta aliyense wosewera mpira za pafupi osewera ndi projectiles - palibe chifukwa chotumizira zambiri zamasewera omwe ali kutali ndi osewera!
3. Zinthu zamasewera pa seva
M'masewera athu, ma projectiles ndi osewera amafanana kwambiri: ndi zinthu zamasewera zozungulira. Kuti titengere mwayi pakufanana kumeneku pakati pa osewera ndi projectiles, tiyeni tiyambe ndi kukhazikitsa gulu loyambira Object:
Kuonjezera mtengo wobwezera ku update(), zomwe ndi zofanana true, ngati projectile ili kunja kwa bwalo (kumbukirani tidakambirana izi mu gawo lomaliza?).
Tiyeni tipitirire Player:
player.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,
};
}
}
Osewera ndi ovuta kwambiri kuposa ma projectiles, kotero kalasi iyi iyenera kusunga magawo ena angapo. Njira yake update() imagwira ntchito zambiri, makamaka kubweza projectile yomwe yangopangidwa kumene ngati palibe yotsala fireCooldown (kumbukirani tidakambirana izi m'gawo lapitalo?). Imawonjezeranso njira serializeForUpdate(), chifukwa tifunika kuphatikiza magawo owonjezera a wosewera pakusintha kwamasewera.
Zomwe zatsala kuti tichite ndikuzindikira ma projectiles akagunda osewera! Kumbukirani kaphatikizidwe ka code iyi kuchokera munjira update() mu class Game:
masewera.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),
);
// ...
}
}
Zinthu zonse zowombana ndi zozungulira, ndipo ichi ndi mawonekedwe osavuta kugwiritsa ntchito kuzindikira kugunda.
Tili nayo kale njira distanceTo(), zomwe tidazitsatira m'kalasi mu gawo lapitalo Object.
Umu ndi momwe kukhazikitsa kwathu kuzindikira kugunda kumawonekera:
kugundana.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;
}
Kuzindikira kugunda kosavuta kumeneku kumatengera mfundo yakuti mabwalo awiri amawombana ngati mtunda pakati pa malo awo uli wocheperapo kuchuluka kwa ma radii awo. Nayi nkhani yomwe mtunda wapakati pakati pa mabwalo awiri ndi wofanana ndendende ndi kuchuluka kwa ma radii awo:
The projectile iyenera kugunda kamodzi kokha ngati kugunda osewera angapo nthawi imodzi. Tidzathetsa vutoli pogwiritsa ntchito woyendetsa break: Wosewerera yemwe akuwombana ndi projectile atapezeka, timasiya kufufuza ndikupita ku projectile yotsatira.
ΠΠΎΠ½Π΅Ρ
Ndizomwezo! Takuphunzitsani zonse zomwe muyenera kudziwa kuti mupange masewera a pa intaneti a .io. Chotsatira ndi chiyani? Pangani masewera anu a .io!