Ho theha papali ea marang-rang ea libapali tse ngata ka mofuta oa .io

Ho theha papali ea marang-rang ea libapali tse ngata ka mofuta oa .io
E hlahisitsoe ka 2015 Agar.io e ile ea e-ba mohloli oa mofuta o mocha lipapali.io, eo botumo ba eona bo eketsehileng haholo ho tloha ka nako eo. Ke bone ho eketseha ha botumo ba lipapali tsa .io ka bonna: lilemong tse tharo tse fetileng, ke e thehile le ho rekisa lipapali tse peli tsa mofuta ona..

Haeba ha u e-so utloele ka lipapali tsena pele, ke lipapali tsa marang-rang tsa mahala tsa libapali tse ngata tseo ho leng bonolo ho li bapala (ha ho na ak'haonte e hlokahalang). Hangata ba kenya libapali tse ngata tse hanyetsanang lebaleng le le leng. Lipapali tse ling tse tsebahalang tsa .io: Slither.io и Lerato.io.

Ka poso ena re tla bona kamoo theha papali ea .io ho tloha qalong. Ho etsa sena, tsebo feela ea Javascript e tla lekana: o hloka ho utloisisa lintho tse kang syntax ES6, lentsoe la sehlooho this и litšepiso tsa. Leha o sa tsebe Javascript hantle, o ntse o ka utloisisa boholo ba poso.

Mohlala oa papali ea .io

Bakeng sa thuso ea koetliso re tla bua ka eona mohlala papali .io. Leka ho e bapala!

Ho theha papali ea marang-rang ea libapali tse ngata ka mofuta oa .io
Papali e bonolo haholo: o laola sekepe lebaleng la lipapali le libapali tse ling. Sekepe sa hau se chesa li-projectiles 'me u leka ho otla libapali tse ling ha u ntse u qoba projectiles ea bona.

1. Kakaretso e khuts'oane / sebopeho sa morero

Khothaletsa khoasolla mohloli khoutu papali ea mohlala hore o tle o ntatele.

Mohlala o sebelisa tse latelang:

  • bontše ke moralo o tsebahalang haholo oa webo bakeng sa Node.js o tsamaisang seva sa marang-rang sa papali.
  • sekotlo.io - laebrari ea li-websocket bakeng sa phapanyetsano ea data lipakeng tsa sebatli le seva.
  • Webpack - mookameli oa module. U ka bala hore na ke hobane'ng ha u sebelisa Webpack mona.

Sena ke seo sebopeho sa buka ea projeke se shebahalang ka sona:

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

phatlalatsa/

Tsohle li ka har'a foldara public/ e tla fetisoa ka statically ke seva. IN public/assets/ e na le litšoantšo tse sebelisoang ke morero oa rona.

src /

Khoutu eohle ea mohloli e ka har'a sephutheli src/. Litlotla client/ и server/ bua ka bobona le shared/ e na le faele ea li-constants e tsoang kantle ho naha ke moreki le seva.

2. Likopano / mekhahlelo ea morero

Joalokaha ho boletsoe ka holimo, re sebelisa mookameli oa mojule ho aha morero Webpack. Ha re shebeng tlhophiso ea rona ea 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',
    }),
  ],
};

Mela ea bohlokoa ka ho fetisisa mona ke e latelang:

  • src/client/index.js ke sebaka sa ho kena sa moreki oa Javascript (JS). Webpack e tla qala ho tloha mona 'me e batle lifaele tse ling tse tsoang kantle ho naha.
  • Sephetho sa JS sa moaho oa rona oa Webpack se tla fumaneha bukeng dist/. Ke tla bitsa faele ena ea rona JS sephutheloana.
  • Rea sebelisa Babele, mme haholo-holo tlhophiso @babel/preset-env ho fetisetsa khoutu ea rona ea JS bakeng sa libatli tsa khale.
  • Re sebelisa plugin ho ntša CSS tsohle tse boletsoeng ke lifaele tsa JS ebe re li kopanya sebakeng se le seng. Ke tla e bitsa ea rona Sephutheloana sa CSS.

Mohlomong u hlokometse mabitso a liphutheloana tse makatsang '[name].[contenthash].ext'. Li na le sebaka sa filename Lebokose la webo: [name] e tla nkeloa sebaka ke lebitso la sebaka sa ho kenya (ho rona ke game), le [contenthash] e tla nkeloa sebaka ke hash ea litaba tsa faele. Re etsa sena ho ntlafatsa morero bakeng sa hashing - re ka bolella bashebelli ho boloka liphutheloana tsa rona tsa JS ka nako e sa lekanyetsoang hobane haeba sephutheloana se fetoha, lebitso la eona la faele le lona lea fetoha (liphetoho contenthash). Sephetho se phethiloeng e tla ba lebitso la faele la pono game.dbeee76e91a97d0c7207.js.

faele webpack.common.js - Ena ke faele ea tlhophiso ea mantlha eo re e kenyang ho nts'etsopele le ho qeta litlhophiso tsa projeke. Ka mohlala, mona ke tlhophiso ea ntlafatso:

webpack.dev.js

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

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

Bakeng sa katleho, re sebelisa ts'ebetsong ea ntlafatso webpack.dev.js, mme e fetohela ho webpack.prod.js, ho ntlafatsa boholo ba liphutheloana ha o tsamaisa tlhahiso.

Tlhophiso ea lehae

Ke khothaletsa ho kenya morero mochining oa hau oa lehae hore o tle o tsebe ho latela mehato e thathamisitsoeng posong ena. Ho seta ho bonolo: pele, sistimi e tlameha ho ba le eona noute и NPM. E latelang u lokela ho etsa

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

mme o loketse ho tsamaya! Ho qala seva sa nts'etsopele, matha feela

$ npm run develop

ebe u ea ho sebatli sa hau sa Marang-rang localhost: 3000. Seva ea nts'etsopele e tla iketsetsa liphutheloana tsa JS le CSS ha liphetoho tsa khoutu li etsahala - nchafatsa leqephe feela ho bona liphetoho tsohle!

3. Lintlha tsa ho kena ha bareki

Ha re theohele ho khoutu ea papali ka boeona. Pele re hloka leqephe index.html, ha u etela sebaka, sebatli se tla se kenya pele. Leqephe la rona le tla ba bonolo haholo:

index.html

Mohlala oa papali ea .io  BAPALA

Mohlala ona oa khoutu o nolofalitsoe hanyenyane bakeng sa ho hlaka, 'me ke tla etsa se tšoanang ka mehlala e meng e mengata posong. Khoutu e felletseng e ka bonoa kamehla ho Github.

Re na le:

  • Karolo ea HTML5 ea Canvas (<canvas>), eo re tla e sebelisa ho fana ka papali.
  • <link> ho kenya sephutheloana sa rona sa CSS.
  • <script> ho kenya sephutheloana sa rona sa Javascript.
  • Menu ea mantlha e nang le lebitso la mosebelisi <input> le konopo ea "PLAY" (<button>).

Hang ha leqephe la lehae le jara, sebatli se tla qala ho sebelisa khoutu ea Javascript, ho qala ka ntlha ea ho kena faeleng ea 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);
  };
});

Sena se ka utloahala se le thata, empa ha e le hantle ha ho na lintho tse ngata tse etsahalang mona:

  1. Kenya lifaele tse ling tse 'maloa tsa JS.
  2. Kenya CSS (kahoo Webpack e tseba ho e kenyelletsa ka har'a sephutheloana sa rona sa CSS).
  3. Qalisa connect() ho theha khokahano ho seva le ho qala downloadAssets() ho khoasolla litšoantšo tse hlokahalang ho fana ka papali.
  4. Ka mor'a ho qeta mohato oa 3 menu e kholo e hlahisoa (playMenu).
  5. Ho theha konopo ea "PLAY" tobetsa konopo. Ha konopo e tobetsa, khoutu e qala papali ebe e bolella seva hore re se re loketse ho bapala.

"Nama" ea mantlha ea logic ea bareki-server e ka har'a lifaele tse kentsoeng ke faele index.js. Joale re tla li sheba kaofela ka tatellano.

4. Phapanyetsano ea data ea bareki

Papaling ena re sebelisa laebrari e tsebahalang ho buisana le seva sekotlo.io. Socket.io e na le tšehetso e hahelletsoeng ka hare Li-WebSocket, tse loketseng hantle bakeng sa puisano ea litsela tse peli: re ka romela melaetsa ho seva и seva e ka romela melaetsa ho rona ka khokahanyo e tšoanang.

Re tla ba le faele e le 'ngoe src/client/networking.jsya tla hlokomela bohle Lipuisano le seva:

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

Khoutu ena e khutsufalitsoe hanyane bakeng sa ho hlaka.

Ho na le lintho tse tharo tsa mantlha tse etsahalang faeleng ena:

  • Re leka ho hokela ho seva. connectedPromise e lumelloa feela ha re thehile khokahano.
  • Haeba khokahano e atlehile, re ngolisa lits'ebetso tsa callback (processGameUpdate() и onGameOver()) bakeng sa melaetsa eo re ka e fumanang ho tsoa ho seva.
  • Re romela kantle ho naha play() и updateDirection()e le hore lifaele tse ling li ka li sebelisa.

5. Tlhahiso ea bareki

Ke nako ea ho hlahisa setšoantšo skrineng!

... empa pele re ka etsa sena, re hloka ho jarolla litšoantšo tsohle (lisebelisoa) tse hlokahalang bakeng sa sena. Ha re ngoleng mookameli oa lisebelisoa:

assets.js

const ASSET_NAMES = ['ship.svg', 'bullet.svg'];

const assets = {};
const downloadPromise = Promise.all(ASSET_NAMES.map(downloadAsset));

function downloadAsset(assetName) {
  return new Promise(resolve => {
    const asset = new Image();
    asset.onload = () => {
      console.log(`Downloaded ${assetName}`);
      assets[assetName] = asset;
      resolve();
    };
    asset.src = `/assets/${assetName}`;
  });
}

export const downloadAssets = () => downloadPromise;
export const getAsset = assetName => assets[assetName];

Tsamaiso ea lisebelisoa ha e thata hakaalo ho e sebelisa! Taba ea mantlha ke ho boloka ntho assets, e tla tlama senotlolo sa filename ho boleng ba ntho Image. Ha mohloli o kentsoeng, re o boloka ho ntho assets bakeng sa ho fumana kapele nakong e tlang. Ho khoasolla sesebelisoa ka seng ho tla lumelloa neng (ke hore, ho tla jarolla tsohle mehloli), rea lumella downloadPromise.

Ka mor'a ho khoasolla lisebelisoa, u ka qala ho fana. Joalokaha ho boletsoe pejana, ho taka leqepheng la webo leo re le sebelisang HTML5 Canvas (<canvas>). Papali ea rona e bonolo haholo, kahoo re hloka feela ho fana ka tse latelang:

  1. Semelo
  2. Sekepe sa libapali
  3. Libapali tse ling papaling
  4. Likhetla

Mona ke lintlha tsa bohlokoa src/client/render.js, e hlalosang hantle lintlha tse 'nè tse thathamisitsoeng ka holimo:

render.js

import { getAsset } from './assets';
import { getCurrentState } from './state';

const Constants = require('../shared/constants');
const { PLAYER_RADIUS, PLAYER_MAX_HP, BULLET_RADIUS, MAP_SIZE } = Constants;

// Get the canvas graphics context
const canvas = document.getElementById('game-canvas');
const context = canvas.getContext('2d');

// Make the canvas fullscreen
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

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

  // Draw background
  renderBackground(me.x, me.y);

  // Draw all bullets
  bullets.forEach(renderBullet.bind(null, me));

  // Draw all players
  renderPlayer(me, me);
  others.forEach(renderPlayer.bind(null, me));
}

// ... Helper functions here excluded

let renderInterval = null;
export function startRendering() {
  renderInterval = setInterval(render, 1000 / 60);
}
export function stopRendering() {
  clearInterval(renderInterval);
}

Khoutu ena e khutsufalitsoe hape bakeng sa ho hlaka.

render() ke mosebetsi o ka sehloohong oa faele ena. startRendering() и stopRendering() laola ts'ebetso ea potoloho ea phepelo ho 60 FPS.

Ts'ebetsong e khethehileng ea mesebetsi ea motho ka mong ea fanang ka thuso (mohlala renderBullet()) ha li bohlokoa hakaalo, empa mohlala o le mong o bonolo ke ona:

render.js

function renderBullet(me, bullet) {
  const { x, y } = bullet;
  context.drawImage(
    getAsset('bullet.svg'),
    canvas.width / 2 + x - me.x - BULLET_RADIUS,
    canvas.height / 2 + y - me.y - BULLET_RADIUS,
    BULLET_RADIUS * 2,
    BULLET_RADIUS * 2,
  );
}

Hlokomela hore re sebelisa mokhoa ona getAsset(), e neng e bonoa pele ho asset.js!

Haeba u thahasella ho hlahloba lits'ebetso tse ling tsa ho fana ka thuso, bala tse ling kaofela src/client/render.js.

6. Kenyelletso ea moreki

Ke nako ea ho etsa papali e ka bapaloang! Lenaneo la taolo le tla ba bonolo haholo: ho fetola tataiso ea motsamao, o ka sebelisa toeba (k'homphieutheng) kapa o ama skrineng (ka sesebelisoa sa mohala). Ho phethahatsa sena re tla ngolisa Bamameli ba Ketsahalo bakeng sa liketsahalo tsa Mouse le Touch.
O tla hlokomela tsena tsohle src/client/input.js:

input.js

import { updateDirection } from './networking';

function onMouseInput(e) {
  handleInput(e.clientX, e.clientY);
}

function onTouchInput(e) {
  const touch = e.touches[0];
  handleInput(touch.clientX, touch.clientY);
}

function handleInput(x, y) {
  const dir = Math.atan2(x - window.innerWidth / 2, window.innerHeight / 2 - y);
  updateDirection(dir);
}

export function startCapturingInput() {
  window.addEventListener('mousemove', onMouseInput);
  window.addEventListener('touchmove', onTouchInput);
}

export function stopCapturingInput() {
  window.removeEventListener('mousemove', onMouseInput);
  window.removeEventListener('touchmove', onTouchInput);
}

onMouseInput() и onTouchInput() ke Bamameli ba Ketsahalo ba letsang updateDirection() (ho tloha networking.js) ha ketsahalo e kenang e etsahala (mohlala, ha toeba e tsamaiswa). updateDirection() e sebetsana le phapanyetsano ea melaetsa le seva, e sebetsanang le ketsahalo ea ho kenya le ho ntlafatsa boemo ba papali ka nepo.

7. Boemo ba moreki

Karolo ena ke eona e thata ka ho fetisisa karolong ea pele ea poso. U se ke ua nyahama ha u sa e utloisise khetlo la pele ha u e bala! U ka e tlola ebe u khutlela ho eona hamorao.

Karolo ea ho qetela ea papali e hlokahalang ho phethela khoutu ea bareki-server ke boemo. Na u sa hopola poleloana ea khoutu e tsoang karolong ea Phepelo ea bareki?

render.js

import { getCurrentState } from './state';

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

  // Do the rendering
  // ...
}

getCurrentState() e lokela ho khona ho re fa boemo ba hajoale ba papali ho moreki ka nako efe kapa efe ho ipapisitsoe le liapdeite tse amohetsoeng ho tsoa ho seva. Mohlala ke ona oa ntlafatso ea papali eo seva e ka e romellang:

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

Nchafatso ka 'ngoe ea papali e na le likarolo tse hlano tse ts'oanang:

  • t: Setempe sa nako sa seva se bontšang hore na ntlafatso ena e entsoe neng.
  • me: Lintlha mabapi le sebapali se fumanang ntlafatso ena.
  • ba bang: Lethathamo la lintlha mabapi le libapali tse ling tse nkang karolo papaling e tšoanang.
  • likulo: lethathamo la tlhahisoleseling mabapi le projectiles papaling.
  • bapalami ba pelehi: Lintlha tsa hajoale tsa boardboard. Re ke ke ra li ela hloko posong ena.

7.1 Boemo ba ho hloka kelello ba moreki

Ts'ebetsong e sa tsebeng letho getCurrentState() e ka khutlisa data ho tsoa ho ntlafatso ea papali e sa tsoa amoheloa.

naive-state.js

let lastGameUpdate = null;

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

export function getCurrentState() {
  return lastGameUpdate;
}

E ntle ebile e hlakile! Empa hoja e ne e le bonolo hakaalo. E 'ngoe ea mabaka a etsang hore ts'ebetso ena e be le mathata: e fokotsa sekhahla sa tlhahiso ho lebelo la oache ea seva.

Sekhahla sa Frame: palo ea liforeimi (i.e. mehala render()) ka motsotsoana, kapa FPS. Hangata lipapali li loanela ho fihlela bonyane 60 FPS.

Sekhahla sa Letšoao: Khafetsa eo seva e romellang liapdeite tsa papali ho bareki. Hangata e tlase ho feta sekhahla sa foreimi. Papaling ea rona, seva se matha ka li-ticks tse 30 motsotsoana.

Haeba re fana ka ntlafatso ea morao-rao ea papali, FPS e ke ke ea hlola e khona ho feta 30 hobane ha ho mohla re fumanang lintlafatso tse fetang 30 motsotsoana ho tsoa ho seva. Leha re ka bitsa render() Ka makhetlo a 60 ka motsotsoana, ebe halofo ea mehala ena e tla hula ntho e tšoanang, ha e le hantle e sa etse letho. Bothata bo bong ka ts'ebetsong e se nang kelello ke eona ka lebaka la tieho. Ka lebelo le nepahetseng la Marang-rang, moreki o tla fumana ntlafatso ea papali hantle ka mor'a 33 ms (30 ka motsotsoana):

Ho theha papali ea marang-rang ea libapali tse ngata ka mofuta oa .io
Ka bomalimabe, ha ho letho le phethahetseng. Senepe sa nnete e ka ba:
Ho theha papali ea marang-rang ea libapali tse ngata ka mofuta oa .io
Ts'ebetso e se nang kelello ke eona ntho e mpe ka ho fetisisa ha ho tluoa tabeng ea latency. Haeba ntlafatso ea papali e amoheloa ka ho lieha ha 50ms, joale moreki o fokotseha ka 50ms e eketsehileng hobane e ntse e fana ka boemo ba papali ho tsoa ho ntlafatso e fetileng. U ka inahanela hore na sena ke tšitiso hakae ho sebapali: ka lebaka la ho fokotseha ho sa tloaelehang, papali e tla bonahala e le thata ebile e sa tsitsa.

7.2 Boemo ba bareki bo ntlafetseng

Re tla etsa lintlafatso tse ling ts'ebetsong e se nang kelello. Pele, re sebelisa tieho ea ho fana ka 100 ms. Sena se bolela hore boemo ba "hona joale" ba moreki bo tla lula bo le 100ms ka morao ho boemo ba papali ho seva. Ka mohlala, haeba nako ea seva e 150, joale mofani o tla fana ka boemo boo seva e neng e le ho bona ka nako eo 50:

Ho theha papali ea marang-rang ea libapali tse ngata ka mofuta oa .io
Sena se re fa 100ms buffer ho pholoha nako e sa lebelloang ea lintlafatso tsa papali:

Ho theha papali ea marang-rang ea libapali tse ngata ka mofuta oa .io
Theko ea sena e tla ba ea ka ho sa feleng ho lieha ho kenya ka 100 ms. Ena ke sehlabelo se fokolang bakeng sa papali e boreleli - libapali tse ngata (haholo-holo tse tloaelehileng) li ke ke tsa hlokomela tieho ena. Ho bonolo haholo hore batho ba ikamahanye le 100ms latency kamehla ho feta ho bapala ka latency e sa lebelloang.

Re ka sebelisa mokhoa o mong o bitsoang "ponelopele ea lehlakore la bareki", e etsang mosebetsi o motle oa ho fokotsa ho nkoa ha latency, empa e ke ke ea tšohloa posong ena.

Ntlafatso e 'ngoe eo re e sebelisang ke kenollo ya mola. Ka lebaka la ho salla morao, hangata re ba le ntlafatso e le 'ngoe pele ho nako ea hajoale ho moreki. Ha o bitsoa getCurrentState(), re ka phethahatsa kenollo ya mola lipakeng tsa lintlafatso tsa papali pele le kamora nako ea hajoale ho moreki:

Ho theha papali ea marang-rang ea libapali tse ngata ka mofuta oa .io
Sena se rarolla bothata ba sekhahla sa foreimi: joale re ka fana ka liforeimi tse ikhethileng ka sekhahla sefe kapa sefe seo re se hlokang!

7.3 Ho kenya tshebetsong boemo bo ntlafetseng ba bareki

Mohlala ts'ebetsong ka src/client/state.js e sebelisa bobeli tieho ea ho fana le tlhaloso ea mola, empa sena ha se tšoarelle nako e telele. Ha re arole khoutu ka likarolo tse peli. Ea pele ke ena:

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

Ntho ea pele eo u lokelang ho e etsa ke ho tseba hore na e sebetsa joang currentServerTime(). Joalokaha re bone pejana, ntlafatso e 'ngoe le e' ngoe ea papali e kenyelletsa setempe sa nako sa seva. Re batla ho sebelisa render latency ho fana ka setšoantšo sa 100ms ka morao ho seva, empa re ke ke ra tseba nako ea hajoale ho seva, hobane ha re tsebe hore na ho nkile nako e kae hore lintlafatso li fihle ho rona. Marang-rang ha a tsejoe esale pele 'me lebelo la eona le ka fapana haholo!

Ho rarolla bothata bona, re ka sebelisa tekanyo e utloahalang: re ha re etse eka ntlafatso ea pele e fihlile hang hang. Haeba sena e ne e le 'nete, re ne re tla tseba nako ea seva ka nako eo! Re boloka setempe sa nako sa seva ho firstServerTimestamp mme o boloke tsa rona sebakeng (client) setempe sa nako ka nako e ts'oanang ho gameStart.

Oho, ema hanyane. Na ha hoa lokela ho ba le nako ho seva = nako ho moreki? Hobaneng re khetholla lipakeng tsa "setempe sa nako ea seva" le "setempe sa nako ea bareki"? Ena ke potso e kholo! Hoa etsahala hore tsena ha se ntho e le 'ngoe. Date.now() e tla khutlisa litempe tsa linako tse fapaneng ho moreki le seva mme sena se ipapisitse le lintlha tsa lehae ho mechini ena. Le ka mohla u se ke ua nahana hore litempe tsa linako li tla tšoana mecheng eohle.

Joale re utloisisa seo e se etsang currentServerTime(): ea khutla setempe sa nako sa seva sa nako ea hona joale ea ho fana. Ka mantsoe a mang, ena ke nako ea hona joale ea seva (firstServerTimestamp <+ (Date.now() - gameStart)) ho tlosa tieho ea tlhahiso (RENDER_DELAY).

Joale a re shebeng hore na re sebetsana joang le liapdeite tsa lipapali. Ha apdeite e amoheloa ho tsoa ho seva, e bitsoa processGameUpdate(), 'me re boloka ntlafatso e ncha ho sehlopha gameUpdates. Ebe, ho lekola ts'ebeliso ea memori, re tlosa liapdeite tsohle tsa khale ho ntlafatso ea motheohobane ha re sa di hloka.

"Nchafatso ea mantlha" ke eng? Sena ntlafatso ea pele eo re e fumanang ka ho khutlela morao ho tloha nakong ea seva ea hona joale. Hopola setšoantšo see?

Ho theha papali ea marang-rang ea libapali tse ngata ka mofuta oa .io
Ntlafatso ea papali ka ho le letšehali ho "Client Render Time" ke eona ntlafatso ea mantlha.

Motheo oa ntlafatso o sebelisetsoa eng? Hobaneng re ka tlohela lintlafatso ho qala? Ho utloisisa sena, a re ke re qetellong ha re shebeng ts'ebetsong getCurrentState():

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

Re sebetsana le linyeoe tse tharo:

  1. base < 0 ho bolela hore ha ho na lintlafatso ho fihlela nako ea hona joale ea ho fana (sheba ts'ebetsong ka holimo getBaseUpdate()). Sena se ka etsahala qalong ea papali ka lebaka la ho lieha ho hoholo. Tabeng ena, re sebelisa lintlafatso tsa morao-rao tse fumanoeng.
  2. base ke ntlafatso ea morao-rao eo re nang le eona. Sena se ka etsahala ka lebaka la ho se sebetse hantle ha marang-rang kapa khokahanyo e fosahetseng ea Marang-rang. Tabeng ena, re sebelisa lisebelisoa tsa morao-rao tseo re nang le tsona.
  3. Re na le ntlafatso pele le ka morao ho nako ea hona joale ea ho fana, kahoo re ka khona kenyeletsa!

Tsohle tse setseng ka hare state.js ke ts'ebetsong ea tlhabollo ea mela e bonolo (empa e bora) lipalo. Haeba u batla ho itlhahloba ka bouena, bula state.js mabapi le Github.

Karolo ea 2. Seva ea morao-rao

Karolong ena re tla sheba Node.js backend e laolang rona mohlala oa papali ea .io.

1. Sebaka sa ho kena ho seva

Ho laola seva sa marang-rang re tla sebelisa moralo o tsebahalang oa marang-rang oa Node.js o bitsoang bontše. E tla hlophisoa ke faele ea rona ea ho kena ha seva src/server/server.js:

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

Hopola hore karolong ea pele re buisane ka Webpack? Mona ke moo re tla sebelisa litlhophiso tsa rona tsa Webpack. Re tla li sebelisa ka litsela tse peli:

  • Sebelisa webpack-dev-middleware ho aha bocha liphutheloana tsa rona tsa ntlafatso, kapa
  • Fetisetsa foldara ka mokhoa o tsitsitseng dist/, moo Webpack e tla ngola lifaele tsa rona ka mor'a mohaho oa tlhahiso.

Mosebetsi o mong oa bohlokoa server.js e kenyelletsa ho seta seva sekotlo.ioe hokelang feela ho seva sa Express:

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

Kamora ho theha khokahano ea socket.io ka katleho le seva, re lokisa bahlokomeli ba ketsahalo bakeng sa sokete e ncha. Batšoantšisi ba liketsahalo ba sebetsana le melaetsa e amohetsoeng ho tsoa ho bareki ka ho abela ntho ea singleton game:

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

Re theha papali ea .io, kahoo re tla hloka kopi e le 'ngoe feela Game ("Papali") - libapali tsohle li bapala lebaleng le le leng! Karolong e latelang re tla bona kamoo sehlopha sena se sebetsang kateng Game.

2. Li-server tsa lipapali

Sehlopha Game e na le mohopolo oa bohlokoa ka ho fetisisa oa lehlakore la seva. E na le mesebetsi e 'meli e meholo: tsamaiso ea libapali и ketsiso ya papali.

Ha re qaleng ka mosebetsi oa pele - ho laola libapali.

game.js, karolo ea 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);
    }
  }

  // ...
}

Papaling ena re tla khetholla libapali ka lebala id socket socket.io (haeba u ferekane, khutlela ho server.js). Socket.io ka boeona e abela sokete e 'ngoe le e' ngoe e ikhethang id, kahoo ha ho hlokahale hore re tšoenyehe ka hona. Ke tla mmitsa ID ea sebapali.

Re nahanne ka seo, a re hlahlobeng mehlala e fapaneng ka phaposing Game:

  • sockets ke ntho e tlamellang ID ya sebapadi soketeng e amanang le sebapadi. E re lumella ho fihlella li-sockets ka li-ID tsa bona tsa libapali ka nako.
  • players ke ntho e tlamellang ID ea sebapali khoutung>Sebapali

bullets ke mokoloko wa dintho Bullet, e se nang taelo e tobileng.
lastUpdateTime - Ena ke setempe sa nako sa ntlafatso ea papali ea ho qetela. Re tla bona hore na e tla sebelisoa joang haufinyane.
shouldSendUpdate ke mofuta o thusang. Hape re tla bona tšebeliso ea eona haufinyane.
Mekhoa addPlayer(), removePlayer() и handleInput() ha ho hlokahale ho hlalosa, li sebelisoa ho server.js. Haeba o hloka ho hlasimolla, khutlela morao hanyane.

Mola oa ho qetela constructor() e qala potoloho ea ntlafatso lipapali (ka makhetlo a 60 liapdeite/s):

game.js, karolo ea 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;
    }
  }

  // ...
}

Mokhoa update() mohlomong e na le karolo ea bohlokoahali ea mohopolo oa lehlakore la seva. Ha re thathamise tsohle tseo e li etsang ka tatellano:

  1. E bala hore na ke nako mang dt haesale ho tloha ho qetela update().
  2. E khatholla projectile e 'ngoe le e' ngoe ebe e e senya ha ho hlokahala. Re tla bona ts'ebetsong ea ts'ebetso ena hamorao. Hajoale ho lekane hore re tsebe seo bullet.update() kgutlisetso true, haeba projectile e tlameha ho senngoa (a tswela kantle ho lebala).
  3. E ntlafatsa sebapali se seng le se seng mme e thehe projectile ha ho hlokahala. Hape re tla bona ts'ebetsong ena hamorao - player.update() e ka busetsa ntho Bullet.
  4. E lekola likhohlano lipakeng tsa projectiles le libapali tse sebelisang applyCollisions(), e khutlisang letoto la li-projectiles tse otlang libapali. Bakeng sa projectile e 'ngoe le e' ngoe e khutlisitsoeng, re eketsa lintlha tsa sebapali se e lelekitseng (re sebelisa player.onDealtDamage()), ebe o tlosa projectile ho sehlopha bullets.
  5. E tsebisa le ho senya libapali tsohle tse bolailoeng.
  6. E romella libapali tsohle tsebiso ea papali motsotsoana o mong le o mong nako eo ho bitswang ka yona update(). Phapang e thusang e boletsoeng ka holimo e re thusa ho latela sena shouldSendUpdate. Hobane update() e bitsoa 60 linako / s, re romella lisebelisoa tsa papali makhetlo a 30 / s. Kahoo, maqhubu a oache seva ke 30 clock cycles/s (re buile ka maqhubu a oache karolong ea pele).

Hobaneng o romella lintlha tsa papali feela ka nako ? Ho boloka kanale. Lintlafatso tse 30 ka motsotsoana li ngata!

Ke hobane'ng ha u sa letse feela ka nako eo? update() makhetlo a 30 ka motsotsoana? Ho ntlafatsa ketsiso ea papali. Hangata e bitsoa update(), papiso ea papali e tla nepahala haholoanyane. Empa u se ke ua khelosoa haholo ke palo ea mathata update(), hobane ona ke mosebetsi o theko e boima haholo - 60 motsotsoana o lekane.

Ba bang ba sehlopha Game e na le mekhoa e thusang e sebelisoang ho update():

game.js, karolo ea 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() E bonolo haholo - e hlophisa libapali ka lintlha, e nka tse hlano tse holimo, 'me e khutlisa lebitso la mosebelisi le lintlha bakeng sa e 'ngoe le e 'ngoe.

createUpdate() sebelisoa ho update() ho etsa liapdeite tsa papali tse abeloang libapali. Mosebetsi oa eona o ka sehloohong ke ho bitsa mekhoa serializeForUpdate(), kenngwa tshebetsong bakeng sa litlelase Player и Bullet. Hlokomela hore e fetisetsa data feela ho sebapali se seng le se seng mabapi le haufinyana libapali le projectiles - ha ho na tlhoko ea ho fetisa tlhahisoleseling mabapi le lintho tsa papali tse fumanehang hole le sebapali!

3. Lintho tsa papali ho seva

Papaling ea rona, projectiles le libapali li hlile li tšoana haholo: ke lintho tse sa bonahaleng tsa papali e tsamaeang. Ho nka monyetla ka ho tšoana hona pakeng tsa libapali le projectiles, ha re qaleng ka ho kenya tšebetsong sehlopha sa motheo Object:

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

Ha ho letho le rarahaneng le etsahalang mona. Sehlopha sena e tla ba sebaka se setle sa ho qala ka ho atolosa. A re bone hore na tlelase joang Bullet sebedisa Object:

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

Ts'ebetsong Bullet e khuts'oane haholo! Re ekelitse ho Object feela litlatsetso tse latelang:

  • Ho sebelisa sephutheloana kgutshoane bakeng sa moloko o sa reroang id projectile.
  • Ho eketsa lebala parentID, e le hore u ka latela sebapali se entseng projectile ena.
  • Ho eketsa boleng ba ho khutlisa ho update(), e lekanang true, haeba projectile e ka ntle ho lebala (hopola re buile ka sena karolong ea ho qetela?).

Ha re tsoeleng pele ho Player:

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

Libapali li rarahane ho feta projectiles, kahoo sehlopha sena se lokela ho boloka libaka tse ling tse 'maloa. Mokhoa oa hae update() e etsa mosebetsi o mongata, haholo-holo ho khutlisa projectile e sa tsoa etsoa haeba ho se na e setseng fireCooldown (hopola re buile ka sena karolong e fetileng?). E boetse e eketsa mokhoa serializeForUpdate(), hobane re hloka ho kenyelletsa masimo a eketsehileng bakeng sa sebapali ho ntlafatso ea papali.

Ho fumaneha ha sehlopha sa motheo Object - mohato oa bohlokoa ho qoba ho pheta-pheta khoutu. Ka mohlala, ntle le sehlopha Object ntho e 'ngoe le e' ngoe ea papali e tlameha ho ba le ts'ebetsong e tšoanang distanceTo(), 'me ho kopitsa-ho manamisa lits'ebetso tsena kaofela ho lifaele tse ngata e ka ba ntho e nyarosang. Sena se ba bohlokoa haholo bakeng sa merero e meholo, ha palo ea ho atolosa Object lihlopha li ntse li eketseha.

4. Ho lemoha ho thulana

Ntho feela e setseng hore re e etse ke ho lemoha ha projectiles e otla libapali! Hopola snippet ena ea khoutu ho tsoa mokhoeng update() ka tlelaseng Game:

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

    // ...
  }
}

Re hloka ho kenya ts'ebetsong mokhoa applyCollisions(), e khutlisang li-projectiles tsohle tse otlang libapali. Ka lehlohonolo, sena ha se thata ho se etsa hobane

  • Lintho tsohle tse thulang ke litikoloho, 'me sena ke sebopeho se bonolo ka ho fetisisa sa ho kenya ts'ebetsong ho lemoha ha ho thulana.
  • Re se re na le mokhoa distanceTo(), eo re e sebelisitseng sehlopheng sa karolo e fetileng Object.

Sena ke seo ts'ebetsong ea rona ea ho lemoha ha likhohlano e shebahalang ka eona:

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

Ho lemoha hona ho bonolo ha ho thulana ho ipapisitse le taba ea hore li-circles tse peli li thulana haeba sebaka se pakeng tsa litsi tsa tsona se le ka tlase ho kakaretso ea radii ea tsona. Mona ke boemo moo sebaka se pakeng tsa litsi tsa selikalikoe tse peli se lekanang hantle le kakaretso ea radii ea tsona:

Ho theha papali ea marang-rang ea libapali tse ngata ka mofuta oa .io
Mona o hloka ho ela hloko lintlha tse ling tse 'maloa:

  • Projectile ha ea lokela ho otla sebapali se e entseng. Sena se ka finyelloa ka ho bapisa bullet.parentID с player.id.
  • The projectile lokela ho otla hang feela tabeng e feteletseng ea ho otla libapali tse ngata ka nako e le 'ngoe. Re tla rarolla bothata bona re sebelisa opareitara break: Hang ha sebapali se thulana le projectile se fumanoa, re khaotsa ho batla ebe re fetela ho projectile e latelang.

Bofelo

Ke phetho! Re kentse ntho e 'ngoe le e 'ngoe eo u hlokang ho e tseba ho theha papali ea tepo ea .io. Ho latela eng? Iketsetse papali ea hau ea .io!

Khoutu eohle ea mohlala ke mohloli o bulehileng mme e ngotsoe ho eona Github.

Source: www.habr.com

Eketsa ka tlhaloso