Yakabudiswa muna 2015
Kana usati wambonzwa nezvemitambo iyi kare, ndeyemahara, yevazhinji mitambo yewebhu iri nyore kutamba (hapana account inodiwa). Vanowanzo pinza vatambi vakawanda vanopikisa munhandare imwe chete. Mimwe mitambo ine mukurumbira .io:
Mune ino post tichaona sei gadzira .io mutambo kubva pakutanga. Kuti uite izvi, ruzivo chete rweJavascript ruchakwana: unoda kunzwisisa zvinhu zvakadai se syntax this
ΠΈ
Muenzaniso wemutambo we.io
Nekuda kwerubatsiro rwekudzidziswa tichareva
Mutambo uyu uri nyore: iwe unodzora ngarava munhandare nevamwe vatambi. Ngarava yako inodzima projectiles uye unoedza kurova vamwe vatambi uchidzivirira projectiles yavo.
1. Muchidimbu muchidimbu/magadzirirwo echirongwa
Ndinokurudzira
download source code muenzaniso mutambo kuti mugonditevera.
Muenzaniso unoshandisa zvinotevera:
ratidza ndiyo inonyanya kufarirwa webhu dhizaini yeNode.js inodzora mutambo wewebhu server.socket.io - websocket raibhurari yekutsinhanisa data pakati pebrowser neserver.Webpack - module maneja. Unogona kuverenga nezve nei uchishandisa Webpackpano .
Izvi ndizvo zvinoita dhairekitori reprojekiti chimiro:
public/
assets/
...
src/
client/
css/
...
html/
index.html
index.js
...
server/
server.js
...
shared/
constants.js
ruzhinji/
Zvese zviri mufolda public/
ichafambiswa nesevha. IN public/assets/
ine mifananidzo inoshandiswa neprojekti yedu.
src /
Yese source code iri mufolder src/
. Titles client/
ΠΈ server/
vanozvitaurira uye shared/
ine faira yeconstants inounzwa kunze nevose mutengi uye server.
2. Assemblies/project parameters
Sezvataurwa pamusoro apa, isu tinoshandisa module maneja kuvaka chirongwa
webpack.common.js:
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: {
game: './src/client/index.js',
},
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ['@babel/preset-env'],
},
},
},
{
test: /.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
'css-loader',
],
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
}),
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'src/client/html/index.html',
}),
],
};
Mitsetse inonyanya kukosha pano ndeiyi inotevera:
src/client/index.js
ndiyo nzvimbo yekupinda yeJavascript (JS) mutengi. Webpack ichatanga kubva pano uye ichitsvagazve mamwe mafaera akaunzwa kunze kwenyika.- Iyo yakabuda JS yeWebpack yedu kuvaka ichave iri mudhairekitori
dist/
. Ndichafonera faira iri redu JS package. - Tinoshandisa
Babheri , uye kunyanya kugadzirisa@babel/preset-env kufambisa yedu JS kodhi yemabhurawuza ekare. - Isu tinoshandisa plugin kuburitsa ese CSS inoratidzwa neJS mafaera toasanganisa munzvimbo imwechete. Ndichazviti ndezvedu CSS package.
Unogona kunge waona zvisinganzwisisike mazita emafaira epasuru '[name].[contenthash].ext'
. Ivo vane [name]
ichatsiviwa nezita renzvimbo yekupinza (munyaya yedu iri game
), a [contenthash]
ichatsiviwa nehashi yezvinyorwa zvefaira. Tinoita izvi contenthash
) Mhedzisiro yapera ichava zita refaira rekuona game.dbeee76e91a97d0c7207.js
.
faira webpack.common.js
-Iyi ndiyo base yekumisikidza faira yatinopinza mukusimudzira uye kupedzisa mapurojekiti. Semuenzaniso, heino gadziriso yekuvandudza:
webpack.dev.js
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'development',
});
Kuti tibudirire, tinoshandisa mukugadzirisa webpack.dev.js
, uye kuchinja ku webpack.prod.js
, kugadzirisa masaizi epasuru paunenge uchiendesa kune kugadzirwa.
Kugadziriswa kwenzvimbo
Ini ndinokurudzira kuisa purojekiti pamushini wako wepanzvimbo kuti ugone kutevedzera matanho akanyorwa mune ino post. Setup iri nyore: kutanga, iyo system inofanirwa kuve nayo
$ git clone https://github.com/vzhou842/example-.io-game.git
$ cd example-.io-game
$ npm install
uye wagadzirira kuenda! Kuti utange sevha yekuvandudza, ingomhanya
$ npm run develop
uye enda kuwebhu browser yako
3. Mutengi ekupinda mapoinzi
Ngatidzikei kune iyo kodhi yemutambo pachayo. Kutanga tinoda peji index.html
, paunoshanyira saiti, bhurawuza rinotanga kuiisa. Peji yedu ichave iri nyore:
index.html
Muenzaniso .io mutambo ITAMBA
Iyi kodhi muenzaniso yakarerutswa zvishoma kuti ijekeswe, uye ini ndichaita zvimwe chete nemimwe mienzaniso yakawanda iri mupositi. Iwe unogona kugara uchitarisa kodhi yakazara pa
Tine:
HTML5 Canvas chinhu (<canvas>
), yatichashandisa kupa mutambo.<link>
kuwedzera yedu CSS package.<script>
kuwedzera yedu Javascript package.- Main menu ine username
<input>
uye bhatani re "PLAY" (<button>
).
Kana peji remba razara, bhurawuza rinotanga kuita Javascript kodhi, kutanga nenzvimbo yekupinda JS faira: src/client/index.js
.
index.js
import { connect, play } from './networking';
import { startRendering, stopRendering } from './render';
import { startCapturingInput, stopCapturingInput } from './input';
import { downloadAssets } from './assets';
import { initState } from './state';
import { setLeaderboardHidden } from './leaderboard';
import './css/main.css';
const playMenu = document.getElementById('play-menu');
const playButton = document.getElementById('play-button');
const usernameInput = document.getElementById('username-input');
Promise.all([
connect(),
downloadAssets(),
]).then(() => {
playMenu.classList.remove('hidden');
usernameInput.focus();
playButton.onclick = () => {
// Play!
play(usernameInput.value);
playMenu.classList.add('hidden');
initState();
startCapturingInput();
startRendering();
setLeaderboardHidden(false);
};
});
Izvi zvingaite sezvakaoma, asi hapana zvakawanda zviri kuitika pano:
- Ngenisa mamwe mafaera akati wandei eJS.
- Ngenisa CSS (saka Webpack inoziva kuvabatanidza muCSS package yedu).
- Kutanga
connect()
kumisikidza chinongedzo kune server uye tangadownloadAssets()
kudhawunirodha mifananidzo inodiwa kupa mutambo. - Mushure mekupedza stage 3 menyu huru inoratidzwa (
playMenu
). - Kumisikidza bhatani re "PLAY" tinya mubati. Kana bhatani radzvanywa, kodhi inotangisa mutambo uye inoudza sevha kuti tagadzirira kutamba.
Iyo huru "nyama" yemutengi-server logic iri mune iwo mafaera akaunzwa kunze nefaira index.js
. Iye zvino tichazvitarisa zvose zvakarongeka.
4. Kuchinjana kwedata revatengi
Mumutambo uyu tinoshandisa raibhurari inozivikanwa kwazvo kutaurirana neserver
Tichava nefaira rimwe chete src/client/networking.js
ndiani achachengeta munhu wose kutaurirana neserver:
networking.js
import io from 'socket.io-client';
import { processGameUpdate } from './state';
const Constants = require('../shared/constants');
const socket = io(`ws://${window.location.host}`);
const connectedPromise = new Promise(resolve => {
socket.on('connect', () => {
console.log('Connected to server!');
resolve();
});
});
export const connect = onGameOver => (
connectedPromise.then(() => {
// Register callbacks
socket.on(Constants.MSG_TYPES.GAME_UPDATE, processGameUpdate);
socket.on(Constants.MSG_TYPES.GAME_OVER, onGameOver);
})
);
export const play = username => {
socket.emit(Constants.MSG_TYPES.JOIN_GAME, username);
};
export const updateDirection = dir => {
socket.emit(Constants.MSG_TYPES.INPUT, dir);
};
Iyi kodhi zvakare yakapfupikiswa zvishoma kuti ijekeswe.
Pane zvinhu zvitatu zvikuru zviri kuitika mufaira iri:
- Tiri kuedza kubatanidza kune server.
connectedPromise
inobvumirwa chete kana tagadzira chinongedzo. - Kana iyo yekubatanidza ikabudirira, tinonyoresa callback mabasa (
processGameUpdate()
ΠΈonGameOver()
) kune mameseji atingagashira kubva kuseva. - Isu tinotumira kunze
play()
ΠΈupdateDirection()
kuitira kuti mamwe mafaera avashandise.
5. Client rendering
Yasvika nguva yekuratidza mufananidzo pachiratidziri!
...asi tisati taita izvi, tinoda kudhawunirodha mifananidzo yese (zviwanikwa) inodiwa pane izvi. Ngatinyorei maneja wezvishandiso:
assets.js
const ASSET_NAMES = ['ship.svg', 'bullet.svg'];
const assets = {};
const downloadPromise = Promise.all(ASSET_NAMES.map(downloadAsset));
function downloadAsset(assetName) {
return new Promise(resolve => {
const asset = new Image();
asset.onload = () => {
console.log(`Downloaded ${assetName}`);
assets[assetName] = asset;
resolve();
};
asset.src = `/assets/${assetName}`;
});
}
export const downloadAssets = () => downloadPromise;
export const getAsset = assetName => assets[assetName];
Resource management haina kuoma kuita! Chinhu chikuru ndechekuchengeta chinhu assets
, iyo inosunga iyo filename kiyi kune kukosha kwechinhu Image
. Kana iyo sosi yakatakurwa, tinoichengeta kune chinhu assets
kuti uwane risiti yekukurumidza mune ramangwana. Kurodha pasi kwega rega rega kunobvumidzwa rinhi (kureva kuti, kudhawunirodha all the zviwanikwa), tinobvumira downloadPromise
.
Mushure mekurodha zviwanikwa, unogona kutanga kupa. Sezvambotaurwa, kudhirowa pawebhu peji yatinoshandisa <canvas>
) Mutambo wedu wakareruka, saka isu tinongoda kupa zvinotevera:
- Shure
- Player ship
- Vamwe vatambi mumutambo
- Shells
Heano zvidimbu zvakakosha src/client/render.js
, iyo inodhirowa mapoinzi mana akanyorwa pamusoro:
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);
}
Kodhi iyi zvakare yakapfupikiswa kuti ijekeswe.
render()
ndiro basa guru refaira iyi. startRendering()
ΠΈ stopRendering()
dzora kushandiswa kweiyo rendering cycle pa60 FPS.
Kuitwa kwakajeka kwemunhu anopa mabasa ekubatsira (semuenzaniso renderBullet()
) hazvina kukosha kudaro, asi heino muenzaniso wakapfava:
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,
);
}
Cherechedza kuti tiri kushandisa nzira getAsset()
, iyo yakamboonekwa mukati asset.js
!
Kana iwe uchifarira kuongorora mamwe mabasa ekubatsira ekupa, wobva waverenga mamwe ese
src/client/render.js .
6. Mutengi anopinza
Yasvika nguva yekuita mutambo inotambika! Iyo control scheme ichave iri nyore kwazvo: kushandura mafambiro ekufamba, unogona kushandisa mbeva (pakombuta) kana kubata chidzitiro (pane nharembozha). Kuti tiite izvi tichanyoresa
Achagadzirisa zvese izvi src/client/input.js
:
input.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()
ndivo Vateereri veChiitiko vanofona updateDirection()
(ye networking.js
) kana chiitiko chekupinza chikaitika (semuenzaniso, kana mbeva yafambiswa). updateDirection()
inobata nekutsinhana kwemameseji neseva, iyo inogadzirisa chiitiko chekupinza uye inovandudza mamiriro emutambo zvinoenderana.
7. Chimiro chemutengi
Ichi chikamu ndicho chakanyanya kuoma muchikamu chekutanga chepositi. Usaore mwoyo kana ukasainzwisisa kekutanga kuiverenga! Iwe unogona kunyange kuidarika uye wozodzoka kwairi gare gare.
Chekupedzisira chidimbu chepuzzle chinodiwa kupedzisa mutengi-server kodhi ndeye mamiriro. Rangarira iyo kodhi snippet kubva kuClient Rendering chikamu?
render.js
import { getCurrentState } from './state';
function render() {
const { me, others, bullets } = getCurrentState();
// Do the rendering
// ...
}
getCurrentState()
inofanirwa kukwanisa kutipa iyo yazvino mutambo mamiriro mune mutengi chero nguva zvichibva pane zvakagadziridzwa zvakagamuchirwa kubva kuseva. Heino muenzaniso wekugadzirisa mutambo unogona kutumira server:
{
"t": 1555960373725,
"me": {
"x": 2213.8050880413657,
"y": 1469.370893425012,
"direction": 1.3082443894581433,
"id": "AhzgAtklgo2FJvwWAADO",
"hp": 100
},
"others": [],
"bullets": [
{
"id": "RUJfJ8Y18n",
"x": 2354.029197099604,
"y": 1431.6848318262666
},
{
"id": "ctg5rht5s",
"x": 2260.546457727445,
"y": 1456.8088728920968
}
],
"leaderboard": [
{
"username": "Player",
"score": 3
}
]
}
Yega yega yekuvandudza yemutambo ine minda mishanu yakafanana:
- t: Server timestamp inoratidza kuti iyi update yakagadzirwa rini.
- me: Ruzivo nezve mutambi anogamuchira iyi update.
- vamwe: Ruzhinji rweruzivo nezve vamwe vatambi vari kutora chikamu mumutambo mumwe chete.
- mabara: rondedzero yeruzivo nezve projectiles mumutambo.
- leaderboard: Yazvino leaderboard data. Hatisi kuzovafunga mune ino post.
7.1 Kusaziva kwemutengi
Naive implementation getCurrentState()
inogona chete kudzosera zvakananga data kubva kuchangobva kugamuchirwa mutambo wekuvandudza.
naive-state.js
let lastGameUpdate = null;
// Handle a newly received game update.
export function processGameUpdate(update) {
lastGameUpdate = update;
}
export function getCurrentState() {
return lastGameUpdate;
}
Yakanaka uye yakajeka! Asi dai zvakanga zviri nyore. Chimwe chezvikonzero nei kuita uku kuine dambudziko: inodzikamisa chiyero chekupa furemu kune server clock kasi.
Frame Rate: nhamba yemafuremu (kureva mafoni
render()
) pasekondi, kana kuti FPS. Mitambo inowanzoyedza kuwana inokwana makumi matanhatu FPS.
Tick ββRate: Iyo frequency iyo sevha inotumira inogadziridza mutambo kune vatengi. Kazhinji yakaderera pane chiyero chefuremu. Mumutambo wedu, sevha inomhanya pamakumi matatu matiki pasekondi.
Kana tikangopa yazvino mutambo wekuvandudza, saka FPS haingambo kwanisa kudarika makumi matatu nekuti isu hatimbogashira zvinopfuura makumi matatu zvigadziriso pasekondi kubva kuseva. Kunyangwe tikafona render()
60 nguva pasekondi, ipapo hafu yemafoni aya anongodhirowa chinhu chimwe chete, hapana chaasingaite. Rimwe dambudziko nekuita zvisina basa nderekuti zvichienderana nekunonoka. Pakumhanya kweInternet kwakaringana, mutengi anogashira mutambo wekugadzirisa chaiwo 33 ms (30 pasekondi):
Zvinosuruvarisa, hapana chakakwana. Mufananidzo wakanyatsojeka ungave:
A naive kuisirwa ndiyo yakanyanya kunaka kesi kana zvasvika kune latency. Kana mutambo wekuvandudza ukagamuchirwa nekunonoka kwe50ms, ipapo mutengi anononoka nekuwedzera 50ms nekuti ichiri kupa iyo mutambo mamiriro kubva kune yakapfuura update. Iwe unogona kufungidzira kuti izvi zvinokanganisa sei kumutambi: nekuda kwekunonoka kunonoka, mutambo uchaita seusina kugadzikana uye usina kugadzikana.
7.2 Kuvandudzwa kwevatengi
Tichaita mamwe magadzirirwo ekuita zvisina basa. Chokutanga, tinoshandisa kupa kunonoka ne100 ms. Izvi zvinoreva kuti "ikozvino" mamiriro emutengi achagara ari 100ms kuseri kwemutambo wenyika pane server. Semuenzaniso, kana nguva ye server iri 150, ipapo mutengi achapa nyika iyo sevha yaive panguva iyoyo 50:
Izvi zvinotipa 100ms buffer kuti tirarame iyo isingafungidzike nguva yekuvandudzwa kwemutambo:
Mutengo weizvi uchava zvachose
Tinogona kushandisa imwe nzira inonzi
"client-side forecasting" , iyo inoita basa rakanaka rekudzikisa kunoonekwa latency, asi haizokurukurwa mune ino post.
Imwe gadziriso yatinoshandisa ndeye mutsara kududzira. Nekuda kwekupa lag, isu kazhinji tinongoita imwe yekuvandudza pamberi penguva yazvino mutengi. Pakufona getCurrentState()
, tinogona kuzadzisa
Izvi zvinogadzirisa dambudziko rechiyero chemuyero: isu tinokwanisa kupa akasarudzika mafuremu chero chiyero chatinoda!
7.3 Kuita mamiriro evatengi akavandudzwa
Muenzaniso wekushandiswa mu src/client/state.js
inoshandisa zvese kunonoka kupa uye mutsara kududzira, asi izvi hazvigare kwenguva refu. Ngatipwanye kodhi muzvikamu zviviri. Heino yekutanga:
state.js, chikamu 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;
}
Chinhu chokutanga chaunofanira kuita ndechokuona kuti chii chaanoita currentServerTime()
. Sezvatakaona pakutanga, yega yega yekuvandudza yemutambo inosanganisira server timestamp. Tinoda kushandisa render latency kupa mufananidzo 100ms kuseri kweseva, asi hatife takaziva nguva iripo pane server, nekuti hatigone kuziva kuti zvakatora nguva yakareba sei kuti chero zvigadziriso zvisvike kwatiri. IInternet haifungidzike uye kumhanya kwayo kunogona kusiyana zvakanyanya!
Kuti titenderere dambudziko iri, tinogona kushandisa fungidziro inonzwisisika: isu toita kunge update yekutanga yasvika ipapo. Dai ichi chaive chokwadi, saka taizoziva nguva yeseva panguva iyoyo chaiyo! Isu tinochengeta server timestamp mukati firstServerTimestamp
uye ponesa yedu local (mutengi) chitambi chenguva panguva imwe chete mukati gameStart
.
O, imbomira zvishoma. Hakufanire kunge paine nguva pane server = nguva pane mutengi? Nei tichisiyanisa pakati pe "server timestamp" uye "mutengi timestamp"? Uyu mubvunzo mukuru! Zvinoitika kuti izvi hazvisi zvakafanana. Date.now()
ichadzosa nguva dzakasiyana mutengi uye server uye izvi zvinoenderana nezvinhu zvemuno kumakina aya. Usambofa wakafungidzira kuti timestamps ichave yakafanana pamichina yese.
Zvino tinonzwisisa zvazvinoita currentServerTime()
: inodzoka server timestamp yenguva yazvino yekuburitsa. Mune mamwe mazwi, iyi ndiyo yazvino server nguva (firstServerTimestamp <+ (Date.now() - gameStart)
) kubvisa kunonoka kupa (RENDER_DELAY
).
Zvino ngatitarisei mabatiro atinoita zvigadziriso zvemitambo. Kana imwe update yagamuchirwa kubva kune server, inodanwa processGameUpdate()
, uye isu tinochengeta iyo nyowani yekuvandudza kune array gameUpdates
. Zvadaro, kutarisa kushandiswa kwendangariro, tinobvisa zvese zvekare zvigadziriso base updatenokuti hatichadzida.
Chii chinonzi "core update"? Izvi yekutanga yekuvandudza yatinowana nekudzokera kumashure kubva kune yazvino server nguva. Rangarira dhiyagiramu iyi?
Iyo yekuvandudza yemutambo yakananga kuruboshwe rwe "Client Render Nguva" ndiyo base update.
Chii chinonzi base update chinoshandiswa? Sei isu tichigona kudonhedza zvigadziriso kuti titange? Kuti tinzwisise izvi, ngationei pakupedzisira ngatitarisei pakuitwa getCurrentState()
:
state.js, chikamu 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),
};
}
}
Tinobata nyaya nhatu:
base < 0
zvinoreva kuti hapana zvigadziriso kudzamara yazvino nguva yekupa (ona kuita pamusorogetBaseUpdate()
) Izvi zvinogona kuitika pakutanga kwemutambo nekuda kwekupa lag. Mune ino kesi, isu tinoshandisa yazvino gadziriso yakagamuchirwa.base
ndiyo yazvino update yatinayo. Izvi zvinogona kuitika nekuda kwetiweki latency kana kushomeka kweinternet. Mune ino kesi zvakare isu tinoshandisa yazvino update yatinayo.- Isu tine inogadziridza pamberi uye mushure menguva yazvino yekupa, kuti tigone interpolate!
Zvese zvasara mukati state.js
iko kushandiswa kwekududzira kwemutsara kuri nyore (asi kunofinha) masvomhu. Kana iwe uchida kuzviongorora iwe pachako, wobva wavhura state.js
pamusoro
Chikamu 2. Backend server
Muchikamu chino tichatarisa iyo Node.js backend inodzora yedu
1. Server yekupinda nzvimbo
Kuti titore sevha yewebhu isu tichashandisa yakakurumbira Node.js web framework inonzi src/server/server.js
:
server.js, chikamu 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}`);
Rangarira kuti muchikamu chekutanga takakurukura Webpack? Apa ndipo patichashandisa yedu Webpack zvigadziriso. Tichazvishandisa munzira mbiri:
- Kushandisa
webpack-dev-middleware kuti tivake patsva mapakeji edu ebudiriro, kana - Tamisa folda
dist/
, iyo Webpack ichanyora mafaera edu mushure mekugadzirwa kwekugadzira.
Rimwe basa rinokosha server.js
inosanganisira kuseta server
server.js, chikamu 2
const socketio = require('socket.io');
const Constants = require('../shared/constants');
// Setup Express
// ...
const server = app.listen(port);
console.log(`Server listening on port ${port}`);
// Setup socket.io
const io = socketio(server);
// Listen for socket.io connections
io.on('connection', socket => {
console.log('Player connected!', socket.id);
socket.on(Constants.MSG_TYPES.JOIN_GAME, joinGame);
socket.on(Constants.MSG_TYPES.INPUT, handleInput);
socket.on('disconnect', onDisconnect);
});
Mushure mekubudirira kumisikidza socket.io yekubatanidza nesevha, isu tinogadzirisa vanobata chiitiko kune socket nyowani. Vabati vezviitiko vanogadzira mameseji anogamuchirwa kubva kune vatengi nekuendesa kune chimwe chinhu che singleton game
:
server.js, chikamu 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);
}
Tiri kugadzira .io mutambo, saka tichangoda kopi imwe chete Game
("Game") - vatambi vese vanotamba munhandare imwe chete! Muchikamu chinotevera tichaona kuti kirasi iyi inoshanda sei Game
.
2. Masevha emutambo
Chikoro Game
ine inonyanya kukosha server-side logic. Iine mabasa makuru maviri: mutambi maneja ΠΈ simulation yemutambo.
Ngatitange nebasa rekutanga - kutonga vatambi.
game.js, chikamu 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);
}
}
// ...
}
Mumutambo uyu tichaona vatambi nenhandare id
socket socket.io (kana iwe wakavhiringidzika, dzokera ku server.js
) Socket.io pachayo inopa socket imwe neimwe yakasarudzika id
, saka hatifaniri kunetseka nezvazvo. ndichamudana Player ID.
Tichifunga izvozvo, ngationgororei misiyano yemuenzaniso mukirasi Game
:
sockets
chinhu chinosungira mutambi ID kune socket yakabatana nemutambi. Inotibvumira kuwana zvigadziko neavo maID ID nekufamba kwenguva.players
chinhu chinosunga mutambi ID kune kodhi> Player chinhu
bullets
hurongwa hwezvinhu Bullet
, isina kurongeka chaiko.
lastUpdateTime
- Iyi ndiyo timestamp yekupedzisira mutambo wekuvandudza. Tichaona kuti ichashandiswa sei munguva pfupi.
shouldSendUpdate
chinhu chekubatsira chinosiyanisa. Tichaonawo kushandiswa kwayo munguva pfupi.
Nzira addPlayer()
, removePlayer()
ΠΈ handleInput()
hapana chikonzero chekutsanangura, ivo vanoshandiswa mukati server.js
. Kana iwe uchida refresher, dzokera kumusoro zvishoma.
Mutsara wekupedzisira constructor()
anotanga update cycle mitambo (ine frequency ye60 updates/s):
game.js, chikamu 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;
}
}
// ...
}
Method update()
rine pamwe chikamu chakakosha che server-side logic. Ngatinyorei zvese zvainoita muhurongwa:
- Inoverengera kuti inguvai
dt
kubvira kareupdate()
. - Inozorodza imwe neimwe projectile uye inovaparadza kana zvichidikanwa. Tichaona kushandiswa kwebasa iri gare gare. Parizvino zvakwana kuti tizive izvozvo
bullet.update()
anodzokatrue
, kana projectile inofanira kuparadzwa (akabuda kunze kwenhandare). - Inogadziridza mutambi wega wega uye inogadzira projectile kana zvichidikanwa. Tichaonawo kuita uku gare gare -
player.update()
anogona kudzorera chinhuBullet
. - Inotarisa kudhumhana pakati peprojekiti nevatambi vachishandisa
applyCollisions()
, iyo inodzosa nhevedzano yeprojekiti inorova vatambi. Kune yega yega projectile yakadzoka, isu tinowedzera mamakisi emutambi akaridzinga (tichishandisaplayer.onDealtDamage()
), uye wobva wabvisa projectile kubva pakurongwabullets
. - Inozivisa uye inoparadza vatambi vese vakaurayiwa.
- Inotumira mutambo wekuvandudza kune vese vatambi Sekondi imwe neimwe nguva dzekudanwa
update()
. Iyo yekubatsira shanduko yataurwa pamusoro inotibatsira kutevedzera izvishouldSendUpdate
. Asupdate()
inonzi 60 times/s, tinotumira game updates 30 times/s. Saka, clock frequency server is 30 clock cycles/s (takataura pamusoro penguva yewachi muchikamu chekutanga).
Sei uchitumira zvigadziriso zvemitambo chete kuburikidza nenguva ? Kuchengetedza chiteshi. 30 mitambo inogadziridza pasekondi yakawanda!
Wadii kungofona ipapo?
update()
30 nguva pasekondi? Kuvandudza mutambo wekufananidza. Iyo yakawanda inowanzonziupdate()
, iyo yakanyatsojeka iyo simulation yemutambo ichave. Asi usanyanya kutakurwa nehuwandu hwematambudzikoupdate()
, nokuti iri ibasa rinodhura - 60 pasekondi yakakwana zvakakwana.
Vamwe vese vekirasi Game
ine nzira dzekubatsira dzinoshandiswa mu update()
:
game.js, chikamu 3
class Game {
// ...
getLeaderboard() {
return Object.values(this.players)
.sort((p1, p2) => p2.score - p1.score)
.slice(0, 5)
.map(p => ({ username: p.username, score: Math.round(p.score) }));
}
createUpdate(player, leaderboard) {
const nearbyPlayers = Object.values(this.players).filter(
p => p !== player && p.distanceTo(player) <= Constants.MAP_SIZE / 2,
);
const nearbyBullets = this.bullets.filter(
b => b.distanceTo(player) <= Constants.MAP_SIZE / 2,
);
return {
t: Date.now(),
me: player.serializeForUpdate(),
others: nearbyPlayers.map(p => p.serializeForUpdate()),
bullets: nearbyBullets.map(b => b.serializeForUpdate()),
leaderboard,
};
}
}
getLeaderboard()
Zviri nyore - inoronga vatambi nezvibodzwa, inotora shanu dzepamusoro, uye inodzosera zita rekushandisa uye zvibodzwa kune yega yega.
createUpdate()
inoshandiswa mu update()
kugadzira zvigadziriso zvemitambo zvinogoverwa kune vatambi. Basa rayo guru nderokudana nzira serializeForUpdate()
, inoshandiswa kumakirasi Player
ΠΈ Bullet
. Ziva kuti inongoendesa data kune yega yega mutambi nezve pedyo vatambi uye projectiles - hapana chikonzero chekufambisa ruzivo nezvezvinhu zvemitambo zviri kure nemutambi!
3. Zvinhu zvemutambo pane server
Mumutambo wedu, projectiles nevatambi vakanyatsofanana: iwo abstract round inofamba zvinhu zvemutambo. Kutora mukana wekufanana uku pakati pevatambi uye projectiles, ngatitange nekuita base kirasi Object
:
object.js
class Object {
constructor(id, x, y, dir, speed) {
this.id = id;
this.x = x;
this.y = y;
this.direction = dir;
this.speed = speed;
}
update(dt) {
this.x += dt * this.speed * Math.sin(this.direction);
this.y -= dt * this.speed * Math.cos(this.direction);
}
distanceTo(object) {
const dx = this.x - object.x;
const dy = this.y - object.y;
return Math.sqrt(dx * dx + dy * dy);
}
setDirection(dir) {
this.direction = dir;
}
serializeForUpdate() {
return {
id: this.id,
x: this.x,
y: this.y,
};
}
}
Hapana chakaoma chiri kuitika pano. Iyi kirasi ichave yakanaka yekutanga nzvimbo yekuwedzera. Ngationei kuti kirasi sei Bullet
anoshandisa 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;
}
}
Kutevedzera Bullet
pfupi kwazvo! Takawedzera Object
chete edzedzero dzinotevera:
- Kushandisa package
shortid kuchizvarwa chisina kujairikaid
projectile. - Kuwedzera ndima
parentID
, kuitira kuti iwe ugone kuteedzera mutambi akagadzira iyi projectile. - Kuwedzera kukosha kwekudzoka ku
update()
, izvo zvakaenzanatrue
, kana projectile iri kunze kwenhandare (rangarirai takataura pamusoro peizvi muchikamu chekupedzisira?).
Ngatienderere mberi 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,
};
}
}
Vatambi vanyanya kuomarara kupfuura projectiles, saka kirasi iyi inofanirwa kuchengeta mimwe minda mishoma. Nzira yake update()
inoita basa rakawanda, kunyanya kudzorera projectile ichangobva kugadzirwa kana pasina yasara fireCooldown
(rangarira isu takataura pamusoro peizvi muchikamu chakapfuura?). Inowedzerawo nzira serializeForUpdate()
, nekuti isu tinofanirwa kusanganisa mimwe minda yemutambi mukuvandudza kwemutambo.
Kuwanikwa kwekirasi yepasi Object
- danho rinokosha rekudzivisa kudzokorora kwekodhi. Somuenzaniso, pasina kirasi Object
chinhu chimwe nechimwe chemutambo chinofanirwa kuve nekuitwa kwakafanana distanceTo()
, uye kukopa-kunamira zvese izvi mashandisirwo pamafaira akawanda zvingave zvinotyisa. Izvi zvinonyanya kukosha kumapurojekiti makuru, apo nhamba yekuwedzera Object
makirasi ari kukura.
4. Kuona kudhumhana
Chinhu chega chasara kuti isu tiite kuziva kana projectiles yarova vatambi! Rangarira iyi kodhi snippet kubva munzira update()
mukirasi Game
:
game.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),
);
// ...
}
}
Tinofanira kushandisa nzira applyCollisions()
, iyo inodzosera ese mapurojekiti anorova vatambi. Sezvineiwo, izvi hazvina kuoma kuita nekuti
- Zvese zvinhu zvinobonderana madenderedzwa, uye ichi ndicho chimiro chakareruka chekushandisa kuona kudhumhana.
- Isu tatova nenzira
distanceTo()
, izvo zvatakashandisa mukirasi muchikamu chapfuuraObject
.
Izvi ndizvo zvinoita kwedu kuita kwekuona kudhumhana kunoita se:
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;
}
Izvi zviri nyore kuona kudhumhana kunobva pakuti madenderedzwa maviri anodhumhana kana chinhambwe chiri pakati penzvimbo dzawo chiri pasi pehuwandu hweradii yawo. Heino nyaya apo chinhambwe pakati pepakati pemadenderedzwa maviri chakanyatso kuenzana nehuwandu hweradii yavo:
Pano iwe unofanirwa kunyatso tarisisa kune akati wandei maficha:
- Iyo projectile haifanirwe kurova mutambi akaigadzira. Izvi zvinogona kuwanikwa nekuenzanisa
bullet.parentID
Ρplayer.id
. - Iyo projectile inofanirwa kurova kamwe chete mune yakanyanyisa kesi yekurova vatambi vakawanda panguva imwe chete. Tichagadzirisa dambudziko iri tichishandisa mushandisi
break
: Kana mutambi ari kudhumhana nepurojekiti awanikwa, tinomira kutsvaga toenda kune inotevera projectile.
Kuguma
Ndizvo zvose! Takabata zvese zvaunoda kuti uzive kugadzira .io web game. Chii chinotevera? Vaka yako wega .io mutambo!
Yese muenzaniso kodhi yakavhurika sosi uye yakatumirwa pairi
Source: www.habr.com