Kugadzira mutambo wewebhu wevazhinji mu.io genre

Kugadzira mutambo wewebhu wevazhinji mu.io genre
Yakabudiswa muna 2015 agar.io akava muvambi werudzi rutsva mitambo.io, ane mukurumbira wakawedzera zvikuru kubvira ipapo. Ndakaona kuwedzera kukurumbira kwe.io mitambo ini pachangu: mumakore matatu apfuura, ini akagadzira uye akatengesa mitambo miviri mumhando iyi..

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: Slither.io ΠΈ Diep.io.

Mune ino post tichaona sei gadzira .io mutambo kubva pakutanga. Kuti uite izvi, ruzivo chete rweJavascript ruchakwana: unoda kunzwisisa zvinhu zvakadai se syntax ES6, keyword this ΠΈ Promises. Kunyangwe iwe usingazive Javascript zvakakwana, iwe uchiri kukwanisa kunzwisisa yakawanda yepositi.

Muenzaniso wemutambo we.io

Nekuda kwerubatsiro rwekudzidziswa tichareva muenzaniso mutambo .io. Edza kuiridza!

Kugadzira mutambo wewebhu wevazhinji mu.io genre
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 Webpack pano.

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. Ngatitarisei kune yedu Webpack kumisikidzwa:

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 filename substitution Webpack [name] ichatsiviwa nezita renzvimbo yekupinza (munyaya yedu iri game), a [contenthash] ichatsiviwa nehashi yezvinyorwa zvefaira. Tinoita izvi gadzirisa purojekiti yehashing - Tinogona kuudza mabhurawuza kuti achengete JS yedu mapakeji nekusingaperi nekuti kana package ikachinja, zita rayo refaira rinochinja futi (kuchinja 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 Node ΠΈ NPM. Next unofanira kuita

$ 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 localhost: 3000. Iyo sevha yekuvandudza inozovakazve iyo JS neCSS mapakeji sezvo shanduko yekodhi inoitika - ingozorodza peji kuti uone shanduko dzese!

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 Github.

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:

  1. Ngenisa mamwe mafaera akati wandei eJS.
  2. Ngenisa CSS (saka Webpack inoziva kuvabatanidza muCSS package yedu).
  3. Kutanga connect() kumisikidza chinongedzo kune server uye tanga downloadAssets() kudhawunirodha mifananidzo inodiwa kupa mutambo.
  4. Mushure mekupedza stage 3 menyu huru inoratidzwa (playMenu).
  5. 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 socket.io. Socket.io ine yakavakirwa-mukati rutsigiro WebSockets, iyo inonyatsokodzera nzira mbiri dzekukurukurirana: tinogona kutumira mameseji kune server ΠΈ sevha inogona kutumira mameseji kwatiri pamusoro pekubatana kwakafanana.

Tichava nefaira rimwe chete src/client/networking.jsndiani 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 HTML5 Canvas (<canvas>) Mutambo wedu wakareruka, saka isu tinongoda kupa zvinotevera:

  1. Shure
  2. Player ship
  3. Vamwe vatambi mumutambo
  4. 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 Chiitiko Vateereri zveMouse uye Bata zviitiko.
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):

Kugadzira mutambo wewebhu wevazhinji mu.io genre
Zvinosuruvarisa, hapana chakakwana. Mufananidzo wakanyatsojeka ungave:
Kugadzira mutambo wewebhu wevazhinji mu.io genre
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:

Kugadzira mutambo wewebhu wevazhinji mu.io genre
Izvi zvinotipa 100ms buffer kuti tirarame iyo isingafungidzike nguva yekuvandudzwa kwemutambo:

Kugadzira mutambo wewebhu wevazhinji mu.io genre
Mutengo weizvi uchava zvachose input lag ne100 ms. Ichi chibayiro chidiki chemutambo wakatsetseka - vatambi vazhinji (kunyanya avo vakajairwa) havatombo cherechedza kunonoka uku. Zviri nyore kuti vanhu vagadzirise kune inogara 100ms latency pane kutamba nekusatarisika latency.

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 mutsara kududzira pakati pekuvandudzwa kwemutambo nguva isati yasvika uye mushure menguva yazvino mutengi:

Kugadzira mutambo wewebhu wevazhinji mu.io genre
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?

Kugadzira mutambo wewebhu wevazhinji mu.io genre
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:

  1. base < 0 zvinoreva kuti hapana zvigadziriso kudzamara yazvino nguva yekupa (ona kuita pamusoro getBaseUpdate()) Izvi zvinogona kuitika pakutanga kwemutambo nekuda kwekupa lag. Mune ino kesi, isu tinoshandisa yazvino gadziriso yakagamuchirwa.
  2. base ndiyo yazvino update yatinayo. Izvi zvinogona kuitika nekuda kwetiweki latency kana kushomeka kweinternet. Mune ino kesi zvakare isu tinoshandisa yazvino update yatinayo.
  3. 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 Github.

Chikamu 2. Backend server

Muchikamu chino tichatarisa iyo Node.js backend inodzora yedu muenzaniso we.io mutambo.

1. Server yekupinda nzvimbo

Kuti titore sevha yewebhu isu tichashandisa yakakurumbira Node.js web framework inonzi ratidza. Iyo ichagadziriswa neyedu server yekupinda point faira 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 socket.ioiyo inongobatana neiyo Express 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:

  1. Inoverengera kuti inguvai dt kubvira kare update().
  2. Inozorodza imwe neimwe projectile uye inovaparadza kana zvichidikanwa. Tichaona kushandiswa kwebasa iri gare gare. Parizvino zvakwana kuti tizive izvozvo bullet.update() anodzoka true, kana projectile inofanira kuparadzwa (akabuda kunze kwenhandare).
  3. Inogadziridza mutambi wega wega uye inogadzira projectile kana zvichidikanwa. Tichaonawo kuita uku gare gare - player.update() anogona kudzorera chinhu Bullet.
  4. Inotarisa kudhumhana pakati peprojekiti nevatambi vachishandisa applyCollisions(), iyo inodzosa nhevedzano yeprojekiti inorova vatambi. Kune yega yega projectile yakadzoka, isu tinowedzera mamakisi emutambi akaridzinga (tichishandisa player.onDealtDamage()), uye wobva wabvisa projectile kubva pakurongwa bullets.
  5. Inozivisa uye inoparadza vatambi vese vakaurayiwa.
  6. Inotumira mutambo wekuvandudza kune vese vatambi Sekondi imwe neimwe nguva dzekudanwa update(). Iyo yekubatsira shanduko yataurwa pamusoro inotibatsira kutevedzera izvi shouldSendUpdate. As update() 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 inowanzonzi update(), iyo yakanyatsojeka iyo simulation yemutambo ichave. Asi usanyanya kutakurwa nehuwandu hwematambudziko update(), 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 kujairika id projectile.
  • Kuwedzera ndima parentID, kuitira kuti iwe ugone kuteedzera mutambi akagadzira iyi projectile.
  • Kuwedzera kukosha kwekudzoka ku update(), izvo zvakaenzana true, 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 chapfuura Object.

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:

Kugadzira mutambo wewebhu wevazhinji mu.io genre
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 Github.

Source: www.habr.com

Voeg