Ukwenza uMdlalo wabadlali abaninzi .io weWebhu

Ukwenza uMdlalo wabadlali abaninzi .io weWebhu
Ikhutshwe ngo-2015 Agar.io yaba ngunozala wohlobo olutsha imidlalo .ioeye yanda ekuthandeni ukususela ngoko. Ndiye ndazibonela ngokwam ukunyuka kokuthandwa kwemidlalo ye-.io: kule minyaka mithathu idlulileyo, ndiyenzile wadala kwaye wathengisa imidlalo emibini yolu hlobo..

Kwimeko apho awuzange uve ngale midlalo ngaphambili, le yimidlalo yewebhu yabadlali abaninzi ekulula ukuyidlala (akukho akhawunti ifunekayo). Ngokuqhelekileyo bajongana nabadlali abaninzi abachasayo kwibala elinye. Eminye imidlalo edumileyo ye.io: Slither.io ΠΈ Umhlobo.io.

Kule post, siza kuphonononga ukuba njani yenza umdlalo we.io ukusuka ekuqaleni. Kule nto, ulwazi lweJavascript kuphela luya kwanela: kufuneka uqonde izinto ezifana ne-syntax ES6, igama elingundoqo this ΠΈ Thembiso. Nokuba ulwazi lwakho lweJavascript alufezekanga, usenokuqonda uninzi lweposti.

.io umdlalo umzekelo

Ukufumana uncedo lokufunda, siya kukhangela .io umdlalo umzekelo. Zama ukuyidlala!

Ukwenza uMdlalo wabadlali abaninzi .io weWebhu
Umdlalo ulula: ulawula inqanawa kwibala lemidlalo apho kukho abanye abadlali. Inqanawa yakho itshisa ngokuzenzekelayo iiprojectiles kwaye uzama ukubetha abanye abadlali ngelixa uphepha iiprojectiles zabo.

1. Amagqabantshintshi amafutshane / ubume beprojekthi

Π Π΅ΠΊΠΎΠΌΠ΅Π½Π΄ΡƒΡŽ Khuphela ikhowudi yemvelaphi umdlalo womzekelo ukuze undilandele.

Umzekelo usebenzisa oku kulandelayo:

  • Bonakalisa sesona sikhokelo sewebhu seNode.js esilawula umncedisi wewebhu womdlalo.
  • iziseko.io -ilayibrari ye-websocket yokutshintshiselana ngedatha phakathi kwesikhangeli kunye nomncedisi.
  • Iphepha lewebhu -umphathi wemodyuli. Unokufunda malunga nokuba kutheni usebenzise iWebpack. apha.

Nantsi indlela ubume bolawulo lweprojekthi bujongeka njani:

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

uluntu/

Yonke into kwifolda public/ iyakuthunyelwa ngumncedisi. IN public/assets/ inemifanekiso esetyenziswa yiprojekthi yethu.

src /

Yonke ikhowudi yemvelaphi ikwifolda src/. Izihloko client/ ΠΈ server/ bazithethele kwaye shared/ iqulathe ifayile yesigxina ethathwa kumazwe angaphandle ngabo bobabini umxhasi kunye nomncedisi.

2. Iindibano/izicwangciso zeprojekthi

Njengoko kukhankanyiwe ngasentla, sisebenzisa umphathi wemodyuli ukwakha iprojekthi. Iphepha lewebhu. Makhe sijonge kuqwalaselo lwethu lweWebpack:

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

Eyona migca ibalulekileyo apha yile:

  • src/client/index.js yindawo yokungena ye Javascript (JS) umxhasi. I-Webpack izakuqala ukusuka apha kwaye ikhangele ngokuphindaphindiweyo ezinye iifayile ezithathwe ngaphandle.
  • Imveliso ye-JS yeWebhu yethu yokwakha iya kufakwa kuluhlu dist/. Le fayile ndiza kuyibiza ngokuba yeyethu js iphakheji.
  • Sisebenzisa IBhabheli, kwaye ngokukodwa uqwalaselo @babel/preset-env ukusasaza ikhowudi yethu ye-JS kukhangeli lwakudala.
  • Sisebenzisa i-plugin ukukhupha yonke i-CSS echazwe kwiifayile ze-JS kwaye idibanise kwindawo enye. Ndizombiza wethu css iphakheji.

Usenokuba uqaphele amagama eefayile ezingaqhelekanga '[name].[contenthash].ext'. Ziqulathe endaweni yegama lefayile Iphepha lewebhu: [name] iya kutshintshwa ngegama lenqaku legalelo (kwimeko yethu, oku game), kunye [contenthash] Iza kutshintshwa ngehash yemixholo yefayile. Senza njalo lungisa iprojekthi yehashing -ungaxelela abakhangeli ukuba bagcine iipakethi zethu ze-JS ngokungenasiphelo, kuba Ukuba umqulu uyatshintsha, ngoko igama layo lefayile liyatshintsha (utshintsho contenthash). Isiphumo sokugqibela siya kuba ligama lefayile yokujonga game.dbeee76e91a97d0c7207.js.

Ifayile webpack.common.js yifayile yoqwalaselo esisiseko esiyingenisayo kuphuhliso kunye noqwalaselo olugqityiweyo lweprojekthi. Nanku umzekelo woqwalaselo lophuhliso:

webpack.dev.js

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

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

Ukusebenza kakuhle, sisebenzisa kwinkqubo yophuhliso webpack.dev.js, kwaye itshintshela kwi webpack.prod.jsukwandisa ubungakanani bepakethe xa usiwa kwimveliso.

Isetingi yendawo

Ndincoma ukufaka iprojekthi kumatshini wendawo ukuze ukwazi ukulandela amanyathelo adweliswe kule post. Ukuseta kulula: okokuqala, inkqubo kufuneka ifakiwe INode ΠΈ NPM. Okulandelayo kufuneka wenze

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

kwaye ulungele ukuhamba! Ukuqala umncedisi wophuhliso, vele ubaleke

$ npm run develop

kwaye uye kwibhrawuza yewebhu yendawo: 3000. Umncedisi wophuhliso uya kwakha ngokuzenzekelayo iiphakheji ze-JS kunye ne-CSS njengoko ikhowudi itshintsha - vele uhlaziye iphepha ukuze ubone zonke iinguqu!

3. Amanqaku okuNgena kwabaThengi

Masingene phantsi kwikhowudi yomdlalo ngokwayo. Okokuqala sifuna iphepha index.html, xa undwendwela indawo, isikhangeli siya kuyilayisha kuqala. Iphepha lethu liya kuba lula kakhulu:

index.html

Umzekelo .io umdlalo  DLALA

Lo mzekelo wekhowudi wenziwe lula kancinci ukuze kucace, kwaye ndiya kwenza okufanayo neminye imizekelo yeposi. Ikhowudi epheleleyo ingasoloko ijongwa kuyo Github.

Si:

  • HTML5 canvas element (<canvas>) esiza kuyisebenzisa ukwenza umdlalo.
  • <link> ukongeza iphakheji yethu yeCSS.
  • <script> ukongeza iphakheji yethu yeJavascript.
  • Imenyu engundoqo enegama lomsebenzisi <input> kunye neqhosha elithi DLALA (<button>).

Emva kokulayisha iphepha lasekhaya, isikhangeli siya kuqalisa ukwenza ikhowudi yeJavascript, ukusuka kwindawo yokungena yefayile yeJS: 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);
  };
});

Oku kunokuvakala kunzima, kodwa akukho nto ingako iqhubekayo apha:

  1. Ukuthathwa ngaphandle kwefayile zeJS ezininzi.
  2. Ukungeniswa kweCSS (ke iWebpack iyazi ukuzifaka kwiphakheji yethu yeCSS).
  3. Qalisa connect() ukuseka udibaniso kunye nomncedisi kwaye uqhube downloadAssets() ukukhuphela imifanekiso efunekayo ukunika umdlalo.
  4. Emva kokugqitywa kwenqanaba lesi-3 imenyu engundoqo iyaboniswa (playMenu).
  5. Ukuseta isibambi sokucofa iqhosha elithi "PLAY". Xa iqhosha licofa, ikhowudi iqalisa umdlalo kwaye ixelele umncedisi ukuba sikulungele ukudlala.

Eyona "inyama" yengqiqo yomxhasi wethu ikwezo fayile zithathwe kumazwe angaphandle ngefayile index.js. Ngoku siza kuziqwalasela zonke ngokulandelelana kwazo.

4. Utshintshiselwano lwedatha yabathengi

Kulo mdlalo, sisebenzisa ithala leencwadi elaziwayo ukunxibelelana nomncedisi iziseko.io. I-Socket.io inenkxaso yendalo IWebSocket, ezilungele kakuhle unxibelelwano lweendlela ezimbini: sinokuthumela imiyalezo kumncedisi ΠΈ umncedisi angathumela imiyalezo kuthi kuqhagamshelwano olufanayo.

Siza kuba nefayile enye src/client/networking.jsngubani oza kuhoya wonke umntu unxibelelwano nomncedisi:

uthungelwano.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 khowudi iphinde yashunqulelwa kancinci ukuze icace.

Kukho izinto ezintathu eziphambili kule fayile:

  • Sizama ukudibanisa kumncedisi. connectedPromise kuvumeleke kuphela xa siseke umdibaniso.
  • Ukuba unxibelelwano luphumelele, sibhalisa imisebenzi yokufowuna (processGameUpdate() ΠΈ onGameOver()) yemiyalezo esinokuyifumana kumncedisi.
  • Sithumela ngaphandle play() ΠΈ updateDirection()ukuze ezinye iifayile zikwazi ukuzisebenzisa.

5. Unikezelo lwabathengi

Lixesha lokubonisa umfanekiso kwiscreen!

...kodwa ngaphambi kokuba senze oko, kufuneka sikhuphele yonke imifanekiso (izixhobo) ezifunekayo kule nto. Masibhale umphathi wemithombo:

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

Ulawulo lwezibonelelo alukho nzima kangako ukuluphumeza! Ingcamango ephambili kukugcina into assets, eya kubophelela isitshixo segama lefayile kwixabiso lento Image. Xa uvimba ulayishiwe, siwugcina kwinto ethile assets ukufikelela ngokukhawuleza kwixesha elizayo. Isixhobo ngasinye siya kuvunyelwa nini ukukhuphela (oko kukuthi, zonke izibonelelo), siyakuvumela downloadPromise.

Emva kokukhuphela izixhobo, ungaqala ukunikezela. Njengoko bekutshiwo ngaphambili, ukuzoba kwiphepha lewebhu, sisebenzisa HTML5 Canvas (<canvas>). Umdlalo wethu ulula kakhulu, ke kufuneka sizobe oku kulandelayo:

  1. Imvelaphi
  2. Inqanawa yomdlali
  3. Abanye abadlali kumdlalo
  4. amaqokobhe

Nazi iziqwengana ezibalulekileyo src/client/render.js, enika kanye izinto ezine ezidweliswe ngasentla:

nikezela.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 khowudi ikwashunqulelwe ukucaca.

render() ngumsebenzi ongundoqo wale fayile. startRendering() ΠΈ stopRendering() lawula ukusebenza kwe-loop ye-render kwi-60 FPS.

Uzalisekiso olululo lomntu ngamnye onikezela imisebenzi yomncedisi (umz. renderBullet()) azibalulekanga kangako, kodwa nanku umzekelo olula:

nikezela.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 ukuba sisebenzisa indlela getAsset(), eyabonwa ngaphambili kwi asset.js!

Ukuba unomdla wokufunda malunga nabanye abancedisi abanikezelayo, funda okunye. src/client/render.js.

6. Igalelo lomthengi

Lixesha lokwenza umdlalo iyadlaleka! Iskimu solawulo siya kuba lula kakhulu: ukutshintsha isalathiso sentshukumo, ungasebenzisa imouse (kwikhompyuter) okanye uthinte isikrini (kwisixhobo esiphathwayo). Ukuphumeza oku, siya kubhalisa Abaphulaphuli beMisitho yeMinyhadala yeMouse kunye neTouch.
Izakuhoya yonke lento 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() Ngabaphula-phuli boMsitho abafowunayo updateDirection() (ukusuka networking.js) xa isiganeko segalelo sisenzeka (umzekelo, xa imouse isusiwe). updateDirection() iphatha imiyalezo kunye nomncedisi, ophethe isiganeko segalelo kunye nokuhlaziya isimo somdlalo ngokufanelekileyo.

7. Isimo soMxumi

Eli candelo lelona linzima kwinxalenye yokuqala yeposi. Musa ukudimazeka xa ungayiqondi okokuqala uyifunda! Ungade uyitsibe kwaye ubuyele kuyo kamva.

Iqhekeza lokugqibela lephazili elifunekayo ukugqiba umxhasi/ikhowudi yomncedisi ngu Lumente. Khumbula isiqwengana sekhowudi esisuka kwicandelo loNikezelo lwaBaxumi?

nikezela.js

import { getCurrentState } from './state';

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

  // Do the rendering
  // ...
}

getCurrentState() kufuneka ikwazi ukusinika imeko yangoku yomdlalo kumxhasi nangaliphi na ixesha ngokusekelwe kuhlaziyo olufunyenwe kumncedisi. Nanku umzekelo wohlaziyo lomdlalo onokuthunyelwa ngumncedisi:

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

Uhlaziyo ngalunye lomdlalo lunemimandla emihlanu efanayo:

  • t: Isitampu sexesha seseva esibonisa ukuba olu hlaziyo lwenziwa nini.
  • me: Ulwazi malunga nomdlali ofumana olu hlaziyo.
  • abanye: Uluhlu lolwazi malunga nabanye abadlali abathatha inxaxheba kumdlalo omnye.
  • iimbumbulu: uluhlu lolwazi malunga neeprojectiles kumdlalo.
  • bhodi: Idatha yebhodi yabaphambili yangoku. Kule post, asiyi kuziqwalasela.

7.1 Ubume bomthengi ongenalwazi

Uzalisekiso olungenangqondo getCurrentState() inokubuyisela ngokuthe ngqo kuphela idatha yohlaziyo lomdlalo olufunyenwe kutshanje.

naive-state.js

let lastGameUpdate = null;

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

export function getCurrentState() {
  return lastGameUpdate;
}

Intle kwaye icacile! Kodwa ukuba ibilula ngolo hlobo. Esinye sezizathu ezibangela ukuba oku kuphunyezwa kube yingxaki: inciphisa unikezelo lwesakhelo somyinge wewotshi yomncedisi.

Umgangatho wefreyimu: inani lezakhelo (okt iminxeba render()) ngesekhondi, okanye iFPS. Imidlalo idla ngokuzama ukufikelela ubuncinci be-60 FPS.

Ireyithi yokuphawula: Amaxesha apho iseva ithumela uhlaziyo lomdlalo kubathengi. Ihlala isezantsi kunereyithi yesakhelo. Kumdlalo wethu, iseva ibaleka rhoqo kwimijikelo engama-30 ngomzuzwana.

Ukuba sinikezela nje uhlaziyo lwamva nje lomdlalo, ke i-FPS ayisoze idlule ngaphaya kwama-30, kuba asisoze sifumane uhlaziyo olungaphezulu kwama-30 ngomzuzwana ukusuka kumncedisi. Nokuba siyafowuna render() 60 amaxesha ngesekondi, emva koko isiqingatha sezi fowuni sizakuzoba kwakhona into enye, singenzi nto. Enye ingxaki ngokuphunyezwa kokungenangqondo kukuba ithanda ukulibaziseka. Ngesantya esifanelekileyo se-Intanethi, umxhasi uya kufumana uhlaziyo lomdlalo kanye rhoqo nge-33ms (30 ngomzuzwana):

Ukwenza uMdlalo wabadlali abaninzi .io weWebhu
Ngelishwa, akukho nto igqibeleleyo. Umfanekiso oyinyani ngakumbi unokuba:
Ukwenza uMdlalo wabadlali abaninzi .io weWebhu
Ukuphunyezwa kokungenangqondo yeyona meko imbi kakhulu xa kufikwa kwi-latency. Ukuba uhlaziyo lomdlalo lufunyenwe ngokulibaziseka kwe-50ms, ngoko iindawo zokuthengisela abathengi i 50ms eyongezelelweyo kuba isanikezela imeko yomdlalo ukusuka kuhlaziyo lwangaphambili. Unokuyithelekelela indlela engonwabanga ngayo oku kumdlali: ukuqhotsa isiqhoboshi ngaphandle kwesizathu kuya kwenza umdlalo uzive uxhuzula kwaye ungazinzanga.

7.2 Ukuphuculwa kwemeko yabaxumi

Siza kwenza uphuculo oluthile kumiliselo olungenangqondo. Okokuqala, sisebenzisa ukulibaziseka kokubonelela nge 100 ms. Oku kuthetha ukuba imeko "yangoku" yomthengi iya kuhlala isala ngasemva kwimeko yomdlalo kumncedisi nge-100ms. Umzekelo, ukuba ixesha kumncedisi li 150, ngoko umxhasi uyakunikezela ngemeko apho umncedisi wayekuyo ngelo xesha 50:

Ukwenza uMdlalo wabadlali abaninzi .io weWebhu
Oku kusinika isithinteli se-100ms ukuze sisinde kumaxesha okuhlaziya umdlalo ongacingelekiyo:

Ukwenza uMdlalo wabadlali abaninzi .io weWebhu
Intlawulo yoku iya kuhlala isisigxina igalelo lag nge 100 ms. Lo ngumbingelelo omncinci womdlalo ogudileyo - uninzi lwabadlali (ingakumbi abadlali abaqhelekileyo) abayi kuqaphela oku kulibaziseka. Kulula kakhulu ukuba abantu baziqhelanise ne-100ms latency rhoqo kunokuba badlale nge-latency engalindelekanga.

Sinokusebenzisa obunye ubuchule obubizwa ngokuba uqikelelo lwecala lomxhasi, owenza umsebenzi omhle wokunciphisa i-latency ebonwayo, kodwa ayiyi kubandakanywa kule post.

Olunye uphuculo esilusebenzisayo utoliko lwemigca. Ngenxa yonikezelo lwalag, siqhele ukuba nohlaziyo olunye ngaphambi kwexesha langoku kumxhasi. Xa ebizwa getCurrentState(), singakwazi ukuphumeza utoliko lwemigca phakathi kohlaziyo lomdlalo ngaphambi nangemva kwexesha langoku kumthengi:

Ukwenza uMdlalo wabadlali abaninzi .io weWebhu
Oku kusombulula umba wesantya sesakhelo: ngoku singanikezela ngezakhelo ezizodwa nangaliphi na izinga lesakhelo esilifunayo!

7.3 Ukuphumeza urhulumente owandisiweyo wabaxhasi

Umzekelo wokuphunyezwa kwi src/client/state.js isebenzisa zombini i-render lag kunye ne-linear interpolation, kodwa hayi ixesha elide. Masiyiqhawule ikhowudi ibe ngamacandelo amabini. Nantsi eyokuqala:

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

Inyathelo lokuqala kukuqonda ukuba yintoni na currentServerTime(). Njengoko sibonile ngaphambili, lonke uhlaziyo lomdlalo lubandakanya isitampu sexesha leseva. Sifuna ukusebenzisa i-render latency ukunika umfanekiso 100ms ngasemva kweseva, kodwa asisoze silazi ixesha langoku kumncedisi, kuba asikwazi ukuba kuthathe ixesha elingakanani ukuba naluphi na uhlaziyo lufike kuthi. I-Intanethi ayiqikeleleki kwaye isantya sayo sinokwahluka kakhulu!

Ukujikeleza le ngxaki, sinokusebenzisa uqikelelo olufanelekileyo: thina yenza ngathi uhlaziyo lokuqala lufikile ngoko nangoko. Ukuba oku bekuyinyani, besiya kulazi ixesha leseva ngalo mzuzu! Sigcina isitampu sexesha somncedisi ngaphakathi firstServerTimestamp kwaye sigcine wethu yendawo (umxhasi) isitampu sexesha ngaxeshanye gameStart.

Owu yima. Akufunekanga ibe lixesha lomncedisi = ixesha lomxhasi? Kutheni sisenza umahluko phakathi "kwesitampu sexesha seseva" kunye "nesitampu sexesha somthengi"? Lo ngumbuzo omkhulu! Kuyavela ukuba azifani. Date.now() izakubuyisela izitampu zexesha ezahlukeneyo kumxhasi kunye nomncedisi, kwaye kuxhomekeke kwizinto zasekuhlaleni kwaba matshini. Ungaze ucinge ukuba izitampu zexesha ziya kufana kubo bonke oomatshini.

Ngoku siyaqonda ukuba yintoni na currentServerTime(): iyabuya isitampu sexesha somncedisi wexesha lonikezelo lwangoku. Ngamanye amazwi, eli lixesha langoku lomncedisi (firstServerTimestamp <+ (Date.now() - gameStart)) thabatha ulibaziseko (RENDER_DELAY).

Ngoku makhe sijonge indlela esiphatha ngayo uhlaziyo lomdlalo. Xa ifunyenwe kwiseva yohlaziyo, ibizwa ngokuba processGameUpdate()kwaye sigcina uhlaziyo olutsha kuluhlu gameUpdates. Emva koko, ukujonga ukusetyenziswa kwememori, sisusa lonke uhlaziyo oludala ngaphambili uhlaziyo lwesisekokuba asisafuni.

Yintoni "uhlaziyo olusisiseko"? Oku uhlaziyo lokuqala esilufumana ngokubuyela umva ukusuka kwixesha langoku lomncedisi. Khumbula lo mzobo?

Ukwenza uMdlalo wabadlali abaninzi .io weWebhu
Uhlaziyo lomdlalo ngokuthe ngqo ngasekhohlo "kweXesha lokuBonelwa koMthengi" luhlaziyo olusisiseko.

Uhlaziyo olusisiseko lusetyenziselwa ntoni? Kutheni sinokuyeka ukuhlaziywa kwisiseko? Ukuqonda oku, makhe ekugqibeleni qwalasela ukuphunyezwa getCurrentState():

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

Sijongana namatyala amathathu:

  1. base < 0 kuthetha ukuba akukho zihlaziyo kude kube lixesha langoku lonikezelo (jonga ukuphunyezwa ngasentla getBaseUpdate()). Oku kwenzeka kanye ekuqaleni komdlalo ngenxa yonikezelo lag. Kule meko, sisebenzisa uhlaziyo lwamva nje olufunyenweyo.
  2. base luhlaziyo lwamva nje esinalo. Oku kunokuba ngenxa yokulibaziseka kwenethiwekhi okanye uqhagamshelo lwe-Intanethi olulambathayo. Kule meko, sikwasebenzisa uhlaziyo lwamva nje esinalo.
  3. Sinohlaziyo phambi nasemva kwexesha langoku lonikezelo, ngoko sinako interpolate!

Yonke into eseleyo state.js kukuphunyezwa koguqulelo lwelayini olulula (kodwa luyadika) lwezibalo. Ukuba ufuna ukuyiphonononga ngokwakho, vula ke state.js phezu Github.

Icandelo 2. Umncedisi wokubuyisela umva

Kule nxalenye, siza kujonga kwi-Node.js backend elawula yethu .io umdlalo umzekelo.

1. Indawo yokuNgena kwiseva

Ukulawula umncedisi wewebhu, siya kusebenzisa isakhelo sewebhu esidumileyo seNode.js ebizwa Bonakalisa. Iya kuqwalaselwa yifayile yethu yendawo yokungena yomncedisi src/server/server.js:

iseva.js inxalenye 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 ukuba kwinxalenye yokuqala sixoxe ngeWebpack? Apha kulapho siza kusebenzisa uqwalaselo lwethu lweWebpack. Siza kuzisebenzisa ngeendlela ezimbini:

  • Sebenzisa webpack-dev-middleware ukwakha ngokuzenzekelayo iipakethe zethu zophuhliso, okanye
  • dlulisa ngokusisigxina isiqulathi seefayili dist/, apho iWebpack iya kubhala iifayile zethu emva kokwakhiwa kwemveliso.

Omnye umsebenzi obalulekileyo server.js kukuseta umncedisi iziseko.ioeqhagamshela nje kwiseva ye-Express:

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

Emva kokuseka ngempumelelo uqhagamshelo lwe-socket.io kumncedisi, siseta abaphathi beminyhadala kwisokethi entsha. Abaphathi bomsitho baphatha imiyalezo efunyenwe kubathengi ngokunikezela kwinto ye-singleton game:

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

Senza umdlalo we.io, ke sifuna ikopi enye kuphela Game ("Umdlalo") - bonke abadlali badlala kwibala elinye! Kwicandelo elilandelayo, siza kubona indlela le klasi isebenza ngayo. Game.

2. Iiseva zomdlalo

I klasi Game iqulethe eyona logic ibalulekileyo kwicala lomncedisi. Inemisebenzi emibini engundoqo: ulawulo lomdlali ΠΈ Ukulinganisa umdlalo.

Masiqale ngomsebenzi wokuqala, ulawulo lomdlali.

umdlalo.js icandelo 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, siza kuchonga abadlali ngebala id i-socket.io yabo (ukuba uyabhideka, emva koko ubuyele ku server.js). I-Socket.io ngokwayo yabela isokethi nganye eyahlukileyo idngoko akuyomfuneko ukuba sizikhathaze ngaloo nto. Ndiza kumbiza Isazisi somdlali.

Ngaloo nto engqondweni, makhe sihlolisise imizekelo eguquguqukayo eklasini Game:

  • sockets yinto ebophelela i-ID yomdlali kwisokethi ehambelana nomdlali. Ivumela ukuba sifikelele kwiziseko ngee-ID zabo zomdlali ngexesha elingatshintshiyo.
  • players yinto ebophelela i-ID yomdlali kwikhowudi> Umdlali into

bullets Luluhlu lwezinto Bullet, olungenalo ucwangco lucacileyo.
lastUpdateTime sisitampu sexesha lokugqibela umdlalo uhlaziyiwe. Siza kubona ukuba isetyenziswa njani kungekudala.
shouldSendUpdate sisiguquli esincedisayo. Siza kubona kwakhona ukusetyenziswa kwayo kungekudala.
Iindlela addPlayer(), removePlayer() ΠΈ handleInput() akukho mfuneko yokuchaza, zisetyenziswa kwi server.js. Ukuba ufuna ukuhlaziya inkumbulo yakho, buyela umva phezulu kancinci.

Umgca wokugqibela constructor() iyaqala umjikelo wokuhlaziya imidlalo (kunye nohlaziyo lwama-60 / s):

umdlalo.js icandelo 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() iqulathe mhlawumbi elona qhekeza libalulekileyo lengqiqo yecala lomncedisi. Nantsi into eyenzayo, ngokulandelelana:

  1. Ubala ixesha elingakanani dt yadlula okokugqibela update().
  2. Ihlaziya iprojectile nganye kwaye iyayitshabalalisa ukuba kuyimfuneko. Siza kubona ukuphunyezwa kwalo msebenzi kamva. Okwangoku, kwanele ukuba siyazi loo nto bullet.update() iimbuyekezo trueukuba iprojectile kufuneka itshatyalaliswe (waphuma ebaleni).
  3. Hlaziya umdlali ngamnye kwaye uvelise iprojekti ukuba kuyimfuneko. Siya kukubona oku kuphunyezwa kamva βˆ’ player.update() inokubuyisela into Bullet.
  4. Iitshekhi zongquzulwano phakathi projectiles kunye nabadlali nge applyCollisions(), ebuyisela uluhlu lweeprojectiles ezibethe abadlali. Kwiprojekthi nganye ebuyisiweyo, sonyusa amanqaku omdlali oyidubileyo (usebenzisa player.onDealtDamage()) kwaye emva koko susa iprojekti kuluhlu bullets.
  5. Yazisa kwaye itshabalalise bonke abadlali ababuleweyo.
  6. Thumela uhlaziyo lomdlalo kubo bonke abadlali yonke isekhondi amaxesha xa ebizwa update(). Oku kusinceda sigcine umkhondo wenguqu encedisayo ekhankanywe ngasentla. shouldSendUpdate. Ngokuba update() ebizwa 60 amaxesha / s, sithumela uhlaziyo umdlalo 30 amaxesha / s. Ngoko, amaza ewotshi iwotshi yomncedisi yi 30 clocks/s (sathetha ngamazinga ewotshi kwindawo yokuqala).

Kutheni ukuthumela uhlaziyo lomdlalo kuphela ngexesha ? Ukugcina isitishi. Uhlaziyo lwemidlalo engama-30 ngomzuzwana luninzi!

Kutheni ungavele ufowune update() Amaxesha angama-30 ngesekhondi? Ukuphucula ukulinganisa komdlalo. Okukhona ukubizwa rhoqo update(), Ukulinganisa komdlalo kuya kuba kuchaneka ngakumbi. Kodwa musa ukuthabatheka kakhulu ngenani lemingeni. update(), kuba lo ngumsebenzi obiza kakhulu - i-60 ngesekhondi yanele.

Yonke iklasi Game iqulathe iindlela zomncedisi ezisetyenziswa kwi update():

umdlalo.js icandelo 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 - ihlela abadlali ngamanqaku, ithatha abahlanu abaphezulu, kwaye ibuyisela igama lomsebenzisi kunye nenqaku ngalinye.

createUpdate() isetyenziswe kwi update() ukwenza uhlaziyo lomdlalo olusasazwa kubadlali. Umsebenzi wayo ophambili kukubiza iindlela serializeForUpdate()iphunyezwe kwiiklasi Player ΠΈ Bullet. Qaphela ukuba idlulisela kuphela idatha kumdlali ngamnye malunga ikufutshane abadlali kunye projectiles - akukho mfuneko yokuhambisa ulwazi malunga izinto umdlalo kude kumdlali!

3. Izinto zomdlalo kumncedisi

Kumdlalo wethu, iiprojectiles kunye nabadlali ziyafana kakhulu: zizinto ezingabonakaliyo, ezingqukuva, ezishukumayo. Ukuthatha ithuba loku kufana phakathi kwabadlali kunye neeprojectiles, masiqale ngokuphumeza iklasi yesiseko 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 nto inzima eyenzekayo apha. Le klasi iya kuba yindawo efanelekileyo ye-anchor yokwandiswa. Makhe sibone indlela iklasi Bullet isebenzisa Object:

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

Ukuphunyezwa Bullet mfutshane kakhulu! Songeze kwi Object kuphela ezi zandiso zilandelayo:

  • Ukusebenzisa ipakethe emfutshane ukwenzela isizukulwana random id projectile.
  • Ukongeza indawo parentIDukuze ukwazi ukulandelela umdlali odale le projectile.
  • Ukongeza ixabiso lokubuyisela kwi update(), elilingana ne trueukuba iprojectile ingaphandle kwebala (khumbula sithetha ngale nto kwicandelo lokugqibela?).

Masiqhubele phambili kwi 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 bantsonkothe ​​ngakumbi kuneprojectiles, ngoko ke amabala ambalwa kufuneka agcinwe kule klasi. Indlela yakhe update() yenza umsebenzi omninzi, ngokukodwa, ibuyisela iprojekti esanda kwenziwa ukuba akukho namnye useleyo fireCooldown (uyakhumbula ukuba sithethe ngale nto kwicandelo elidlulileyo?). Kwakhona yandisa indlela serializeForUpdate(), kuba kufuneka sibandakanye imimandla eyongezelelweyo yomdlali kuhlaziyo lomdlalo.

Ukuba neklasi yesiseko Object - inyathelo elibalulekileyo lokuphepha ukuphinda ikhowudi. Umzekelo, akukho klasi Object umdlalo ngamnye kufuneka ube nokuphunyezwa okufanayo distanceTo(), kunye nokuncamathisela-kokuncamathisela konke oku kuphunyezwa kwiifayile ezininzi kuya kuba liphupha elibi. Oku kubaluleke ngakumbi kwiiprojekthi ezinkulu.xa inani lokwandisa Object iiklasi ziyakhula.

4. Ukubona ukungqubana

Ekuphela kwento eseleyo kuthi kukuqonda xa iprojekti ibetha abadlali! Khumbula le nxalenye yekhowudi kwindlela update() eklasini 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),
    );

    // ...
  }
}

Kufuneka siphumeze indlela applyCollisions(), ebuyisela zonke iiprojectiles ezibethe abadlali. Ngethamsanqa, akukho nzima ukwenza kuba

  • Zonke izinto ezingqubanayo zizizangqa, kwaye le yeyona milo ilula yokuphumeza ubhaqo lokungqubana.
  • Sele sinayo indlela distanceTo(), esiye sayiphumeza kwicandelo langaphambili kwiklasi Object.

Nantsi indlela uphumezo lwethu lokubona ukungqubana lujongeka njani:

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

Oku kubhaqwa kongquzulwano olulula kusekelwe kwinto yokuba izangqa ezibini ziyangqubana ukuba umgama phakathi kwamaziko azo ungaphantsi kwesambuku seradiya yazo. Nantsi imeko apho umgama phakathi kwamaziko eezangqa ezibini ulingana ncam nesixa seradiya yazo:

Ukwenza uMdlalo wabadlali abaninzi .io weWebhu
Kukho eminye imiba ekufuneka iqwalaselwe apha:

  • Iprojekti akufuneki ibethe umdlali oyidalileyo. Oku kunokufezekiswa ngokuthelekisa bullet.parentID с player.id.
  • Iprojekti kufuneka ibethe kube kanye kuphela kwimeko yokunciphisa yabadlali abaninzi abagilana ngaxeshanye. Siza kusombulula le ngxaki ngokusebenzisa umqhubi break: Ngokukhawuleza ukuba umdlali ongqubuzana neprojekti efunyenwe, siyayeka ukukhangela kwaye siqhubele phambili kwiprojekthi elandelayo.

Isiphelo

Kuko konke! Sifake yonke into oyifunayo ukuze ukwazi ukwenza umdlalo wewebhu we-.io. Yintoni elandelayo? Yakha owakho umdlalo we.io!

Yonke ikhowudi yesampulu ivulekile kwaye ithunyelwe kuyo Github.

umthombo: www.habr.com

Yongeza izimvo