Creando Multiplayer .io Web Ludus

Creando Multiplayer .io Web Ludus
Dimisit in MMXV Agar.io factus est progenitor novi generis games.iocuius gratia iam tum maxime crevit. Ego ipse ludos .io popularis ortum expertus sum : per tres annos duos ludos in hoc genere creavit et vendidit..

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; Slither.io ΠΈ Diep.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 ES6, keyword this ΠΈ promissa. Etiamsi Javascript perfecte non cognoscis, plus tamen intelligere potes.

Exemplum de ludo .io

Ad exercitium auxilium referemus exemplum ludum .io. Conare ludere hoc!

Creando Multiplayer .io Web Ludus
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 download source code exemplum ludo sic me sequere potes.

Exemplum hoc utitur:

  • express maxime popularis interretialis compages pro Node.js qui in ludo telae server procurat.
  • socket.io - bibliotheca interretialis ad permutandas notitias inter navigatrum et ministratorem.
  • Webpack β€” Moduli procurator. Potes legere de causa utendi Webpack hic.

Hoc est quod structuram directorium exertus similis est:

public/
    assets/
        ...
src/
    client/
        css/
            ...
        html/
            index.html
        index.js
        ...
    server/
        server.js
        ...
    shared/
        constants.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 Webpack. 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.js punctum 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 Babel,et in specie de configuratione @babel/preset-env 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 filename substitutio Webpack: [name] reponendum erit cum nomine initus punctum (in nostro casu est game), Autem [contenthash] Nullam tabellariorum erit reponi cum contentis. Nos hoc facere optimize in project ad hashing - 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 nodi ΠΈ NPM. Deinde debes facere

$ git clone https://github.com/vzhou842/example-.io-game.git
$ cd example-.io-game
$ npm install

et ire parati estis! Incipere progressionem servo, modo currere

$ npm run develop

et vade ad navigatrum tuum localhost: 3000. 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 Github.

Habemus:

  • HTML5 Canvas elementum (<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;

  1. Plures alias files JS importare.
  2. Import CSS (ita Webpack novit ut eas includant in sarcina nostra CSS).
  3. Запуск connect() constituere nexum in calculonis servi ac satus downloadAssets() ad imagines detrahendas opus est ad ludum reddendum.
  4. Peracta gradu III " elencho ostenditur (playMenu).
  5. 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. Socket.io aedificavit in subsidium WebSocketsquae 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. connectedPromise modo 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 HTML5 Canvas (<canvas>). Ludus noster est admodum simplex, ut solum sequentia reddere oporteat;

  1. background
  2. Ludio ludius navis
  3. Alii lusores in ludo
  4. 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 src/client/render.js.

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 res Listeners 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);

Creando Multiplayer .io Web Ludus
Donec non purus nulla. Verior pictura esset;
Creando Multiplayer .io Web Ludus
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:

Creando Multiplayer .io Web Ludus
Hoc nobis 100ms quiddam praebet ut inaestimabile leo ludi updates:

Creando Multiplayer .io Web Ludus
Pretium huius rei perpetuum erit initus lag 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 "clientem-partem forecasting"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 linearibus interpolationem inter ludum updates mox ante et post current tempus in clientelam;

Creando Multiplayer .io Web Ludus
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?

Creando Multiplayer .io Web Ludus
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:

  1. base < 0 significat nullas esse updates dum temporis praesentis reddendi (vide supra exsecutionem getBaseUpdate()). Hoc contingere potest statim in initio ludi ob TARDO reddendo. In hoc casu recentissima renovatio recepta utimur.
  2. base recentissimus est update habemus. Hoc contingere potest ob latentiam retis vel interretialem interretialem connexionem. In hoc quoque casu utimur recentissima renovatione quam habemus.
  3. 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 Github.

Pars 2. Backend server

In hac parte videbimus ad Node.js backend quae noster imperat exempli gratia de .io ludum.

1. Servo viscus punctus

Ad gubernationem interretialem server utemur populari compage pro Node.js vocati express. 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 webpack-dev-media 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 socket.ioquae 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:

  • sockets obiectum est quod ligat lusor ID ad nervum quod cum scaenicis coniungitur. Accessum ad bases per ids lusoriis per tempus nobis concedit.
  • players objectum 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:

  1. Computat quod tempus est dt suus 'been ab ultimo update().
  2. Unumquodque proiectum reficit et, si necesse est, destruit. Exsecutionem huius functionis postea videbimus. Nunc enim satis est nobis scire bullet.update() redit truesi proiectum delendum est (Exiit arenam).
  3. Quisque ludio ludius renovat et proiectum, si necesse est, creat. Hanc exsecutionem postea etiam videbimus - player.update() redire potest objectum Bullet.
  4. 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 ordinata bullets.
  5. Notificat et destruit omnes histriones occidit.
  6. Ludum update omnibus histriones mittit omne secundo quando dicitur update(). Auxiliaris variabilis, de qua supra, hanc indagare nos adiuvat shouldSendUpdate. As update() 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 dicitur update()quanto accuratior simulatio lusus erit. Sed non nimis rapiuntur numero provocationum update()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 shortid nam temere generation id proiectum.
  • Addit agro parentIDut indagare possis histrionem qui hoc proiectum creavit.
  • Addit reditus valorem ad update()quod est aequalis truesi 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 inseruimus Object.

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;

Creando Multiplayer .io Web Ludus
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 Github.

Source: www.habr.com