د ملټي پلیر .io ویب لوبې رامینځته کول

د ملټي پلیر .io ویب لوبې رامینځته کول
په 2015 کې خپور شو Agar.io د یوه نوي ژانر مشر شو لوبې .ioکوم چې له هغه وخت راهیسې په شهرت کې وده کړې. ما په شخصي توګه د .io لوبو د شهرت زیاتوالی تجربه کړی: په تیرو دریو کلونو کې، ما لري د دې ژانر دوه لوبې جوړې او پلورلې..

په هغه حالت کې چې تاسو مخکې هیڅکله د دې لوبو په اړه ندي اوریدلي ، دا وړیا ملټي پلیر ویب لوبې دي چې لوبې کول اسانه دي (هیڅ حساب ته اړتیا نشته). دوی معمولا په ورته ډګر کې د ډیری مخالف لوبغاړو سره مخ کیږي. نورې مشهورې .io لوبې: Slither.io и Diep.io.

پدې پوسټ کې ، موږ به وګورو چې څنګه له سکریچ څخه io لوبه جوړه کړئ. د دې لپاره ، یوازې د جاواسکریپټ پوهه به کافي وي: تاسو اړتیا لرئ د نحو په څیر شیانو پوه شئ ES6, کلیدي کلمه this и ژمنې. حتی که د جاواسکریپټ په اړه ستاسو پوهه کامل نه وي، تاسو لاهم کولی شئ ډیری پوسټ پوه شئ.

io د لوبې مثال

د زده کړې مرستې لپاره، موږ به مراجعه وکړو io د لوبې مثال. د لوبې کولو هڅه وکړئ!

د ملټي پلیر .io ویب لوبې رامینځته کول
لوبه خورا ساده ده: تاسو په میدان کې کښتۍ کنټرول کوئ چیرې چې نور لوبغاړي شتون لري. ستاسو کښتۍ په اوتومات ډول د توغندیو ډزې کوي او تاسو هڅه کوئ چې نور لوبغاړي ووهئ پداسې حال کې چې د دوی د توغندیو مخنیوی وکړئ.

1. د پروژې لنډه کتنه / جوړښت

زه سپارښتنه کوم د سرچینې کوډ ډاونلوډ کړئ د مثال لوبه نو تاسو کولی شئ ما تعقیب کړئ.

مثال په لاندې ډول کاروي:

  • بیانول د Node.js خورا مشهور ویب چوکاټ دی چې د لوبې ویب سرور اداره کوي.
  • socket.io - د براوزر او سرور ترمنځ د معلوماتو تبادلې لپاره د ویب ساکټ کتابتون.
  • ویبپیک - د ماډل مدیر. تاسو کولی شئ د ویبپیک کارولو په اړه ولولئ. دلته.

دلته هغه څه دي چې د پروژې لارښود جوړښت داسې ښکاري:

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

عامه/

هر څه په فولډر کې public/ په ثابت ډول به د سرور لخوا وسپارل شي. IN public/assets/ زموږ د پروژې لخوا کارول شوي عکسونه لري.

src /

ټول سرچینې کوډ په فولډر کې دی src/. عنوانونه client/ и server/ د ځان لپاره خبرې وکړئ او shared/ د ثابت فایل لري چې د پیرودونکي او سرور دواړو لخوا وارد شوي.

2. مجلسونه/د پروژې ترتیبات

لکه څنګه چې پورته یادونه وشوه، موږ د پروژې د جوړولو لپاره د ماډل مدیر کاروو. ویبپیک. راځئ چې زموږ د ویب پیک ترتیب ته یو نظر وګورو:

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) پیرودونکي د ننوتلو نقطه ده. ویبپیک به له دې ځایه پیل شي او د نورو وارد شوي فایلونو لپاره به په تکراري ډول لټون وکړي.
  • زموږ د ویبپیک جوړونې محصول JS به په لارښود کې موقعیت ولري dist/. زه به دې فایل ته زموږ نوم ورکړم js بسته.
  • موږ یی استعمالوو د بابل، او په ځانګړې توګه ترتیب @babel/preset-env د زړو براوزرونو لپاره زموږ د JS کوډ لیږدولو لپاره.
  • موږ د JS فایلونو لخوا راجع شوي ټول CSS استخراج لپاره یو پلگ ان کاروو او په یو ځای کې یې یوځای کوو. زه به هغه ته زموږ غږ وکړم 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

او ویب براوزر ته لاړ شئ ځايي کوربه: 3000. پرمختیایي سرور به په اتوماتيک ډول د JS او CSS کڅوړې بیا رغوي لکه څنګه چې کوډ بدلیږي - یوازې د ټولو بدلونونو لیدلو لپاره پاڼه تازه کړئ!

3. د پیرودونکي د ننوتلو نقطې

راځئ چې پخپله د لوبې کوډ ته ښکته شو. لومړی موږ یوه پاڼه ته اړتیا لرو index.html، کله چې سایټ څخه لیدنه وکړئ ، براوزر به لومړی دا پورته کړي. زموږ پاڼه به ډیره ساده وي:

index.html د

یو مثال .io لوبه  لوبه وکړئ

د دې کوډ مثال د وضاحت لپاره یو څه ساده شوی ، او زه به د نورو پوسټ مثالونو سره ورته کار وکړم. بشپړ کوډ تل لیدل کیدی شي Github.

مونږیۍ لرو:

  • د HTML5 کینوس عنصر (<canvas>) کوم چې موږ به د لوبې وړاندې کولو لپاره وکاروو.
  • <link> زموږ د CSS بسته اضافه کولو لپاره.
  • <script> زموږ د جاواسکریپټ بسته اضافه کولو لپاره.
  • د کارن نوم سره اصلي مینو <input> او د لوبې تڼۍ (<button>).

د کور پاڼې له پورته کولو وروسته، براوزر به د جاوا سکرپٹ کوډ اجرا کول پیل کړي، د ننوتلو نقطې 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);
  };
});

دا کیدای شي پیچلي ښکاري، مګر دلته ډیر څه شتون نلري:

  1. د څو نورو JS فایلونو واردول.
  2. د CSS واردول (نو Webpack پوهیږي چې دوی زموږ په CSS بسته کې شامل کړي).
  3. په لاره اچول connect() د سرور سره پیوستون رامینځته کول او چلول downloadAssets() د لوبې وړاندې کولو لپاره اړین عکسونو ډاونلوډ کولو لپاره.
  4. د 3 مرحلې بشپړولو وروسته اصلي مینو ښودل کیږي (playMenu).
  5. د "PLAY" تڼۍ فشارولو لپاره هینډلر تنظیم کول. کله چې تڼۍ کیښودل شي، کوډ لوبه پیلوي او سرور ته وایي چې موږ لوبې کولو ته چمتو یو.

زموږ د پیرودونکي سرور منطق اصلي "غوښه" په هغه فایلونو کې ده چې د فایل لخوا وارد شوي index.js. اوس موږ به دوی ټول په ترتیب سره وګورو.

4. د پیرودونکو معلوماتو تبادله

پدې لوبه کې، موږ د سرور سره د خبرو اترو لپاره یو مشهور کتابتون کاروو socket.io. Socket.io اصلي ملاتړ لري ویب ساکټونه، کوم چې د دوه اړخیزو اړیکو لپاره مناسب دي: موږ کولی شو سرور ته پیغامونه واستوو и سرور کولی شي موږ ته په ورته اړیکه کې پیغامونه واستوي.

موږ به یوه فایل ولرو 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.

د سرچینو ډاونلوډ کولو وروسته ، تاسو کولی شئ رینډر پیل کړئ. لکه څنګه چې مخکې وویل شول، د ویب پاڼې د انځور کولو لپاره، موږ کاروو 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 ځله، بیا د دغو تلیفونونو نیمایي به یوازې ورته شی بیرته راوباسي، په اصل کې هیڅ نه کوي. د ساده پلي کولو سره بله ستونزه دا ده د ځنډ سره مخ دي. د مثالي انټرنیټ سرعت سره، پیرودونکی به په هر 33ms (په هره ثانیه کې 30) د لوبې تازه معلومات ترلاسه کړي:

د ملټي پلیر .io ویب لوبې رامینځته کول
له بده مرغه، هیڅ شی بشپړ نه دی. یو ډیر حقیقي انځور به وي:
د ملټي پلیر .io ویب لوبې رامینځته کول
ساده پلي کول په عملي ډول ترټولو خراب قضیه ده کله چې د ځنډ خبره راځي. که د لوبې تازه معلومات د 50ms ځنډ سره ترلاسه شي نو بیا د پیرودونکو سټالونه یو اضافي 50ms ځکه چې دا لاهم د تیر تازه څخه د لوبې حالت وړاندې کوي. تاسو تصور کولی شئ چې دا د لوبغاړي لپاره څومره نا آرامه دی: په خپل سري بریک کول به لوبه د ټکان او بې ثباته احساس کړي.

7.2 د پیرودونکي حالت ښه شوی

موږ به د ساده پلي کولو لپاره ځینې پرمختګونه وکړو. لومړی، موږ کاروو ځنډول د 100 ms لپاره دا پدې مانا ده چې د پیرودونکي "اوسني" حالت به تل د 100ms لخوا په سرور کې د لوبې حالت څخه وروسته پاتې وي. د مثال په توګه، که په سرور کې وخت وي 150، بیا پیرودونکی به هغه حالت وړاندې کړي چې سرور په هغه وخت کې و 50:

د ملټي پلیر .io ویب لوبې رامینځته کول
دا موږ ته د 100ms بفر راکوي ترڅو د غیر متوقع لوبې تازه وختونو ژوندي پاتې شي:

د ملټي پلیر .io ویب لوبې رامینځته کول
د دې لپاره تادیه به دایمي وي د ننوتلو ځنډ د 100 ms لپاره دا د اسانه لوبې لوبې لپاره کوچنۍ قرباني ده - ډیری لوبغاړي (په ځانګړي توګه عادي لوبغاړي) به حتی دا ځنډ ونه ګوري. دا د خلکو لپاره خورا اسانه دی چې د 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(). لکه څنګه چې موږ مخکې ولیدل، د هرې لوبې تازه معلومات د سرور مهال ویش شامل دي. موږ غواړو د سرور شاته د عکس 100ms وړاندې کولو لپاره د رینډر لیټینسی وکاروو ، مګر موږ به هیڅکله په سرور کې اوسنی وخت نه پوهیږو، ځکه چې موږ نه پوهیږو چې موږ ته د کوم تازه معلوماتو ترلاسه کولو لپاره څومره وخت نیولی. انټرنیټ غیر متوقع دی او سرعت یې خورا ډیر توپیر کولی شي!

د دې ستونزې په شاوخوا کې د ترلاسه کولو لپاره، موږ کولی شو یو مناسب اټکل وکاروو: موږ فرض کړئ چې لومړی تازه سمدلاسه راغلی. که دا ریښتیا وي ، نو موږ به پدې ځانګړي شیبه کې د سرور وخت پوه شو! موږ د سرور مهال ویش ذخیره کوو 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. موږ د اوسني رینډر وخت دمخه او وروسته دواړه تازه معلومات لرو، نو موږ کولی شو interpolate!

ټول هغه څه چې پاتې دي state.js د خطي انټرپولیشن تطبیق دی چې ساده (مګر ستړي) ریاضی دی. که تاسو غواړئ دا پخپله وپلټئ، نو خلاص کړئ state.js په Github.

برخه 2. د شاتړ سرور

په دې برخه کې، موږ به د Node.js backend ته یوه کتنه وکړو چې زموږ کنټرول کوي 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-dev-midleware په اتوماتيک ډول زموږ د پراختیا کڅوړې بیا رغول، یا
  • په جامد ډول لیږد فولډر dist/په کوم کې چې ویبپیک به زموږ فایلونه د تولید جوړیدو وروسته ولیکئ.

بل مهم کار server.js د سرور تنظیم کول دي socket.ioکوم چې یوازې د ایکسپریس سرور سره نښلوي:

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

په بریالیتوب سره سرور ته د ساکټ.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.

د دې سره په ذهن کې، راځئ چې په ټولګي کې د مثال متغیر وپلټو Game:

  • sockets یو څیز دی چې د لوبغاړي ID ساکټ سره تړلی چې د لوبغاړي سره تړاو لري. دا موږ ته اجازه راکوي چې په دوامداره وخت کې د دوی د پلیر ID لخوا ساکټونو ته لاسرسی ومومئ.
  • players یو څیز دی چې د لوبغاړي ID د کوډ> پلیر اعتراض سره تړلی دی

bullets د شیانو لړۍ ده Bullet، کوم چې کوم ټاکلی حکم نلري.
lastUpdateTime د وروستي ځل لپاره مهال ویش دی چې لوبه تازه شوې وه. موږ به وګورو چې دا څنګه په لنډ وخت کې کارول کیږي.
shouldSendUpdate یو معاون متغیر دی. موږ به په لنډ وخت کې د هغې کارول هم وګورو.
میتودونه addPlayer(), removePlayer() и handleInput() تشریح کولو ته اړتیا نشته، دوی کارول کیږي server.js. که تاسو اړتیا لرئ خپل حافظه تازه کړئ، یو څه لوړ بیرته لاړ شئ.

وروستۍ کرښه constructor() پیل کیږي دوره تازه کول لوبې (د 60 تازه معلوماتو / s فریکونسۍ سره):

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 ځله/s په نوم یادیږي، موږ د لوبې تازه معلومات 30 ځله/s لیږو. په دې توګه، د ساعت فریکونسۍ د سرور ساعت 30 ساعتونه/s دی (موږ په لومړۍ برخه کې د ساعت نرخونو په اړه خبرې وکړې).

ولې یوازې د لوبې تازه معلومات واستوئ د وخت له لارې ? د چینل خوندي کولو لپاره. په هره ثانیه کې د 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:

اعتراض.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.

سرچینه: www.habr.com

Add a comment