Ukudala Igeyimu Yewebhu Yabadlali abaningi .io

Ukudala Igeyimu Yewebhu Yabadlali abaningi .io
Ikhishwe ngo-2015 I-Agar.io waba umsunguli wohlobo olusha imidlalo.io, odumo lwakhe luye lwakhula kakhulu kusukela ngaleso sikhathi. Ngizibonele ukwanda kokuthandwa kwemidlalo ye-.io ngokwami: kule minyaka emithathu edlule, I udale futhi wathengisa imidlalo emibili kulolu hlobo..

Esimeni lapho ungakaze uzwe ngale midlalo ngaphambilini, ingamahhala, imidlalo yewebhu yabadlali abaningi okulula ukuyidlala (ayikho i-akhawunti edingekayo). Ngokuvamile bafaka abadlali abaningi abaphikisanayo enkundleni eyodwa. Eminye imidlalo edumile ye-.io: Slither.io ΠΈ I-Diep.io.

Kule post sizobona ukuthi kanjani dala igeyimu ye-.io kusukela ekuqaleni. Ukuze wenze lokhu, ulwazi lweJavascript kuphela oluzokwanela: udinga ukuqonda izinto ezifana ne-syntax ES6, igama elingukhiye this ΠΈ Izithembiso. Ngisho noma ungayazi i-Javascript ngokuphelele, usengaqonda okuningi kokuthunyelwe.

Isibonelo segeyimu ye-.io

Ukuze uthole usizo lokuqeqeshwa sizobhekisela kukho isibonelo umdlalo .io. Zama ukuyidlala!

Ukudala Igeyimu Yewebhu Yabadlali abaningi .io
Umdlalo ulula: ulawula umkhumbi enkundleni nabanye abadlali. Umkhumbi wakho udubula ngokuzenzakalelayo ama-projectiles bese uzama ukushaya abanye abadlali ngenkathi ugwema ama-projectiles abo.

1. Uhlolojikelele olufushane/isakhiwo sephrojekthi

Ngincoma landa ikhodi yomthombo isibonelo umdlalo ukuze ukwazi ukungilandela.

Isibonelo sisebenzisa okulandelayo:

  • Veza wuhlaka lwewebhu oludume kakhulu lwe-Node.js oluphethe iseva yewebhu yomdlalo.
  • isokhethi.io β€” umtapo wezincwadi we-websocket wokushintshana ngedatha phakathi kwesiphequluli neseva.
  • I-Webpack - Umphathi wemojula. Ungafunda mayelana nokuthi kungani kufanele usebenzise i-Webpack lapha.

Yile ndlela uhlaka lwemibhalo yephrojekthi lubukeka ngayo:

public/
    assets/
        ...
src/
    client/
        css/
            ...
        html/
            index.html
        index.js
        ...
    server/
        server.js
        ...
    shared/
        constants.js

umphakathi/

Yonke into ikufolda public/ izodluliswa ngokwezibalo yiseva. IN public/assets/ iqukethe izithombe ezisetshenziswa iphrojekthi yethu.

src /

Yonke ikhodi yomthombo ikufolda src/. Izihloko client/ ΠΈ server/ bazikhulumele futhi shared/ iqukethe ifayela elingaguquki elingeniswe yikho kokubili iklayenti neseva.

2. Imingcele yemihlangano/yephrojekthi

Njengoba kushiwo ngenhla, sisebenzisa umphathi wemojuli ukwakha iphrojekthi I-Webpack. Ake sibheke ukucushwa kwethu kwe-Webpack:

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',
    }),
  ],
};

Imigqa ebaluleke kakhulu lapha yile elandelayo:

  • src/client/index.js iyindawo yokungena yeklayenti le-Javascript (JS). I-Webpack izoqala lapha futhi ibheke ngokuphindaphindiwe amanye amafayela angenisiwe.
  • I-JS ephumayo yokwakhiwa kwe-Webpack yethu izotholakala kuhlu lwemibhalo dist/. Ngizobiza leli fayela elethu Iphakheji ye-JS.
  • Sisebenzisa Babel, futhi ikakhulukazi ukucushwa @babel/preset-env ukudlulisa ikhodi yethu ye-JS yeziphequluli ezindala.
  • Sisebenzisa i-plugin ukuze sikhiphe yonke i-CSS ebalulwe ngamafayela e-JS futhi siwahlanganise endaweni eyodwa. Ngizoyibiza ngeyethu Iphakheji ye-CSS.

Kungenzeka ukuthi uqaphele amagama efayela lephakheji angajwayelekile '[name].[contenthash].ext'. Ziqukethe ukufaka esikhundleni segama lefayela I-Webpack: [name] izothathelwa indawo igama lephoyinti lokufaka (kithina kunjalo game), futhi [contenthash] izothathelwa indawo i-hash yokuqukethwe kwefayela. Senza lokhu ukuze thuthukisa iphrojekthi ye-hashing - singatshela iziphequluli ukuthi zigcine amaphakheji ethu e-JS unomphela ngoba uma iphakheji lishintsha, igama lefayela layo liyashintsha (izinguquko contenthash). Umphumela oqediwe uzoba igama lefayela lokubuka game.dbeee76e91a97d0c7207.js.

Π€Π°ΠΉΠ» webpack.common.js - Leli yifayela lokucushwa eliyisisekelo esilingenisa ekuthuthukisweni nasekuqedeni ukulungiselelwa kwephrojekthi. Isibonelo, nakhu ukucushwa kokuthuthukiswa:

webpack.dev.js

const merge = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
  mode: 'development',
});

Ngokusebenza kahle, sisebenzisa kunqubo yokuthuthukisa webpack.dev.js, bese ushintshela ku webpack.prod.js, ukuthuthukisa osayizi bephakheji lapho kuthunyelwa ekukhiqizeni.

Ukusethwa kwendawo

Ngincoma ukuthi ufake iphrojekthi emshinini wangakini ukuze ukwazi ukulandela izinyathelo ezibalwe kulokhu okuthunyelwe. Ukusetha kulula: okokuqala, isistimu kufanele ibe nayo I-Node ΠΈ I-NPM. Okulandelayo udinga ukwenza

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

futhi usulungele ukuhamba! Ukuze uqale iseva yokuthuthukisa, vele uqalise

$ npm run develop

bese uye kusiphequluli sakho sewebhu localhost: i-3000. Iseva yokuthuthukisa izokwakha kabusha ngokuzenzakalelayo amaphakheji e-JS ne-CSS njengoba kwenzeka izinguquko zekhodi - vele uvuselele ikhasi ukuze ubone zonke izinguquko!

3. Amaphuzu okungena eklayenti

Ake sehlele kukhodi yegeyimu ngokwayo. Okokuqala sidinga ikhasi index.html, uma uvakashela isayithi, isiphequluli sizoyilayisha kuqala. Ikhasi lethu lizoba lula kakhulu:

index.html

Isibonelo segeyimu ye-.io  DLALA

Lesi sibonelo sekhodi senziwe lula kancane ukuze kucace, futhi ngizokwenza okufanayo ngezinye izibonelo eziningi kokuthunyelwe. Ungahlala ubheka ikhodi egcwele kokuthi I-Github.

Sine:

  • Isici sekhanvasi ye-HTML5 (<canvas>), esizoyisebenzisa ukwenza umdlalo.
  • <link> ukwengeza iphakheji yethu ye-CSS.
  • <script> ukwengeza iphakheji yethu ye-Javascript.
  • Imenyu enkulu enegama lomsebenzisi <input> kanye nenkinobho ethi β€œDLALA” (<button>).

Lapho ikhasi lasekhaya selilayishiwe, isiphequluli sizoqala ukusebenzisa ikhodi ye-Javascript, siqale ngendawo yokungena yefayela le-JS: 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);
  };
});

Lokhu kungase kuzwakale kuyinkimbinkimbi, kodwa empeleni akukho okuningi okuqhubekayo lapha:

  1. Ngenisa amanye amafayela e-JS ambalwa.
  2. Ngenisa i-CSS (ukuze iWebpack yazi ukuyifaka kuphakheji yethu ye-CSS).
  3. Qalisa connect() ukusungula uxhumano kuseva bese uqala downloadAssets() ukulanda izithombe ezidingekayo ukuze unikeze umdlalo.
  4. Ngemva kokuqeda isigaba sesi-3 imenyu enkulu iyaboniswa (playMenu).
  5. Ukusetha isibambi sokuchofoza inkinobho ethi "PLAY". Lapho inkinobho icindezelwa, ikhodi iqalisa umdlalo futhi itshele iseva ukuthi sesilungele ukudlala.

β€œInyama” eyinhloko yomqondo wesiphakeli seklayenti lethu ikulawo mafayela angeniswe ngefayela index.js. Manje sizozibheka zonke ngokulandelana kwazo.

4. Ukushintshaniswa kwedatha yeklayenti

Kulomdlalo sisebenzisa umtapo wezincwadi owaziwayo ukuxhumana neseva isokhethi.io. I-Socket.io inosekelo olwakhelwe ngaphakathi AmaWebSocket, ezifaneleke kahle ukuxhumana kwezindlela ezimbili: singathumela imilayezo kuseva ΠΈ iseva ingathumela imilayezo kithi ngoxhumano olufanayo.

Sizoba nefayela elilodwa src/client/networking.jsngubani ozonakekela wonke umuntu ukuxhumana neseva:

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);
};

Le khodi iphinde ifushaniswe kancane ukuze icace.

Kunezinto ezintathu ezibalulekile ezenzeka kuleli fayela:

  • Sizama ukuxhuma kuseva. connectedPromise kuvunyelwe kuphela uma sesisungule uxhumano.
  • Uma ukuxhumeka kuphumelele, sibhalisa imisebenzi yokushayela emuva (processGameUpdate() ΠΈ onGameOver()) ngemiyalezo esingayithola kuseva.
  • Siyathekelisa play() ΠΈ updateDirection()ukuze amanye amafayela akwazi ukuwasebenzisa.

5. Ukunikezwa kweklayenti

Sekuyisikhathi sokubonisa isithombe esikrinini!

...kodwa ngaphambi kokuthi senze lokhu, sidinga ukulanda zonke izithombe (izinsiza) ezidingekayo kulokhu. Masibhale umphathi wensiza:

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];

Ukuphathwa kwezinsiza akunzima kangako ukukusebenzisa! Iphuzu eliyinhloko ukugcina into assets, ezobophezela ukhiye wegama lefayela enani lento Image. Uma insiza ilayishwa, siyilondoloza entweni assets ukuze uthole iresidi esheshayo esikhathini esizayo. Kuzovunyelwa nini ukulandwa kwensiza ngayinye (okungukuthi, kuzodawuniloda konke izinsiza), siyakuvumela downloadPromise.

Ngemva kokulanda izinsiza, ungaqala ukunikeza. Njengoba kushiwo ngaphambili, ukudweba ekhasini lewebhu esilisebenzisayo I-HTML5 Canvas (<canvas>). Umdlalo wethu ulula, ngakho-ke sidinga kuphela ukunikeza okulandelayo:

  1. Isendlalelo
  2. Umkhumbi womdlali
  3. Abanye abadlali emdlalweni
  4. Amagobolondo

Nawa amazwibela abalulekile src/client/render.js, okudweba amaphuzu amane abhalwe ngenhla:

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);
}

Le khodi iphinde ifushaniswe ukuze icace.

render() umsebenzi oyinhloko waleli fayela. startRendering() ΠΈ stopRendering() lawula ukwenziwa kusebenze komjikelezo wokunikeza ku-60 FPS.

Ukuqaliswa okuqondile komsebenzi womsizi wokunikezela ngawodwana (isibonelo renderBullet()) azibalulekile kangako, kodwa nasi isibonelo esisodwa esilula:

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,
  );
}

Qaphela ukuthi sisebenzisa indlela getAsset(), okwabonwa ngaphambilini ku asset.js!

Uma ungathanda ukuhlola eminye imisebenzi yomsizi wokunikeza, bese ufunda okusele src/client/render.js.

6. Okokufaka kweklayenti

Isikhathi sokwenza umdlalo okudlalekayo! Uhlelo lokulawula luzoba lula kakhulu: ukushintsha isiqondiso sokunyakaza, ungasebenzisa igundane (kukhompyutha) noma uthinte isikrini (kumakhalekhukhwini). Ukwenza lokhu sizobhalisa Abalaleli Bemicimbi yemicimbi ye-Mouse and Touch.
Uzokunakekela konke lokhu 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() ngabalaleli bomcimbi abafonayo updateDirection() (ye networking.js) uma kwenzeka umcimbi wokufaka (isibonelo, uma igundane lihanjiswa). updateDirection() ibhekana nokushintshisana kwemilayezo neseva, ecubungula umcimbi wokufakwayo futhi ibuyekeze isimo segeyimu ngokufanele.

7. Isimo seklayenti

Lesi sigaba sinzima kakhulu engxenyeni yokuqala yokuthunyelwe. Ungadangali uma ungakuqondi lapho uyifunda okokuqala! Ungakwazi nokuyeqa bese ubuyela kuyo ngemva kwesikhathi.

Ucezu lokugcina lwendida edingekayo ukuqedela ikhodi yeseva yeklayenti ithi isimo. Khumbula amazwibela ekhodi asuka esigabeni Sokunikezwa Kwekhasimende?

render.js

import { getCurrentState } from './state';

function render() {
  const { me, others, bullets } = getCurrentState();

  // Do the rendering
  // ...
}

getCurrentState() kufanele ikwazi ukusinikeza isimo samanje segeyimu kuklayenti nganoma yisiphi isikhathi ngokusekelwe kuzibuyekezo ezitholwe kuseva. Nasi isibonelo sesibuyekezo somdlalo esingase sithunyelwe yiseva:

{
  "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
    }
  ]
}

Isibuyekezo somdlalo ngamunye siqukethe izinkambu ezinhlanu ezifanayo:

  • t: Isitembu sesikhathi seseva esibonisa ukuthi lesi sibuyekezo sadalwa nini.
  • me: Ulwazi mayelana nomdlali othola lesi sibuyekezo.
  • abanye: Iqoqo lolwazi mayelana nabanye abadlali ababamba iqhaza kugeyimu efanayo.
  • amachashazi: ulwazi oluningi mayelana nama-projectiles emdlalweni.
  • Ibhodi yabaphambili: Idatha yamanje yebhodi yabaphambili. Ngeke sizicabangele kulokhu okuthunyelwe.

7.1 Isimo sokungazi kwekhasimende

Ukuqaliswa kokungazi getCurrentState() ingabuyisela ngokuqondile idatha evela kusibuyekezo somdlalo osanda kutholwa.

naive-state.js

let lastGameUpdate = null;

// Handle a newly received game update.
export function processGameUpdate(update) {
  lastGameUpdate = update;
}

export function getCurrentState() {
  return lastGameUpdate;
}

Kuhle futhi kucace! Kodwa ukube nje bekulula kanjalo. Esinye sezizathu ezenza lokhu kuqaliswa kube yinkinga: ikhawulela ukunikezwa kwezinga lozimele kusivinini sewashi leseva.

Ukukalwa kwe fulemu: inombolo yozimele (okungukuthi amakholi render()) ngomzuzwana, noma i-FPS. Imidlalo ivamise ukulwela ukuzuza okungenani ama-FPS angama-60.

Isilinganiso sokumaka: Imvamisa iseva ethumela ngayo izibuyekezo zegeyimu kumakhasimende. Ivamise ukuba ngaphansi kwezinga lozimele. Emdlalweni wethu, iseva isebenza kumakhikhi angama-30 ngomzuzwana.

Uma nje sinikeza isibuyekezo sakamuva somdlalo, khona-ke i-FPS empeleni ngeke ikwazi ukudlula ama-30 ngoba asilokothi sithole izibuyekezo ezingaphezu kuka-30 ngomzuzwana kusuka kuseva. Noma sifona render() Izikhathi ezingu-60 ngomzuzwana, bese ingxenye yalezi zingcingo izomane idwebe kabusha into efanayo, empeleni ingenzi lutho. Enye inkinga ngokuqaliswa kokungazi ukuthi kunjalo ngaphansi kokubambezeleka. Ngesivinini esikahle se-inthanethi, iklayenti lizothola isibuyekezo segeyimu njalo nje ngo-33 ms (30 ngomzuzwana):

Ukudala Igeyimu Yewebhu Yabadlali abaningi .io
Ngeshwa, akukho lutho oluphelele. Isithombe esingokoqobo kungaba:
Ukudala Igeyimu Yewebhu Yabadlali abaningi .io
Ukuqaliswa kokungenangqondo kuyisimo esibi kakhulu uma kukhulunywa nge-latency. Uma isibuyekezo somdlalo sitholwa ngokulibaziseka okungu-50ms, khona-ke iklayenti lehliswa isivinini ngo-50ms engeziwe ngoba isanikeza isimo somdlalo kusukela kusibuyekezo sangaphambilini. Ungacabanga ukuthi lokhu kuphazamisa kangakanani kumdlali: ngenxa yokwehla kwejubane okungahleliwe, umdlalo uzobonakala ushubile futhi ungazinzile.

7.2 Isimo seklayenti esithuthukisiwe

Sizokwenza ukuthuthukiswa okuthile ekusetshenzisweni okungenangqondo. Okokuqala, sisebenzisa ukulibaziseka kokunikeza ngo 100ms. Lokhu kusho ukuthi isimo "samanje" seklayenti sizohlala singu-100ms ngemuva kwesimo segeyimu kuseva. Isibonelo, uma isikhathi seseva si 150, bese iklayenti lizonikeza isimo lapho iseva yayikuso ngaleso sikhathi 50:

Ukudala Igeyimu Yewebhu Yabadlali abaningi .io
Lokhu kusinika ibhafa engu-100ms ukuze sisinde esikhathini esingalindelekile sezibuyekezo zegeyimu:

Ukudala Igeyimu Yewebhu Yabadlali abaningi .io
Intengo yalokhu izoba ngunaphakade i-lag yokufaka ngo 100ms. Lokhu ukuzidela okuncane komdlalo wegeyimu oshelelayo - iningi labadlali (ikakhulukazi abavamile) ngeke likubone nokulibaziseka lokhu kubambezeleka. Kulula kakhulu ukuthi abantu bazijwayeze ukubambezeleka okungaguquki okungu-100ms kunokudlala ngokubambezeleka okungalindelekile.

Singasebenzisa enye indlela ebizwa ngokuthi "isibikezelo sohlangothi lweklayenti", okwenza umsebenzi omuhle wokunciphisa ukubambezeleka okubonwayo, kodwa ngeke kuxoxwe ngakho kulokhu okuthunyelwe.

Okunye ukuthuthukiswa esikusebenzisayo ukuhumusha ngomugqa. Ngenxa yokungasebenzi kahle, ngokuvamile siba okungenani isibuyekezo esisodwa ngaphambi kwesikhathi samanje ekhasimendeni. Uma ebizwa getCurrentState(), singafeza ukuhumusha ngomugqa phakathi kwezibuyekezo zegeyimu ngaphambi nangemva kwesikhathi samanje kuklayenti:

Ukudala Igeyimu Yewebhu Yabadlali abaningi .io
Lokhu kuxazulula inkinga yesilinganiso sozimele: manje singakwazi ukunikeza ozimele abahlukile nganoma isiphi isilinganiso sozimele esisidingayo!

7.3 Ukusebenzisa isimo sekhasimende esithuthukisiwe

Ukuqaliswa kwesibonelo ku src/client/state.js isebenzisa kokubili ukubambezeleka kokunikeza kanye nokuhumusha ngomugqa, kodwa lokhu akuhlali isikhathi eside. Ake sihlukanise ikhodi ibe izingxenye ezimbili. Nansi eyokuqala:

state.js, ingxenye 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;
}

Into yokuqala okudingeka ukwenze ukuthola ukuthi yenzani currentServerTime(). Njengoba sibonile ngaphambili, sonke isibuyekezo somdlalo sifaka isitembu sesikhathi seseva. Sifuna ukusebenzisa i-render latency ukuze sinikeze isithombe ngo-100ms ngemuva kweseva, kodwa ngeke sazi isikhathi samanje kuseva, ngoba asikwazi ukwazi ukuthi kuthathe isikhathi esingakanani ukuthi noma yiziphi izibuyekezo zisifinyelele. I-inthanethi ayibikezeleki futhi ijubane layo lingahluka kakhulu!

Ukuxazulula le nkinga, singasebenzisa isilinganiso esinengqondo: thina ake senze sengathi isibuyekezo sokuqala sifike ngokushesha. Ukube lokhu bekuyiqiniso, besizokwazi isikhathi seseva ngaleso sikhathi! Sigcina isitembu sesikhathi seseva ngaphakathi firstServerTimestamp futhi ugcine wethu wendawo (iklayenti) isitembu sesikhathi ngesikhathi esifanayo gameStart.

O, ima kancane. Akufanele yini kube nesikhathi kuseva = isikhathi kuklayenti? Kungani sihlukanisa phakathi "kwesitembu sesikhathi seseva" kanye "nesitembu sesikhathi seklayenti"? Umbuzo omkhulu lo! Kuvele ukuthi lezi azifani. Date.now() izobuyisela izitembu zesikhathi ezihlukile kuklayenti neseva futhi lokhu kuncike ezintweni zendawo kule mishini. Ungalokothi ucabange ukuthi izitembu zesikhathi zizofana kuyo yonke imishini.

Manje sesiyaqonda ukuthi lenzani currentServerTime(): iyabuya isitembu sesikhathi seseva sesikhathi samanje sokunikezwa. Ngamanye amazwi, lesi yisikhathi samanje seseva (firstServerTimestamp <+ (Date.now() - gameStart)) khipha ukubambezeleka kokukhishwa (RENDER_DELAY).

Manje ake sibheke ukuthi sizisingatha kanjani izibuyekezo zegeyimu. Uma isibuyekezo sitholwa kwiseva, siyabizwa processGameUpdate(), futhi silondoloza isibuyekezo esisha ohlwini gameUpdates. Bese, ukuhlola ukusetshenziswa kwememori, sisusa zonke izibuyekezo ezindala kuzo i-base updatengoba asisawadingi.

Kuyini "isibuyekezo esiyinhloko"? Lokhu isibuyekezo sokuqala esisithola ngokuhlehla sisuka esikhathini samanje seseva. Uyawukhumbula lo mdwebo?

Ukudala Igeyimu Yewebhu Yabadlali abaningi .io
Isibuyekezo somdlalo ngokuqondile kwesokunxele "Sesikhathi Sokunikezwa Kweklayenti" siyisibuyekezo esiyisisekelo.

Sisetshenziselwa ini isibuyekezo esiyisisekelo? Kungani singayeka izibuyekezo zisekelwe? Ukuze siqonde lokhu, ake ekugcineni ake sibheke ukuqaliswa getCurrentState():

state.js, ingxenye 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),
    };
  }
}

Siphatha amacala amathathu:

  1. base < 0 kusho ukuthi azikho izibuyekezo kuze kube yisikhathi samanje sokunikezwa (bona ukusetshenziswa ngenhla getBaseUpdate()). Lokhu kungenzeka ekuqaleni komdlalo ngenxa ye-rendering lag. Kulokhu, sisebenzisa isibuyekezo sakamuva esitholiwe.
  2. base isibuyekezo sakamuva kakhulu esinaso. Lokhu kungenzeka ngenxa yokubambezeleka kwenethiwekhi noma ukuxhumeka kwe-inthanethi okubi. Nakulokhu sisebenzisa isibuyekezo sakamuva esinaso.
  3. Sinesibuyekezo ngaphambi nangemuva kwesikhathi samanje sokunikezwa, ukuze sikwazi chaza!

Konke okusele phakathi state.js ukuqaliswa kokuhumusha ngomugqa okulula (kodwa okuyisicefe) izibalo. Uma ufuna ukuzihlolela ngokwakho, vula state.js on I-Github.

Ingxenye 2. Iseva engemuva

Kule ngxenye sizobheka i-backend ye-Node.js elawula yethu isibonelo somdlalo we-.io.

1. Indawo yokungena kwiseva

Ukuphatha iseva yewebhu sizosebenzisa uhlaka lwewebhu oludumile lwe-Node.js olubizwa Veza. Izolungiselelwa ifayela lethu lephoyinti lokungena leseva src/server/server.js:

iseva.js, ingxenye 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}`);

Khumbula ukuthi engxenyeni yokuqala sixoxe ngeWebpack? Lapha yilapho sizosebenzisa khona izilungiselelo zethu ze-Webpack. Sizozisebenzisa ngezindlela ezimbili:

  • Sebenzisa webpack-dev-middleware ukwakha kabusha ngokuzenzakalelayo amaphakheji ethu okuthuthukisa, noma
  • Dlulisa ifolda ngokuqinile dist/, lapho i-Webpack izobhala khona amafayela ethu ngemva kokwakhiwa kokukhiqiza.

Omunye umsebenzi obalulekile server.js iqukethe ukusetha iseva isokhethi.iookuvele kuxhumeke kuseva ye-Express:

iseva.js, ingxenye 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);
});

Ngemva kokusungula ngempumelelo uxhumano lwe-socket.io neseva, silungiselela izibambi zomcimbi zesokhethi entsha. Izibambi zomcimbi zicubungula imilayezo etholwe kumakhasimende ngokuthumela entweni ye-singleton game:

iseva.js, ingxenye 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);
}

Sakha igeyimu ye-.io, ngakho sizodinga ikhophi eyodwa kuphela Game (β€œUmdlalo”) – bonke abadlali badlala enkundleni efanayo! Esigabeni esilandelayo sizobona ukuthi leli klasi lisebenza kanjani Game.

2. Amaseva egeyimu

Класс Game iqukethe i-logic yohlangothi lweseva ebaluleke kakhulu. Inemisebenzi emibili esemqoka: ukuphathwa komdlali и sekulingisa umdlalo.

Ake siqale ngomsebenzi wokuqala - ukuphatha abadlali.

umdlalo.js, ingxenye 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);
    }
  }

  // ...
}

Kulo mdlalo sizohlonza abadlali ngokwenkundla id isokhethi yabo.io (uma udidekile, bese ubuyela ku server.js). I-Socket.io ngokwayo yabela isokhethi ngayinye okuhlukile id, ngakho akudingeki sikhathazeke ngakho. Ngizombiza I-ID yomdlali.

Unalokho engqondweni, ake sihlole izibonelo eziguquguqukayo ekilasini Game:

  • sockets into ebophezela i-ID yomdlali kusokhethi ehlotshaniswa nomdlali. Kusivumela ukuthi sifinyelele amasokhethi ngomazisi bawo abadlali ngokuhamba kwesikhathi.
  • players into ebophezela i-ID yomdlali kukhodi>Into yomdlali

bullets iwuxha lwezinto Bullet, engenawo umyalo othize.
lastUpdateTime - Lesi isitembu sesikhathi sokubuyekezwa komdlalo wokugcina. Sizobona ukuthi isetshenziswa kanjani maduze.
shouldSendUpdate iwukuguquguquka okusizayo. Sizobona nokusetshenziswa kwayo maduze.
Izindlela addPlayer(), removePlayer() ΠΈ handleInput() asikho isidingo sokuchaza, zisetshenziswa ku server.js. Uma udinga isiqalisi, buyela emuva kancane.

Umugqa wokugcina constructor() iyaqala buyekeza umjikelezo imidlalo (enemvamisa yezibuyekezo ezingama-60/s):

umdlalo.js, ingxenye 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;
    }
  }

  // ...
}

Indlela update() cishe iqukethe ingxenye ebaluleke kakhulu yomqondo wohlangothi lweseva. Masibhale yonke into eyenzayo ngokulandelana kwayo:

  1. Ibala ukuthi yisiphi isikhathi dt selokhu kwaba okokugcina update().
  2. Ivuselela i-projectile ngayinye futhi iwacekele phansi uma kunesidingo. Sizobona ukuqaliswa kwalokhu kusebenza kamuva. Okwamanje sekwanele ukuthi sikwazi lokho bullet.update() izimbuyiselo true, uma i-projectile kufanele ichithwe (waphumela ngaphandle kwenkundla).
  3. Ibuyekeza umdlali ngamunye futhi idale i-projectile uma kudingeka. Sizobona lokhu kuqaliswa kamuva - player.update() ingabuyisela into Bullet.
  4. Ihlola ukushayisana phakathi kwama-projectiles nabadlali abasebenzisayo applyCollisions(), ebuyisela inqwaba yama-projectiles ashaya abadlali. Kuphrojekthi ngayinye ebuyisiwe, sinyusa isikolo somdlali oyidubule (sisebenzisa player.onDealtDamage()), bese ususa i-projectile ohlwini bullets.
  5. Yazisa futhi ibhubhise bonke abadlali ababulewe.
  6. Ithumela isibuyekezo somdlalo kubo bonke abadlali njalo ngomzuzwana izikhathi lapho ebizwa update(). I-axiliary variable eshiwo ngenhla isisiza ukuthi silandelele lokhu shouldSendUpdate. Ngoba update() ebizwa izikhathi ezingu-60/s, sithumela izibuyekezo zegeyimu izikhathi ezingu-30/s. Ngakho, imvamisa yewashi iseva ingu-30 clock cycles/s (sikhulume ngobuningi bewashi engxenyeni yokuqala).

Kungani uthumele izibuyekezo zegeyimu kuphela ngokuhamba kwesikhathi ? Ukugcina isiteshi. Ukubuyekezwa kwegeyimu engama-30 ngomzuzwana kuningi!

Kungani ungavele ufone ke? update() 30 izikhathi ngomzuzwana? Ukuthuthukisa sekulingisa game. Kuvame ukubizwa ngokuthi update(), ukulingisa komdlalo kuzoba nembe kakhulu. Kodwa ungathathwa kakhulu inani lezinselelo update(), ngoba lokhu kuwumsebenzi obiza kakhulu - 60 ngomzuzwana kwanele.

Ikilasi lonke Game siqukethe izindlela ezisizayo ezisetshenziswa ku update():

umdlalo.js, ingxenye 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() Ilula kakhulu - ihlunga abadlali ngamaphuzu, ithatha abahlanu abaphezulu, bese ibuyisela igama lomsebenzisi nemiphumela ngayinye.

createUpdate() isetshenziswe ku update() ukudala izibuyekezo zegeyimu ezisatshalaliswa kubadlali. Umsebenzi wawo omkhulu ukubiza izindlela serializeForUpdate(), yenzelwe amakilasi Player ΠΈ Bullet. Qaphela ukuthi idlulisela kuphela idatha kumdlali ngamunye mayelana eliseduze abadlali nama-projectiles - asikho isidingo sokudlulisa ulwazi mayelana nezinto zomdlalo ezitholakala kude nomdlali!

3. Izinto zegeyimu kuseva

Emdlalweni wethu, ama-projectiles nabadlali bafana kakhulu: bayizinto ezingabonakali eziyindilinga ezihambayo. Ukuze usebenzise lokhu kufana phakathi kwabadlali nama-projectiles, ake siqale ngokusebenzisa isigaba sesisekelo 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,
    };
  }
}

Akukho okuxakayo okwenzekayo lapha. Lesi sigaba sizoba yisiqalo esihle sokwandisa. Ake sibone ukuthi ikilasi kanjani Bullet isebenzisa Object:

inhlamvu.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;
  }
}

Ukuqaliswa Bullet kufushane kakhulu! Sengeze ku Object izandiso ezilandelayo kuphela:

  • Usebenzisa iphakheji shortid okwesizukulwane esingahleliwe id projectile.
  • Yengeza inkambu parentID, ukuze ukwazi ukulandelela umdlali odale le projectile.
  • Yengeza inani lokubuyisela ku update(), okulingana true, uma i-projectile ingaphandle kwenkundla (khumbula sikhulume ngalokhu esigabeni sokugcina?).

Asiqhubekele phambili Player:

umdlali.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,
    };
  }
}

Abadlali bayinkimbinkimbi kakhulu kunama-projectile, ngakho-ke leli klasi kufanele ligcine izinkambu ezimbalwa ezengeziwe. Indlela yakhe update() yenza umsebenzi owengeziwe, ikakhulukazi ukubuyisela i-projectile esanda kwakhiwa uma ingekho esele fireCooldown (khumbula sikhulume ngalokhu esigabeni esidlule?). Iphinde inwebe indlela serializeForUpdate(), ngoba sidinga ukufaka izinkambu ezengeziwe zomdlali kusibuyekezo somdlalo.

Ukutholakala kwekilasi lesisekelo Object - isinyathelo esibalulekile sokugwema ukuphindaphinda kwekhodi. Isibonelo, ngaphandle kwekilasi Object yonke into yegeyimu kufanele ibe nokusetshenziswa okufanayo distanceTo(), futhi ukukopisha-ukunamathisela konke lokhu kuqaliswa kuwo wonke amafayela amaningi kungaba yiphupho elibi. Lokhu kubaluleka ikakhulukazi kumaphrojekthi amakhulu, lapho inani elandayo Object amakilasi ayakhula.

4. Ukutholwa kokushayisana

Okuwukuphela kwento esele okufanele siyenze ukubona lapho ama-projectiles eshaya abadlali! Khumbula la mazwibela ekhodi asuka kundlela update() ekilasini Game:

umdlalo.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),
    );

    // ...
  }
}

Kudingeka sisebenzise indlela applyCollisions(), ebuyisela wonke ama-projectiles ashaya abadlali. Ngenhlanhla, lokhu akunzima ukukwenza ngoba

  • Zonke izinto ezishayisanayo ziyiziyingi, futhi lesi yisimo esilula kakhulu sokuqalisa ukutholwa kokushayisana.
  • Sesivele sinendlela distanceTo(), esisisebenzise ekilasini esigabeni esidlule Object.

Nansi indlela ukuqaliswa kwethu kokuthola ukushayisana kubukeka ngayo:

ukungqubuzana.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;
}

Lokhu kutholwa kokushayisana okulula kusekelwe eqinisweni lokuthi imibuthano emibili ishayisana uma ibanga phakathi kwezikhungo zazo lingaphansi kwesamba se-radii yazo. Nasi isimo lapho ibanga eliphakathi kwezikhungo zemibuthano emibili lilingana ncamashi nesamba serediya yazo:

Ukudala Igeyimu Yewebhu Yabadlali abaningi .io
Lapha udinga ukunaka kakhulu izici ezimbalwa ezengeziwe:

  • I-projectile akumele ishaye umdlali oyidalile. Lokhu kungafezwa ngokuqhathanisa bullet.parentID с player.id.
  • I-projectile kufanele ishaye kanye kuphela esimweni esibi kakhulu sokushaya abadlali abaningi ngesikhathi esisodwa. Sizoxazulula le nkinga sisebenzisa opharetha break: Uma umdlali oshayisane ne-projectile etholakele, siyayeka ukusesha bese sidlulela ku-projectile elandelayo.

I-ΠšΠΎΠ½Π΅Ρ†

Yilokho kuphela! Siphathe konke odinga ukukwazi ukuze udale igeyimu yewebhu ye-.io. Yini elandelayo? Yakha eyakho igeyimu ye-.io!

Yonke ikhodi eyisibonelo ingumthombo ovulekile futhi ithunyelwe kuwo I-Github.

Source: www.habr.com

Engeza amazwana