
Dimisit in MMXV factus est progenitor novi generis cuius gratia iam tum maxime crevit. Ego ipse ludos .io popularis ortum expertus sum : per tres annos .
Si numquam ante hos ludos audisti, liberi sunt, lusoribus interretialibus ludis faciles ad ludendum (nulla ratio requiritur). Solent in una arena fovere multos histriones repugnantes. Ludi celebres .io; и .
In hoc post nos instar sicco quomodo creare per .io ludum a VULNUS. Ad hoc, sola cognitio Javascripti satis erit: res ut syntaxin intelligere debes , keyword this и . Etiamsi Javascript perfecte non cognoscis, plus tamen intelligere potes.
Exemplum de ludo .io
Ad exercitium auxilium referemus . Conare ludere hoc!

Lusus est admodum simplex: navem in arena cum aliis lusoribus moderaris. Navis tua sponte projectilia accendit et alios histriones evitando proiectis ferire conaris.
Overview Brevis 1. / structuram project
suadeo exemplum ludo sic me sequere potes.
Exemplum hoc utitur:
- maxime popularis interretialis compages pro Node.js qui in ludo telae server procurat.
- - bibliotheca interretialis ad permutandas notitias inter navigatrum et ministratorem.
- — Moduli procurator. Potes legere de causa utendi Webpack .
Hoc est quod structuram directorium exertus similis est:
publicus/ bona/ ... src/ cliens/ css/ ... html/ index.html index.js ... servitor/ servitor.js ... communicatus/ constantes.js
public/
Omnia in folder public/ stabiliter tradetur a servo. IN' public/assets/ imagines continet a nostro delineatio adhibitas.
sRC /
Omnis fons code est in folder src/. азвания client/ и server/ dicere pro se et shared/ continentes fasciculi constantes ab utroque cliente et servo importati sunt.
2. Conventibus / project parametri
Ut dictum est, procurator utimur moduli ad opus faciendum . Vide nostram Webpack configuratione:
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',
}),
],
};Praecipuae lineae hic sunt sequentes;
src/client/index.jspunctum aculeus Javascript (JS) clientis est. Webpack hinc incipiet et recursively alia documenta importata quaere.- The output JS of our Webpack constructum erit in indicem
dist/. Hanc limam appellabo nostram JS sarcina. - Utimur et in specie de configuratione ut nostrum codicem JS ad navigatores vetustiores traduceremus.
- Plugino utimur ad omnes CSS referenced per JS lima extrahendum et eas in unum locum iungendum. Dicam nostrum CSS sarcina.
Ut notavi sarcina nova nomina file '[name].[contenthash].ext'. Continent Webpack: [name] reponendum erit cum nomine initus punctum (in nostro casu est game), Autem [contenthash] Nullam tabellariorum erit reponi cum contentis. Nos hoc facere - navigatores indicare possumus ut sarcinas nostras emittat JS quia indefinite si sarcina mutatur, eius nomen lima mutat (mutationes contenthash). Effectus effectus erit tabella sententiarum nomen game.dbeee76e91a97d0c7207.js.
lima webpack.common.js - Haec est basis configurationis fasciculi, quem in evolutione et perfecto schematis figurationibus importamus. Exempli gratia: hic explicatio schematismi est:
webpack.dev.js
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'development',
}); Ad efficientiam utimur in processu evolutionis webpack.dev.jsAc permutat webpack.prod.js, ad optimize magnitudinum sarcinarum tendentes ad productionem.
Locus setup
Commendo instituendo consilium in machina locali tui ut gradus in hoc poste recensiti sequi possis. Setup simplex est: primo, ratio habere debet и . Deinde debes facere
$ git clone https://github.com/vzhou842/example-.io-game.git
$ cd example-.io-game
$ npm installet ire parati estis! Incipere progressionem servo, modo currere
$ npm run developet vade ad navigatrum tuum . Procurator progressus statim sarcinas JS et CSS reaedificabit sicut in codice mutationes occurrunt - modo refice paginam ad omnes mutationes vide!
3. Klients ingressum puncta
Descendamus ad ipsum codicem ludum. Primum opus est paginam index.htmlcum locum invisis, navigatrum illud prius oneres. Nostra pagina satis simplex erit;
index.html
Exemplum .io game PLAY
Exemplar hoc codice leviter ad evidenciam facilior est, et idem faciam cum multis aliis exemplis in fronte. Semper spectare potes ad plenam codice at .
Habemus:
- (
<canvas>) quo ludo utemur reddere. <link>CSS sarcina nostra addere.<script>Javascript nostri sarcina addere.- Pelagus menu cum nomen usoris
<input>et "ludere" button (<button>).
Cum paginae paginae onerat, navigatrum in codice Javascript exsequi incipiet, incipiens a puncto JS lima: 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);
};
});Hoc sonare potest perplexum, sed non multum hic agitur;
- Plures alias files JS importare.
- Import CSS (ita Webpack novit ut eas includant in sarcina nostra CSS).
- Запуск
connect()constituere nexum in calculonis servi ac satusdownloadAssets()ad imagines detrahendas opus est ad ludum reddendum. - Peracta gradu III " elencho ostenditur (
playMenu). - Prorex "fabula" button click tracto. Cum puga pyga premetur, signum ludum initialem facit et servo narrat nos parati sumus ad ludendum.
Praecipuum "cibum" logicae clientis nostri est in iis fasciculis quae tabella invecta sunt index.js. Nunc omnes ordine spectabimus.
4. Commutatio clientis notitia
In hoc ludo utimur bibliotheca nota vulgata ad communicandum cum servo . Socket.io aedificavit in subsidium quae bene apta sunt ad communicationem duarum viarum: nuntios mittere possumus servo и Servus nuntios ad nos mittere potest per eundem nexum.
Unum file habebimus src/client/networking.jsqui cura ab omnibus communications cum servo:
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);
};Codex hic quoque leviter ad claritatem abbreviatus est.
Tria praecipue in hoc documento aguntur:
- Conamur cum servo coniungere.
connectedPromisemodo liceat, cum nexum instituimus. - Si nexus felix est, munera callback nominamus (
processGameUpdate()иonGameOver()) pro nuntiis ut accipiamus a servo. - Nos export
play()иupdateDirection()ut aliis fasciculis uti possit.
5. Client reddens
Tempus est picturam in screen proponere!
...sed antequam id facere possimus, omnes imagines (opum) quae ad hoc opus sunt extrahere debemus. Scriptor resource procurator:
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 procuratio non est difficilis ad efficiendum! Praecipuum illud est condere aliquid assets, qui clavem filename ad valorem obiecti obligabit Image. Cum subsidium oneratur, servamus illud assets pro celeribus receptis in futurum. Quando singulae resource deprimendae erunt (id est, detrahebo) omnes facultates) concedimus downloadPromise.
Acceptis opibus, potes reddere. Ut supra dictum est, ad hauriendam in pagina interreti utimur (<canvas>). Ludus noster est admodum simplex, ut solum sequentia reddere oporteat;
- background
- Ludio ludius navis
- Alii lusores in ludo
- Shells
Hic sunt momenti excerpta src/client/render.jsquae quatuor punctis supra recensitis exacte trahunt;
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);
}Codex hic ad claritatem etiam abbreviatus est.
render() munus principale huius fasciculi est. startRendering() и stopRendering() moderari activationem cycli reddendi ad 60 FPS.
Imprimis exsecutiones singularum functionum adiutorium reddendi (exempli gratia) renderBullet()) non magni momenti, sed unum simplex exemplum.
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,
);
} Nota quod modum utendi sumus getAsset()quod prius visum est asset.js!
Si curae es in explorandis aliis functionibus adiutoris reddendis, tunc lege cetera .
6. Client input
Aliquam ludum facere playable! Ratio moderatio valde simplex erit: directionem motus mutare, murem (in computatrale) uti potes vel velum (in mobili fabrica). Ad efficiendum hoc nos subcriptio pro Mure et Tactu certe.
Haec omnia curabo 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() sunt Event auditores vocationis updateDirection() (de networking.js) cum initus evenit (exempli gratia cum mure movetur). updateDirection() commercium epistularum cum servo agit, quod eventum inputationis processit et statum lusum updates renovat.
7. Client status
Difficillima est haec sectio in prima parte poste. Noli esse pusillanimes, si primum illud legisti non intellegis! Etiam illud praeterire potes et ad illud postea redi.
Ultima pars puzzle opus perficere client-servo codice is statum. Mementote codicem PRAECISIO e Client Reddendo sectionem?
render.js
import { getCurrentState } from './state';
function render() {
const { me, others, bullets } = getCurrentState();
// Do the rendering
// ...
} getCurrentState() nobis praebere possit in re publica vena lusus in clientelam umquam secundum updates accepi a calculonis servi. En exemplum ludi renovationis ut minister mitteret:
{
"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
}
]
}Singulis ludum update quinque idem agri continet:
- t: Servo indicatione temporis designata haec renovatio creata est.
- me: Informationes de ludio ludius accipiendo hanc renovationem.
- alii: Ordinatio informationum circa alios lusores in eodem ludo participando.
- indicibus: ordinata informationum de projectionibus in ludo.
- leaderboard: Praesens data auctrix. Nos eos in hac statione non inputamus.
7.1 Client scriptor simplicem statum
Simplex exsecutionem getCurrentState() nonnisi directe notitias ex recentissimo ludo renovationis receptas referre potest.
rustica-state.js
let lastGameUpdate = null;
// Handle a newly received game update.
export function processGameUpdate(update) {
lastGameUpdate = update;
}
export function getCurrentState() {
return lastGameUpdate;
}Pulchra et clara! sed si modo esset ille simplex. Una e causis haec exsecutio est problematica; determinat ad ratem reddendam frame servo celeritas horologii.
Modus imaginis: numerus tabularum (i.e. vocat
render()) secundo, vel FPS. Ludi plerumque contendunt ut saltem 60 FPS.
Tick Rate: Frequentia quacum servo updates ludum mittit ad clientes. Est saepe humiliores superficie frame rate. In ludo nostro, servo ricini per alterum ad 30 currit.
Si iustam renovationem lusus recentem reddemus, FPS essentialiter numquam 30 excedere poterit, quia nos non plus quam XXX updates per secundam a servo. Etiam si vocamus render() 60 vicibus secundo, medium horum vocat simpliciter redraxabit idem, essentialiter nihil faciens. Alterum problema de simplici exsecutione est sub mora. Ad idealem Interreti celeritatem, client lusum renovationis prorsus omnem 33 ms accipiet (30 per secundam);

Donec non purus nulla. Verior pictura esset;

Simplex exsecutio est fere pessimum casum cum latency adveniens. Si ludus renovatio recipitur cum 50ms mora, tunc in clientis retardari ab extra 50ms quia adhuc statum ludum e renovatione praecedente reddens. Potes fingere quam incommodum sit histrionis: propter arbitrariam tarditates, ludus hiulcus et instabilis videbuntur.
7.2 Melior status clientis
Aliquot emendationes dabimus ad simplicem exsecutionem. Uno modo utimur reddens mora ex C ma. Id significat statum clientis semper 100ms esse post ludum civitatis in calculonis servi. Exempli gratia, si servo tempus est 150, cliens rempublicam reddet , in qua tunc temporis minister erat 50:

Hoc nobis 100ms quiddam praebet ut inaestimabile leo ludi updates:

Pretium huius rei perpetuum erit ex C ma. Hoc sacrificium est minoris pro lusu levi - plerique histriones (praesertim casuales) ne moras quidem animadvertunt. Multo facilius est homines ad constantem 100ms latentiam accommodare quam cum inopinata latency ludere.
Alia ars uti possumus quae bene deminuendi latencem percipit, sed in hac statione non discutietur.
Alius melius utimur est linearibus interpolationem. Propter pigritiam reddendam, solemus unum saltem renovatio ante tempus hodiernum in clientelam. cum dicitur getCurrentState(), implere possumus inter ludum updates mox ante et post current tempus in clientelam;

Hoc problema solvendum replo- sationem solvit: nunc singulas tabulas reddere possumus quovis rato pacto nobis opus!
7.3 an meliorem statum clientem deducentes
Exemplum exsecutionis in src/client/state.js utitur utraque mora et interpolatione lineari, sed hoc non diuturnum. In duas partes codicem rumpamus. Hic est primum:
state.js, part 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;
} Primum illud quod debes facere est instar ex eo quod facit currentServerTime(). Ut antea vidimus, omnis renovatio ludi servo indicationem includit. Latitudinem reddere volumus ut imaginem 100morum post servo reddendam esse volumus, sed numquam scire current tempus in calculonis serviquia nescimus quantum temporis ad nos pervenerit ad quemlibet ex updates pervenire. Interreti vagus est et celeritas eius valde variari potest!
Ut circa hanc problema, approximatione rationabili uti possumus: nos lets simulare primum update venit statim. Si hoc verum esset, tunc temporis calculonis servi sciremus! Condimus servo indicatione in firstServerTimestamp et salvare nostrum loci (Cliens) indicatione temporis in eodem tempore gameStart.
Oh, expecta paulisper. Non debet tempus esse in servo = tempus in cliente est? Cur distinguimus inter "servum indicationem" et "per indicationem clientis"? Magna quaestio haec est! Hoc non idem evenit. Date.now() varia indicia reddet in cliente et servo et hoc pendet a factoribus localibus ad has machinas. Numquam id in omnibus machinis indicatione temporis idem erit.
Nunc intelligimus quid agat currentServerTime(): redit server indicatione temporis de current reddens tempore. Aliis verbis, hoc est tempus currentis server (firstServerTimestamp <+ (Date.now() - gameStart)) Minus mora reddens (RENDER_DELAY).
Nunc inspiciamus quomodo updates lusum tractamus. Cum renovatio a servo recipitur, appellatur processGameUpdate()et novam renovationem ad ordinem servamus gameUpdates. Deinde, ad usum memoriae reprimendum, omnes veteres updates to removemus basis renovatioquia eis amplius non opus est.
Quid est "cor renovatio"? Hoc primam renovationem invenimus movendo retrorsum ab hodierna servo tempore. Memento huius schematis?

Ludus update directe ad sinistram "Client Render Time" est basis renovationis.
Quid est basis update propter? Quid possumus updates ut basis stilla? Ut hoc notum sit, scriptor tandem Intueamur exsequendam getCurrentState():
state.js, part 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),
};
}
}Tres casus tractamus:
base < 0significat nullas esse updates dum temporis praesentis reddendi (vide supra exsecutionemgetBaseUpdate()). Hoc contingere potest statim in initio ludi ob TARDO reddendo. In hoc casu recentissima renovatio recepta utimur.baserecentissimus est update habemus. Hoc contingere potest ob latentiam retis vel interretialem interretialem connexionem. In hoc quoque casu utimur recentissima renovatione quam habemus.- Renovatio habemus tam ante quam post hodiernam tempus reddere, ut possumus interpolate!
Quidquid reliquit in state.js est exsecutio interpolationis linearis simplicis (sed taedi) math. Si vis ipsum explorare, aperi state.js on .
Pars 2. Backend server
In hac parte videbimus ad Node.js backend quae noster imperat .
1. Servo viscus punctus
Ad gubernationem interretialem server utemur populari compage pro Node.js vocati . a nostro servo viscus punctus file configurabitur src/server/server.js:
server.js, part 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}`);Memento quod in prima parte Webpack egimus? Hoc est ubi figurationibus Webpack nostris utemur. Dupliciter applicabimus eas;
- ad usum ut sponte nostram progressionem reficere packages, seu "
- Statically transferre folder
dist/in quas Webpack scribet tabularia nostra post fabricationem fabricandi.
Alius momenti negotium server.js ex servo usque ad constituendum quae simpliciter coniungit cum servo Express:
server.js, part 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);
}); Postquam feliciter nexum socket.io constituendum cum servo, res tracto pro novis nervum configuramus. Eventus tracto processus nuntii ab clientibus acceptis ad objectum singleton game:
server.js, part 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);
} Ludum .io creamus, uno tantum exemplari egebimus Game ("Ludus") - omnes lusores in eadem arena ludunt! In altera sectione videbimus quomodo hoc genus opera sit Game.
2. Ludus servers
Класс Game maxime momenti servo latus continet logicam. Duo praecipua opera habet: ludio ludius procuratio и ludum simulation.
Incipiamus a primo munere — histriones administrandi.
game.js, part 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);
}
}
// ...
} In hoc ludo lusores ex agro cognoscemus id nervum eorum socket.io (si confusi, revertere ad server.js). Socket.io ipsa singula nervum singularem assignat id, ne de eo solliciti sitis . Vocabo eum Ludio ludius ID.
Hoc animo perscrutemur instantia variabilium in genere Game:
socketsobiectum est quod ligat lusor ID ad nervum quod cum scaenicis coniungitur. Accessum ad bases per ids lusoriis per tempus nobis concedit.playersobjectum ligat ludio id in codice> ludius objectum
bullets est ordinata obiectorum Bulletnon habens certum ordinem.
lastUpdateTime - Hoc indicatione temporis ultimi ludi renovationis. Videbimus quomodo mox usus est.
shouldSendUpdate auxilia variabilis est. Usum quoque ejus mox videbimus.
modi addPlayer(), removePlayer() и handleInput() non opus est explicare, adhibita sunt server.js. Si refrigerio opus est, paulo altius repete.
Ultima linea constructor() incipit in update exolvuntur ludi (cum frequentia 60 updates/s);
game.js, part 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;
}
}
// ...
} modum update() probabiliter maxima pars logicae ministrantis continet. Indicemus omnia quae agat ordine:
- Computat quod tempus est
dtsuus 'been ab ultimoupdate(). - Unumquodque proiectum reficit et, si necesse est, destruit. Exsecutionem huius functionis postea videbimus. Nunc enim satis est nobis scire
bullet.update()redittruesi proiectum delendum est (Exiit arenam). - Quisque ludio ludius renovat et proiectum, si necesse est, creat. Hanc exsecutionem postea etiam videbimus -
player.update()redire potest objectumBullet. - Checks in concursu inter proiectos et histriones utens
applyCollisions()quae reddit agmine telorum pulsantium. Pro unoquoque reddito proiectum augemus ustulo lusoris qui illum incendit (utendo"player.onDealtDamage()) Et remove proiectum ab ordinatabullets. - Notificat et destruit omnes histriones occidit.
- Ludum update omnibus histriones mittit omne secundo quando dicitur
update(). Auxiliaris variabilis, de qua supra, hanc indagare nos adiuvatshouldSendUpdate. Asupdate()appellatus 60 times/s, lusus updates mittimus 30 times/s. Sic, horologium frequency server cyclos horologii 30/s (de horologii frequentia in prima parte locuti sumus).
Cur mitto ludum updates only per tempus ? Ut in varius sapien. 30 updates per secundam ludum multum est!
Cur ergo non tantum vocas?
update()XXX vicibus secundo? Ad meliorem ludum simulationem. Quod saepius diciturupdate()quanto accuratior simulatio lusus erit. Sed non nimis rapiuntur numero provocationumupdate()quoniam haec computationally sumptuosa est — 60 secundo satis est.
Reliqua classis Game ex adiutorio modi usus est update():
game.js, part 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() Suus 'pulchellus simplex est — scaenicorum genera per score, summum quinque capit, et usoris et ustulo pro singulis reddit.
createUpdate() in update() ad ludum creare updates qui lusoribus distribuuntur. Praecipuum opus est vocare modos serializeForUpdate(), implemented for classes Player и Bullet. Nota quod notitias tantum transmittit ad unumquemque ludionum de proxima histriones et missilia - non opus est ut informationes transmittant de obiectis venationibus longe ab histrionum locatis!
3. Ludus obiecti in calculonis servi
In ludo nostro, missilia et histriones sunt actu simillima: abstracta sunt circa objecta ludi mobilia. Ut hac similitudine utamur inter histriones et missilia, incipiamus turpissimum genus efficere 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,
};
}
} Nihil complicatum hic geritur. Hoc genus bonum dilatationis initium erit. Videamus quomodo classis Bullet usus 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;
}
} Реализация Bullet brevissimum! Adiecimus Object modo sequentes extensiones;
- Uti ad sarcina nam temere generation
idproiectum. - Addit agro
parentIDut indagare possis histrionem qui hoc proiectum creavit. - Addit reditus valorem ad
update()quod est aequalistruesi proiectum extra arenam est (de hoc superiore sectione meminimus).
Lets proficiscantur 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,
};
}
} Histriones magis implicati quam missiles sunt, ideo hoc genus paucos agros magis condere debet. eius methodum update() plus operatur, in speciali reverso proiecto novo creato, si desunt nulli supersunt fireCooldown (meminimus de hoc in praecedenti articulo locuti sumus?). Etiam modum extendit serializeForUpdate()propterea quod additos agros pro lusori in ludo renovationis includere oportet.
Availability of a base class Object - magni gradus ad vitare codice repetitio. Exempli gratia, sine classe Object omne ludum quod idem exsecutionem distanceTo()et omnia haec exsecutiones per multiplices tabulas exscribere visio nocturna esset. Hoc fit maxime momenti pro magnis inceptisCum numerus expanding Object genera nascuntur.
4. Occursum deprehendatur
Sola res superest ut cognoscamus cum proiecta histriones feriunt! Memento huius codicis PRAECISIO ex methodo update() in genere 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),
);
// ...
}
} Opus efficiendi modum applyCollisions()quae reddit omnia proiecta pulsantia. Fortunate, hoc non difficile est facere
- Omnia objecta concurrentia sunt circuli, et haec figura simplicissima est ad concursum deprehendendum deducendi.
- Nos iam modum
distanceTo(), quam in priori sectione inseruimusObject.
Haec nostra deprehensio concursus colliculorum similis est:
collisiones.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;
}Hoc simplex occursum deprehensio fundatur in eo quod duo circuli inter se colliduntur, si distantia centra ipsorum minor est quam summa earum radiorum. Hic casus est ubi distantia centra duorum circulorum prorsus aequalis est summae radiorum eorum;

Hic debes animadvertere duobus aspectibus pluribus:
- Proiectum ludio ludius qui eum creavit non debet. Hoc potest fieri per comparationem
bullet.parentIDсplayer.id. - Proiectum solum semel in extrema casu feriendi plures lusores simul ferire debet. Hanc quaestionem solvemus usura operator
breakCum histrio cum proiecto impactus est inventus, quaerendo cessamus et progredimur ad proximum proiectum.
finis
Ita est! Perteximus omnia quae scire debes creare ludum interretialem .io. Quid suus 'postero? .io ludum pro- prium construe!
Omne exemplum codicem fons apertum est et on missum .
Source: www.habr.com
