Cluiche Gréasáin Il-imreora .io a Chruthú

Cluiche Gréasáin Il-imreora .io a Chruthú
Foilsithe i 2015 Agar.io tháinig an progenitor de seánra nua cluichí.io, a bhfuil a tóir tar éis fás go mór ó shin i leith. Tá taithí agam ar an méadú ar an éileamh atá ar chluichí .io mé féin: le trí bliana anuas, I chruthaigh agus a dhíol dhá chluiche sa seánra..

Ar eagla nár chuala tú trácht ar na cluichí seo riamh, is cluichí il-imreora saor in aisce iad atá éasca le himirt (níl aon chuntas ag teastáil). De ghnáth cuireann siad go leor imreoirí freasúracha in aon réimse amháin. Cluichí cáiliúla eile .io: Slither.io и Diep.io.

Sa phost seo déanfaimid amach conas cruthaigh cluiche .io ón tús. Chun seo a dhéanamh, ní leor ach eolas ar Javascript: ní mór duit rudaí mar chomhréir a thuiscint ES6, eochairfhocal this и gealltanais. Fiú mura bhfuil Javascript ar eolas agat go foirfe, is féidir leat an chuid is mó den phostáil a thuiscint go fóill.

Sampla de chluiche .io

Maidir le cúnamh oiliúna déanfaimid tagairt cluiche samplach .io. Déan iarracht é a imirt!

Cluiche Gréasáin Il-imreora .io a Chruthú
Tá an cluiche simplí go leor: rialaíonn tú long i réimse le himreoirí eile. Lasann do long teilgeáin go huathoibríoch agus déanann tú iarracht imreoirí eile a bhualadh agus a gcuid teilgeáin á seachaint.

1. Forbhreathnú gairid/struchtúr tionscadail

Molaim Íoslódáil an cód foinse cluiche sampla ionas gur féidir leat mé a leanúint.

Úsáideann an sampla seo a leanas:

  • Express Is é an creat gréasáin is mó tóir do Node.js a bhainistíonn freastalaí gréasáin an chluiche.
  • soicéad.io — leabharlann soicéad gréasáin chun sonraí a mhalartú idir an brabhsálaí agus an freastalaí.
  • Webpack - bainisteoir modúl. Is féidir leat léamh faoi na fáthanna le Webpack a úsáid anseo.

Seo an chuma atá ar struchtúr eolaire an tionscadail:

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

poiblí/

Tá gach rud san fhillteán public/ a tharchur go statach ag an bhfreastalaí. IN public/assets/ ina bhfuil íomhánna a úsáideann ár dtionscadal.

src /

Tá gach cód foinse san fhillteán src/. Teidil client/ и server/ labhairt ar a son féin agus shared/ ina bhfuil comhad tairisigh a allmhairíonn an cliant agus an freastalaí araon.

2. Paraiméadair tionóil/tionscadal

Mar a dúradh thuas, úsáidimid bainisteoir modúil chun an tionscadal a thógáil Webpack. Breathnaímid ar ár gcumraíocht Webpack:

webpack.common.js:

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  entry: {
    game: './src/client/index.js',
  },
  output: {
    filename: '[name].[contenthash].js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
        test: /.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            presets: ['@babel/preset-env'],
          },
        },
      },
      {
        test: /.css$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
          },
          'css-loader',
        ],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash].css',
    }),
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'src/client/html/index.html',
    }),
  ],
};

Is iad seo a leanas na línte is tábhachtaí anseo:

  • src/client/index.js Is é pointe iontrála an chliaint Javascript (JS). Tosóidh Webpack as seo agus lorgóidh sé go hathchúrsach comhaid iompórtáilte eile.
  • Beidh an t-aschur JS ónár dtógáil Webpack le fáil san eolaire dist/. Glaofaidh mé ar an gcomhad seo JS pacáiste.
  • Úsáidimid Babel, agus go háirithe an chumraíocht @babel/preset-env chun ár gcód JS do bhrabhsálaithe níos sine a thrasuíomh.
  • Bainimid úsáid as breiseán chun an CSS go léir dá dtagraítear ag comhaid JS a bhaint as agus le chéile in aon áit amháin iad. Glaofaidh mé orainne é Pacáiste css.

Seans gur thug tú faoi deara ainmneacha comhaid pacáiste aisteach '[name].[contenthash].ext'. Go bhfuil iontu ionadú ainm comhaid Pacáiste Gréasáin: [name] cuirfear ainm an phointe ionchuir in ionad (is é ár gcás é game), agus [contenthash] cuirfear hash d'ábhar an chomhaid ina ionad. Déanaimid é seo chun bharrfheabhsú an tionscadail le haghaidh hashing - is féidir linn a rá le brabhsálaithe ár bpacáistí JS a thaisceadh ar feadh tréimhse éiginnte toisc má athraíonn pacáiste, athraíonn ainm an chomhaid freisin (athruithe contenthash). Is é an toradh críochnaithe ainm comhaid an amhairc game.dbeee76e91a97d0c7207.js.

comhad webpack.common.js - Seo é an bunchomhad cumraíochta a iompórtálann muid isteach i bhfoirmíochtaí forbartha agus críochnaithe an tionscadail. Mar shampla, seo an chumraíocht forbartha:

pacáiste gréasáin.dev.js

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

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

Ar mhaithe le héifeachtacht, úsáidimid sa phróiseas forbartha webpack.dev.js, agus lasca go webpack.prod.js, chun méideanna pacáiste a bharrfheabhsú nuair a imscartar chuig táirgeadh.

Socrú áitiúil

Molaim an tionscadal a shuiteáil ar do mheaisín áitiúil ionas gur féidir leat na céimeanna atá liostaithe sa phost seo a leanúint. Socrú atá simplí: an chéad, ní mór an córas a bheith Nód и NPM. Next ní mór duit a dhéanamh

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

agus tá tú réidh le dul! Chun an freastalaí forbartha a thosú, níl le déanamh ach rith

$ npm run develop

agus téigh go dtí do bhrabhsálaí gréasáin localhost: 3000. Déanfaidh an freastalaí forbartha na pacáistí JS agus CSS a atógáil go huathoibríoch de réir mar a tharlaíonn athruithe cód - níl le déanamh ach an leathanach a athnuachan chun na hathruithe go léir a fheiceáil!

3. Pointí iontrála cliant

A ligean ar a fháil síos go dtí an cód cluiche féin. Ar dtús ní mór dúinn leathanach index.html, nuair a thugann tú cuairt ar an suíomh, luchtóidh an brabhsálaí ar dtús é. Beidh ár leathanach simplí go leor:

index.html

An example .io cluiche  IMIR

Tá an sampla cód seo simplithe beagán ar mhaithe le soiléireacht, agus déanfaidh mé an rud céanna le go leor de na samplaí eile sa phost. Is féidir leat breathnú i gcónaí ar an gcód iomlán ag Github.

Tá:

  • HTML5 eilimint chanbhás (<canvas>), a úsáidfimid chun an cluiche a sholáthar.
  • <link> chun ár bpacáiste CSS a chur leis.
  • <script> chun ár bpacáiste Javascript a chur leis.
  • Príomh-roghchlár le hainm úsáideora <input> agus an cnaipe “PLAY” (<button>).

Nuair a bheidh an leathanach baile lódáilte, tosóidh an brabhsálaí cód Javascript a fhorghníomhú, ag tosú leis an gcomhad pointe iontrála JS: src/client/index.js.

innéacs.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);
  };
});

Seans go bhfuil cuma casta air seo, ach i ndáiríre níl mórán ar siúl anseo:

  1. Iompórtáil roinnt comhad JS eile.
  2. Iompórtáil CSS (mar sin tá a fhios ag Webpack iad a áireamh inár bpacáiste CSS).
  3. Seoladh connect() chun nasc leis an bhfreastalaí a bhunú agus a thosú downloadAssets() Íoslódáil na híomhánna is gá chun an cluiche.
  4. Tar éis céim 3 a chríochnú taispeántar an príomhroghchlár (playMenu).
  5. Socrú an láimhseálaí cliceáil ar an gcnaipe “PLAY”. Nuair a bhrúitear an cnaipe, cuireann an cód tús leis an gcluiche agus insíonn sé don fhreastalaí go bhfuil muid réidh le himirt.

Tá príomh “feoil” loighic ár gcliant-fhreastalaí sna comhaid sin a d’allmhairigh an comhad index.js. Anois féachfaimid orthu go léir in ord.

4. Malartú sonraí cliant

Sa chluiche seo úsáidimid leabharlann-aitheanta go maith chun cumarsáid a dhéanamh leis an bhfreastalaí soicéad.io. Tá tacaíocht ionsuite ag Socket.io WebSockets, atá feiliúnach go maith le haghaidh cumarsáide déthreo: is féidir linn teachtaireachtaí a sheoladh chuig an bhfreastalaí и is féidir leis an bhfreastalaí teachtaireachtaí a sheoladh chugainn thar an nasc céanna.

Beidh comhad amháin againn src/client/networking.jsa thabharfaidh aire gach duine cumarsáid leis an bhfreastalaí:

líonrú.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);
};

Déantar an cód seo a ghiorrú beagán freisin ar mhaithe le soiléireacht.

Tá trí phríomhrud ag tarlú sa chomhad seo:

  • Táimid ag iarraidh ceangal leis an bhfreastalaí. connectedPromise ní cheadaítear ach amháin nuair a bheidh nasc bunaithe againn.
  • Má éiríonn leis an gceangal, cláróimid feidhmeanna aisghlao (processGameUpdate() и onGameOver()) le haghaidh teachtaireachtaí a d’fhéadfaimis a fháil ón bhfreastalaí.
  • Onnmhairímid play() и updateDirection()ionas gur féidir comhaid eile a úsáid.

5. Rindreáil cliant

Tá sé in am an pictiúr a thaispeáint ar an scáileán!

...ach sular féidir linn é seo a dhéanamh, caithfimid na híomhánna go léir (acmhainní) atá ag teastáil chuige seo a íoslódáil. Scríobhaimis bainisteoir acmhainní:

sócmhainní.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];

Níl bainistíocht acmhainní chomh deacair sin a chur i bhfeidhm! Is é an príomhphointe réad a stóráil assets, a cheangail eochair ainm an chomhaid le luach an réad Image. Nuair a bhíonn an acmhainn luchtaithe, ní mór dúinn é a shábháil ar rud assets le haghaidh admháil tapa sa todhchaí. Cathain a cheadófar gach acmhainn ar leith a íoslódáil (is é sin, íoslódálfar go léir acmhainní), ceadaímid downloadPromise.

Tar éis duit na hacmhainní a íoslódáil, is féidir leat tosú ag rindreáil. Mar a dúradh níos luaithe, chun tarraingt ar leathanach gréasáin a úsáidimid Canbhás HTML5 (<canvas>). Tá ár gcluiche simplí go leor, mar sin ní gá dúinn ach na rudaí seo a leanas a sholáthar:

  1. Cúlra
  2. Long imreoir
  3. Imreoirí eile sa chluiche
  4. sliogáin

Seo na gearrthóga tábhachtacha src/client/render.js, a tharraingíonn go díreach na ceithre phointe atá liostaithe thuas:

rindreáil.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);
}

Giorraítear an cód seo freisin ar mhaithe le soiléireacht.

render() príomhfheidhm an chomhaid seo. startRendering() и stopRendering() gníomhachtú an timthrialla rindreála a rialú ag 60 FPS.

Feidhmeanna sonracha cúntóirí rindreála a chur i bhfeidhm ar leith (mar shampla renderBullet()) nach bhfuil chomh tábhachtach sin, ach seo sampla simplí amháin:

rindreáil.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,
  );
}

Tabhair faoi deara go bhfuil muid ag baint úsáide as an modh getAsset(), a chonacthas roimhe seo i asset.js!

Más spéis leat feidhmeanna cúntóra rindreála eile a fhiosrú, léigh an chuid eile de src/client/render.js.

6. Ionchur cliant

Tá sé in am cluiche a dhéanamh in imirt! Beidh an scéim rialaithe an-simplí: chun treo na gluaiseachta a athrú, is féidir leat an luch a úsáid (ar ríomhaire) nó teagmháil a dhéanamh leis an scáileán (ar ghléas soghluaiste). Chun é seo a chur i bhfeidhm cláróimid Éisteoirí Imeachtaí le haghaidh imeachtaí Luiche agus Tadhaill.
An mbeidh cúram a ghlacadh de seo go léir src/client/input.js:

ionchur.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() is éisteoirí Imeachtaí a ghlaonn updateDirection()networking.js) nuair a tharlaíonn teagmhas ionchuir (mar shampla, nuair a bhogtar an luch). updateDirection() déileálann sé le malartú teachtaireachtaí leis an bhfreastalaí, a phróiseálann an t-imeacht ionchuir agus a nuashonraíonn staid an chluiche dá réir.

7. Stádas cliaint

Tá an chuid seo ar an gceann is deacra sa chéad chuid den phost. Ná bíodh leisc ort mura dtuigeann tú é an chéad uair a léann tú é! Is féidir leat é a scipeáil fiú agus teacht ar ais chuige níos déanaí.

Is é an píosa deireanach den bhfreagra is gá chun an cód cliant-freastalaí a chomhlánú Bhí. An cuimhin leat an mhír chóid ón rannán Rindreála Cliant?

rindreáil.js

import { getCurrentState } from './state';

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

  // Do the rendering
  // ...
}

getCurrentState() Ba chóir go mbeadh siad in ann a chur ar fáil dúinn leis an staid cluiche reatha sa chliant am ar bith bunaithe ar nuashonruithe a fuarthas ón bhfreastalaí. Seo sampla de nuashonrú cluiche a d’fhéadfadh an freastalaí a sheoladh:

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

Tá cúig réimse chomhionanna i ngach nuashonrú cluiche:

  • t: Stampa ama an fhreastalaí ag cur in iúl cathain a cruthaíodh an nuashonrú seo.
  • me: Eolas faoin imreoir a fhaigheann an nuashonrú seo.
  • daoine eile: Sraith faisnéise faoi imreoirí eile a ghlacann páirt sa chluiche céanna.
  • urchair: sraith faisnéise faoi teilgeáin sa chluiche.
  • bord ceannais: Sonraí clár ceannairí reatha. Ní chuirfimid iad san áireamh sa phost seo.

7.1 Staid naive an chliaint

Cur i bhfeidhm naive getCurrentState() ní féidir ach sonraí a thabhairt ar ais go díreach ón nuashonrú cluiche is déanaí a fuarthas.

naive-stáit.js

let lastGameUpdate = null;

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

export function getCurrentState() {
  return lastGameUpdate;
}

Álainn agus soiléir! Ach más rud é amháin go raibh sé chomh simplí sin. Ceann de na cúiseanna atá leis an gcur i bhfeidhm seo: cuireann sé teorainn leis an ráta fráma rindreála go luas clog an fhreastalaí.

Ráta fhráma: líon na bhfrámaí (i.e. glaonna render()) in aghaidh an tsoicind, nó CCT. De ghnáth déanann cluichí a ndícheall 60 CCT ar a laghad a bhaint amach.

Ráta Tic: A mhinice a sheolann an freastalaí nuashonruithe cluiche chuig cliaint. Is minic a bhíonn sé níos ísle ná an ráta fráma. In ár gcluiche, ritheann an freastalaí ag 30 tic in aghaidh an tsoicind.

Mura ndéanaimid ach an nuashonrú cluiche is déanaí, go bunúsach ní bheidh an FPS in ann dul thar 30 mar gheall ar ní fhaighimid níos mó ná 30 nuashonrú in aghaidh an tsoicind ón bhfreastalaí. Fiú má ghlaoimid render() 60 uair in aghaidh an tsoicind, ansin ní dhéanfaidh leath de na glaonna seo ach an rud céanna a tharraingt siar, gan aon rud a dhéanamh go bunúsach. Fadhb eile le cur i bhfeidhm naive go bhfuil sé faoi ​​réir moille. Ag luas idéalach Idirlín, gheobhaidh an cliant nuashonrú cluiche go díreach gach 33 ms (30 in aghaidh an tsoicind):

Cluiche Gréasáin Il-imreora .io a Chruthú
Ar an drochuair, níl aon rud foirfe. Pictiúr níos réadúla a bheadh ​​ann:
Cluiche Gréasáin Il-imreora .io a Chruthú
Is é cur i bhfeidhm naive an cás is measa go leor maidir le foighne. Má fhaightear nuashonrú cluiche le moill 50ms, ansin tá an cliant moill síos le 50ms breise toisc go bhfuil sé fós ag déanamh staid an chluiche ón nuashonrú roimhe seo. Is féidir leat a shamhlú cé chomh deacair is atá sé seo don imreoir: mar gheall ar mhoilliú treallach, beidh cuma jerky agus éagobhsaí ar an gcluiche.

7.2 Staid cliaint feabhsaithe

Déanfaimid roinnt feabhsuithe ar an gcur i bhfeidhm naive. Ar dtús, úsáidimid moill rindreáil faoi ​​100 ms. Ciallaíonn sé seo go mbeidh staid "reatha" an chliaint i gcónaí 100ms taobh thiar den stát cluiche ar an bhfreastalaí. Mar shampla, má tá an t-am freastalaí 150, ansin déanfaidh an cliant an staid ina raibh an freastalaí ag an am 50:

Cluiche Gréasáin Il-imreora .io a Chruthú
Tugann sé seo maolán 100ms dúinn chun teacht slán ó uainiú dothuartha nuashonruithe cluiche:

Cluiche Gréasáin Il-imreora .io a Chruthú
Beidh an praghas le haghaidh seo buan moill ionchuir faoi ​​100 ms. Is mion-íobairt é seo le haghaidh gameplay mín - ní thabharfaidh formhór na n-imreoirí (go háirithe cinn ócáideacha) an mhoill seo faoi deara fiú. Tá sé i bhfad níos éasca do dhaoine dul i dtaithí ar fhola tairiseach 100ms ná imirt le foighne nach féidir a thuar.

Is féidir linn teicníc eile ar a dtugtar "réamhaisnéis ar thaobh an chliaint", a dhéanann jab maith chun latency braite a laghdú, ach ní phléifear sa phost seo é.

Feabhsú eile a úsáidimid ná idirshuíomh líneach. De bharr moille rindreála, is gnách go mbíonn nuashonrú amháin ar a laghad againn roimh an am reatha sa chliant. Nuair a ghlaoitear air getCurrentState(), is féidir linn a chomhlíonadh idirshuíomh líneach idir nuashonruithe cluiche díreach roimh agus tar éis an t-am reatha sa chliant:

Cluiche Gréasáin Il-imreora .io a Chruthú
Réitíonn sé seo fadhb an fhráma-ráta: is féidir linn frámaí uathúla a sholáthar anois ag aon ráta fráma a theastaíonn uainn!

7.3 Staid cliaint feabhsaithe a chur i bhfeidhm

Sampla cur i bhfeidhm i src/client/state.js úsáideann an dá mhoill rindreála agus idirshuíomh líneach, ach ní mhaireann sé seo fada. Déanaimis an cód a bhriseadh ina dhá chuid. Seo an chéad cheann:

stát.js, cuid 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 é an chéad rud is gá duit a dhéanamh a dhéanamh amach cad a dhéanann sé currentServerTime(). Mar a chonaic muid níos luaithe, cuimsíonn gach nuashonrú cluiche stampa ama freastalaí. Ba mhaith linn latency rindreála a úsáid chun an íomhá a sholáthar 100ms taobh thiar den fhreastalaí, ach ní bheidh a fhios againn go deo an t-am reatha ar an bhfreastalaí, mar níl a fhios againn cé chomh fada agus a thóg sé ar aon cheann de na nuashonruithe a bhaint amach. Ní féidir an tIdirlíon a thuar agus féadann a luas athrú go mór!

Chun teacht timpeall ar an bhfadhb seo, is féidir linn comhfhogasú réasúnta a úsáid: táimid ligean orainn gur tháinig an chéad nuashonrú láithreach. Dá mbeadh sé seo fíor, bheadh ​​a fhios againn am an fhreastalaí ag an nóiméad áirithe sin! Stórálaimid stampa ama an fhreastalaí isteach firstServerTimestamp agus a shábháil ar ár áitiúil (cliant) stampa ama ag an nóiméad céanna i gameStart.

Ó, fan nóiméad. Nár cheart go mbeadh am ar an bhfreastalaí = am ar an gcliant? Cén fáth a ndéanaimid idirdhealú idir "stampa ama an fhreastalaí" agus "stampa ama an chliaint"? Is ceist iontach í seo! Tharlaíonn sé go raibh nach iad seo an rud céanna. Date.now() seolfar stampaí ama éagsúla ar ais sa chliant agus sa fhreastalaí agus braitheann sé seo ar fhachtóirí a bhaineann leis na meaisíní seo. Ná glac leis go mbeidh na stampaí ama mar a chéile ar gach meaisín.

Anois tuigimid cad a dhéanann sé currentServerTime(): filleann sé stampa ama an fhreastalaí ar an am rindreála reatha. I bhfocail eile, seo é an t-am freastalaí reatha (firstServerTimestamp <+ (Date.now() - gameStart)) lúide mhoill rindreála (RENDER_DELAY).

Anois, déanaimis féachaint ar conas a láimhseáilimid nuashonruithe cluiche. Nuair a fhaightear nuashonrú ón bhfreastalaí, glaoitear é processGameUpdate(), agus sábhálaimid an nuashonrú nua in eagar gameUpdates. Ansin, chun úsáid chuimhne a sheiceáil, bainimid na sean-nuashonraithe go léir nuashonrú bonnmar níl siad de dhíth orainn níos mó.

Cad is “croí-nuashonrú” ann? seo an chéad nuashonrú a aimsímid trí bhogadh ar gcúl ón am freastalaí reatha. Cuimhnigh an léaráid seo?

Cluiche Gréasáin Il-imreora .io a Chruthú
Is é an nuashonrú cluiche díreach ar an taobh clé de "Am Rindreála Cliant" an nuashonrú bonn.

Cad a úsáidtear an nuashonrú bonn dó? Cén fáth gur féidir linn nuashonruithe a scaoileadh chuig an mbonn? Chun é seo a thuiscint, déanaimis faoi ​​dheireadh a ligean ar breathnú ar a chur i bhfeidhm getCurrentState():

stát.js, cuid 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),
    };
  }
}

Láimhseálaimid trí chás:

  1. base < 0 Ciallaíonn sé seo nach bhfuil aon nuashonruithe ann go dtí an t-am rindreála reatha (féach an cur i bhfeidhm thuas getBaseUpdate()). Féadfaidh sé seo tarlú díreach ag tús an chluiche mar gheall ar aga moille rindreála. Sa chás seo, úsáidimid an nuashonrú is déanaí a fuarthas.
  2. base an nuashonrú is déanaí atá againn. D’fhéadfadh sé seo tarlú de bharr latency líonra nó nasc idirlín lag. Sa chás seo freisin úsáidimid an nuashonrú is déanaí atá againn.
  3. Tá nuashonrú againn roimh an am rindreála reatha agus ina dhiaidh, ionas gur féidir linn idirshuí!

Gach rud atá fágtha i state.js cur i bhfeidhm idirshuíomh líneach atá simplí (ach leadránach) matamaitic. Más mian leat é a iniúchadh duit féin, ansin oscail state.js ar Github.

Cuid 2. Freastalaí Inneall

Sa chuid seo féachfaimid ar an inneall Node.js a rialaíonn ár sampla de chluiche .io.

1. Pointe iontrála freastalaí

Chun an freastalaí gréasáin a bhainistiú úsáidfimid creat gréasáin coitianta do Node.js ar a dtugtar Express. Beidh sé cumraithe ag ár gcomhad pointe iontrála freastalaí src/server/server.js:

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

Cuimhnigh gur phléamar Webpack sa chéad chuid? Seo an áit a úsáidfimid ár bhfoirmíochtaí Webpack. Cuirfimid i bhfeidhm iad ar dhá bhealach:

  • Úsáid webpack-dev-meánearraí chun ár bpacáistí forbartha a atógáil go huathoibríoch, nó
  • Aistrigh fillteán go statach dist/, ina mbeidh Webpack ag scríobh ár gcomhaid tar éis an tógáil táirgeachta.

Tasc tábhachtach eile server.js comhdhéanta de bhunú an fhreastalaí soicéad.ioa nascann go simplí leis an bhfreastalaí Express:

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

Tar éis nasc socket.io a bhunú go rathúil leis an bhfreastalaí, déanaimid láimhseálaithe imeachtaí a chumrú don soicéad nua. Próiseálann láimhseálaithe imeachtaí teachtaireachtaí a fhaightear ó chliaint trí tharmligean chuig réad singleton game:

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

Táimid ag cruthú cluiche .io, mar sin ní bheidh ach cóip amháin de dhíth orainn Game (“Cluiche”) – imríonn na himreoirí go léir sa réimse céanna! Sa chéad chuid eile feicfimid conas a oibríonn an rang seo Game.

2. Freastalaithe cluiche

Rang Game ina bhfuil an loighic freastalaí-taobh is tábhachtaí. Tá dhá phríomhthasc aige: bainistíocht imreoir и insamhalta cluiche.

Let tús leis an gcéad tasc - na himreoirí a bhainistiú.

cluiche.js, cuid 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);
    }
  }

  // ...
}

Sa chluiche seo aithneoimid imreoirí de réir páirce id their socket socket.io (má tá mearbhall ort, ansin téigh ar ais go dtí server.js). Sannann Socket.io féin soicéad uathúil do gach soicéad id, mar sin ní gá dúinn a bheith buartha faoi. Glaofaidh mé air ID imreoir.

Agus é sin san áireamh, déanaimis scrúdú ar na hathróga cásanna sa rang Game:

  • sockets is réad é a cheanglaíonn aitheantas an imreora leis an soicéad a bhaineann leis an imreoir. Ligeann sé dúinn rochtain a fháil ar shoicéid trína n-aitheantais imreora le himeacht ama.
  • players is réad é a cheanglaíonn aitheantas an imreora leis an gcód> oibiacht Imreoir

bullets Is sraith de rudaí Bullet, gan ordú ar leith.
lastUpdateTime - Seo stampa ama an nuashonraithe cluiche deiridh. Feicfimid conas a úsáidtear é go luath.
shouldSendUpdate is athróg cúnta é. Feicfimid é a úsáid go luath freisin.
Modhanna addPlayer(), removePlayer() и handleInput() ní gá a mhíniú, úsáidtear iad i server.js. Má theastaíonn athnuachan uait, téigh ar ais beagán níos airde.

Líne dheireanach constructor() tosaíonn suas timthriall nuashonraithe cluichí (le minicíocht 60 nuashonrú/s):

cluiche.js, cuid 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() is dócha go bhfuil an chuid is tábhachtaí den loighic taobh an fhreastalaí. Déanaimis gach rud a dhéanann sé a liostú in ord:

  1. Ríomhann sé cén t-am atá ann dt tá sé ó bhí an ceann deireanach update().
  2. Athnuachan gach diúracán agus scriosann iad más gá. Feicfimid cur i bhfeidhm na feidhme seo níos déanaí. Chun anois is leor dúinn a fhios sin bullet.update() filleann true, más rud é nach mór an projectile a scrios (chuaigh sé lasmuigh den réimse).
  3. Nuashonraítear gach imreoir agus cruthaítear teilgeán más gá. Feicfimid an cur i bhfeidhm seo níos déanaí freisin - player.update() Is féidir le réad a thabhairt ar ais Bullet.
  4. Seiceálacha le haghaidh imbhuailtí idir teilgeáin agus imreoirí ag baint úsáide as applyCollisions(), a thugann ar ais sraith teilgeáin a bhuaileann imreoirí. I gcás gach diúracán a chuirtear ar ais, méadóimid scór an imreora a scaoil é (ag baint úsáide as player.onDealtDamage()), agus ansin bain an teilgeán as an eagar bullets.
  5. Fógraíonn agus scriosann gach imreoir a maraíodh.
  6. Seolann nuashonrú cluiche chuig gach imreoir gach soicind amanna nuair a ghlaoitear air update(). Cuidíonn an athróg chúnta atá luaite thuas linn é seo a rianú shouldSendUpdate. Mar update() ar a dtugtar 60 uair / s, cuirimid nuashonruithe cluiche 30 uair / s. Mar sin, minicíocht clog Is é an freastalaí 30 timthriallta cloig/s (labhair muid faoi mhinicíocht an chloig sa chéad chuid).

Cén fáth a sheoladh nuashonruithe cluiche amháin tríd an am ? Chun cainéal a shábháil. Tá 30 nuashonrú cluiche in aghaidh an tsoicind go leor!

Cén fáth nach glaoch díreach ansin? update() 30 uair in aghaidh an tsoicind? Chun feabhas a chur ar an insamhalta cluiche. An níos minice a thugtar air update(), an níos cruinne a bheidh an insamhalta cluiche. Ach ná bíodh imní ort faoi líon na ndúshlán update(), toisc gur tasc costasach ríomhaireachtúil é seo - tá 60 in aghaidh an tsoicind sách go leor.

An chuid eile den rang Game comhdhéanta de mhodhanna cúntóir a úsáidtear i update():

cluiche.js, cuid 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() Tá sé simplí go leor - déanann sé imreoirí a shórtáil de réir scór, tógann sé na cúig is airde, agus cuireann sé an t-ainm úsáideora agus an scór ar ais do gach ceann acu.

createUpdate() úsáidtear i update() chun nuashonruithe cluiche a chruthú a dháiltear ar imreoirí. Is é a phríomhthasc ná modhanna a ghlaoch serializeForUpdate(), i bhfeidhm do ranganna Player и Bullet. Tabhair faoi deara nach ndéanann sé ach sonraí a aistriú chuig gach imreoir faoi gaire imreoirí agus teilgeáin - ní gá faisnéis a tharchur faoi rudaí cluiche atá suite i bhfad ón imreoir!

3. Rudaí cluiche ar an bhfreastalaí

Inár gcluiche, tá teilgeáin agus imreoirí an-chosúil i ndáiríre: is réada cruinne teibí gluaiseachta iad. Chun leas a bhaint as an gcosúlacht seo idir imreoirí agus teilgeáin, déanaimis tosú trí bhunrang a chur i bhfeidhm Object:

réad.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,
    };
  }
}

Níl aon rud casta ar siúl anseo. Beidh an rang seo ina phointe tosaigh maith don leathnú. A ligean ar a fheiceáil conas an rang Bullet úsáidí Object:

piléar.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;
  }
}

Cur i bhFeidhm Bullet an-ghearr! Táimid tar éis cur le Object na síntí seo a leanas amháin:

  • Ag baint úsáide as an bpacáiste gearrid do ghiniúint randamach id teilgean.
  • Ag cur réimse parentID, ionas gur féidir leat an t-imreoir a chruthaigh an teilgean seo a rianú.
  • Ag cur an luach aischuir le update(), atá comhionann true, má tá an diúracán lasmuigh den réimse (cuimhnigh ar labhair muid faoi seo sa chuid dheireanach?).

A ligean ar bogadh ar aghaidh go dtí Player:

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

Tá imreoirí níos casta ná teilgeáin, mar sin ba chóir don rang seo cúpla réimse eile a stóráil. A modh update() níos mó oibre a dhéanamh, go háirithe an teilgeán nuachruthaithe a thabhairt ar ais mura bhfuil aon cheann fágtha fireCooldown (cuimhnigh ar labhair muid faoi seo san alt roimhe seo?). Leathnaíonn sé an modh freisin serializeForUpdate(), mar ní mór dúinn réimsí breise a chur san áireamh don imreoir sa nuashonrú cluiche.

Infhaighteacht bunrang Object - céim thábhachtach chun athrá cód a sheachaint. Mar shampla, gan rang Object ní mór an cur i bhfeidhm céanna a bheith ag gach réad cluiche distanceTo(), agus ba mhór an tromluí é cóip-ghreamú a dhéanamh ar na feidhmithe seo go léir trasna comhaid iolracha. Éiríonn sé seo thar a bheith tábhachtach do thionscadail mhóra, nuair a bheidh líon na leathnú Object ranganna ag fás.

4. Imbhualadh a bhrath

Is é an t-aon rud atá fágtha againn le déanamh ná a aithint nuair a bhuaileann na teilgeáin na himreoirí! Cuimhnigh an blúire cód seo ón modh update() sa rang Game:

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

    // ...
  }
}

Caithfimid an modh a chur i bhfeidhm applyCollisions(), a thugann ar ais gach teilgeáin a bhuaileann imreoirí. Ar ámharaí an tsaoil, níl sé seo chomh deacair sin a dhéanamh mar gheall ar

  • Is ciorcail iad gach réad imbhuailte, agus is é seo an cruth is simplí chun brath imbhuailte a chur i bhfeidhm.
  • Tá modh againn cheana féin distanceTo(), a chuireamar i bhfeidhm sa rang sa roinn roimhe seo Object.

Seo an chuma atá ar ár gcur i bhfeidhm ar bhrath imbhuailtí:

imbhuailtí.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;
}

Tá an bhrath imbhuailte simplí seo bunaithe ar an bhfíric go bhfuil imbhuaileann dhá chiorcal má bhíonn an fad idir a lárionaid níos lú ná suim a ngathanna. Seo cás ina bhfuil an fad idir lárionaid dhá chiorcal díreach comhionann le suim a ngathanna:

Cluiche Gréasáin Il-imreora .io a Chruthú
Anseo ní mór duit aird ghéar a thabhairt ar chúpla gné eile:

  • Níor cheart don diúracán an t-imreoir a chruthaigh é a bhualadh. Is féidir é seo a bhaint amach trí chomparáid a dhéanamh bullet.parentID с player.id.
  • Níor cheart go mbuailfeadh an diúracán ach uair amháin i gcás mhór na n-imreoirí iolracha a bhualadh ag an am céanna. Déanfaimid an fhadhb seo a réiteach ag baint úsáide as an oibreoir break: Nuair a aimsítear imreoir atá ag bualadh le diúracán, stopaimid den chuardach agus bogaimid ar aghaidh go dtí an chéad diúracán eile.

Конец

Sin é an méid! Táimid tar éis gach rud a theastaíonn uait a bheith agat chun cluiche gréasáin .io a chruthú. Cad atá romhainn? Tóg do chluiche .io féin!

Tá gach cód samplach foinse oscailte agus postáilte ar Github.

Foinse: will.com

Add a comment