Mamorona lalao Web Multiplayer .io

Mamorona lalao Web Multiplayer .io
Navoaka tamin'ny 2015 Agar.io lasa razamben'ny karazana vaovao lalao .ioizay nitombo ny lazany hatramin’izay. Izaho manokana dia niaina ny fisondrotan'ny lazan'ny lalao .io: tao anatin'ny telo taona lasa, dia nanana namorona sy namidy lalao roa amin'ity karazana ity..

Raha toa ka mbola tsy naheno momba an'ireo lalao ireo ianao teo aloha, dia lalao an-tranonkala Multiplayer maimaim-poana izay mora lalaovina (tsy mila kaonty). Matetika izy ireo no miatrika mpilalao mpanohitra maro ao amin'ny kianja iray ihany. Lalao .io malaza hafa: Slither.io ΠΈ Diep.io.

Amin'ity lahatsoratra ity dia hijery ny fomba mamorona lalao .io hatrany am-boalohany. Ho an'izany dia ampy ny fahalalana ny Javascript: mila mahatakatra zavatra toy ny syntax ianao ES6, teny fototra this ΠΈ fampanantenana. Na dia tsy tonga lafatra aza ny fahalalanao ny Javascript, dia mbola azonao ny ankamaroan'ny lahatsoratra.

.io lalao ohatra

Ho fanampiana amin'ny fianarana dia hiresaka momba ny .io lalao ohatra. Andramo milalao izany!

Mamorona lalao Web Multiplayer .io
Tsotra ny lalao: mifehy sambo ianao ao amin'ny kianja misy mpilalao hafa. Mandefa projectiles ho azy ny sambonao ary manandrana mamely mpilalao hafa ianao rehefa misoroka ny projectiles.

1. Fijery fohy / firafitry ny tetikasa

Manoro hevitra aho alaivo ny kaody loharano lalao ohatra mba ahafahanao manaraka ahy.

Ny ohatra dia mampiasa ireto manaraka ireto:

  • Express no rafitra tranonkala Node.js malaza indrindra izay mitantana ny mpizara tranonkalan'ny lalao.
  • socket.io - tranomboky websocket ahafahana mifanakalo angona eo amin'ny navigateur sy ny mpizara.
  • webpack - mpitantana ny module. Azonao atao ny mamaky momba ny antony hampiasana Webpack. eto.

Toy izao ny endriky ny firafitry ny lahatahiry tetikasa:

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

ampahibemaso/

Ao anaty lahatahiry ny zava-drehetra public/ dia hatolotry ny mpizara statika. IN public/assets/ misy sary ampiasain'ny tetikasanay.

src /

Ny kaody loharano rehetra dia ao anaty lahatahiry src/. anaram-boninahitra client/ ΠΈ server/ miteny ho azy ary shared/ misy rakitra constants izay nafaran'ny mpanjifa sy ny mpizara.

2. Fivoriambe/tetik'asa

Araka ny voalaza etsy ambony dia mampiasa ny mpitantana ny module izahay hananganana ny tetikasa. webpack. Andeha hojerentsika ny fandrindrana 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',
    }),
  ],
};

Ny andalana manan-danja indrindra eto dia:

  • src/client/index.js dia ny fidirana amin'ny mpanjifa Javascript (JS). Hanomboka eto ny Webpack ary hikaroka miverimberina ny rakitra hafa nafarana.
  • Ny famoahana JS amin'ny fananganana Webpack dia ho hita ao amin'ny lahatahiry dist/. Hantsoiko hoe anay ity rakitra ity js fonosana.
  • Mampiasa izahay Babel, ary indrindra ny fandrindrana @babel/preset-env mba handika ny kaody JS ho an'ny mpitety tranonkala taloha.
  • Mampiasa plugin izahay hanesorana ny CSS rehetra resahin'ny rakitra JS ary hanambatra azy ireo amin'ny toerana iray. Hantsoiko hoe antsika izy css fonosana.

Mety ho nahatsikaritra anarana fichier hafahafa ianao '[name].[contenthash].ext'. Izy ireo dia misy fanoloana anarana fonosana web: [name] dia hosoloina amin'ny anaran'ny teboka fampidirana (amin'ity tranga ity, ity game), ary [contenthash] hosoloina hash amin'ny votoatin'ny rakitra. Manao izany izahay manatsara ny tetikasa ho an'ny hashing - azonao atao ny milaza amin'ny mpitety tranonkala mba hametaka ny fonosana JS anay mandritra ny fotoana tsy voafetra, satria raha miova ny fonosana dia miova koa ny anaran'ny rakitra (fiovana contenthash). Ny vokatra farany dia ny anaran'ny rakitra fijerena game.dbeee76e91a97d0c7207.js.

rakitra webpack.common.js dia ny rakitra fanamafisam-peo fototra izay ampidirintsika ao amin'ny famolavolana tetikasa fampandrosoana sy vita. Ity misy ohatra iray amin'ny configuration development:

webpack.dev.js

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

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

Ho an'ny fahombiazana dia ampiasainay amin'ny dingana fampandrosoana webpack.dev.js, ary mifamadika amin'ny webpack.prod.jshanatsara ny haben'ny fonosana rehefa apetraka amin'ny famokarana.

Toerana eo an-toerana

Manoro hevitra ny fametrahana ny tetikasa amin'ny milina eo an-toerana ianao mba hahafahanao manaraka ireo dingana voatanisa ato amin'ity lahatsoratra ity. Tsotra ny fametrahana: voalohany, tsy maintsy napetraka ny rafitra node ΠΈ NPM. Manaraka izay mila ataonao

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

ary vonona ny handeha ianao! Mba hanombohana ny lohamilina fampandrosoana, mandehana fotsiny

$ npm run develop

ary mandehana amin'ny navigateur web localhost: 3000. Ny lohamilina fampandrosoana dia hanangana ho azy ny fonosana JS sy CSS rehefa miova ny kaody - havaozy fotsiny ny pejy hahitana ny fiovana rehetra!

3. Teboka fidirana amin'ny mpanjifa

Andao hidina any amin'ny kaody lalao mihitsy. Voalohany dia mila pejy isika index.html, rehefa mitsidika ilay tranokala dia ny navigateur no hampiditra azy voalohany. Ny pejy dia ho tsotra be:

index.html

Ohatra .io lalao  MILAY

Ity ohatra kaody ity dia nohamafisina kely mba hanazavana, ary hanao toy izany koa aho amin'ny ohatra maro hafa. Ny kaody feno dia azo jerena amin'ny Github.

Manana isika:

  • Element canvas HTML5 (<canvas>) izay hampiasainay hanaovana ny lalao.
  • <link> hanampy ny fonosana CSS.
  • <script> hanampiana ny fonosanay Javascript.
  • Main menu misy solonanarana <input> ary ny bokotra PLAY (<button>).

Aorian'ny famenoana ny pejy fandraisana, ny navigateur dia manomboka manatanteraka ny code Javascript, manomboka amin'ny rakitra JS fidirana: 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);
  };
});

Mety ho sarotra izany, saingy tsy dia misy dikany loatra izany:

  1. Manafatra rakitra JS maro hafa.
  2. Fanafarana CSS (mba hain'ny Webpack ny hampiditra azy ireo ao anatin'ny fonosana CSS).
  3. fandefasana connect() hanorina fifandraisana amin'ny mpizara ary mihazakazaka downloadAssets() mba tΓ©lΓ©charger sary ilaina handikana ny lalao.
  4. Rehefa vita ny dingana 3 ny sakafo fototra dia aseho (playMenu).
  5. Fametrahana ny mpitantana amin'ny fanindriana ny bokotra "PLAY". Rehefa voatsindry ny bokotra dia manomboka ny lalao ny code ary milaza amin'ny mpizara fa vonona ny hilalao isika.

Ny "hena" lehibe amin'ny lojikan'ny mpizara mpanjifanay dia ao amin'ireo rakitra nafaran'ny rakitra index.js. Hodinihintsika araka ny filaharany izy rehetra izao.

4. Fifanakalozana ny angona mpanjifa

Amin'ity lalao ity dia mampiasa tranomboky malaza izahay hifandraisana amin'ny mpizara socket.io. Socket.io dia manana fanohanana teratany faladia web, izay mifanentana tsara amin'ny fifandraisana roa: afaka mandefa hafatra amin'ny mpizara izahay ΠΈ ny mpizara dia afaka mandefa hafatra aminay amin'ny fifandraisana mitovy.

Hanana rakitra iray isika src/client/networking.jsiza no hikarakara Ny olon-drehetra fifandraisana amin'ny mpizara:

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

Nohafohezina kely ihany koa ity kaody ity mba hanazavana.

Misy hetsika telo lehibe amin'ity rakitra ity:

  • Miezaka mifandray amin'ny mpizara izahay. connectedPromise navela ihany rehefa nametraka fifandraisana.
  • Raha mahomby ny fifandraisana, dia misoratra anarana amin'ny antso miverina (processGameUpdate() ΠΈ onGameOver()) ho an'ny hafatra azontsika raisina avy amin'ny mpizara.
  • Manondrana izahay play() ΠΈ updateDirection()mba ahafahan'ny rakitra hafa mampiasa azy ireo.

5. Fanolorana mpanjifa

Fotoana izao hanehoana ny sary eo amin'ny efijery!

…fa alohan'ny hanaovana izany dia mila misintona ny sary rehetra (loharanom-baovao) ilaina amin'izany isika. Andao hanoratra mpitantana loharano:

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

Tsy sarotra ny mampihatra ny fitantanana ny loharanon-karena! Ny hevi-dehibe dia ny fitahirizana zavatra iray assets, izay hamatotra ny fanalahidin'ny anaran-drakitra amin'ny sandan'ny zavatra Image. Rehefa feno ny loharanon-karena dia tehirizinay ao anaty zavatra iray assets ho an'ny fidirana haingana amin'ny ho avy. Rahoviana ny loharano tsirairay no avela hisintona (izany hoe, ny rehetra loharano), mamela izahay downloadPromise.

Aorian'ny fampidinana ireo loharanon-karena dia afaka manomboka mamadika ianao. Araka ny voalaza tetsy aloha, ny manao sary amin'ny pejy web dia mampiasa HTML5 Canvas (<canvas>). Tsotra ny lalaontsika, ka ireto manaraka ireto ihany no ilaintsika:

  1. lafika
  2. Sambo mpilalao
  3. Mpilalao hafa amin'ny lalao
  4. akorandriaka

Ireto ny sombintsombiny manan-danja src/client/render.js, izay mandika marina ireo singa efatra voalaza etsy ambony:

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

Ity kaody ity dia nohafohezina ihany koa mba ho mazava.

render() no tena fiasan'ity rakitra ity. startRendering() ΠΈ stopRendering() mifehy ny fampahavitrihana ny render loop amin'ny 60 FPS.

Fampiharana mivaingana amin'ny asan'ny mpanampy famandrihana tsirairay (oh. renderBullet()) dia tsy dia zava-dehibe, fa ity misy ohatra tsotra:

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

Mariho fa mampiasa ny fomba izahay getAsset(), izay hita teo aloha tao asset.js!

Raha liana amin'ny fijerena ireo mpanampy amin'ny fandikana hafa ianao dia vakio ny ambiny src/client/render.js.

6. Fidiran'ny mpanjifa

Fotoana hanaovana lalao izao azo lalaovina! Ny rafitra fanaraha-maso dia ho tena tsotra: hanova ny tari-dalana ny hetsika, dia afaka mampiasa ny totozy (amin'ny solosaina) na mikasika ny efijery (amin'ny finday fitaovana). Ho fampiharana izany dia hisoratra anarana izahay Mpihaino hetsika ho an'ny hetsika Mouse sy Touch.
Hikarakara izany rehetra izany 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() dia Event Listeners izay miantso updateDirection() (avy amin'ny networking.js) rehefa misy hetsika fampidirana (ohatra, rehefa mihetsika ny totozy). updateDirection() mitantana hafatra miaraka amin'ny mpizara, izay mitantana ny hetsika fampidirana ary manavao ny toetry ny lalao mifanaraka amin'izany.

7. Toetran'ny mpanjifa

Ity fizarana ity no sarotra indrindra amin'ny ampahany voalohany amin'ny lahatsoratra. Aza kivy raha tsy azonao ilay izy tamin'ny fotoana voalohany namakianao azy! Azonao atao mihitsy aza ny mandingana azy io ary miverina amin'ny manaraka.

Ny ampahany farany amin'ny piozila ilaina hamenoana ny code client/server dia fanjakana. Tadidinao ny sombin-kaody avy amin'ny fizarana Client Rendering?

render.js

import { getCurrentState } from './state';

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

  // Do the rendering
  // ...
}

getCurrentState() tokony ho afaka hanome antsika ny toetry ny lalao amin'izao fotoana izao ao amin'ny mpanjifa amin'ny fotoana rehetra mifototra amin'ny fanavaozana azo avy amin'ny mpizara. Ity misy ohatra amin'ny fanavaozana lalao azon'ny mpizara alefa:

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

Ny fanavaozana lalao tsirairay dia misy saha dimy mitovy:

  • t: Tombom-potoanan'ny mpizara manondro ny fotoana namoronana ity fanavaozana ity.
  • me: Fampahalalana momba ny mpilalao mahazo ity fanavaozana ity.
  • ny hafa: Fampahalalana maromaro momba ny mpilalao hafa mandray anjara amin'ny lalao mitovy.
  • ny bala: andiana fampahalalana momba ny projectiles amin'ny lalao.
  • Leaderboard: Angon-drakitra eo amin'ny solaitrabe ankehitriny. Amin'ity lahatsoratra ity dia tsy handinika azy ireo isika.

7.1 Toetran'ny mpanjifa naive

Fampiharana tsy misy dikany getCurrentState() afaka mamerina mivantana ny angon-drakitra momba ny fanavaozana lalao vao haingana voaray.

naive-state.js

let lastGameUpdate = null;

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

export function getCurrentState() {
  return lastGameUpdate;
}

Tsara sady mazava! Fa raha mba tsotra ihany izany. Ny iray amin'ireo antony mahatonga ity fampiharana ity dia olana: mametra ny tahan'ny fandrafetana amin'ny tahan'ny famantaranandron'ny mpizara izany.

Frame Rate: isan'ny frames (izany hoe antso render()) isan-tsegondra, na FPS. Matetika ny lalao dia miezaka hahatratra 60 FPS farafahakeliny.

Tick ​​Rate: Ny faharetan'ny mpizara mandefa fanavaozana lalao amin'ny mpanjifa. Matetika izy io dia ambany noho ny tahan'ny frame. Ao amin'ny lalaontsika, ny mpizara dia mandeha amin'ny 30 cycles isan-tsegondra.

Raha manome ny fanavaozana farany amin'ny lalao fotsiny isika, dia tsy hihoatra ny 30 ny FPS, satria Tsy mahazo fanavaozana mihoatra ny 30 isan-tsegondra avy amin'ny mpizara izahay. Na dia miantso aza izahay render() In-60 isan-tsegondra, avy eo ny antsasak'ireo antso ireo dia hamerina hanao zavatra mitovy, tsy manao na inona na inona. Ny olana iray hafa amin'ny fampiharana tsy misy dikany dia izany mora tara. Miaraka amin'ny hafainganam-pandeha tsara indrindra amin'ny Internet, ny mpanjifa dia hahazo fanavaozana lalao isaky ny 33ms (30 isan-tsegondra):

Mamorona lalao Web Multiplayer .io
Indrisy fa tsy misy tonga lafatra. Ny sary tena misy kokoa dia mety:
Mamorona lalao Web Multiplayer .io
Ny fampiharana tsy misy dikany dia saika ny tranga ratsy indrindra amin'ny resaka latency. Raha misy fanavaozana lalao voaray miaraka amin'ny fahatarana 50ms, dia toeram-pivarotana mpanjifa 50ms fanampiny satria mbola mamerina ny toetry ny lalao tamin'ny fanavaozana teo aloha. Azonao alaina sary an-tsaina hoe tsy mahazo aina ho an'ny mpilalao izany: ny braking tsy misy dikany dia hahatonga ny lalao hahatsiaro ho mihozongozona sy tsy milamina.

7.2 Fanatsarana ny toetry ny mpanjifa

Hanao fanatsarana vitsivitsy amin'ny fampiharana tsy misy dikany izahay. Voalohany, mampiasa izahay fanemorana ny famoahana ho 100ms. Midika izany fa ny toetry ny "ankehitriny" an'ny mpanjifa dia hihemotra hatrany amin'ny 100ms amin'ny toetry ny lalao eo amin'ny server. Ohatra, raha ny fotoana eo amin'ny mpizara dia 150, avy eo ny mpanjifa dia hanolotra ny fanjakana nisy ny mpizara tamin'izany fotoana izany 50:

Mamorona lalao Web Multiplayer .io
Izany dia manome antsika buffer 100ms mba ho tafavoaka velona amin'ny fotoana fanavaozana lalao tsy ampoizina:

Mamorona lalao Web Multiplayer .io
Haharitra ny tambin-karama amin'izany fidirana lag ho 100ms. Sorona kely ho an'ny lalao milamina izany - ny ankamaroan'ny mpilalao (indrindra ny mpilalao mahazatra) dia tsy hahatsikaritra izany fahatarana izany. Mora kokoa ho an'ny olona ny manitsy amin'ny fahatarana 100ms tsy miova noho ny milalao amin'ny fahatarana tsy ampoizina.

Afaka mampiasa teknika hafa antsoina koa isika vinavina amin'ny lafiny mpanjifa, izay miasa tsara amin'ny fampihenana ny fahatarana heverina fa tsy horesahina ato amin'ity lahatsoratra ity.

Ny fanatsarana iray hafa ampiasaintsika dia interpolation linear. Noho ny fahatarana amin'ny famoahana, matetika isika dia farafaharatsiny iray farafahakeliny mialoha ny fotoana ankehitriny amin'ny mpanjifa. Rehefa antsoina getCurrentState(), azontsika atao interpolation linear eo anelanelan'ny fanavaozana lalao alohan'ny sy aorian'ny fotoana ankehitriny amin'ny mpanjifa:

Mamorona lalao Web Multiplayer .io
Mamaha ny olan'ny tahan'ny frame izany: afaka manolotra sary tokana amin'izay tahan'ny frame tiantsika isika izao!

7.3 Fampiharana ny fanjakana mpanjifa nohatsaraina

Ohatra fampiharana amin'ny src/client/state.js dia mampiasa render lag sy linear interpolation, fa tsy maharitra. Andeha hozaraina roa ny kaody. Ity ny voalohany:

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

Ny dingana voalohany dia ny mamantatra hoe inona currentServerTime(). Araka ny efa hitantsika teo aloha, ny fanavaozana lalao rehetra dia misy marika famantarana famantaranandro. Te-hampiasa render latency izahay hamerenana ny sary 100ms ao ambadiky ny mpizara, fa tsy ho fantatsika na oviana na oviana ny ora amin'izao fotoana izao amin'ny mpizara, satria tsy fantatray hoe hafiriana vao tonga tany aminay ny iray amin'ireo fanavaozana. Tsy azo vinaniana ny Internet ary mety miovaova be ny hafainganam-pandehany!

Mba hamahana ity olana ity dia afaka mampiasa tombantombana mety isika: isika mody tonga avy hatrany ny fanavaozana voalohany. Raha marina izany dia ho fantatsika ny fotoanan'ny mpizara amin'izao fotoana izao! Mitahiry ny famantaranandron'ny mpizara izahay firstServerTimestamp ary tazomy ny anay an-toerana (mpanjifa) famantaranandro amin'ny fotoana mitovy gameStart.

Oh andraso. Tsy tokony ho fotoanan'ny mpizara = fotoanan'ny mpanjifa? Nahoana isika no manavaka ny "vanim-potoanan'ny mpizara" sy ny "tombom-potoanan'ny mpanjifa"? Fanontaniana lehibe izany! Hita fa tsy mitovy izy ireo. Date.now() dia hamerina ny famantaranandro samy hafa ao amin'ny mpanjifa sy ny mpizara, ary miankina amin'ny anton-javatra eo an-toerana amin'ireo milina ireo. Aza mihevitra mihitsy fa hitovy ny famantaranandro amin'ny milina rehetra.

Ankehitriny dia azontsika ny atao currentServerTime(): miverina ny famantaranandron'ny mpizara amin'ny fotoana fandefasana ankehitriny. Amin'ny teny hafa, izao no fotoanan'ny mpizara (firstServerTimestamp <+ (Date.now() - gameStart)) minus fanemorana (RENDER_DELAY).

Andeha hojerentsika ny fomba fitantanana ny fanavaozana lalao. Rehefa voaray avy amin'ny mpizara fanavaozana dia antsoina izy io processGameUpdate()ary tehirizinay amin'ny array ny fanavaozana vaovao gameUpdates. Avy eo, mba hanamarinana ny fampiasana fahatsiarovana, dia esorinay ny fanavaozana taloha rehetra teo aloha fanavaozana fototrasatria tsy mila azy ireo intsony isika.

Inona no atao hoe "fanavaozana fototra"? izany ny fanavaozana voalohany hitantsika amin'ny alΓ lan'ny fihemorana amin'ny fotoanan'ny mpizara ankehitriny. Tadidinao ity kisary ity?

Mamorona lalao Web Multiplayer .io
Ny fanavaozana ny lalao mivantana eo ankavian'ny "Fotoan'ny mpanjifa Render" dia ny fanavaozana fototra.

Inona no ampiasaina amin'ny fanavaozana fototra? Nahoana isika no afaka manafoana ny fanavaozam-baovao amin'ny fototra? Mba hamantarana izany, andao Farany diniho ny fampiharana getCurrentState():

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

Mikarakara tranga telo izahay:

  1. base < 0 midika fa tsy misy fanavaozana mandra-pahatongan'ny fotoana fandefasana ankehitriny (jereo ny fampiharana etsy ambony getBaseUpdate()). Mety hitranga eo am-piandohan'ny lalao izany noho ny fahatarana amin'ny famoahana. Amin'ity tranga ity, mampiasa ny fanavaozana farany voaray izahay.
  2. base dia ny fanavaozana farany ananantsika. Mety noho ny fahataran'ny tambajotra na ny faharatsian'ny fifandraisana Internet. Amin'ity tranga ity dia mampiasa ny fanavaozana farany ananantsika ihany koa izahay.
  3. Manana fanavaozam-baovao izahay mialoha sy aorian'ny fotoana fandefasana ankehitriny, ka afaka manao izany izahay interpolate!

Izay rehetra tavela ao state.js dia fampiharana ny interpolation linear izay matematika tsotra (fa mankaleo). Raha te hijery azy ianao dia sokafy state.js amin'ny Github.

Fizarana 2. Backend server

Amin'ity ampahany ity dia hojerentsika ny backend Node.js izay mifehy ny antsika .io lalao ohatra.

1. Toerana fidirana amin'ny mpizara

Mba hitantana ny mpizara tranonkala dia hampiasa rafitra tranonkala malaza antsoina hoe Node.js izahay Express. Izy io dia hamboarina amin'ny alΓ lan'ny rakitra fidirana amin'ny servery src/server/server.js:

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

Tadidinao fa tamin'ny tapany voalohany dia niresaka momba ny Webpack isika? Eto no hampiasantsika ny fandrindrana Webpack. Hampiasa azy ireo amin'ny fomba roa isika:

  • Ampiasao webpack-dev-middleware mba hanorina ho azy indray ny fonosana fampandrosoana, na
  • static transfer folder dist/, izay hanoratan'ny Webpack ny rakitray aorian'ny fananganana famokarana.

Asa lehibe iray hafa server.js dia ny fametrahana ny mpizara socket.ioizay mifandray fotsiny amin'ny mpizara Express:

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

Rehefa vita ny fametrahana fifandraisana socket.io amin'ny mpizara dia nanangana mpitantana hetsika ho an'ny socket vaovao izahay. Ny mpitantana hetsika dia mitantana hafatra voaray avy amin'ny mpanjifa amin'ny alΓ lan'ny fanolorana zavatra tokana game:

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

Mamorona lalao .io izahay, ka dika iray ihany no ilainay Game ("Lalao") - milalao ao amin'ny kianja iray ihany ny mpilalao rehetra! Ho hitantsika ao amin'ny fizarana manaraka ny fomba fiasan'ity kilasy ity. Game.

2. Mpizara lalao

kilasy Game misy ny lojika manan-danja indrindra eo amin'ny lafiny mpizara. Manana asa roa lehibe izy io: fitantanana mpilalao ΠΈ lalao simulation.

Andeha isika hanomboka amin'ny asa voalohany, ny fitantanana ny mpilalao.

lalao.js ampahany 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);
    }
  }

  // ...
}

Amin'ity lalao ity dia hamantatra ireo mpilalao amin'ny alΓ lan'ny kianja id ny socket.io socket (raha very hevitra ianao dia miverena any server.js). Socket.io mihitsy no manome ny socket tsirairay ho tokana idka tsy mila manahy momba izany isika. hiantso azy aho ID mpilalao.

Miaraka amin'izany ao an-tsaina, andeha hojerentsika ny fari-pahaizana ohatra ao anaty kilasy Game:

  • sockets dia zavatra mamatotra ny ID mpilalao amin'ny socket izay mifandray amin'ny mpilalao. Izany dia ahafahantsika miditra amin'ny socket amin'ny alΓ lan'ny ID mpilalao ao anatin'ny fotoana fohy.
  • players dia zavatra mamatotra ny ID mpilalao amin'ny kaody>object Player

bullets dia fitambaran-javatra Bullet, izay tsy misy filaharana voafaritra.
lastUpdateTime dia ny famantarana ny fotoana farany nanavaozana ny lalao. Ho hitantsika tsy ho ela ny fampiasana azy.
shouldSendUpdate dia faribolana fanampiny. Ho hitantsika ihany koa ny fampiasana azy tsy ho ela.
fomba addPlayer(), removePlayer() ΠΈ handleInput() tsy mila hazavaina fa ampiasaina ao server.js. Raha mila mamelombelona ny fitadidianao ianao dia miverena ambony kely.

Andalana farany constructor() manomboka tsingerina fanavaozana lalao (miaraka amin'ny fanavaozana 60 / s matetika):

lalao.js ampahany 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;
    }
  }

  // ...
}

fomba update() misy angamba ny ampahany manan-danja indrindra amin'ny lojika amin'ny lafiny server. Izao no ataony, araka ny filaharany:

  1. Kajy ny halavany dt lasa hatramin'ny farany update().
  2. Mamelombelona ny projectile tsirairay ary manimba azy ireo raha ilaina. Ho hitantsika eo ny fampiharana io fampiasa io. Amin'izao fotoana izao dia ampy ho antsika ny mahafantatra izany bullet.update() miverina trueraha tokony ho potika ny projectile (nivoaka ny kianja izy).
  3. Manavao ny mpilalao tsirairay ary mamoaka projectile raha ilaina. Ho hitantsika ihany koa izany fampiharana izany any aoriana βˆ’ player.update() afaka mamerina zavatra iray Bullet.
  4. Manamarina ny fifandonana eo amin'ny projectiles sy ny mpilalao amin'ny applyCollisions(), izay mamerina andiana projectiles izay namely ny mpilalao. Ho an'ny projectile tsirairay miverina dia ampitomboinay ny isan'ny mpilalao nitifitra azy (mampiasa player.onDealtDamage()) ary esory avy eo ny projectile amin'ny array bullets.
  5. Mampandre sy mamotika ireo mpilalao maty rehetra.
  6. Mandefa fanavaozana lalao amin'ny mpilalao rehetra isaky ny segondra fotoana niantsoana update(). Izany dia manampy antsika hanara-maso ny fari-piainana fanampiny voalaza etsy ambony. shouldSendUpdate. SATRIA update() antsoina in-60/s, mandefa fanavaozana lalao in-30/s izahay. Noho izany, matetika ny famantaranandro Ny famantaranandro mpizara dia famantaranandro 30/s (niresaka momba ny tahan'ny famantaranandro izahay tamin'ny ampahany voalohany).

Nahoana no mandefa fanavaozana lalao ihany mandritra ny fotoana ? Mba hamonjy ny fantsona. Fanavaozana lalao 30 isan-tsegondra dia betsaka!

Maninona raha miantso fotsiny update() in-30 isan-tsegondra? Mba hanatsarana ny lalao simulation. Ny antsoina matetika kokoa update(), dia ho marina kokoa ny lalao simulation. Fa aza variana loatra amin'ny isan'ny fanamby. update(), satria asa lafo vidy io - 60 isan-tsegondra dia ampy.

Ny ambiny amin'ny kilasy Game dia ahitana fomba mpanampy ampiasaina amin'ny update():

lalao.js ampahany 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() tena tsotra - manasokajy ny mpilalao amin'ny isa, maka ny dimy voalohany, ary mamerina ny solon'anarana sy isa ho an'ny tsirairay.

createUpdate() ampiasaina amin'ny update() mba hamoronana fanavaozana lalao izay zaraina amin'ny mpilalao. Ny tena asany dia ny miantso ny fomba serializeForUpdate()ampiharina ho an'ny kilasy Player ΠΈ Bullet. Marihina fa mampita angon-drakitra ho an'ny mpilalao tsirairay fotsiny izy io akaiky indrindra mpilalao sy projectiles - tsy ilaina ny mampita vaovao momba ny lalao zavatra izay lavitra ny mpilalao!

3. Zavatra lalao eo amin'ny mpizara

Amin'ny lalaontsika, ny projectiles sy ny mpilalao dia tena mitovy: zavatra abstract, boribory, azo mihetsika. Mba hanararaotra izany fitoviana eo amin'ny mpilalao sy ny projectiles, andao hanomboka amin'ny fampiharana ny kilasy fototra Object:

object.js

class Object {
  constructor(id, x, y, dir, speed) {
    this.id = id;
    this.x = x;
    this.y = y;
    this.direction = dir;
    this.speed = speed;
  }

  update(dt) {
    this.x += dt * this.speed * Math.sin(this.direction);
    this.y -= dt * this.speed * Math.cos(this.direction);
  }

  distanceTo(object) {
    const dx = this.x - object.x;
    const dy = this.y - object.y;
    return Math.sqrt(dx * dx + dy * dy);
  }

  setDirection(dir) {
    this.direction = dir;
  }

  serializeForUpdate() {
    return {
      id: this.id,
      x: this.x,
      y: this.y,
    };
  }
}

Tsy misy zavatra sarotra mitranga eto. Ity kilasy ity dia ho toerana vatofantsika tsara ho an'ny fanitarana. Andeha hojerentsika hoe ahoana ny kilasy Bullet fampiasana 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;
  }
}

fanatanterahana Bullet fohy be! Nampianay ny Object ireto fanitarana ireto ihany:

  • Mampiasa fonosana shortid ho an'ny taranaka kisendrasendra id projectile.
  • Manampy saha parentIDmba hahafahanao manara-maso ny mpilalao namorona ity projectile ity.
  • Manampy sanda miverina amin'ny update(), izay mitovy amin'ny trueraha any ivelan'ny kianja ny projectile (tadidio ve fa niresaka an'io tamin'ny fizarana farany isika?).

Andao hiroso amin'ny Player:

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

Ny mpilalao dia sarotra kokoa noho ny projectiles, noho izany dia tokony hotehirizina amin'ity kilasy ity ny saha vitsivitsy. Ny fombany update() manao asa be, indrindra fa mamerina ilay projectile vao noforonina raha tsy misy tavela fireCooldown (tadidio ve ny niresaka an'io tamin'ny fizarana teo aloha?). Manitatra ny fomba fiasa koa izany serializeForUpdate(), satria mila mampiditra saha fanampiny ho an'ny mpilalao amin'ny fanavaozana ny lalao.

Manana kilasy fototra Object - dingana iray manan-danja mba hisorohana ny famerenana ny code. Ohatra, tsy misy kilasy Object ny zavatra lalao rehetra dia tsy maintsy manana fampiharana mitovy distanceTo(), ary ny fanaovana kopia mametaka ireo fampiharana rehetra ireo amin'ny rakitra maro dia ho nofy ratsy. Izany dia lasa zava-dehibe indrindra ho an'ny tetikasa lehibe.rehefa ny isan`ny fanitarana Object mitombo ny kilasy.

4. Fikarohana fifandonana

Ny hany sisa ho antsika dia ny mahafantatra hoe rahoviana ny projectiles no namely ny mpilalao! Tsarovy ity sombin-kaody avy amin'ny fomba ity update() ao an-dakilasy Game:

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

    // ...
  }
}

Mila mampihatra ny fomba isika applyCollisions(), izay mamerina ny projectiles rehetra izay namely ny mpilalao. Soa ihany fa tsy sarotra ny manao izany satria

  • Ny zavatra mifandona rehetra dia faribolana, izay endrika tsotra indrindra hampiharana ny fifandonana.
  • Efa manana fomba fiasa isika distanceTo(), izay nampiharinay tamin'ny fizarana teo aloha tao amin'ny kilasy Object.

Toy izao ny fomba fampiharana ny fitiliana fifandonana:

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

Ity fitadiavana fifandonana tsotra ity dia mifototra amin'ny zava-misy izay mifandona ny faribolana roa raha kely noho ny isan'ny radiiny ny elanelan'ny foibeny. Toy izao ny elanelana misy eo amin'ny ivon'ny faribolana roa dia mitovy tanteraka amin'ny fitambaran'ny radiiny:

Mamorona lalao Web Multiplayer .io
Misy lafiny roa hafa tokony hodinihina eto:

  • Ny projectile dia tsy tokony hitifitra ilay mpilalao namorona azy. Izany dia azo atao amin'ny fampitahana bullet.parentID с player.id.
  • Ny projectile dia tsy maintsy mamely indray mandeha ihany raha misy mpilalao marobe mifandona amin'ny fotoana iray ihany. Hamaha ity olana ity izahay amin'ny fampiasana ny opΓ©rateur break: Raha vao hita ilay mpilalao nifandona tamin'ny projectile dia atsahatra ny fikarohana ary mandroso amin'ny projectile manaraka.

The End

Izay ihany! Nohazavainay ny zavatra rehetra tokony ho fantatrao mba hamoronana lalao web .io. Inona ny manaraka? Manamboara lalao .io anao manokana!

Ny kaody santionany rehetra dia loharano misokatra ary apetraka amin'ny Github.

Source: www.habr.com

Add a comment