A’ cruthachadh geama lìn ioma-chluicheadair .io

A’ cruthachadh geama lìn ioma-chluicheadair .io
Air fhoillseachadh ann an 2015 Agar.io thàinig e gu bhith na neach-tòiseachaidh de ghnè ùr geamannan .ioa tha air a bhith mòr-chòrdte bhon uairsin. Tha mi gu pearsanta air eòlas fhaighinn air àrdachadh mòr-chòrdte geamannan .io: thairis air na trì bliadhna a dh’ fhalbh, tha mi air chruthaich agus reic iad dà gheama den ghnè seo..

Air eagal ‘s nach cuala tu a-riamh mu na geamannan sin roimhe, is e geamannan lìn ioma-chluiche an-asgaidh a tha seo a tha furasta an cluich (chan eil feum air cunntas). Mar as trice bidh iad an aghaidh mòran de chluicheadairean dùbhlanach san aon raon. Geamannan .io ainmeil eile: Slither.io и Diop.io.

Anns an dreuchd seo, nì sinn sgrùdadh air ciamar cruthaich geama .io bhon fhìor thoiseach. Airson seo, cha bhith ach eòlas air Javascript gu leòr: feumaidh tu rudan mar cho-chòrdadh a thuigsinn ES6, prìomh-fhacal this и Geallaidhean. Fiù mura h-eil an t-eòlas agad air Javascript foirfe, faodaidh tu fhathast a’ mhòr-chuid den phost a thuigsinn.

.io eisimpleir cluiche

Airson taic ionnsachaidh, bheir sinn iomradh air .io eisimpleir cluiche. Feuch ris a chluich!

A’ cruthachadh geama lìn ioma-chluicheadair .io
Tha an geama gu math sìmplidh: bidh smachd agad air bàta ann an raon far a bheil cluicheadairean eile. Bidh an soitheach agad gu fèin-ghluasadach a’ losgadh projectiles agus bidh thu a’ feuchainn ri cluicheadairean eile a bhualadh fhad ‘s a tha thu a’ seachnadh na projectiles aca.

1. Geàrr-shealladh / structar a 'phròiseict

Рекомендую luchdaich sìos source code geama eisimpleir gus an urrainn dhut mo leantainn.

Tha an eisimpleir a’ cleachdadh na leanas:

  • cur an cèill am frèam lìn Node.js as mòr-chòrdte a bhios a’ riaghladh frithealaiche lìn a’ gheama.
  • socaid.io - leabharlann websocket airson dàta iomlaid eadar brobhsair agus frithealaiche.
  • Pasgan lìn - manaidsear modal. Faodaidh tu leughadh mu carson a chleachdas tu Webpack. an seo.

Seo cò ris a tha structar eòlaire a’ phròiseict coltach:

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

poblach/

A h-uile rud ann am pasgan public/ thèid a chuir a-steach gu statach leis an fhrithealaiche. ANNS public/assets/ anns a bheil dealbhan air an cleachdadh leis a’ phròiseact againn.

src /

Tha a h-uile còd stòr sa phasgan src/. Nàdarrach client/ и server/ labhairt air an son fein agus shared/ tha faidhle cunbhalach ann a tha air a thoirt a-steach leis an dà chuid an neach-dèiligidh agus an frithealaiche.

2. Co-chruinneachaidhean / roghainnean pròiseict

Mar a chaidh ainmeachadh gu h-àrd, bidh sinn a’ cleachdadh manaidsear a’ mhodal gus am pròiseact a thogail. Pasgan lìn. Bheir sinn sùil air an rèiteachadh Webpack againn:

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

Is iad na loidhnichean as cudromaiche an seo:

  • src/client/index.js Is e seo an t-àite inntrigidh don neach-dèiligidh Javascript (JS). Tòisichidh Webpack às an seo agus nì e sgrùdadh air ais airson faidhlichean eile a chaidh a thoirt a-steach.
  • Bidh an toradh JS den togalach Webpack againn suidhichte san eòlaire dist/. Canaidh mi am faidhle seo againn js paca.
  • Bidh sinn a ’cleachdadh Babel, agus gu sònraichte an rèiteachadh @babel/preset-env gus ar còd JS a thar-chuir airson brobhsairean nas sine.
  • Tha sinn a’ cleachdadh plugan gus a h-uile CSS air an tug na faidhlichean JS iomradh a thoirt a-mach agus an cur còmhla ann an aon àite. Canaidh mi sinne ris pasgan css.

Is dòcha gun do mhothaich thu ainmean faidhle pacaid neònach '[name].[contenthash].ext'. Tha iad a’ toirt a-steach luchd-ionaid ainm faidhle Pasgan lìn: [name] thèid ainm a’ phuing cuir a-steach a chuir na àite (anns a’ chùis againn, seo game), agus [contenthash] thèid hash de shusbaint an fhaidhle a chuir na àite. Bidh sinn ga dhèanamh gu am pròiseact as fheàrr a dhèanamh airson hashing - faodaidh tu innse dha brobhsairean na pacaidean JS againn a thasgadh gu bràth, oir ma dh'atharraicheas pasgan, bidh ainm an fhaidhle aige cuideachd ag atharrachadh (atharraichean contenthash). Is e an toradh deireannach ainm an fhaidhle seallaidh game.dbeee76e91a97d0c7207.js.

faidhl webpack.common.js is e am faidhle rèiteachaidh bunaiteach a bheir sinn a-steach do leasachadh agus rèiteachadh pròiseict crìochnaichte. Seo eisimpleir de rèiteachadh leasachaidh:

webpack.dev.js

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

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

Airson èifeachdas, bidh sinn a 'cleachdadh anns a' phròiseas leasachaidh webpack.dev.js, agus a' tionndadh gu webpack.prod.jsgus meudan pacaid a mheudachadh nuair a thèid an cleachdadh gu cinneasachadh.

Suidheachadh ionadail

Tha mi a’ moladh am pròiseact a chuir a-steach air inneal ionadail gus an urrainn dhut na ceumannan a tha air an liostadh san dreuchd seo a leantainn. Tha an rèiteachadh sìmplidh: an toiseach, feumaidh an siostam a bhith air a chuir a-steach Node и NPM. An ath feumaidh tu a dhèanamh

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

agus tha thu deiseil airson falbh! Gus am frithealaiche leasachaidh a thòiseachadh, dìreach ruith

$ npm run develop

agus rachaibh gu brabhsair lìn localhost: 3000. Bidh am frithealaiche leasachaidh ag ath-thogail gu fèin-ghluasadach na pacaidean JS agus CSS mar a bhios an còd ag atharrachadh - dìreach ùraich an duilleag gus na h-atharrachaidhean gu lèir fhaicinn!

3. Puingean Inntrigidh Cliant

Leigamaid sìos gu còd a 'gheama fhèin. An toiseach feumaidh sinn duilleag index.html, nuair a thadhlas tu air an làrach, luchdaichidh am brabhsair e an toiseach. Bidh an duilleag againn gu math sìmplidh:

index.html

An eisimpleir .io cluiche  CLÀRADH

Chaidh an eisimpleir còd seo a dhèanamh nas sìmplidhe beagan airson soilleireachd, agus nì mi an aon rud le mòran de na h-eisimpleirean puist eile. Faodar an còd slàn fhaicinn an-còmhnaidh aig GitHub.

Tha againn:

  • Html5 eileamaid canabhas (<canvas>) a chleachdas sinn gus an geama a thoirt seachad.
  • <link> gus ar pasgan CSS a chuir ris.
  • <script> gus ar pasgan Javascript a chuir ris.
  • Prìomh chlàr le ainm-cleachdaidh <input> agus am putan PLAY (<button>).

Às deidh an duilleag dachaigh a luchdachadh, tòisichidh am brabhsair a’ cur an gnìomh còd Javascript, a’ tòiseachadh bhon àite inntrigidh faidhle JS: src/client/index.js.

clàr-amais.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);
  };
});

Is dòcha gu bheil seo iom-fhillte, ach chan eil mòran a’ dol air adhart an seo:

  1. A’ toirt a-steach grunn fhaidhlichean JS eile.
  2. In-mhalairt CSS (mar sin tha fios aig Webpack gun cuir iad a-steach iad sa phasgan CSS againn).
  3. Запуск connect() gus ceangal a stèidheachadh leis an fhrithealaiche agus ruith downloadAssets() Gus dealbhan a luchdachadh a-nuas a tha a dhìth airson a ' gheama.
  4. Às deidh ìre 3 a chrìochnachadh tha am prìomh chlàr air a thaisbeanadh (playMenu).
  5. A 'suidheachadh an inneal-làimhe airson putadh air a' phutan "PLAY". Nuair a thèid am putan a bhrùthadh, tòisichidh an còd a’ gheama agus ag innse don fhrithealaiche gu bheil sinn deiseil airson a chluich.

Tha prìomh “feòil” ar loidsig frithealaiche teachdaiche anns na faidhlichean sin a chaidh a thoirt a-steach leis an fhaidhle index.js. A-nis beachdaichidh sinn orra uile ann an òrdugh.

4. Malairt dàta luchd-cleachdaidh

Anns a’ gheama seo, bidh sinn a’ cleachdadh leabharlann ainmeil airson conaltradh leis an fhrithealaiche socaid.io. Tha taic dhùthchasach aig Socket.io socaidean lìn, a tha gu math freagarrach airson conaltradh dà-shligheach: is urrainn dhuinn teachdaireachdan a chuir chun fhrithealaiche и faodaidh am frithealaiche teachdaireachdan a chuir thugainn air an aon cheangal.

Bidh aon fhaidhle againn src/client/networking.jscò bheir an aire a h-uile duine conaltradh leis an fhrithealaiche:

lìonrachadh.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);
};

Tha an còd seo cuideachd air a ghiorrachadh beagan airson soilleireachd.

Tha trì prìomh ghnìomhan san fhaidhle seo:

  • Tha sinn a’ feuchainn ri ceangal ris an fhrithealaiche. connectedPromise ceadaichte a-mhàin nuair a tha sinn air ceangal a stèidheachadh.
  • Ma tha an ceangal soirbheachail, bidh sinn a’ clàradh gnìomhan gairm air ais (processGameUpdate() и onGameOver()) airson teachdaireachdan a gheibh sinn bhon fhrithealaiche.
  • Bidh sinn às-mhalairt play() и updateDirection()gus an urrainn do fhaidhlichean eile an cleachdadh.

5. Client Rendering

Tha an t-àm ann an dealbh a thaisbeanadh air an sgrion!

…ach mus urrainn dhuinn sin a dhèanamh, feumaidh sinn na h-ìomhaighean gu lèir (goireasan) a tha a dhìth airson seo a luchdachadh sìos. Sgrìobhamaid manaidsear ghoireasan:

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

Chan eil riaghladh ghoireasan cho duilich a bhuileachadh! Is e am prìomh bheachd stuth a stòradh assets, a cheanglas iuchair ainm an fhaidhle ri luach an nì Image. Nuair a bhios an goireas air a luchdachadh, bidh sinn ga stòradh ann an nì assets airson ruigsinneachd luath san àm ri teachd. Cuin a bhios cead aig gach goireas fa leth a luchdachadh sìos (is e sin, uile goireasan), leigidh sinn downloadPromise.

Às deidh dhut na goireasan a luchdachadh sìos, faodaidh tu tòiseachadh air rendering. Mar a chaidh a ràdh na bu tràithe, airson tarraing air duilleag-lìn, bidh sinn a 'cleachdadh Canabhas HTML5 (<canvas>). Tha an geama againn gu math sìmplidh, agus mar sin chan fheum sinn ach na leanas a tharraing:

  1. Cùl-fhiosrachadh
  2. Soitheach cluicheadair
  3. Cluicheadairean eile sa gheama
  4. sligean

Seo na criomagan cudromach src/client/render.js, a tha a’ nochdadh dìreach na ceithir nithean air an liostadh gu h-àrd:

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

Tha an còd seo cuideachd air a ghiorrachadh airson soilleireachd.

render() 's e prìomh obair an fhaidhle seo. startRendering() и stopRendering() smachd a chumail air gnìomhachd an lùb render aig 60 FPS.

Gnìomhan concrait de ghnìomhan neach-cuideachaidh tairgse fa leth (m.e. renderBullet()) nach eil sin cudromach, ach seo eisimpleir shìmplidh:

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

Thoir an aire gu bheil sinn a 'cleachdadh an dòigh getAsset(), a chunnacas roimhe ann an asset.js!

Ma tha ùidh agad ann a bhith ag ionnsachadh mu luchd-cuideachaidh tairgse eile, leugh an còrr. src/client/render.js.

6. Teachd a-steach cliant

Tha an t-àm ann geam a dhèanamh a ghabhas cluich! Bidh an sgeama smachd gu math sìmplidh: gus slighe gluasad atharrachadh, faodaidh tu an luchag a chleachdadh (air coimpiutair) no suathadh air an sgrion (air inneal gluasadach). Gus seo a chur an gnìomh, bidh sinn a 'clàradh Luchd-èisteachd Tachartais airson tachartasan Mouse and Touch.
Bheir e aire do seo uile src/client/input.js:

cuir a-steach.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() a bheil Luchd-èisteachd Tachartas a’ gairm updateDirection() (bho networking.js) nuair a thachras tachartas cuir a-steach (mar eisimpleir, nuair a thèid an luchag a ghluasad). updateDirection() a’ làimhseachadh teachdaireachdan leis an t-seirbheisiche, a bhios a’ làimhseachadh an tachartais cuir a-steach agus ag ùrachadh staid a’ gheama a rèir sin.

7. Inbhe Cliant

Is e an earrann seo an rud as duilghe anns a 'chiad phàirt den dreuchd. Na bi mì-mhisneachail mura h-eil thu ga thuigsinn a’ chiad uair a leugh thu e! Faodaidh tu eadhon leum air agus tilleadh thuige nas fhaide air adhart.

Is e am pìos mu dheireadh den tòimhseachan a dh’ fheumar gus an còd teachdaiche/frithealaiche a chrìochnachadh stàite. Cuimhnich air a’ chriomag còd bhon roinn Cliant Rendering?

toirt seachad.js

import { getCurrentState } from './state';

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

  // Do the rendering
  // ...
}

getCurrentState() bu chòir dha a bhith comasach air suidheachadh gnàthach a’ gheama a thoirt dhuinn anns a ’chliant aig àm sam bith stèidhichte air ùrachaidhean a fhuaireadh bhon fhrithealaiche. Seo eisimpleir de ùrachadh geama as urrainn don fhrithealaiche a chuir:

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

Tha còig raointean co-ionann anns gach ùrachadh geama:

  • t: Clàr-ama an fhrithealaiche a' comharrachadh cuin a chaidh an t-ùrachadh seo a chruthachadh.
  • me: Fiosrachadh mun chluicheadair a gheibh an t-ùrachadh seo.
  • feadhainn eile: Sreath de dh'fhiosrachadh mu chluicheadairean eile a tha a 'gabhail pàirt san aon gheama.
  • peilearan: sreath de dh'fhiosrachadh mu dheidhinn projectiles sa gheama.
  • bòrd-stiùiridh: Dàta clàr stiùiridh gnàthach. Anns an dreuchd seo, cha bheachdaich sinn orra.

7.1 Stàite teachdaiche naive

Cur an gnìomh naive getCurrentState() chan urrainn ach dàta an ùrachadh geama as ùire a fhuaireadh a thilleadh gu dìreach.

naive-state.js

let lastGameUpdate = null;

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

export function getCurrentState() {
  return lastGameUpdate;
}

Glè mhath agus soilleir! Ach nam biodh e dìreach cho sìmplidh. Is e aon de na h-adhbharan a tha an gnìomh seo duilich: bidh e a’ cuingealachadh an ìre frèam tairgse gu ìre gleoc an fhrithealaiche.

Ìre frèam: àireamh de fhrèamaichean (i.e. gairmean render()) gach diog, no FPS. Mar as trice bidh geamannan a’ strì ri co-dhiù 60 FPS a choileanadh.

Tick ​​Ìre: An tricead aig am bi am frithealaiche a’ cur ùrachaidhean geama gu teachdaichean. Gu math tric tha e nas ìsle na an ìre frèam. Anns a’ gheama againn, bidh am frithealaiche a’ ruith aig tricead 30 cearcallan gach diog.

Ma bheir sinn seachad an ùrachadh as ùire den gheama, gu ìre mhòr cha tèid an FPS thairis air 30, oir chan fhaigh sinn a-riamh barrachd air 30 ùrachadh gach diog bhon t-seirbheisiche. Fiù ma tha sinn a 'gairm render() 60 uair san diog, an uairsin bidh leth de na gairmean sin dìreach ag ath-tharraing an aon rud, gu ìre mhòr a’ dèanamh dad. Is e duilgheadas eile le buileachadh naive gu bheil e buailteach do dàil. Le astar eadar-lìn air leth math, gheibh an neach-dèiligidh ùrachadh geama dìreach gach 33ms (30 san diog):

A’ cruthachadh geama lìn ioma-chluicheadair .io
Gu mì-fhortanach, chan eil dad foirfe. Bhiodh dealbh nas reusanta:
A’ cruthachadh geama lìn ioma-chluicheadair .io
Is e buileachadh naive cha mhòr a’ chùis as miosa nuair a thig e gu latency. Ma gheibhear ùrachadh geama le dàil de 50ms, an uairsin stàilichean luchd-dèiligidh 50ms a bharrachd oir tha e fhathast a’ toirt seachad staid a ’gheama bhon ùrachadh roimhe. Faodaidh tu smaoineachadh cho mì-chofhurtail sa tha seo don chluicheadair: bheir breiceadh neo-riaghailteach air a’ gheama a bhith a’ faireachdainn borb agus neo-sheasmhach.

7.2 Staid teachdaiche nas fheàrr

Nì sinn beagan leasachaidhean air buileachadh naive. An toiseach, bidh sinn a 'cleachdadh dàil a' toirt seachad airson 100 ms. Tha seo a’ ciallachadh gum bi staid “gnàthach” an neach-dèiligidh an-còmhnaidh air dheireadh air staid a’ gheama air an fhrithealaiche le 100ms. Mar eisimpleir, ma tha an ùine air an fhrithealaiche 150, an uairsin bheir an neach-dèiligidh an staid anns an robh am frithealaiche aig an àm 50:

A’ cruthachadh geama lìn ioma-chluicheadair .io
Bheir seo bufair 100ms dhuinn gus a bhith beò ann an amannan ùrachadh geama nach gabh a thuigsinn:

A’ cruthachadh geama lìn ioma-chluicheadair .io
Bidh an tuarastal airson seo maireannach dàil a-steach airson 100 ms. Is e ìobairt bheag a tha seo airson cluiche rèidh - cha mhothaich a’ mhòr-chuid de chluicheadairean (gu sònraichte cluicheadairean casual) eadhon an dàil seo. Tha e tòrr nas fhasa do dhaoine atharrachadh gu latency seasmhach 100ms na tha e a bhith a’ cluich le latency nach gabh a thuigsinn.

Faodaidh sinn cuideachd innleachd eile ris an canar ro-innse taobh teachdaiche, a nì obair mhath ann a bhith a’ lughdachadh cianalas, ach nach tèid a chòmhdach san dreuchd seo.

Is e leasachadh eile a tha sinn a’ cleachdadh eadar-theangachadh sreathach. Mar thoradh air dàil tairgse, mar as trice bidh sinn co-dhiù aon ùrachadh air thoiseach air an àm a th’ ann an-dràsta sa neach-dèiligidh. Nuair a chaidh a ghairm getCurrentState(), is urrainn dhuinn a chur an gnìomh eadar-theangachadh sreathach eadar ùrachaidhean geama dìreach ro agus às deidh an ùine làithreach sa neach-dèiligidh:

A’ cruthachadh geama lìn ioma-chluicheadair .io
Fuasglaidh seo cùis ìre frèam: is urrainn dhuinn a-nis frèamaichean gun samhail a thoirt seachad aig ìre frèam sam bith a tha sinn ag iarraidh!

7.3 Cur an gnìomh staid teachdaiche leasaichte

Eisimpleir buileachadh ann an src/client/state.js a’ cleachdadh an dà chuid lag render agus eadar-fhilleadh sreathach, ach chan ann airson ùine mhòr. Bidh sinn a 'briseadh a' chòd gu dà phàirt. Seo a’ chiad fhear:

stàite.js pàirt 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;
}

Is e a’ chiad cheum faighinn a-mach dè currentServerTime(). Mar a chunnaic sinn na bu thràithe, tha clàr-ama frithealaiche aig a h-uile ùrachadh geama. Tha sinn airson latency render a chleachdadh gus an ìomhaigh a thoirt seachad 100ms air cùl an fhrithealaiche, ach cha bhi fios againn gu bràth air an àm làithreach air an fhrithealaiche, oir chan eil fios againn dè cho fada 'sa thug e airson gin de na h-ùrachaidhean fhaighinn thugainn. Tha an eadar-lìn neo-fhaicsinneach agus faodaidh an astar aige atharrachadh gu mòr!

Gus faighinn timcheall air an duilgheadas seo, is urrainn dhuinn tuairmse reusanta a chleachdadh: sinn leig ort gun tàinig a’ chiad ùrachadh sa bhad. Nam biodh seo fìor, bhiodh fios againn air àm an fhrithealaiche aig an àm shònraichte seo! Bidh sinn a’ stòradh clàr-ama an fhrithealaiche a-staigh firstServerTimestamp agus cùm ar ionadail (cliant) stampa-ama aig an aon àm a-steach gameStart.

O fuirich. Nach bu chòir dha a bhith mar àm an fhrithealaiche = àm teachdaiche? Carson a nì sinn eadar-dhealachadh eadar “stampa-ama an fhrithealaiche” agus “stampa-ama teachdaiche”? Is e ceist air leth a tha seo! Tha e a 'tionndadh a-mach nach eil iad an aon rud. Date.now() tillidh e diofar chlàran-ama anns an neach-dèiligidh agus frithealaiche, agus tha e an urra ri feartan ionadail dha na h-innealan sin. Na gabh a-steach gu bràth gum bi clàran-ama an aon rud air a h-uile inneal.

A-nis tha sinn a 'tuigsinn dè a nì currentServerTime(): tillidh e clàr-ama an fhrithealaiche den ùine render làithreach. Ann am faclan eile, seo an t-àm aig an fhrithealaiche (firstServerTimestamp <+ (Date.now() - gameStart)) às aonais dàil render (RENDER_DELAY).

A-nis leig dhuinn sùil a thoirt air mar a làimhsicheas sinn ùrachaidhean geama. Nuair a gheibhear e bhon t-seirbheisiche ùrachaidh, canar ris processGameUpdate()agus sàbhailidh sinn an ùrachadh ùr gu raon gameUpdates. An uairsin, gus sùil a thoirt air cleachdadh na cuimhne, bheir sinn air falbh na seann ùrachaidhean roimhe ùrachadh bunaiteachoir chan eil feum againn orra tuilleadh.

Dè a th’ ann an “ùrachadh bunaiteach”? Seo a’ chiad ùrachadh a lorgas sinn le bhith a’ gluasad air ais bho àm làithreach an fhrithealaiche. Cuimhnich an diagram seo?

A’ cruthachadh geama lìn ioma-chluicheadair .io
Is e an ùrachadh geama dìreach air taobh clì "Cliant Render Time" an ùrachadh bunaiteach.

Carson a thathas a’ cleachdadh an ùrachadh bunaiteach? Carson as urrainn dhuinn ùrachaidhean a leigeil sìos chun bhun-loidhne? Gus seo a thuigsinn, leig dhuinn mu dheireadh beachdachadh air buileachadh getCurrentState():

stàite.js pàirt 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),
    };
  }
}

Bidh sinn a’ làimhseachadh trì cùisean:

  1. base < 0 a’ ciallachadh nach eil ùrachadh sam bith ann gus an àm tairgse gnàthach (faic buileachadh gu h-àrd getBaseUpdate()). Faodaidh seo tachairt dìreach aig toiseach a’ gheama air sgàth dàil render. Anns a 'chùis seo, bidh sinn a' cleachdadh an ùrachadh as ùire a gheibhear.
  2. base an ùrachadh as ùire a th’ againn. Dh’ fhaodadh seo a bhith mar thoradh air dàil lìonraidh no droch cheangal eadar-lìn. Anns a 'chùis seo, tha sinn cuideachd a' cleachdadh an ùrachaidh as ùire a th 'againn.
  3. Tha ùrachadh againn an dà chuid ro agus às deidh na h-ùine tairgse gnàthach, gus an urrainn dhuinn eadar-phearsanta!

Na tha air fhàgail a-staigh state.js a tha na bhuileachadh de eadar-fhilleadh sreathach a tha sìmplidh (ach dòrainneach). Ma tha thu airson a sgrùdadh leat fhèin, fosgail e state.js air GitHub.

Pàirt 2. Backend Server

Anns a’ phàirt seo, bheir sinn sùil air backend Node.js a bhios a’ cumail smachd air ar .io eisimpleir cluiche.

1. Puing inntrigidh an fhrithealaiche

Gus am frithealaiche lìn a riaghladh, cleachdaidh sinn frèam lìn mòr-chòrdte airson Node.js ris an canar cur an cèill. Bidh e air a rèiteachadh le faidhle puing inntrigidh an fhrithealaiche againn src/server/server.js:

server.js pàirt 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}`);

Cuimhnich gun do bhruidhinn sinn sa chiad phàirt air Webpack? Seo far am bi sinn a’ cleachdadh ar rèiteachadh Webpack. Cleachdaidh sinn iad ann an dà dhòigh:

  • Airson a chleachdadh webpack-dev-meadhan-bathair gus na pasganan leasachaidh againn ath-thogail gu fèin-ghluasadach, no
  • pasgan gluasad gu statach dist/, anns am bi Webpack a’ sgrìobhadh na faidhlichean againn às deidh an togail riochdachaidh.

Obair chudromach eile server.js is e am frithealaiche a stèidheachadh socaid.ioa tha dìreach a 'ceangal ris an fhrithealaiche Express:

server.js pàirt 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);
});

Às deidh dhuinn ceangal socket.io a stèidheachadh gu soirbheachail ris an fhrithealaiche, stèidhich sinn làimhseachadh tachartais airson an t-socaid ùr. Bidh luchd-làimhseachaidh thachartasan a’ làimhseachadh teachdaireachdan a gheibhear bho luchd-dèiligidh le bhith gan tiomnadh gu nì singilte game:

server.js pàirt 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);
}

Tha sinn a’ cruthachadh geama .io, agus mar sin chan fheum sinn ach aon leth-bhreac Game ("Gèam") - bidh na cluicheadairean uile a 'cluich san aon raon! Anns an ath earrann, chì sinn mar a tha an clas seo ag obair. Game.

2. Cluicheadairean frithealaichean

Clas Game anns a bheil an loidsig as cudromaiche air taobh an fhrithealaiche. Tha dà phrìomh obair aige: stiùireadh cluicheadair и atharrais cluiche.

Feuch an tòisich sinn leis a’ chiad obair, riaghladh chluicheadairean.

geama.js pàirt 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);
    }
  }

  // ...
}

Anns a’ gheama seo, aithnichidh sinn na cluicheadairean leis an raon id an socaid socket.io aca (ma gheibh thu troimh-chèile, an uairsin till air ais gu server.js). Bidh Socket.io fhèin a’ sònrachadh gach socaid sònraichte idmar sin chan fheum sinn a bhith draghail mu dheidhinn sin. Canaidh mi e ID cluicheadair.

Le sin san amharc, leig dhuinn sgrùdadh a dhèanamh air caochladairean eisimpleir ann an clas Game:

  • sockets na nì a cheanglas ID a’ chluicheadair ris an t-socaid a tha co-cheangailte ris a’ chluicheadair. Leigidh e leinn faighinn gu socaidean leis na IDan cluicheadair aca ann an ùine chunbhalach.
  • players na nì a cheanglas ID a’ chluicheadair ris a’ chòd> nì cluicheadair

bullets tha e na raon de nithean Bullet, aig nach eil òrdugh cinnteach.
lastUpdateTime is e seo an clàr-ama den turas mu dheireadh a chaidh an geama ùrachadh. Chì sinn mar a thèid a chleachdadh a dh’ aithghearr.
shouldSendUpdate tha e na chaochladair cuideachail. Chì sinn a chleachdadh a dh’ aithghearr cuideachd.
Dòighean addPlayer(), removePlayer() и handleInput() chan eil feum air mìneachadh, tha iad air an cleachdadh ann an server.js. Ma dh'fheumas tu do chuimhne ùrachadh, till air ais beagan nas àirde.

An loidhne mu dheireadh constructor() a ’tòiseachadh cearcall ùrachadh geamannan (le tricead 60 ùrachadh / s):

geama.js pàirt 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;
    }
  }

  // ...
}

Modh update() anns a bheil 's dòcha am pìos as cudromaiche de loidsig taobh an fhrithealaiche. Seo na nì e, ann an òrdugh:

  1. Obraich a-mach dè cho fada dt air a dhol seachad bhon fhear mu dheireadh update().
  2. Ag ùrachadh gach projectile agus gan sgrios ma tha sin riatanach. Chì sinn buileachadh a’ ghnìomhachd seo nas fhaide air adhart. Airson a-nis, tha e gu leòr dhuinn fios a bhith againn air sin bullet.update() a' tilleadh truema bu chòir an projectile a sgrios (chaidh e a-mach às an raon).
  3. Ag ùrachadh gach cluicheadair agus a’ sìolachadh projectile ma tha sin riatanach. Chì sinn am buileachadh seo nas fhaide air adhart cuideachd - player.update() urrainn nì a thilleadh Bullet.
  4. Sgrùdaidhean airson tubaistean eadar projectiles agus cluicheadairean le applyCollisions(), a thilleas sreath de projectiles a bhuaileas cluicheadairean. Airson gach projectile a thilleas, bidh sinn ag àrdachadh puingean a’ chluicheadair a loisg e (a’ cleachdadh player.onDealtDamage()) agus an uairsin thoir air falbh am projectile bhon raon bullets.
  5. A’ cur fios agus a’ sgrios a h-uile cluicheadair a chaidh a mharbhadh.
  6. Luchdaich a-nuas an cluicheadair airson a h-uile geama gach diog amannan nuair a thèid an gairm update(). Cuidichidh seo sinn le bhith a’ cumail sùil air a’ chaochladair cuideachaidh a chaidh ainmeachadh gu h-àrd. shouldSendUpdate. Air sgàth update() ris an canar 60 uair / s, bidh sinn a’ cur ùrachaidhean geama 30 uair / s. Mar sin, tricead cloc is e gleoc an fhrithealaiche 30 cloc / s (bhruidhinn sinn mu ìrean gleoc sa chiad phàirt).

Carson a luchdadh a-nuas geamannan a-mhàin tro thìde ? Gus sianal a shàbhaladh. Tha 30 ùrachadh geama gach diog tòrr!

Carson nach cuir thu fòn gu dìreach update() 30 uair san diog? Gus atharrais nan geamannan a leasachadh. Mar as trice canar update(), mar as cinntiche a bhios an atharrais geama. Ach na gabh cus air falbh leis an àireamh de dhùbhlain. update(), oir is e obair coimpiutaireachd daor a tha seo - tha 60 gach diog gu leòr.

An còrr den chlas Game air a dhèanamh suas de dhòighean cuideachaidh a thathar a’ cleachdadh ann an update():

geama.js pàirt 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() gu math sìmplidh - bidh e a’ seòrsachadh nan cluicheadairean a rèir sgòr, a’ toirt na còig as àirde, agus a’ tilleadh an ainm-cleachdaidh agus an sgòr airson gach fear.

createUpdate() cleachdadh ann an update() gus ùrachaidhean geama a chruthachadh a thèid a sgaoileadh gu cluicheadairean. Is e a phrìomh obair dòighean a ghairm serializeForUpdate()air a chur an gnìomh airson clasaichean Player и Bullet. Thoir an aire nach toir e ach dàta gu gach cluicheadair mu dheidhinn as fhaisge cluicheadairean agus projectiles - chan fheumar fiosrachadh a chuir a-mach mu nithean geama a tha fada bhon chluicheadair!

3. Rudan geama air an fhrithealaiche

Anns a’ gheama againn, tha projectiles agus cluicheadairean glè choltach ri chèile: tha iad nan nithean geama eas-chruthach, cruinn, gluasadach. Gus brath a ghabhail air an coltas seo eadar cluicheadairean agus projectiles, tòisichidh sinn le bhith a 'cur an gnìomh a' chlas bunaiteach Object:

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

Chan eil dad iom-fhillte a’ dol air adhart an seo. Bidh an clas seo na dheagh àite acair airson an leudachadh. Chì sinn mar a tha an clas Bullet cleachdaidhean Object:

peileir.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 glè ghoirid! Tha sinn air cur ris Object dìreach na leudachain a leanas:

  • A 'cleachdadh pasgan goirid airson ginealach air thuaiream id tilgeadh.
  • A 'cur raon ris parentIDgus an urrainn dhut sùil a chumail air a’ chluicheadair a chruthaich am projectile seo.
  • A’ cur luach tilleadh ri update(), a tha co-ionann ri truema tha an projectile taobh a-muigh an raon (cuimhnich gun do bhruidhinn sinn mu dheidhinn seo san earrann mu dheireadh?).

Gluaisidh sinn air adhart gu Player:

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

Tha cluicheadairean nas iom-fhillte na projectiles, agus mar sin bu chòir beagan raointean a bharrachd a stòradh sa chlas seo. An dòigh-obrach aige update() a’ dèanamh tòrr obrach, gu sònraichte, a’ tilleadh an projectile a chaidh a chruthachadh às ùr mura h-eil gin air fhàgail fireCooldown (cuimhnich gun do bhruidhinn sinn mu dheidhinn seo san earrann roimhe seo?). Tha e cuideachd a 'leudachadh an dòigh-obrach serializeForUpdate(), oir feumaidh sinn raointean a bharrachd a thoirt a-steach don chluicheadair san ùrachadh geama.

A bhith aig bun-chlas Object - ceum cudromach gus còd ath-aithris a sheachnadh. Mar eisimpleir, chan eil clas ann Object feumaidh an aon bhuileachadh a bhith aig gach nì geama distanceTo(), agus bhiodh e na throm-laighe a bhith a’ dèanamh lethbhreac de na buileachadh sin thairis air grunn fhaidhlichean. Bidh seo gu sònraichte cudromach airson pròiseactan mòra.nuair a tha an àireamh de leudachadh Object tha clasaichean a’ fàs.

4. Lorgaidh tubaist

Is e an aon rud a tha air fhàgail dhuinn aithneachadh nuair a bhuail na projectiles na cluicheadairean! Cuimhnich am pìos còd seo bhon dòigh update() anns a' chlas Game:

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

    // ...
  }
}

Feumaidh sinn an dòigh-obrach a chur an gnìomh applyCollisions(), a thilleas a h-uile projectiles a bhuail cluicheadairean. Gu fortanach, chan eil e cho doirbh sin a dhèanamh oir

  • Is e cearcallan a th’ anns a h-uile nì a tha a’ bualadh, is e sin an cumadh as sìmplidh airson lorg thubaistean a chuir an gnìomh.
  • Tha dòigh-obrach againn mu thràth distanceTo(), a chuir sinn an gnìomh anns an earrann roimhe seo sa chlas Object.

Seo cò ris a tha ar buileachadh air lorg thubaistean coltach:

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

Tha an lorg tubaist sìmplidh seo stèidhichte air an fhìrinn gu bheil buailidh dà chearcall ma tha an astar eadar na h-ionadan aca nas lugha na suim an radii. Seo a’ chùis far a bheil an astar eadar ionadan dà chearcall dìreach co-ionann ri suim nan radii aca:

A’ cruthachadh geama lìn ioma-chluicheadair .io
Tha dà thaobh eile ri beachdachadh an seo:

  • Chan fhaod am pròiseict bualadh air a’ chluicheadair a chruthaich e. Faodar seo a choileanadh le bhith a 'dèanamh coimeas bullet.parentID с player.id.
  • Chan fhaod am projectile bualadh ach aon turas ann an cùis cuingealaichte de dh'iomadh cluicheadair a' bualadh aig an aon àm. Fuasglaidh sinn an duilgheadas seo a 'cleachdadh a' ghnìomhaiche break: cho luath ‘s a lorgar an cluicheadair a tha a’ bualadh leis an projectile, stadaidh sinn an rannsachadh agus gluaisidh sinn air adhart chun ath projectile.

An deireadh

Sin e! Tha sinn air a h-uile dad a dh’ fheumas tu a bhith eòlach air gus geama lìn .io a chruthachadh. Dè an ath rud? Tog do gheama .io fhèin!

Tha a h-uile còd sampall fosgailte agus air a phostadh air GitHub.

Source: www.habr.com

Cuir beachd ann