Эҷоди веб-бозии мултипликатори .io

Эҷоди веб-бозии мултипликатори .io
Дар соли 2015 бароварда шудааст Агар.ио пешвои жанри нав гардид бозиҳо .io, ки аз он вакт инчониб шухраташ хеле афзуд. Ман худам афзоиши маъруфияти бозиҳои .io-ро эҳсос кардам: дар тӯли се соли охир, ман ду бозии ин жанр офарида ва фурӯхта шуд..

Агар шумо қаблан ҳеҷ гоҳ дар бораи ин бозиҳо нашунида бошед, онҳо бепул ва бозиҳои веби мултипликатори мебошанд, ки бозӣ кардан осон аст (ҳеҷ ҳисоб лозим нест). Онҳо одатан бисёр бозигарони мухолифро дар як майдон мегузоранд. Дигар бозиҳои машҳури .io: Сомонӣ и Diep.io.

Дар ин мақола мо мефаҳмем, ки чӣ тавр аз сифр бозии .io эҷод кунед. Барои ин танҳо дониши Javascript кифоя хоҳад буд: шумо бояд чизҳоеро ба мисли синтаксис бифаҳмед ES6, калимаи калидӣ this и Ваъдаҳо. Ҳатто агар дониши шумо дар бораи Javascript комил набошад ҳам, шумо ба ҳар ҳол метавонед қисми зиёди паёмро дарк кунед.

Намунаи бозии .io

Барои кӯмак ба омӯзиш мо ба он муроҷиат хоҳем кард Намунаи бозии .io. Кӯшиш кунед, ки онро бозӣ кунед!

Эҷоди веб-бозии мултипликатори .io
Бозӣ хеле содда аст: шумо киштиро дар аренае идора мекунед, ки дар он бозигарони дигар ҳастанд. Киштии шумо ба таври худкор снарядҳоро партоб мекунад ва шумо кӯшиш мекунед, ки ба бозигарони дигар зарба занед ва аз снарядҳои онҳо канорагирӣ кунед.

1. Шарҳи мухтасар / сохтори лоиҳа

Тавсия рамзи сарчашмаро зеркашӣ кунед мисоли бозӣ, то шумо метавонед ба ман пайравӣ кунед.

Мисоли зеринро истифода мебарад:

  • изҳор маъмултарин чаҳорчӯбаи веб Node.js аст, ки сервери веби бозиро идора мекунад.
  • socket.io - китобхонаи websocket барои мубодилаи маълумот байни браузер ва сервер.
  • Веб-саҳифа - мудири модул. Шумо метавонед дар бораи чӣ истифода бурдани Webpack хонед дар ин ҷо.

Ин аст сохтори директорияи лоиҳа чӣ гуна аст:

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

ҷамъиятӣ/

Ҳама чиз дар папка аст public/ аз ҷониби сервер статикӣ интиқол дода мешавад. ДАР public/assets/ дорои тасвирҳое мебошад, ки лоиҳаи мо истифода кардааст.

src /

Ҳама рамзи сарчашма дар ҷузвдон аст src/. Унвонҳо client/ и server/ барои худ сухан меронанд ва shared/ дорои файли доимӣ мебошад, ки ҳам аз ҷониби муштарӣ ва ҳам сервер ворид карда мешавад.

2. Маҷлисҳо/танзимоти лоиҳа

Тавре ки дар боло зикр гардид, мо менеҷери модулро барои сохтани лоиҳа истифода мебарем. Веб-саҳифа. Биёед конфигуратсияи 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',
    }),
  ],
};

Муҳимтарин сатрҳо дар ин ҷо инҳоянд:

  • src/client/index.js нуқтаи вуруди муштарии Javascript (JS) мебошад. Webpack аз ин ҷо оғоз мешавад ва ба таври рекурсивӣ файлҳои дигари воридшударо ҷустуҷӯ мекунад.
  • Натиҷаи JS-и сохтани Webpack мо дар директория ҷойгир хоҳад шуд dist/. Ман ин файлро мо меномам js баста.
  • Мо истифода мебарем Бабел, ва махсусан конфигуратсия @babel/preset-env Барои интиқол додани рамзи JS-и мо барои браузерҳои кӯҳна.
  • Мо плагинро истифода мебарем, то ҳамаи CSS-ҳои аз ҷониби файлҳои JS истинодшуда истинод карда шаванд ва онҳоро дар як ҷо муттаҳид созем. Ман онро азони мо хоҳам гуфт бастаи css.

Шумо шояд номҳои файлҳои бастаи аҷибро мушоҳида карда бошед '[name].[contenthash].ext'. Онҳо дорои иваз кардани номи файл Веб-пакет: [name] бо номи нуқтаи вуруд иваз карда мешавад (дар ҳолати мо ин аст game), ва [contenthash] бо хэши мундариҷаи файл иваз карда мешавад. Мо инро мекунем оптимизатсия кардани лоиҳа барои ҳашинг - шумо метавонед ба браузерҳо бигӯед, ки бастаҳои JS-и моро ба таври номуайян кэш кунанд, зеро агар баста тағир ёбад, номи файли он низ тағир меёбад (тағйирот contenthash). Натиҷаи ниҳоӣ номи файли намоиш хоҳад буд game.dbeee76e91a97d0c7207.js.

файл webpack.common.js файли конфигуратсияи асосӣ мебошад, ки мо ба конфигуратсияҳои лоиҳаи таҳия ва анҷомёфта ворид мекунем. Масалан, ин аст конфигуратсияи таҳия:

webpack.dev.js

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

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

Барои самаранокӣ, мо дар раванди рушд истифода мебарем webpack.dev.js, ва ба он мегузарад webpack.prod.jsбарои оптимизатсия кардани андозаи бастаҳо ҳангоми ҷойгиркунӣ дар истеҳсолот.

Танзимоти маҳаллӣ

Ман тавсия медиҳам, ки лоиҳаро дар як мошини маҳаллӣ насб кунед, то шумо метавонед қадамҳои дар ин паём номбаршударо иҷро кунед. Танзимот оддӣ аст: аввал, система бояд насб карда шавад Нод и NPM. Минбаъд шумо бояд кор кунед

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

ва шумо ба рафтан омодаед! Барои оғоз кардани сервери рушд, танҳо иҷро кунед

$ npm run develop

ва ба браузери веб равед localhost: 3000. Сервери таҳия ба таври худкор бастаҳои JS ва CSS-ро аз нав месозад, вақте ки тағйироти кодҳо ба амал меоянд - танҳо саҳифаро нав кунед, то ҳамаи тағиротҳоро бубинед!

3. Нуқтаҳои вуруди муштарӣ

Биёед ба худи рамзи бозӣ биравем. Аввал ба мо як саҳифа лозим аст index.html, ҳангоми боздид аз сайт, браузер аввал онро бор мекунад. Саҳифаи мо хеле содда хоҳад буд:

index.html

Намунаи бозии .io  БОЗӢ

Ин мисоли рамзӣ барои возеҳӣ каме содда карда шудааст ва ман бо бисёре аз мисолҳои дигари пост низ ҳамин тавр мекунам. Рамзи пурраро ҳамеша дар он дидан мумкин аст Github.

Мо дорем:

  • Унсури рони HTML5 (<canvas>), ки мо барои намоиш додани бозӣ истифода хоҳем кард.
  • <link> барои илова кардани бастаи CSS-и мо.
  • <script> барои илова кардани бастаи Javascript мо.
  • Менюи асосӣ бо номи корбар <input> ва тугмаи PLAY (<button>).

Пас аз боркунии саҳифаи хонагӣ, браузер аз файли JS нуқтаи воридшавӣ ба иҷроиши коди Javascript оғоз мекунад: src/client/index.js.

индекс.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);
  };
});

Ин метавонад мураккаб ба назар расад, аммо дар ин ҷо чизи зиёде вуҷуд надорад:

  1. Воридоти якчанд файлҳои дигари JS.
  2. Воридоти CSS (аз ин рӯ Webpack медонад, ки онҳоро ба бастаи CSS мо дохил кунад).
  3. Запуск connect() ки бо сервер алока мукаррар кунад ва кор кунад downloadAssets() Барои зеркашии тасвирҳо, ки барои намоиш додани бозӣ лозиманд.
  4. Пас аз анҷоми марҳилаи 3 менюи асосӣ нишон дода мешавад (playMenu).
  5. Муқаррар кардани коркардкунанда барои пахш кардани тугмаи "PLAY". Вақте ки тугма пахш карда мешавад, код бозиро оғоз мекунад ва ба сервер мегӯяд, ки мо ба бозӣ омодаем.

"Гӯшт"-и асосии мантиқи муштарӣ-сервери мо дар он файлҳое аст, ки тавассути файл ворид карда шудаанд index.js. Акнун мо хамаи онхоро бо тартиб дида мебароем.

4. Мубодилаи маълумоти муштариён

Дар ин бозӣ, мо барои муошират бо сервер китобхонаи маъруфро истифода мебарем socket.io. Socket.io дастгирии маҳаллӣ дорад WebSockets, ки барои муоширати дутарафа хеле мувофиқанд: мо метавонем ба сервер паём фиристем и сервер метавонад дар як пайвастшавӣ ба мо паём фиристад.

Мо як файл хоҳем дошт src/client/networking.jsкй гамхорй мекунад аз ҷониби ҳама иртибот бо сервер:

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

Ин код низ барои равшанӣ каме кӯтоҳ карда шудааст.

Дар ин файл се амали асосӣ вуҷуд дорад:

  • Мо кӯшиш мекунем, ки ба сервер пайваст шавем. connectedPromise танҳо вақте иҷозат дода мешавад, ки мо алоқа барқарор кунем.
  • Агар пайвастшавӣ бомуваффақият анҷом дода шавад, мо функсияҳои бозгаштро қайд мекунем (processGameUpdate() и onGameOver()) барои паёмҳое, ки мо метавонем аз сервер қабул кунем.
  • Мо содирот мекунем play() и updateDirection()то ки файлҳои дигар онҳоро истифода баранд.

5. Таъмини муштарӣ

Вақти он расидааст, ки тасвирро дар экран нишон диҳед!

... аммо пеш аз он ки мо ин корро кунем, мо бояд ҳамаи тасвирҳоро (манбаъҳоро) зеркашӣ кунем, ки барои ин заруранд. Биёед мудири захираро нависед:

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

Идоракунии захираҳо он қадар душвор нест! Идеяи асосӣ нигоҳ доштани объект аст assets, ки калиди номи файлро ба арзиши объект мепайвандад Image. Вақте ки манбаъ бор карда мешавад, мо онро дар объект нигоҳ медорем assets барои дастрасии зуд дар оянда. Кай ба ҳар як манбаи инфиродӣ иҷозат дода мешавад, ки зеркашӣ карда шавад (яъне, ҳама захирахо), мо ичозат медихем downloadPromise.

Пас аз зеркашии захираҳо, шумо метавонед намоишро оғоз кунед. Тавре ки қаблан гуфта шуда буд, барои кашидан дар саҳифаи веб, мо истифода мебарем Canvas HTML5 (<canvas>). Бозии мо хеле содда аст, бинобар ин ба мо лозим аст, ки танҳо зеринро кашем:

  1. Асосӣ
  2. Киштии бозигар
  3. Бозингарони дигар дар бозӣ
  4. снарядхо

Инҳоянд порчаҳои муҳим src/client/render.js, ки маҳз чаҳор нуқтаи дар боло номбаршударо ҷалб мекунанд:

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

Ин рамз инчунин барои равшанӣ кӯтоҳ карда шудааст.

render() вазифаи асосии ин файл мебошад. startRendering() и stopRendering() фаъолсозии ҳалқаи намоишро дар 60 FPS назорат кунед.

Амалисозии мушаххаси функсияҳои ёрирасони инфиродӣ (масалан renderBullet()) он қадар муҳим нест, аммо ин ҷо як мисоли оддӣ аст:

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

Дар хотир доред, ки мо усулро истифода мебарем getAsset(), ки пештар дар asset.js!

Агар шумо ба омӯхтани ёрирасонҳои дигари визуалӣ таваҷҷӯҳ дошта бошед, пас боқимондаро хонед. src/client/render.js.

6. Вуруди муштарӣ

Вақти он расидааст, ки бозӣ созед бозишаванда! Нақшаи идоракунӣ хеле содда хоҳад буд: барои тағир додани самти ҳаракат шумо метавонед мушро истифода баред (дар компютер) ё ба экран ламс кунед (дар дастгоҳи мобилӣ). Барои татбиқи ин, мо сабти ном мекунем Шунавандагони чорабинӣ барои рӯйдодҳои муш ва ламс.
Хамаи инро гамхорй мекунад 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() шунавандагони чорабиниҳо мебошанд, ки занг мезананд updateDirection() (аз networking.js) ҳангоми рух додани ҳодисаи вуруд (масалан, ҳангоми ҳаракат додани муш). updateDirection() паёмнависиро бо сервер, ки ҳодисаи вурудро идора мекунад ва ҳолати бозиро мувофиқи он нав мекунад.

7. Ҳолати муштарӣ

Ин бахш дар қисми аввали мақола мушкилтарин аст. Агар бори аввал хондан онро нафаҳмидӣ, рӯҳафтода нашав! Шумо ҳатто метавонед онро гузаред ва баъдтар ба он баргардед.

Қисми ниҳоии муаммо барои анҷом додани рамзи муштарӣ/сервер лозим аст давлат. Дар хотир доред, ки пораи код аз бахши Таъмини муштарӣ?

render.js

import { getCurrentState } from './state';

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

  // Do the rendering
  // ...
}

getCurrentState() бояд қодир бошад, ки ба мо ҳолати кунунии бозӣ дар муштарӣ диҳад дар вақти дилхоҳ дар асоси навсозиҳои аз сервер гирифташуда. Ин аст як мисоли навсозии бозӣ, ки сервер метавонад фиристад:

{
  "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: Тамғаи вақти сервер нишон медиҳад, ки кай ин навсозӣ сохта шудааст.
  • me: Маълумот дар бораи плеер, ки ин навсозиро мегирад.
  • дигарон: Маҷмӯи маълумот дар бораи бозигарони дигар, ки дар як бозӣ иштирок мекунанд.
  • мағрҳо: як қатор маълумот дар бораи снарядҳо дар бозӣ.
  • лидерӣ: Маълумоти ҷории пешсаф. Дар ин мақола мо онҳоро баррасӣ намекунем.

7.1 Ҳолати муштарии соддалавҳона

Татбиқи соддалавҳона getCurrentState() метавонад танҳо мустақиман маълумоти навсозии бозии охиринро баргардонад.

naive-state.js

let lastGameUpdate = null;

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

export function getCurrentState() {
  return lastGameUpdate;
}

Зебо ва равшан! Аммо агар ин қадар оддӣ мебуд. Яке аз сабабҳои мушкилоте, ки ин амалӣ карда мешавад: он суръати чаҳорчӯбаи намоишро бо суръати соати сервер маҳдуд мекунад.

Меъёри кадр: шумораи кадрҳо (яъне зангҳо render()) дар як сония ё FPS. Бозиҳо одатан кӯшиш мекунанд, ки ҳадди аққал 60 FPS ба даст оранд.

Меъёри тик: Басомаде, ки сервер навсозиҳои бозиро ба мизоҷон мефиристад. Он аксар вақт аз суръати чаҳорчӯба пасттар аст. Дар бозии мо сервер бо басомади 30 давра дар як сония кор мекунад.

Агар мо танҳо навсозии охирини бозиро пешниҳод кунем, пас FPS аслан ҳеҷ гоҳ аз 30 боло нахоҳад рафт, зеро мо ҳеҷ гоҳ аз сервер зиёда аз 30 навсозиро дар як сония қабул намекунем. Ҳатто агар занг занем render() 60 маротиба дар як сония, пас нисфи ин зангҳо ҳамон чизеро аз нав кашида, аслан ҳеҷ кор намекунанд. Мушкилоти дигари татбиқи соддалавҳона дар он аст моил ба таъхир. Бо суръати беҳтарини интернет, муштарӣ маҳз ҳар 33 мс (30 дар як сония) навсозии бозиро мегирад:

Эҷоди веб-бозии мултипликатори .io
Мутаассифона, ҳеҷ чиз комил нест. Тасвири воқеӣ бештар хоҳад буд:
Эҷоди веб-бозии мултипликатори .io
Татбиқи соддалавҳона амалан бадтарин ҳолат аст, вақте ки сухан дар бораи таъхир меравад. Агар навсозии бозӣ бо таъхири 50ms қабул карда шавад, пас дӯконҳои муштариён 50ms иловагӣ, зеро он то ҳол ҳолати бозиро аз навсозии қаблӣ нишон медиҳад. Шумо метавонед тасаввур кунед, ки ин барои плеер то чӣ андоза нороҳат аст: тормози худсарона бозиро ноором ва ноустувор месозад.

7.2 Ҳолати беҳтари муштарӣ

Мо ба татбиқи соддалавҳона каме такмил медиҳем. Аввалан, мо истифода мебарем таъхир додани барои 100 мс. Ин маънои онро дорад, ки ҳолати "ҷории" муштарӣ ҳамеша аз ҳолати бозӣ дар сервер 100 мс ақиб мемонад. Масалан, агар вақт дар сервер бошад 150, пас муштарӣ ҳолатеро, ки сервер дар он вақт буд, медиҳад 50:

Эҷоди веб-бозии мултипликатори .io
Ин ба мо буфери 100 мс медиҳад, то замони навсозии бозиро пешгӯинашаванда наҷот диҳем:

Эҷоди веб-бозии мултипликатори .io
Мукофот барои ин доимӣ хоҳад буд таъхири воридот барои 100 мс. Ин як қурбонии ночиз барои бозии ҳамвор аст - аксари бозигарон (хусусан бозигарони тасодуфӣ) ин таъхирро ҳатто пай намебаранд. Барои одамон мутобиқ шудан ба таъхири доимии 100ms нисбат ба бозӣ бо таъхири пешгӯинашаванда хеле осонтар аст.

Мо метавонем техникаи дигареро истифода барем "пешгӯии муштарӣ", ки кори хуберо барои коҳиш додани таъхири даркшуда иҷро мекунад, аммо дар ин мақола фаро гирифта намешавад.

Такмили дигаре, ки мо истифода мебарем интерполяцияи хатӣ. Аз сабаби таъхири рендеринг, мо одатан ҳадди аққал як навсозӣ пеш аз вақти ҷорӣ дар муштарӣ ҳастем. Вақте ки даъват getCurrentState(), мо метавонем иҷро кунем интерполяцияи хатӣ байни навсозиҳои бозӣ пеш аз ва баъд аз вақти ҷорӣ дар муштарӣ:

Эҷоди веб-бозии мултипликатори .io
Ин масъалаи суръати кадрҳоро ҳал мекунад: мо ҳоло метавонем чаҳорчӯбаҳои беназирро бо ҳар суръати кадрамон, ки мехоҳем, пешкаш кунем!

7.3 Татбиқи ҳолати мукаммали муштарӣ

Намунаи татбиқ дар src/client/state.js ҳам ақибмонӣ ва ҳам интерполясияи хатиро истифода мебарад, аммо на барои муддати тӯлонӣ. Биёед кодро ба ду қисм тақсим кунем. Дар ин ҷо аввалин аст:

state.js қисми 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;
}

Қадами аввал ин аст, ки муайян кунед, ки чӣ currentServerTime(). Тавре ки мо қаблан дидем, ҳар як навсозии бозӣ тамғаи вақти серверро дар бар мегирад. Мо мехоҳем аз таъхири render истифода барем, то тасвирро 100 мс паси сервер нишон диҳем, аммо мо ҳеҷ гоҳ вақти ҷорӣ дар сервер медонем, зеро мо наметавонем бидонем, ки то ба мо расидани ягон навсозиҳо чӣ қадар вақт лозим шуд. Интернет пешгӯинашаванда аст ва суръати он метавонад хеле фарқ кунад!

Барои бартараф кардани ин мушкилот, мо метавонем тахминии оқилонаро истифода барем: мо вонамуд кунед, ки навсозии аввалин фавран расидааст. Агар ин дуруст мебуд, мо вақти серверро дар ин лаҳза медонистем! Мо тамғаи вақтро дар сервер нигоҳ медорем firstServerTimestamp ва моро нигоҳ доред маҳаллӣ (муштарӣ) тамғаи вақт дар ҳамон лаҳза дар gameStart.

Оҳ интизор. Оё набояд вақт дар сервер = вақт дар мизоҷ бошад? Чаро мо байни "тамгаи вақтҳои сервер" ва "тамгаи вақтҳои мизоҷ" фарқ мекунем? Ин саволи олӣ аст! Маълум мешавад, ки онҳо як чиз нестанд. Date.now() тамғаҳои вақтҳои гуногунро дар мизоҷ ва сервер бармегардонад ва он аз омилҳои маҳаллии ин мошинҳо вобаста аст. Ҳеҷ гоҳ фикр накунед, ки тамғаҳои вақт дар ҳама мошинҳо якхела хоҳанд буд.

Акнун мо мефаҳмем, ки он чӣ кор мекунад currentServerTime(): бармегардад тамғаи вақти сервери вақти ҷорӣ. Ба ибораи дигар, ин вақти ҳозираи сервер (firstServerTimestamp <+ (Date.now() - gameStart)) минус таъхири намоиш (RENDER_DELAY).

Акнун биёед бубинем, ки мо навсозиҳои бозиро чӣ гуна идора мекунем. Вақте ки аз сервери навсозӣ гирифта мешавад, он даъват карда мешавад processGameUpdate()ва мо навсозии навро ба массив захира мекунем gameUpdates. Сипас, барои тафтиши истифодаи хотира, мо ҳама навсозиҳои кӯҳнаро пеш аз он хориҷ мекунем навсозии базавӣзеро ба мо дигар даркор нест.

"Навсозии асосӣ" чист? Ин навсозии аввалине, ки мо тавассути ҳаракат ба ақиб аз вақти ҳозираи сервер пайдо мекунем. Ин диаграммаро дар хотир доред?

Эҷоди веб-бозии мултипликатори .io
Навсозии бозӣ мустақиман дар тарафи чапи "Вақти расонидани муштарӣ" навсозии асосӣ аст.

Навсозии базавӣ барои чӣ истифода мешавад? Чаро мо метавонем навсозиҳоро ба сатҳи асосӣ гузорем? Барои фаҳмидани ин, биёед ниҳоят татбицро дида мебароянд getCurrentState():

state.js қисми 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),
    };
  }
}

Мо се парвандаро баррасӣ мекунем:

  1. base < 0 маънои онро дорад, ки то замони ҳозираи пешниҳодкунӣ ягон навсозӣ вуҷуд надорад (ниг getBaseUpdate()). Ин метавонад бевосита дар оғози бозӣ аз сабаби таъхири рендеринг рӯй диҳад. Дар ин ҳолат, мо навсозии охирини гирифташударо истифода мебарем.
  2. base навсозии охирини мост. Ин метавонад сабаби таъхири шабака ё пайвасти сусти интернет бошад. Дар ин ҳолат, мо инчунин навсозии охиринеро, ки мо дорем, истифода мебарем.
  3. Мо ҳам пеш аз ва ҳам баъд аз вақти ҷорӣ навсозӣ дорем, то мо метавонем интерполясия кардан!

Ҳама он чизе, ки дар он боқӣ мондааст state.js татбиқи интерполясияи хатӣ аст, ки математикаи оддӣ (вале дилгиркунанда) аст. Агар шумо хоҳед, ки онро худатон омӯзед, пас кушоед state.js ба Github.

Қисми 2. Сервери пушти сар

Дар ин қисм, мо ба пуштибонии Node.js, ки моро назорат мекунад, дида мебароем мисоли бозии .io.

1. Нуқтаи воридшавии сервер

Барои идоракунии веб-сервер, мо чаҳорчӯбаи веби маъмули Node.js-ро истифода хоҳем бурд изҳор. Он аз ҷониби файли нуқтаи вуруди сервери мо танзим карда мешавад src/server/server.js:

server.js қисми 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}`);

Дар хотир доред, ки дар қисми аввал мо Webpack-ро муҳокима кардем? Дар ин ҷо мо конфигуратсияҳои Webpack-и худро истифода хоҳем бурд. Мо онҳоро бо ду роҳ татбиқ мекунем:

  • Барои истифода webpack-dev-middleware барои ба таври худкор аз нав сохтани бастаҳои рушди мо, ё
  • папкаи статикӣ интиқол дода мешавад dist/, ки дар он Webpack файлҳои моро пас аз сохтани истеҳсолот менависад.

Боз як вазифаи мухим server.js танзим кардани сервер аст socket.ioки танҳо ба сервери Express пайваст мешавад:

server.js қисми 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);
});

Пас аз бомуваффақият таъсис додани пайвасти socket.io ба сервер, мо коркардкунандагони рӯйдодҳоро барои васлаки нав насб кардем. Корбарони рӯйдодҳо паёмҳои аз муштариён гирифташударо тавассути интиқол ба объекти синглтон идора мекунанд game:

server.js қисми 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);
}

Мо як бозии .io эҷод карда истодаем, аз ин рӯ ба мо танҳо як нусха лозим аст Game ("Бозӣ") - ҳама бозигарон дар як майдон бозӣ мекунанд! Дар фасли оянда мо мебинем, ки ин синф чӣ гуна кор мекунад. Game.

2. Серверҳои бозӣ

Синф Game дорои мантиқи муҳимтарин дар тарафи сервер аст. Он ду вазифаи асосӣ дорад: идоракунии бозингар и симулятсияи бозӣ.

Биёед аз вазифаи аввал, идоракунии плеер оғоз кунем.

game.js қисми 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);
    }
  }

  // ...
}

Дар ин бозӣ мо бозигаронро аз рӯи майдон муайян мекунем id васлаки socket.io онҳо (агар шумо ошуфта бошед, пас ба server.js). Худи Socket.io ба ҳар як васлаки беназир таъин мекунад idбинобар ин ба мо лозим нест, ки дар ин бора хавотир шавем. Ман ба ӯ занг мезанам ID Player.

Бо дарназардошти ин, биёед тағирёбандаҳои мисолро дар синф омӯзем Game:

  • sockets объектест, ки ID-и плеерро ба розетка, ки бо плеер алоқаманд аст, мепайвандад. Он ба мо имкон медиҳад, ки дар вақти доимӣ ба розеткаҳо тавассути ID-и плеери онҳо дастрасӣ пайдо кунем.
  • players объектест, ки ID-и бозигарро ба код> Объекти бозигар мепайвандад

bullets массиви объектхо мебошад Bullet, ки тартиби муайяне надорад.
lastUpdateTime тамғаи вақти охирини навсозии бозӣ аст. Мо ба наздикӣ мебинем, ки он чӣ гуна истифода мешавад.
shouldSendUpdate тағирёбандаи ёрирасон аст. Мо инчунин ба зудӣ истифодаи онро хоҳем дид.
Усулҳо addPlayer(), removePlayer() и handleInput() ба тавзеҳ додан лозим нест, онҳо дар истифода мешаванд server.js. Агар ба шумо лозим ояд, ки хотираи худро нав кунед, каме баландтар баргардед.

Хатти охирин constructor() оғоз меёбад давраи навсозӣ бозиҳо (бо басомади 60 навсозӣ/с):

game.js қисми 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;
    }
  }

  // ...
}

метод update() эҳтимолан муҳимтарин қисмати мантиқи серверро дар бар мегирад. Ин аст он чи кор мекунад, бо тартиби:

  1. Чӣ қадар вақт ҳисоб мекунад dt аз охирон гузашт update().
  2. Ҳар як снарядро нав мекунад ва агар лозим бошад, онҳоро нест мекунад. Мо татбиқи ин функсияро дертар мебинем. Ҳоло барои мо донистани он кифоя аст bullet.update() бармегардад trueагар снаряд нобуд карда шавад (ӯ аз майдон баромад).
  3. Ҳар як бозигарро навсозӣ мекунад ва агар лозим бошад, снарядро тавлид мекунад. Мо ин татбиқро дертар мебинем - player.update() метавонад объектро баргардонад Bullet.
  4. Бархӯрди байни снарядҳо ва бозигаронро тафтиш мекунад applyCollisions(), ки як қатор снарядҳоро бармегардонад, ки ба бозигарон зарба мезанад. Барои ҳар як снаряде, ки баргардонида шуд, мо холҳои бозингареро, ки онро паррондааст, зиёд мекунем (бо истифода аз player.onDealtDamage()), ва сипас снарядро аз массив хориҷ кунед bullets.
  5. Ҳамаи бозигарони кушташударо огоҳ мекунад ва нест мекунад.
  6. Навсозии бозиро ба ҳамаи бозигарон мефиристад ҳар сония вақтҳое, ки занг мезананд update(). Тағйирёбандаи ёрирасони дар боло зикршуда ба мо дар пайгирии ин кӯмак мекунад shouldSendUpdate. Зеро update() даъват 60 маротиба / .Дар, мо ирсол навсозиҳои бозӣ 30 маротиба / .Дар. Ҳамин тариқ, басомади соат соати сервер 30 соат / с аст (мо дар бораи суръати соат дар қисми аввал сӯҳбат кардем).

Чаро танҳо навсозиҳои бозӣ фиристед ба воситаи вақт ? Барои захира кардани канал. 30 навсозии бозӣ дар як сония хеле зиёд аст!

Чаро танҳо занг назанед update() 30 маротиба дар як сония? Барои беҳтар кардани симулятсияи бозӣ. Чӣ қадаре ки он бештар номида мешавад update(), симулятсияи бозӣ ҳамон қадар дақиқтар хоҳад буд. Аммо бо шумораи душвориҳо аз ҳад зиёд дур нашавед. update(), зеро ин як кори ҳисоббарор аст - 60 дар як сония кифоя аст.

Боқимондаи синф Game аз усулхои ёрирасон иборат аст, ки дар update():

game.js қисми 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() хеле содда - он бозигаронро аз рӯи хол ҷудо мекунад, панҷ нафари беҳтаринро мегирад ва номи корбар ва холро барои ҳар як бармегардонад.

createUpdate() дар истифода бурда мешавад update() барои эҷоди навсозиҳои бозӣ, ки ба бозингарон тақсим карда мешаванд. Вазифаи асосии он даъват кардани усулҳо мебошад serializeForUpdate(), барои синфҳо амалӣ карда мешавад Player и Bullet. Аҳамият диҳед, ки он танҳо ба ҳар як бозигар маълумот медиҳад наздиктарин бозигарон ва снарядҳо - интиқоли маълумот дар бораи объектҳои бозӣ, ки аз плеер дуранд, лозим нест!

3. Объектҳои бозӣ дар сервер

Дар бозии мо снарядҳо ва бозигарон воқеан ба ҳам монанданд: онҳо объектҳои абстрактӣ, мудаввар ва ҳаракаткунандаи бозӣ мебошанд. Барои истифода бурдани ин монандӣ байни бозигарон ва снарядҳо, биёед аз татбиқи синфи асосӣ оғоз кунем 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,
    };
  }
}

Дар ин ҷо ҳеҷ чизи мураккабе нест. Ин синф як нуқтаи хубе барои васеъшавӣ хоҳад буд. Биёед бубинем, ки синф чӣ гуна аст Bullet истифода мебарад 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;
  }
}

Реализация Bullet хеле кӯтоҳ! Мо илова кардем Object танҳо васеъшавии зерин:

  • Истифодаи баста кӯтоҳ барои насли тасодуфӣ id снаряд.
  • Илова кардани майдон parentIDто ки шумо метавонед плеереро, ки ин снарядро офаридааст, пайгирӣ кунед.
  • Илова кардани арзиши бозгашт ба update()ки баробар аст trueагар снаряд берун аз майдон бошад (дар хотир доред, ки мо дар ин бора дар фасли охир сухан ронда будем?).

Биёед ба 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,
    };
  }
}

Бозингарон нисбат ба снарядҳо мураккабтаранд, аз ин рӯ дар ин синф бояд якчанд майдони бештар нигоҳ дошта шаванд. Усули ӯ update() бисьёр кор мекунад, аз чумла, снаряди навбуньёдшударо агар ягонто намондааст, бармегардонад fireCooldown (Дар хотир доред, ки мо дар ин бора дар фасли қаблӣ сӯҳбат карда будем?). Он инчунин усулро васеъ мекунад serializeForUpdate(), зеро мо бояд майдонҳои иловагиро барои плеер дар навсозии бозӣ дохил кунем.

Доштани синфи асосӣ Object - як қадами муҳим барои пешгирӣ кардани такрори код. Масалан, синф нест Object ҳар як объекти бозӣ бояд татбиқи якхела дошта бошад distanceTo(), ва нусхабардорӣ кардани ҳамаи ин татбиқҳо дар байни якчанд файлҳо даҳшатовар хоҳад буд. Ин махсусан барои лоиҳаҳои калон муҳим мегардад., вакте ки шумораи васеъ Object синфхо меафзояд.

4. Муайян кардани бархӯрд

Ягона чизе, ки барои мо боқӣ мондааст, фаҳмидани он аст, ки кай снарядҳо ба бозингарон меафтанд! Ин пораи кодро аз усул дар хотир нигоҳ доред update() дар синф 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),
    );

    // ...
  }
}

Мо бояд усулро ба амал барорем applyCollisions(), ки ҳамаи снарядҳоеро, ки ба бозигарон задаанд, бармегардонад. Хушбахтона, ин корро кардан он қадар душвор нест, зеро

  • Ҳама объектҳои бархӯрд доираҳо мебошанд, ки шакли соддатарин барои амалисозии муайянкунии бархӯрд мебошад.
  • Мо аллакай як усул дорем distanceTo(), ки мо дар фасли гузашта дар синф ба амал овардем Object.

Ин аст, ки татбиқи муайянкунии бархӯрди мо чӣ гуна аст:

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

Ин ошкоркунии оддии бархӯрд ба он асос ёфтааст, ки ду доира бархӯрд мекунанд, агар масофаи байни марказҳои онҳо аз ҷамъи радиусҳои онҳо камтар бошад. Ин аст ҳолате, ки масофаи байни марказҳои ду давра ба маблағи радиусҳои онҳо комилан баробар аст:

Эҷоди веб-бозии мултипликатори .io
Дар ин ҷо шумо бояд ба чанд ҷанбаи дигар диққати ҷиддӣ диҳед:

  • Снаряд набояд ба бозигаре, ки онро офаридааст, бархӯрад. Инро бо роҳи муқоиса кардан мумкин аст bullet.parentID с player.id.
  • Снаряд бояд танҳо як маротиба дар ҳолати маҳдуди бархӯрди якчанд бозигарон дар як вақт зада шавад. Мо ин масъаларо бо истифода аз оператор ҳал мекунем break: баробари пайдо шудани плеер, ки бо снаряд бархурд, мо чустучуро бас карда, ба снаряди навбатй мегузарем.

Дар охири

Ҳамааш ҳамин! Мо ҳама чизро фаро гирифтем, ки шумо барои сохтани веб-бозии .io донед. Баъд чӣ? Бозии .io-и худро созед!

Ҳама рамзи намунавӣ манбаи кушода аст ва дар он ҷойгир карда шудааст Github.

Манбаъ: will.com

Илова Эзоҳ