ባለብዙ ተጫዋቜ .io ዚድር ጚዋታ መፍጠር

ባለብዙ ተጫዋቜ .io ዚድር ጚዋታ መፍጠር
በ2015 ተለቋል አግአርዮ.ዮ. ዚአዲሱ ዘውግ ቅድመ አያት ሆነ ጚዋታዎቜ .ioኚዚያን ጊዜ ጀምሮ በታዋቂነት ያደገው. በግሌ ዹ.io ጚዋታዎቜ ተወዳጅነት መጹመር አጋጥሞኛል፡ ባለፉት ሶስት አመታት ውስጥ፣ አለኝ ዹዚህ ዘውግ ሁለት ጚዋታዎቜን ፈጠሹ እና ተሜጧል።.

ስለእነዚህ ጚዋታዎቜ ኹዚህ በፊት ሰምተህ ዚማታውቀው ኚሆነ፣ ለመጫወት ቀላል ዹሆኑ ነጻ ባለብዙ ተጫዋቜ ዚድር ጚዋታዎቜ ናቾው (ምንም መለያ አያስፈልግም)። ብዙውን ጊዜ በተመሳሳይ መድሚክ ብዙ ተቃራኒ ተጫዋ቟ቜን ይጋፈጣሉ። ሌሎቜ ታዋቂ ዹ.io ጚዋታዎቜ፡- Slither.io О ዲፒፒዮ.

በዚህ ጜሑፍ ውስጥ, እንዎት እንደሆነ እንመሚምራለን ኚባዶ ዹ.io ጚዋታ ይፍጠሩ. ለዚህ, ዚጃቫስክሪፕት እውቀት ብቻ በቂ ይሆናል: እንደ አገባብ ያሉ ነገሮቜን መሚዳት ያስፈልግዎታል ES6፣ ቁልፍ ቃል this О ተስፋዎቜ. ዚጃቫስክሪፕት እውቀትዎ ፍፁም ባይሆንም አብዛኛውን ልጥፉን መሚዳት ይቜላሉ።

.io ጚዋታ ምሳሌ

ለትምህርት እርዳታ፣ እንጠቅሳለን። .io ጚዋታ ምሳሌ. እሱን ለመጫወት ይሞክሩ!

ባለብዙ ተጫዋቜ .io ዚድር ጚዋታ መፍጠር
ጚዋታው በጣም ቀላል ነው፡ ሌሎቜ ተጫዋ቟ቜ ባሉበት መድሚክ ላይ መርኚብን ትቆጣጠራላቜሁ። ዚእርስዎ መርኚብ በራስ-ሰር ፕሮጄክተሮቜን ያቃጥላል እና ሌሎቜ ተጫዋ቟ቜን ፕሮጄክተሮቜን በማስወገድ ላይ እያሉ ለመምታት ይሞክራሉ።

1. ዚፕሮጀክቱ አጭር መግለጫ / መዋቅር

እኔ አመሰግናለሁ ምንጭ ኮድ አውርድ እኔን መኹተል እንዲቜሉ ዚምሳሌ ጚዋታ።

ምሳሌው ዚሚኚተሉትን ይጠቀማል:

  • ይግለጹ ዚጚዋታውን ዚድር አገልጋይ ዚሚያስተዳድር በጣም ታዋቂው ዹ Node.js ዚድር ማዕቀፍ ነው።
  • ሶኬት.io - በአሳሜ እና በአገልጋይ መካኚል ውሂብ ለመለዋወጥ ዚዌብሶኬት ቀተ-መጜሐፍት።
  • Webpack። - ሞጁል አስተዳዳሪ. ለምን 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 ዚጃቫስክሪፕት (JS) ደንበኛ መግቢያ ነጥብ ነው። ዌብፓክ ኹዚህ ይጀምራል እና ሌሎቜ ወደ ሀገር ውስጥ ዚሚገቡ ፋይሎቜን በዹጊዜው ይፈልጋል።
  • ዚእኛ ዚዌብፓክ ግንባታ ውጀት JS በማውጫው ውስጥ ይቀመጣል dist/. ይህንን ፋይል ዹኛ እደውላለሁ። js ጥቅል.
  • እኛ እንጠቀማለን ባቀል, እና በተለይም አወቃቀሩ @babel/preset-env ለአሮጌ አሳሟቜ ዹኛን JS ኮድ ለመፃፍ።
  • በJS ፋይሎቜ ዚተጠቀሱ ሁሉንም CSS ለማውጣት እና በአንድ ቊታ ለማጣመር ፕለጊን እዚተጠቀምን ነው። ዹኛ እለዋለሁ። css ጥቅል.

እንግዳ ዚጥቅል ፋይል ስሞቜን አስተውለህ ይሆናል። '[name].[contenthash].ext'. ይይዛሉ ዹፋይል ስም ምትክ ዚድር ቊርሳ [name] በግቀት ነጥቡ ስም ይተካዋል (በእኛ ሁኔታ ይህ game) ፣ እና [contenthash] በፋይሉ ይዘት ሃሜ ይተካል። እናደርገዋለን ፕሮጀክቱን ለ hashing ያመቻቹ - አሳሟቜ ዚእኛን 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 ጚዋታ  ተጫወት

ይህ ዚኮድ ምሳሌ ግልጜ ለማድሚግ በትንሹ ቀለል ያለ ነው፣ እና ኚብዙ ሌሎቜ ዚልጥፍ ምሳሌዎቜ ጋር ተመሳሳይ ነገር አደርጋለሁ። ሙሉ ኮድ ሁል ጊዜ በ ላይ ሊታይ ይቜላል። ዹፊልሙ.

እና አለነ:

  • HTML5 ሞራ አባል (<canvas>) ጚዋታውን ለመስራት ዚምንጠቀምበት።
  • <link> ዚእኛን CSS ጥቅል ለመጚመር።
  • <script> ዚእኛን ጃቫስክሪፕት ጥቅል ለመጚመር።
  • ዋና ምናሌ ኹተጠቃሚ ስም ጋር <input> እና ዹ PLAY ቁልፍ (<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 ማስመጣት (ስለዚህ ዌብፓክ በCSS ጥቅል ውስጥ እንደሚያካትታ቞ው ያውቃል)።
  3. Запуск connect() ኚአገልጋዩ ጋር ግንኙነት ለመመስሚት እና ለማሄድ downloadAssets() ጚዋታውን ለመስራት ዚሚያስፈልጉ ምስሎቜን ለማውሚድ።
  4. ደሹጃ 3 ኹተጠናቀቀ በኋላ ዋናው ምናሌ ይታያል (playMenu).
  5. ዹ"PLAY" ቁልፍን ለመጫን ተቆጣጣሪውን በማዘጋጀት ላይ። ቁልፉ ሲጫን ኮዱ ጚዋታውን ይጀምራል እና እኛ ለመጫወት ዝግጁ መሆናቜንን ለአገልጋዩ ይነግሚዋል።

ዚእኛ ዹደንበኛ-አገልጋይ አመክንዮ ዋናው "ስጋ" በፋይሉ ወደ ሀገር ውስጥ በገቡት ፋይሎቜ ውስጥ ነው index.js. አሁን ሁሉንም በቅደም ተኹተል እንመለኚታለን.

4. ዹደንበኛ ውሂብ መለዋወጥ

በዚህ ጚዋታ ኚአገልጋዩ ጋር ለመገናኘት ዚታወቀ ቀተ-መጜሐፍትን እንጠቀማለን። ሶኬት.io. Socket.io ቀተኛ ድጋፍ አለው። ዌብሳይቶቜ, ለሁለት መንገድ ግንኙነት በጣም ተስማሚ ዚሆኑት: ወደ አገልጋዩ መልእክት መላክ እንቜላለን О አገልጋዩ በተመሳሳይ ግንኙነት ላይ መልእክት ሊልክልን ይቜላል።

አንድ ፋይል ይኖሹናል src/client/networking.jsማን ይንኚባኚባል ሁሉም ሰው ኚአገልጋዩ ጋር መገናኘት;

አውታሚ መሚብ.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. ዹደንበኛ አቀራሚብ

ምስሉን በስክሪኑ ላይ ለማሳዚት ጊዜው አሁን ነው!

ነገር ግን ያንን ኚማድሚጋቜን በፊት፣ ለዚህ ​​ዚሚያስፈልጉትን ሁሉንም ምስሎቜ (ሃብቶቜ) ማውሚድ አለብን። ዚሀብት አስተዳዳሪን እንፃፍ፡-

ንብሚቶቜ.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ኹላይ ዚተዘሚዘሩትን አራት ነገሮቜ በትክክል ዚሚያቀርብ፡-

ማቅሚብ.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() ዹ render loop ን እንቅስቃሎ በ60 FPS ይቆጣጠሩ።

ዚግለሰባዊ ሚዳት ተግባራት ተጚባጭ ትግበራዎቜ (ለምሳሌ ፣ renderBullet()) ያን ያህል አስፈላጊ አይደሉም፣ ግን አንድ ቀላል ምሳሌ እዚህ አለ፡-

ማቅሚብ.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/ደንበኛ/render.js.

6. ዹደንበኛ ግቀት

ጚዋታ ለመስራት ጊዜው አሁን ነው። ሊጫወት ዚሚቜል! ዚቁጥጥር መርሃግብሩ በጣም ቀላል ይሆናል-ዚእንቅስቃሎውን አቅጣጫ ለመለወጥ, አይጀውን (በኮምፒተር ላይ) መጠቀም ወይም ማያ ገጹን (በሞባይል መሳሪያ ላይ) መንካት ይቜላሉ. ይህንን ተግባራዊ ለማድሚግ እንመዘግባለን። ዚዝግጅት አድማጮቜ ለ Mouse እና Touch ክስተቶቜ.
ይህንን ሁሉ ይንኚባኚባል src/client/input.js:

ግብዓት.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. ዹደንበኛ ሁኔታ

ይህ ክፍል በፖስታው ዚመጀመሪያ ክፍል ውስጥ በጣም አስ቞ጋሪው ነው. ለመጀመሪያ ጊዜ ስታነቡት ካልተሚዳቜሁት ተስፋ አትቁሚጡ! እንዲያውም መዝለል ትቜላለህ እና በኋላ ወደ እሱ መመለስ ትቜላለህ።

ዹደንበኛው/ዹአገልጋይ ኮድ ለማጠናቀቅ ዚሚያስፈልገው ዚመጚሚሻው ዚእንቆቅልሜ ክፍል ነው። ግዛት. ኹደንበኛ አተሹጓጎም ክፍል ዹተገኘውን ዚኮድ ቅንጣቢ አስታውስ?

ማቅሚብ.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 Naive ደንበኛ ሁኔታ

ዹዋህ አተገባበር 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 መዘግዚት ኹደሹሰ ፣ ኚዚያ ዹደንበኛ መሞጫዎቜ ተጚማሪ 50 ሚሮ ምክንያቱም አሁንም ዚጚዋታውን ሁኔታ ኹቀደመው ዝማኔ እያቀሚበ ነው። ይህ ለተጫዋቹ ምን ያህል ዚማይመቜ እንደሆነ መገመት ትቜላላቜሁ፡ ዹዘፈቀደ ብሬኪንግ ጚዋታውን ያሞበሚቀ እና ያልተሚጋጋ ያደርገዋል።

7.2 ዚተሻሻለ ዹደንበኛ ሁኔታ

በአፈፃፀሙ ላይ አንዳንድ ማሻሻያዎቜን እናደርጋለን። በመጀመሪያ, እንጠቀማለን መዘግዚት ማሳዚት ለ 100 ms. ይህ ማለት ዹደንበኛው "ዹአሁኑ" ሁኔታ ሁል ጊዜ በአገልጋዩ ላይ ካለው ዚጚዋታ ሁኔታ በ100 ሚ.ሎ. ለምሳሌ, በአገልጋዩ ላይ ያለው ጊዜ ኹሆነ 150, ኚዚያም ደንበኛው አገልጋዩ በወቅቱ ዹነበሹውን ሁኔታ ያቀርባል 50:

ባለብዙ ተጫዋቜ .io ዚድር ጚዋታ መፍጠር
ይህ ዚማይገመት ዚጚዋታ ዝመና ጊዜዎቜን ለመትሚፍ ዹ100ms ቋት ይሰጠናል፡

ባለብዙ ተጫዋቜ .io ዚድር ጚዋታ መፍጠር
ለዚህ ያለው ክፍያ ዘላቂ ይሆናል ዚግቀት መዘግዚት ለ 100 ms. ይህ ለስላሳ አጚዋወት ቀላል መስዋእትነት ነው - አብዛኞቹ ተጫዋ቟ቜ (በተለይ ተራ ተጫዋ቟ቜ) ይህን መዘግዚት እንኳን አያስተውሉም። ባልተጠበቀ መዘግዚት ኚመጫወት ይልቅ ሰዎቜ ቋሚ ዹሆነ ዹ100 ሚ.ሜ መዘግዚትን ማስተካኚል ቀላል ነው።

ሌላ ዚሚባል ቮክኒክ መጠቀም እንቜላለን ዹደንበኛ-ጎን ትንበያ, ይህም ዚታሰበውን መዘግዚትን በመቀነስ ጥሩ ስራ ይሰራል, ነገር ግን በዚህ ጜሑፍ ውስጥ አይካተትም.

ሌላው እዚተጠቀምንበት ያለው ማሻሻያ ነው። መስመራዊ ጣልቃገብነት. በማሳዚት መዘግዚት ምክንያት፣ እኛ ብዙውን ጊዜ በደንበኛው ውስጥ ካለው ዹአሁኑ ጊዜ በፊት ቢያንስ አንድ ዝመና እንሆናለን። ሲጠራ getCurrentState(), መፈጾም እንቜላለን መስመራዊ ጣልቃገብነት በደንበኛው ውስጥ ካለው ዹአሁኑ ጊዜ በፊት እና በኋላ ባለው ዚጚዋታ ዝመናዎቜ መካኚል፡-

ባለብዙ ተጫዋቜ .io ዚድር ጚዋታ መፍጠር
ይሄ ዚፍሬም ተመን ቜግርን ይፈታል፡ አሁን ልዩ ፍሬሞቜን በምንፈልገው ዚፍሬም ፍጥነት መስራት እንቜላለን!

7.3 ዚተሻሻለ ዹደንበኛ ሁኔታን በመተግበር ላይ

ዚትግበራ ምሳሌ በ src/client/state.js ሁለቱንም render lag እና linear interpolation ይጠቀማል፣ ግን ለሹጅም ጊዜ አይደለም። ኮዱን በሁለት ክፍሎቜ እንኚፋፍል። ዚመጀመሪያው ይኞውና፡-

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 ለማቅሚብ ዚምስል ስራ መዘግዚትን መጠቀም እንፈልጋለን ነገር ግን በአገልጋዩ ላይ ያለውን ጊዜ ፈጜሞ አናውቅም፣ ምክንያቱም ማሻሻያዎቹ ወደ እኛ ለመድሚስ ምን ያህል ጊዜ እንደወሰደ ማወቅ አንቜልም። በይነመሚቡ ዚማይታወቅ እና ፍጥነቱ በጣም ሊለያይ ይቜላል!

በዚህ ቜግር ዙሪያ ለማግኘት, እኛ ምክንያታዊ approximation መጠቀም ይቜላሉ: እኛ ዚመጀመሪያው ዝማኔ ወዲያውኑ እንደደሚሰ አስመስሎ. ይህ እውነት ኚሆነ፣ በዚህ ጊዜ ዚአገልጋዩን ጊዜ እናውቅ ነበር! ዚአገልጋዩን ዹጊዜ ማህተም እናኚማቻለን። 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 ላይ ዹፊልሙ.

ክፍል 2. ዚጀርባ አገልጋይ

በዚህ ክፍል ዚእኛን ዚሚቆጣጠሚውን ዹ Node.js ጀርባ እንመለኚታለን .io ጚዋታ ምሳሌ.

1. ዹአገልጋይ መግቢያ ነጥብ

ዚድር አገልጋዩን ለማስተዳደር ለ Node.js ዚሚታወቅ ታዋቂ ዚድር ማዕቀፍ እንጠቀማለን። ይግለጹ. በእኛ አገልጋይ መግቢያ ነጥብ ፋይል ይዋቀራል። src/server/server.js:

አገልጋይ.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 እንደተነጋገርን አስታውስ? ዹኛን ዚዌብፓክ አወቃቀሮቜን ዚምንጠቀምበት ይህ ነው። በሁለት መንገዶቜ እንጠቀማ቞ዋለን፡-

  • ተጠቀም ዚዌብፓክ-ዮቭ-ሚድልዌር ዚእኛን ዚእድገት ፓኬጆቜን በራስ ሰር እንደገና ለመገንባት ወይም
  • በስታቲስቲክስ አቃፊ ያስተላልፉ dist/ኚምርቱ ግንባታ በኋላ ፋይሎቻቜንን በዚትኛው ዚዌብፓክ እንጜፋለን።

ሌላው አስፈላጊ ተግባር server.js አገልጋዩን ማዋቀር ነው። ሶኬት.ioኚኀክስፕሚስ አገልጋይ ጋር ዚሚገናኘው፡-

አገልጋይ.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:

አገልጋይ.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 their socket.io socket (ግራ ኚተጋቡ፣ ኚዚያ ይመለሱ server.js). Socket.io ራሱ ለእያንዳንዱ ሶኬት ልዩ ይመድባል idስለዚህ ስለዚህ ጉዳይ መጹነቅ አያስፈልገንም. እደውልለታለሁ። ዚተጫዋቜ መታወቂያ.

ያንን በአእምሯቜን ይዘን፣ በአንድ ክፍል ውስጥ ያሉ ዚአብነት ተለዋዋጮቜን እንመርምር Game:

  • sockets ዚተጫዋቜ መታወቂያውን ኚተጫዋቹ ጋር ኹተገናኘው ሶኬት ጋር ዚሚያገናኝ ነገር ነው። በቋሚ ጊዜ ውስጥ ሶኬቶቜን በተጫዋቜ መታወቂያ቞ው እንድንደርስ ያስቜለናል።
  • players ዚተጫዋቜ መታወቂያውን ኚኮዱ>ተጫዋቜ ነገር ጋር ዚሚያገናኝ ዕቃ ነው።

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:

ነገር.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:

ጥይት.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 ዚሚኚተሉት ቅጥያዎቜ ብቻ:

  • ጥቅሉን በመጠቀም shortid ለአጋጣሚ ትውልድ id ፕሮጄክት.
  • መስክ መጹመር parentIDይህንን ፕሮጀክት ዹፈጠሹውን ተጫዋቜ መኚታተል እንድትቜሉ።
  • ዚመመለሻ እሎት በማኹል ላይ update(), ይህም ጋር እኩል ነው trueፕሮጀክቱ ኚመድሚኩ ውጭ ኹሆነ (በመጚሚሻው ክፍል ላይ ስለዚህ ጉዳይ እንደተነጋገርን ያስታውሱ?)

ወደዚህ እንሂድ 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:

ጚዋታ.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.

ዚእኛ ዚግጭት ማወቂያ አተገባበር ምን ይመስላል።

ግጭቶቜ.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 ጚዋታ ይገንቡ!

ሁሉም ዹናሙና ኮድ ክፍት ምንጭ እና ዹተለጠፈ ነው። ዹፊልሙ.

ምንጭ: hab.com

አስተያዚት ያክሉ