Abuuritaanka ciyaarta mareegaha badan ee nooca .io

Abuuritaanka ciyaarta mareegaha badan ee nooca .io
La sii daayay 2015kii Agar.io noqday farcanka nooc cusub ciyaaraha.io, kuwaas oo caannimadiisu aad u korodhay tan iyo markaas. Waxaan la kulmay kor u kaca caannimada ciyaaraha .io nafteyda: saddexdii sano ee la soo dhaafay, I abuuray oo iibiyay laba ciyaarood oo noocaan ah..

Haddii ay dhacdo inaadan waligaa maqlin ciyaarahan hore, waa bilaash, ciyaaraha webka ee ciyaartoy badan oo ay fududahay in la ciyaaro (wax xisaab ah looma baahna). Caadi ahaan waxay isku daraan ciyaartoy badan oo iska soo horjeeda hal garoon. Ciyaaraha kale ee caanka ah .io: Slither.io ΠΈ Diep.io.

Maqaalkan waxaan ku ogaan doonaa sida ka samee ciyaar .io meel eber ah. Si tan loo sameeyo, kaliya aqoonta Javascript ayaa ku filnaan doonta: waxaad u baahan tahay inaad fahamto waxyaabaha sida syntax ES6, ereyga muhiimka ah this ΠΈ Balanqaadyo. Xitaa haddii aadan si fiican u aqoon Javascript, waxaad weli fahmi kartaa inta badan boostada.

Tusaalaha ciyaarta .io

Caawinta tababarka waxaan tixraaci doonaa ciyaarta tusaale .io. Isku day inaad ciyaarto!

Abuuritaanka ciyaarta mareegaha badan ee nooca .io
Ciyaartu aad ayay u fudud tahay: waxaad ku maamulaysaa markab meel fagaare ah kula jira ciyaartoy kale. Markabkaagu si otomaatig ah ayuu u shidaa madfaca oo waxaad isku daydaa inaad garaacdo ciyaartoyda kale adigoo iska ilaalinaya madfacooda.

1. Dulmar kooban/qaabdhismeedka mashruuca

Talo soo jeedin soo deji koodhka isha ciyaarta tusaale si aad ii raacdo.

Tusaalahan wuxuu adeegsadaa kuwan soo socda:

  • Fikraddaada waa qaabka ugu caansan shabakada Node.js ee maamusha serverka shabakada ciyaarta.
  • socket.io - maktabadda websocket-ka si ay xogta isku dhaafsadaan browserka iyo serverka.
  • Bogagga shabakadda - maareeyaha moduleka. Waxaad ka akhrisan kartaa sababta loo isticmaalo Webpack halkan.

Tani waa sida uu u eg yahay hagaha mashruuca:

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

dadweyne/

Wax walba waxay ku jiraan galka public/ waxaa si toos ah u gudbin doona server-ku. IN public/assets/ waxa ku jira sawiro uu isticmaalay mashruucayagu.

src /

Dhammaan koodka isha ayaa ku jira galka src/. Cinwaanada client/ ΠΈ server/ iska hadla oo shared/ ka kooban yahay faylka joogtada ah ee ay keeneen macmiilka iyo server-ka labadaba.

2. Shirarka/qiyamka mashruuca

Sida kor lagu sheegay, waxaan isticmaalnaa maareeyaha moduleka si aan u dhisno mashruuca Bogagga shabakadda. Aynu eegno qaabaynta xidhmadayada:

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

Khadadka ugu muhiimsan halkan waa kuwan soo socda:

  • src/client/index.js waa barta laga soo galo macmiilka Javascript (JS). Baakad shabakadeed ayaa halkan ka bilaabi doonta oo si isdaba joog ah u raadin doonta faylasha kale ee la soo dejiyay.
  • Wax soo saarka JS ee dhismeedkeena Webpack wuxuu ku yaalaa hagaha dist/. Waxaan ugu yeeri doonaa feylkan annaga xirmo JS.
  • Waxaan isticmaalnaa Babel, iyo gaar ahaan qaabeynta @babel/preset-env si aan u daabacno koodhkayaga JS ee daalacashada hore.
  • Waxaan isticmaalnaa plugin si aan u soo saarno dhammaan CSS-ta ay tixraaceen faylalka JS oo aan isugu geyno hal meel. Anaga ayaan ugu yeeri doonaa xirmo CSS.

Waxaa laga yaabaa inaad dareentay magacyo faylal xidhmo qariib ah '[name].[contenthash].ext'. Waxay ka kooban yihiin beddelka magaca faylka Boorsada internetka: [name] waxaa lagu bedeli doonaa magaca barta gelinta (xaaladkeena waa game), [contenthash] waxaa lagu beddeli doonaa xashiish ka kooban faylka. Waxaan tan u samaynaa kor u qaad mashruuca xashiishka - Waxaan u sheegi karnaa daalacashada inay kaydiyaan baakooyinkayada JS si aan xad lahayn sababtoo ah haddii xirmo beddelo, magaceedu sidoo kale wuu isbedelaa (isbedel contenthash). Natiijadu waxay noqon doontaa magaca faylka aragtida game.dbeee76e91a97d0c7207.js.

file webpack.common.js - Kani waa faylka qaabeynta saldhigga ah ee aan u soo dejino horumarinta iyo qaabaynta mashruuca ee dhammaatay. Tusaale ahaan, halkan waa qaabaynta horumarka:

webpack.dev.js

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

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

Waxtarka, waxaan u isticmaalnaa habka horumarinta webpack.dev.js, oo u wareegta webpack.prod.js, si kor loogu qaado cabbirrada xirmada marka la geynayo wax soo saarka.

Habaynta deegaanka

Waxaan kugula talinayaa inaad ku rakibto mashruuca mashiinka deegaankaaga si aad u raacdo tillaabooyinka ku taxan qoraalkan. Dejintu waa sahlan tahay: marka hore, nidaamku waa inuu lahaadaa noode ΠΈ NPM. Marka xigta waxaad u baahan tahay inaad sameyso

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

oo diyaar baad u tahay inaad tagto! Si aad u bilowdo serfarka horumarinta, kaliya orod

$ npm run develop

oo aad web browserkaaga localhost: 3000. Serfarka horumarinta ayaa si toos ah dib u dhisi doona xirmooyinka JS iyo CSS marka koodhka isbeddelku dhaco - kaliya dib u cusbooneysii bogga si aad u aragto dhammaan isbeddellada!

3. Goobaha laga soo galo macmiilka

Aan hoos ugu dhaadhacno koodka ciyaarta laftiisa. Marka hore waxaan u baahanahay bog index.html, markaad booqato goobta, browserka ayaa marka hore ku shubi doona. Boggayagu wuxuu noqon doonaa mid fudud:

index.html

Tusaale .io game  Ciyaar

Tusaalaha koodhkan waxa loo fududeeyay in yar si loo caddeeyo, waxaanan ku samayn doonaa qaar badan oo ka mid ah tusaalooyinka kale ee ku jira boostada. Mar walba waxaad ka eegi kartaa koodka buuxa Github.

Waxaan leenahay:

  • HTML5 Canvas element (<canvas>), kaas oo aan u isticmaali doono si aan u bandhigno ciyaarta.
  • <link> si loogu daro xirmadayada CSS.
  • <script> si loogu daro xirmadayada Javascript.
  • Menu-ga ugu weyn oo leh magaca isticmaalaha <input> iyo badhanka "CIYAAR" (<button>).

Marka bogga gurigu buuxsamo, browserku wuxuu bilaabi doonaa fulinta code Javascript, isagoo ka bilaabaya barta gelitaanka faylka 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);
  };
});

Tani waxay u ekaan kartaa mid adag, laakiin runtii halkan wax badan kama socdaan:

  1. Soo deji dhowr faylal kale oo JS ah.
  2. Soo rar CSS (sidaas darteed xirmada Webka ayaa og inay ku daraan xirmadayada CSS).
  3. Buuxi connect() in la sameeyo xiriir server-ka oo la bilaabo downloadAssets() si aad u soo dejiso sawirada loo baahan yahay si ciyaarta loo sameeyo.
  4. Kadib dhamaystirka marxaladda 3 menu ugu weyn ayaa soo bandhigay (playMenu).
  5. Dejinta batoonka "CIYAAR" guji maamulaha. Marka badhanka la riixo, koodhka ayaa bilaabaya ciyaarta wuxuuna u sheegayaa server-ka inaan diyaar u nahay inaan ciyaaro.

"Hilibka" ugu weyn ee macquulka-serverkeena macmiilka ayaa ku jira faylalkaas la keenay faylka index.js. Hadda waxaan u eegi doonaa dhammaantood si ay u kala horreeyaan.

4. Isweydaarsiga xogta macmiilka

Ciyaartan waxaan isticmaalnaa maktabad caan ah si aan ula xiriirno serverka socket.io. Socket.io waxay leedahay taageero ku dhex dhisan WebSockets, kuwaas oo si fiican ugu habboon isgaarsiinta laba-geesoodka ah: waxaan u diri karnaa farriimaha server-ka ΠΈ Server-ku wuxuu noo soo diri karaa fariimo isku mid ah.

Waxaan yeelan doonaa hal fayl src/client/networking.jsyaa wax ka qaban doona qof walba isgaarsiinta server-ka:

isku xidhka.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);
};

Xeerkan sidoo kale wax yar ayaa loo soo gaabiyay si loo caddeeyo.

Waxa jira saddex waxyaalood oo waaweyn oo ka dhacaya faylkan:

  • Waxaan isku dayeynaa inaan ku xirno serverka connectedPromise kaliya waxaa la ogol yahay marka aan aasaasno xiriir.
  • Haddii xidhiidhku guulaysto, waxaanu diiwaan gelinaa hawlaha dib u soo celintaprocessGameUpdate() ΠΈ onGameOver()) fariimaha laga yaabo in aan ka helno server-ka.
  • Waxaan dhoofin play() ΠΈ updateDirection()si faylasha kale ay u isticmaalaan.

5. U gudbinta macmiilka

Waa waqtigii lagu soo bandhigi lahaa sawirka shaashadda!

...laakin ka hor intaanan tan samayn, waxaan u baahanahay inaan soo dejino dhammaan sawirada (khayraadka) ee loo baahan yahay tan. Aan qorno maamulaha kheyraadka:

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

Maareynta kheyraadka ma aha mid aad u adag in la hirgeliyo! Qodobka ugu muhiimsan waa in la kaydiyo shay assets, kaas oo ku xidhi doona furaha filename qiimaha shayga Image. Marka kheyraadka la raro, waxaan ku keydineynaa shay assets rasiidka degdega ah mustaqbalka. Goorma ayaa la ogolaan doonaa soo dejinta kheyraad kasta (taas oo ah, waa la soo dejin doonaa oo dhan kheyraadka), waan ogolnahay downloadPromise.

Kadib markaad soo dejiso agabka, waxaad bilaabi kartaa samaynta. Sidii hore loo sheegay, si aan ugu sawirno bogga internetka ee aan isticmaalno HTML5 Canvas (<canvas>). Ciyaartayadu waa mid fudud, marka waxaan kaliya u baahanahay inaan samayno kuwa soo socda:

  1. Taariikhda
  2. Markabka ciyaartoyga
  3. Ciyaartoyda kale ee ciyaarta
  4. qolof

Halkan waxaa ah qaybo muhiim ah src/client/render.js, kuwaas oo si sax ah u soo saaray afarta qodob ee kor ku xusan:

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

Xeerkan waxa kale oo loo soo gaabiyey si loo caddeeyo.

render() waa shaqada ugu weyn ee faylkan. startRendering() ΠΈ stopRendering() Xakamee firfircoonida wareegga wax-qabadka 60 FPS.

Hirgelinta gaarka ah ee hawlaha caawinta wax qabad ee shakhsi ahaaneed (tusaale renderBullet()) ma aha sidaas muhiim, laakiin halkan waa hal tusaale oo fudud:

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

Ogow in aan isticmaaleyno habka getAsset(), oo hore loogu arki jiray asset.js!

Haddii aad xiisaynayso inaad sahamiso hawlaha kale ee gacan-siinta, ka dib akhri inta kale src/macmiil/render.js.

6. Gelida macmiilka

Waa waqtigii ciyaarta la samayn lahaa la ciyaari karo! Nidaamka xakamaynta ayaa noqon doona mid aad u fudud: si aad u bedesho jihada dhaqdhaqaaqa, waxaad isticmaali kartaa jiirka (kombiyuutarka) ama taabo shaashadda (qalabka gacanta). Si taas loo hirgeliyo waanu diiwaan gelin doonaa Dhageystayaasha Dhacdada dhacdooyinka Mouse and Touch.
Waxan oo dhan ayaa daryeeli doona src/client/input.js:

wax gelin.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() waa Dhageysteyaal soo wacaya updateDirection() (ee networking.js) marka ay dhacdo galinta (tusaale ahaan, marka mouse-ka la dhaqaajiyo). updateDirection() wax ka qabta isdhaafsiga fariimaha ee server-ka, kaas oo ka baaraandegaya dhacdada wax gelinta iyo cusbooneysiinta xaaladda ciyaarta si waafaqsan.

7. Heerka macmiilka

Qaybtani waa tan ugu adag qaybta hore ee boostada. Ha niyad jabin haddii aadan fahmin marka ugu horeysa ee aad akhrido! Xitaa waad ka boodi kartaa oo dib ugu soo laaban kartaa.

Qaybta ugu dambeysa ee halxiraalaha loo baahan yahay si loo dhamaystiro koodka macmiilka-server waa gobolka. Xusuusnow qayb ka mid ah koodka qaybta Bandhigga Macmiilka?

bixin.js

import { getCurrentState } from './state';

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

  // Do the rendering
  // ...
}

getCurrentState() waa inay awoodaan inay na siiyaan xaaladda ciyaarta hadda ee macmiilka goor kasta ku salaysan updates laga helay server-ka. Waa kuwan tusaale ku saabsan casriyeynta ciyaarta oo seerfarku soo diri karo:

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

Cusbooneysii ciyaar kasta waxa ay ka kooban tahay shan goobood oo isku mid ah:

  • t: Shaabada wakhtiyada serverka ee tilmaamaya goorta cusboonaysiinta la sameeyay.
  • meMacluumaad ku saabsan ciyaaryahanka helay update this.
  • kuwa kale: Warbixinno kala duwan oo ku saabsan ciyaartoyga kale ee ka qayb qaadanaya isla ciyaartaas.
  • rasaas: xog badan oo ku saabsan madfaca ciyaarta.
  • hogaanka sareXogta sabuuradda hogaaminta ee hadda. Ku xisaabtami mayno qoraalkan.

7.1 Gobolka macmiisha ee naive

Hirgelinta nafiska ah getCurrentState() kaliya waxay si toos ah u soo celin kartaa xogta cusboonaysiinta ciyaarta ee ugu dambeysay.

naive-state.js

let lastGameUpdate = null;

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

export function getCurrentState() {
  return lastGameUpdate;
}

Qurux badan oo cad! Laakiin haddii kaliya ay sidaas u fududaan lahayd. Mid ka mid ah sababaha hirgelinta tani waa dhibaato: waxay xaddidaysaa heerka qaabaynta xawaaraha saacada server-ka.

Heerka jir: tirada fareemooyinka (sida wicitaanada render()) ilbiriqsikiiba, ama FPS. Ciyaaruhu waxay inta badan ku dadaalaan inay gaadhaan ugu yaraan 60 FPS.

Qiimaha saxda ahInta jeer ee uu serverku u soo diro cusboonaysiinta ciyaarta macaamiisha. Inta badan way ka hooseysaa heerka jir. Ciyaartayada, server-ku wuxuu ku socdaa 30 shilin ilbiriqsikii.

Haddii aan bixino cusboonaysiinta ciyaarta ugu dambeysay, markaa FPS runtii weligeed ma awoodi doonto inay dhaafto 30 sababtoo ah Marna ma helin wax ka badan 30 cusbooneysiin ilbiriqsi kasta oo ka yimaada serverka. Xitaa haddii aan wacno render() 60 jeer ilbiriqsikii, ka dib kala badh wicitaanadaas waxay si fudud u sawiri doonaan wax la mid ah, asal ahaan waxba ma samayn doonaan. Dhibaato kale oo leh hirgelinta nafiska ah waa taas ku xiran dib u dhac. Xawaaraha ugu fiican ee internetka, macmiilku wuxuu heli doonaa cusboonaysiinta ciyaarta si sax ah 33 ms (30 ilbiriqsi kasta):

Abuuritaanka ciyaarta mareegaha badan ee nooca .io
Nasiib darro, ma jiraan wax qumman. Sawirka dhabta ah ayaa noqon doona:
Abuuritaanka ciyaarta mareegaha badan ee nooca .io
Hirgelinta nafiska ah ayaa aad ugu badan kiiska ugu xun marka ay timaado daahitaanka. Haddii cusboonaysiinta ciyaarta lagu helo daahitaan 50ms ah, markaa macmiilka waa la dhimay 50ms oo dheeri ah sababtoo ah waxay wali ka dhigaysaa xaalada ciyaarta cusboonaysiintii hore. Waxaad qiyaasi kartaa sida ay tani ugu dhib yar tahay ciyaaryahanka: sababtoo ah gaabis aan sabab lahayn, ciyaartu waxay u ekaan doontaa mid qallafsan oo aan degganayn.

7.2 Gobolka macmiilka oo la hagaajiyay

Waxoogaa horumar ah ayaanu ku samayn doonaa hirgalinta nafiska ah. Marka hore, waxaan isticmaalnaa dib u dhigista 100 ms Tani waxay ka dhigan tahay in xaaladda "hadda" ee macmiilku ay had iyo jeer noqon doonto 100ms ka dambeeya gobolka ciyaarta ee server-ka. Tusaale ahaan, haddii wakhtiga serverku yahay 150, ka dibna macmiilku wuxuu soo bandhigi doonaa gobolka uu serverku ku jiray wakhtiga 50:

Abuuritaanka ciyaarta mareegaha badan ee nooca .io
Tani waxay na siinaysaa kayd 100ms ah si aan uga badbaadno wakhtiga aan la saadaalin karin ee cusboonaysiinta ciyaarta:

Abuuritaanka ciyaarta mareegaha badan ee nooca .io
Qiimaha tani waxay noqon doontaa mid joogto ah daahitaanka gelinta 100 ms Tani waa allabari yar oo loogu talagalay ciyaarta fudud - ciyaartoyda badankood (gaar ahaan kuwa caadiga ah) xitaa ma dareemi doonaan daahitaankan. Aad bay ugu fududahay dadka inay la qabsadaan daahitaanka 100ms ee joogtada ah halkii ay ku ciyaari lahaayeen daahitaanka aan la saadaalin karin.

Waxaan isticmaali karnaa farsamo kale oo la yiraahdo "saadaasha dhinaca macmiilka", kaas oo qabta shaqo wanaagsan oo yaraynta daahitaanka la dareemayo, laakiin lagama hadli doono qoraalkan.

Horumar kale oo aan isticmaalno waa isdhexgalka toosan. Daahitaanka soo-dejinta awgeed, waxaan inta badan nahay ugu yaraan hal cusbooneysiin ka hor wakhtiga hadda ee macmiilka. Marka loo yeero getCurrentState(), waan buuxin karnaa isdhexgalka toosan inta u dhaxaysa cusboonaysiinta ciyaarta isla markiiba ka hor iyo ka dib wakhtiga hadda ee macmiilka:

Abuuritaanka ciyaarta mareegaha badan ee nooca .io
Tani waxay xallisaa dhibaatada heerka jir: waxaan hadda samayn karnaa xirmo gaar ah heer kasta oo aan u baahanahay!

7.3 Hirgelinta xaalad macmiil oo la hagaajiyay

Tusaale fulinta gudaha src/client/state.js waxay isticmaashaa dib u dhigista iyo isdhexgalka tooska ah labadaba, laakiin tani ma sii soconayso wakhti dheer. Aan u kala qaadno koodhka laba qaybood. Waa kan kan ugu horreeya:

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

Waxa ugu horreeya ee aad u baahan tahay inaad sameyso waa inaad ogaataa waxa uu qabanayo currentServerTime(). Sidaan horay u soo aragnay, ciyaar kasta oo cusub waxaa ku jira waqti-shabad server ah. Waxaan rabnaa inaan isticmaalno daahitaanka si aan sawirka uga dhigno 100ms gadaasha serverka, laakiin Marna ma ogaan doonno waqtiga hadda ee server-ka, sababtoo ah ma ogaan karno inta ay qaadatay in mid kasta oo ka mid ah cusbooneysiinta uu na soo gaaro. Internetku lama saadaalin karo xawaarihiisana aad buu u kala duwanaan karaa!

Si loo xalliyo dhibaatadan, waxaan isticmaali karnaa qiyaas macquul ah: anaga aan iska dhigno in cusboonaysiintii ugu horreysay ay isla markiiba timid. Hadday tani run tahay, markaa waxaan ogaan lahayn wakhtiga server-ka wakhtigaas gaarka ah! Waxaan ku kaydinnaa wakhtiga server-ka firstServerTimestamp oo naga badbaadi deegaanka (macmiilka) timestamp isla wakhtigaas gudaha gameStart.

Oh, sug hal daqiiqo. Miyaanay ahayn in wakhti ku jiro server-ka = wakhtiga macmiilka? Maxaan u kala soocnaa "waqtiyada server-ka" iyo "waqtiga macmiilka"? Tani waa su'aal weyn! Waxaa soo baxday in kuwani aysan isku mid ahayn. Date.now() waxay ku soo celin doontaa wakhtiyada kala duwan ee macmiilka iyo server-ka tanina waxay ku xidhan tahay arrimaha maxaliga ah ee mashiinadan. Weligaa ha u qaadan in wakhtiyada wakhtiyadu isku mid yihiin dhammaan mishiinnada.

Hadda waxaan fahamsanahay waxa ay qabato currentServerTime(): way soo noqotaa server timestamp ee wakhtiga wax bixinta hadda. Si kale haddii loo dhigo, tani waa wakhtiga server-ka hadda (firstServerTimestamp <+ (Date.now() - gameStart)) laga jaray dib u dhigista (RENDER_DELAY).

Hadda aan eegno sida aan u maarayno cusboonaysiinta ciyaarta. Marka update laga helo server-ka, waa la wacaa processGameUpdate(), oo waxa aanu u kaydinay cusboonaysiinta cusub si ay u diyaariyaan gameUpdates. Kadib, si loo hubiyo isticmaalka xusuusta, waxaan ka saareynaa dhammaan cusbooneysiinta hore update salkasababtoo ah hadda ka dib uma baahnno.

Waa maxay "cusboonaysiinta asaasiga ah"? Tani Cusboonaysiinta ugu horreysa ee aan helno annagoo dib uga soconayna wakhtiga server-ka ee hadda jira. Xusuusnow jaantuskan?

Abuuritaanka ciyaarta mareegaha badan ee nooca .io
Cusboonaysiinta ciyaarta si toos ah dhanka bidix ee "Waqtiga Bixinta Macmiilka" waa cusbooneysiinta saldhiga.

Waa maxay cusboonaysiinta saldhiga loo isticmaalo? Waa maxay sababta aan u tuuri karno wararka saldhigga? Si aan tan u fahanno, aynu ugu danbeyn aan eegno fulinta getCurrentState():

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

Waxaan qabanaa saddex xaaladood:

  1. base < 0 macneheedu waxa weeye in aanay jirin wax cusub oo cusub ilaa wakhtiga wax bixinta ee hadda la joogo (eeg fulinta sare getBaseUpdate()). Tani waxay dhici kartaa isla markiiba bilawga ciyaarta sababtoo ah dib u dhac ku yimid. Xaaladdan oo kale, waxaan isticmaalnaa cusbooneysiintii ugu dambeysay ee la helay.
  2. base waa wararkii ugu dambeeyay ee aan hayno. Tani waxay ku dhici kartaa daahitaanka shabakada ama isku xirnaanta internetka oo liita. Xaaladdan oo kale waxaan isticmaalnaa cusbooneysiintii ugu dambeysay ee aan hayno.
  3. Waxaan haynaa cusboonaysiin ka hor iyo ka dib wakhtiga wax bixinta ee hadda, si aan awoodno isku xidhid!

Dhammaan waxa ku haray state.js waa hirgelinta isdhexgalka toosan oo fudud (laakiin caajis ah) xisaabta. Haddii aad rabto inaad adigu sahamiso, ka dibna fur state.js on Github.

Qaybta 2. Backend server

Qaybtan waxaan ku eegi doonaa dhabarka Node.js ee gacanta ku haya tusaale ahaan ciyaarta .io.

1. Barta laga soo galo Server-ka

Si loo maareeyo server-ka shabakada waxaan u isticmaali doonaa qaab-dhismeedka shabakada caanka ah ee Node.js oo la yiraahdo Fikraddaada. Waxaa lagu habeyn doonaa faylka barta barta serverka src/server/server.js:

server.js, qaybta 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}`);

Xusuusnow in qeybtii hore aan ka wada hadalnay Webpack? Tani waa halka aan ku isticmaali doono qaabeynta xirmooyinka Webka. Waxaan u dabaqi doonaa laba siyaabood:

  • Si aad u isticmaasho xirmo-dev-middleware inaan si toos ah dib ugu dhisno xirmooyinkeena horumarinta, ama
  • Si toos ah u wareeji gal dist/, kaas oo Webpack uu ku qori doono faylalkayaga ka dib dhisidda wax soo saarka.

Hawl kale oo muhiim ah server.js wuxuu ka kooban yahay dejinta server-ka socket.iokaas oo si fudud ugu xidha serverka Express

server.js, qaybta 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);
});

Ka dib markii si guul leh loo aasaasey xiriirka socket.io ee serferka, waxaan u habeyneynaa maamulayaasha dhacdada godka cusub. Maamulayaasha dhacdada waxay habeeyaan farriimaha laga helo macaamiisha iyagoo u wakiishay shay keliya game:

server.js, qaybta 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);
}

Waxaan abuureynaa ciyaarta .io, marka waxaan u baahan doonaa hal nuqul oo kaliya Game ("Ciyaarta") - Dhammaan ciyaartoydu waxay ku ciyaaraan garoon isku mid ah! Qaybta xigta waxaan ku arki doonaa sida fasalkani u shaqeeyo Game.

2. Ciyaartoyda

Класс Game ka kooban yahay macquulka-dhinaca server-ka ugu muhiimsan. Waxay leedahay laba hawlood oo waaweyn: maamulka ciyaartoyda и jilitaanka ciyaarta.

Aan ku bilowno hawsha ugu horeysa - maamulida ciyaartoyda.

ciyaarta.js, qaybta 1aad

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

  // ...
}

Ciyaartan waxaan ku aqoonsan doonaa ciyaartoyda garoonka dhexdiisa id socket socket.io (haddii aad wareersan tahay, ka dibna ku noqo server.js). Socket.io lafteedu waxay u qoondaysaa god kasta mid gaar ah id, markaa uma baahnid inaan ka walwalno. waan u wici doonaa Aqoonsiga ciyaartoyga.

Iyadoo taas maskaxda lagu hayo, aynu eegno doorsoomayaasha tusaale ahaan fasalka Game:

  • sockets waa shay ku xidha aqoonsiga ciyaartoyga godka ku xidhan ciyaaryahanka. Waxay noo ogolaataa in aan galno saldhigyada aqoonsigooda ciyaartoyga waqti ka dib.
  • players waa shay ku xidha aqoonsiga ciyaartoyga koodka>Shayga ciyaartoyga

bullets waa shay kala duwan Bullet, oo aan lahayn amar gaar ah.
lastUpdateTime - Kani waa shaambada wakhtiga ee cusboonaysiinta ciyaarta u dambaysay. Waxaan arki doonaa sida loo isticmaalo dhawaan.
shouldSendUpdate waa doorsoome caawiye. Waxaan sidoo kale arki doonaa isticmaalkeeda dhawaan.
Dariiqooyinka addPlayer(), removePlayer() ΠΈ handleInput() looma baahna in la sharaxo, waxaa loo isticmaalaa in server.js. Haddii aad u baahan tahay dib-u-cusbooneysiiye, wax yar dib ugu noqo.

Khadka u dambeeya constructor() bilaabaa wareegga update ciyaaraha (oo leh inta jeer ee 60 cusbooneysiin/s):

ciyaarta.js, qaybta 2aad

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

  // ...
}

Habka update() waxaa ku jira malaha qaybta ugu muhiimsan ee macquulka dhinaca server-ka. Aynu u taxno wax kasta oo ay qabato:

  1. Wuxuu xisaabiyaa inta saacaddu tahay dt waa tan iyo markii ugu dambeysay update().
  2. Waxay dib u cusboonaysiisaa madfac kasta oo ay burburisaa haddii loo baahdo. Waxaan arki doonaa hirgelinta hawshan dambe. Hadda waa nagu filan tahay inaan ogaano taas bullet.update() soo noqda true, haddii madfaca la burburiyo ( banaanka ayuu u baxay saaxada).
  3. Cusbooneysii ciyaaryahan kasta oo abuuraa madfac haddii loo baahdo. Waxaan sidoo kale arki doonaa hirgelinta tan dambe - player.update() soo celin kara shay Bullet.
  4. Waxay hubisaa isku dhaca u dhexeeya madfacyada iyo ciyaartoyda isticmaalaya applyCollisions(), kaas oo soo celinaya tiro madfac ah oo ku dhacay ciyaartoyda. Mashruuc kasta oo la soo celiyay, waxaanu kordhinaynaa dhibcaha ciyaartoyga tuuray (adoo isticmaalaya player.onDealtDamage()), ka dibna ka saar madfaca shaxanka bullets.
  5. Ogaysii oo baabi'iyaa dhammaan ciyaartoydii la dilay.
  6. U soo dirtaa cusboonaysiinta ciyaarta dhammaan ciyaartoyda ilbiriqsi kasta waqtiyo marka loo yeero update(). Doorsoomiyaha kaaliyaha ah ee aan kor ku soo xusnay ayaa naga caawinaya in aan la socono tan shouldSendUpdate. Sida update() loo yaqaan 60 jeer/s, waxaanu soo dirnaa cusboonaysiinta ciyaarta 30 jeer/s. Haddaba, inta jeer ee saacadda server waa 30 saacadood wareegyada / s (waxaan ka hadalnay inta jeer ee saacada qaybta hore).

Waa maxay sababta u soo diri wararka cusub ee ciyaarta kaliya waqtiga ? Si loo badbaadiyo kanaalka Cusbooneysii 30 ciyaar ilbiriqsiki waa wax badan!

Waa maxay sababta aad u wacdid kaliya? update() 30 jeer ilbiriqsikii? Si loo hagaajiyo jilitaanka ciyaarta. Inta badan waxaa la yiraahdaa update(), sida saxda ah ee jilitaanka ciyaarta ayaa noqon doona. Laakin ha ku daalin tirada caqabadaha update(), sababtoo ah tani waa hawl xisaabeed qaali ah - 60 ilbiriqsi kasta ayaa ku filan.

Inta kale ee fasalka Game wuxuu ka kooban yahay hababka caawiye ee loo isticmaalo update():

ciyaarta.js, qaybta 3aad

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() Way fududahay - waxay ku kala saartaa ciyaartoyda dhibco, waxay qaadataa shanta ugu sareysa, waxayna soo celisaa magaca isticmaale iyo dhibcaha mid kasta.

createUpdate() loo adeegsaday update() si loo abuuro cusboonaysiinta ciyaarta oo loo qaybiyo ciyaartoyga. Shaqadeeda ugu weyn waa inay wacdo hababka serializeForUpdate(), loo hirgeliyay fasallada Player ΠΈ Bullet. Ogsoonow inay kaliya u gudbiso xogta ciyaaryahan kasta ugu dhow ciyaartoyda iyo madfacyada - looma baahna in la gudbiyo macluumaadka ku saabsan walxaha ciyaarta ee ku yaal meel ka fog ciyaaryahanka!

3. Walxaha ciyaarta ee serverka

Ciyaartayada, madfacyada iyo ciyaartoygu runtii aad bay isugu shabahaan: waa walxaha ciyaarta wareega wareega ee aan la taaban karin. Si aad uga faa'iidaysato isku ekaanshaha ciyaartoyga iyo madxafyada, aynu ku bilowno hirgelinta fasalka aasaasiga ah Object:

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

Ma jiraan wax adag oo halkan ka socda. Fasalkani wuxuu noqon doonaa barta bilawga wanaagsan ee ballaarinta. Aan aragno sida fasalka Bullet adeegsadaa Object:

rasaas.js

const shortid = require('shortid');
const ObjectClass = require('./object');
const Constants = require('../shared/constants');

class Bullet extends ObjectClass {
  constructor(parentID, x, y, dir) {
    super(shortid(), x, y, dir, Constants.BULLET_SPEED);
    this.parentID = parentID;
  }

  // Returns true if the bullet should be destroyed
  update(dt) {
    super.update(dt);
    return this.x < 0 || this.x > Constants.MAP_SIZE || this.y < 0 || this.y > Constants.MAP_SIZE;
  }
}

РСализация Bullet aad u gaaban! Waanu ku darnay Object kaliya kordhinta soo socota:

  • Isticmaalka xirmada gaaban jiil random id madmadow.
  • Beer lagu daro parentID, si aad ula socoto cayaartoyga sameeyay mashruucan.
  • Ku darida qiimaha soo celinta update(), oo la siman true, haddii madfaca uu ka baxsan yahay saaxadda (xusuusnow inaan arrintan uga hadalnay qaybta ugu dambeysa?).

Aan u gudubno Player:

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

Ciyaartoyda way ka adag yihiin kuwa madfaca, markaa fasalkani waa inuu kaydiyaa dhawr garoon oo dheeraad ah. Habkiisa update() wuxuu qabtaa shaqo badan, gaar ahaan soo celinta madfaca cusub ee la abuuray haddii aysan jirin wax ka haray fireCooldown (xusuusnow inaan qaybta hore kaga hadalnay arrintan?). Waxa kale oo ay kordhisaa habka serializeForUpdate(), sababtoo ah waxaan u baahanahay inaan ku darno garoomo dheeri ah oo loogu talagalay ciyaaryahanka ku jira cusboonaysiinta ciyaarta.

Helitaanka fasalka aasaasiga ah Object - tallaabo muhiim ah si looga fogaado soo celinta code. Tusaale ahaan, fasal la'aan Object shay kasta oo cayaaruhu waa inuu lahaadaa hirgelin isku mid ah distanceTo(), iyo ku-koobidda dhammaan hirgelintaas oo dhan faylal badan waxay noqon doontaa riyo xun. Tani waxay si gaar ah muhiim ugu noqotaa mashaariicda waaweyn, marka tirada ballaarinta Object fasallada ayaa koraya.

4. Ogaanshaha isku dhaca

Waxa kaliya ee noo haray waa inaan ogaano marka madfacyadu ay garaacaan ciyaartoyda! Xusuusnow qoraalkan koodka ee habka update() fasalka dhexdiisa Game:

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

    // ...
  }
}

Waxaan u baahanahay inaan hirgelino habka applyCollisions(), kaas oo soo celinaya dhammaan madxafyada ku dhacay ciyaartoyda. Nasiib wanaag, tani maaha wax adag in la sameeyo sababtoo ah

  • Dhammaan walxaha isku dhacaya waa goobo, tani waa qaabka ugu fudud ee lagu hirgeliyo ogaanshaha isku dhaca.
  • Waxaan horey u haysanay hab distanceTo(), kaas oo aanu ku dhaqan gelinay fasalka qaybtii hore Object.

Tani waa waxa fulinteena ogaanshaha isku dhaca u eg yahay:

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

Ogaanshahan fudud ee isku dhaca waxay ku salaysan tahay xaqiiqda laba wareeg ayaa isku dhaca haddii masaafada u dhaxaysa xarumahooda ay ka yar tahay wadarta radiyadooda. Halkan waxaa ah kiis masaafada u dhexeysa xarumaha labada goobood ay si sax ah u siman tahay wadarta radiyadooda:

Abuuritaanka ciyaarta mareegaha badan ee nooca .io
Halkan waxaad u baahan tahay inaad fiiro gaar ah u yeelato dhowr arrimood oo kale:

  • Madxafku waa inuusan ku dhicin ciyaaryahankii abuuray. Tan waxaa lagu gaari karaa isbarbardhigga bullet.parentID с player.id.
  • Mashruucu waa inuu hal mar kaliya ku dhufto kiiska aadka u daran ee garaacaya ciyaartoy badan isku mar. Waxaan xallin doonaa dhibaatadan annaga oo adeegsanayna hawlwadeenka break: Marka la helo ciyaaryahan ku dhacay madfac, waan joojineynaa raadinta waxaana u gudubnaa saadaasha xigta.

Dhammaad

Waa intaas! Waxaan daboolnay wax walba oo aad u baahan tahay inaad ogaato si aad u abuurto ciyaarta webka .io. Maxaa xiga? Dhis ciyaartaada .io!

Dhammaan koodka tusaalaha waa il furan oo lagu dhejiyay Github.

Source: www.habr.com

Add a comment