ื™ืฆื™ืจืช ืžืฉื—ืง ืื™ื ื˜ืจื ื˜ ืžืจื•ื‘ื” ืžืฉืชืชืคื™ื .io

ื™ืฆื™ืจืช ืžืฉื—ืง ืื™ื ื˜ืจื ื˜ ืžืจื•ื‘ื” ืžืฉืชืชืคื™ื .io
ื™ืฆื ื‘-2015 ืื’ืจ.ื™ื• ื”ืคืš ืœื”ื™ื•ืช ื”ืื‘ ืฉืœ ื–'ืื ืจ ื—ื“ืฉ ืžืฉื—ืงื™ื .ioืฉื’ื“ืœื” ื‘ืคื•ืคื•ืœืจื™ื•ืช ืžืื–. ืื ื™ ืื™ืฉื™ืช ื—ื•ื•ื™ืชื™ ืืช ื”ืขืœื™ื™ื” ื‘ืคื•ืคื•ืœืจื™ื•ืช ืฉืœ ืžืฉื—ืงื™ .io: ื‘ืžื”ืœืš ืฉืœื•ืฉ ื”ืฉื ื™ื ื”ืื—ืจื•ื ื•ืช, ืงืจื” ืœื™ ื™ืฆืจ ื•ืžื›ืจ ืฉื ื™ ืžืฉื—ืงื™ื ืžื”ื–'ืื ืจ ื”ื–ื”..

ืœืžืงืจื” ืฉืžืขื•ืœื ืœื ืฉืžืขืชื ืขืœ ื”ืžืฉื—ืงื™ื ื”ืืœื” ื‘ืขื‘ืจ, ืืœื• ื”ื ืžืฉื—ืงื™ ืื™ื ื˜ืจื ื˜ ืžืจื•ื‘ื™ ืžืฉืชืชืคื™ื ื‘ื—ื™ื ื ืฉืงืœ ืœืฉื—ืง ื‘ื”ื (ืื™ืŸ ืฆื•ืจืš ื‘ื—ืฉื‘ื•ืŸ). ื‘ื“ืจืš ื›ืœืœ ื”ื ืžืชืžื•ื“ื“ื™ื ืขื ืฉื—ืงื ื™ื ื™ืจื™ื‘ื™ื ืจื‘ื™ื ื‘ืื•ืชื” ื–ื™ืจื”. ืžืฉื—ืงื™ io ืžืคื•ืจืกืžื™ื ืื—ืจื™ื: Slither.io ะธ Diep.io.

ื‘ืคื•ืกื˜ ื–ื” ื ื‘ื“ื•ืง ื›ื™ืฆื“ ืฆื•ืจ ืžืฉื—ืง .io ืžืืคืก. ื‘ืฉื‘ื™ืœ ื–ื”, ืจืง ื™ื“ืข ื‘-Javascript ื™ืกืคื™ืง: ืืชื” ืฆืจื™ืš ืœื”ื‘ื™ืŸ ื“ื‘ืจื™ื ื›ืžื• ืชื—ื‘ื™ืจ ES6, ืžื™ืœืช ืžืคืชื— this ะธ ื”ื‘ื˜ื—ื•ืช. ื’ื ืื ื”ื™ื“ืข ืฉืœืš ื‘-Javascript ืื™ื ื• ืžื•ืฉืœื, ืืชื” ืขื“ื™ื™ืŸ ื™ื›ื•ืœ ืœื”ื‘ื™ืŸ ืืช ืจื•ื‘ ื”ืคื•ืกื˜.

ื“ื•ื’ืžื” ืœืžืฉื—ืง .io

ืœืกื™ื•ืข ื‘ืœืžื™ื“ื”, ื ืคื ื” ืœ ื“ื•ื’ืžื” ืœืžืฉื—ืง .io. ื ืกื” ืœืฉื—ืง ื‘ื•!

ื™ืฆื™ืจืช ืžืฉื—ืง ืื™ื ื˜ืจื ื˜ ืžืจื•ื‘ื” ืžืฉืชืชืคื™ื .io
ื”ืžืฉื—ืง ื“ื™ ืคืฉื•ื˜: ืืชื” ืฉื•ืœื˜ ื‘ืกืคื™ื ื” ื‘ื–ื™ืจื” ืฉื‘ื” ื™ืฉ ืฉื—ืงื ื™ื ืื—ืจื™ื. ื”ืกืคื™ื ื” ืฉืœืš ื™ื•ืจื” ืงืœื™ืขื™ื ืื•ื˜ื•ืžื˜ื™ืช ื•ืืชื” ืžื ืกื” ืœืคื’ื•ืข ื‘ืฉื—ืงื ื™ื ืื—ืจื™ื ืชื•ืš ื›ื“ื™ ื”ื™ืžื ืขื•ืช ืžื”ืงืœื™ืขื™ื ืฉืœื”ื.

1. ืกืงื™ืจื” ืงืฆืจื” / ืžื‘ื ื” ื”ืคืจื•ื™ืงื˜

ืื ื™ ืžืžืœื™ืฅ ืœื”ื•ืจื™ื“ ืงื•ื“ ืžืงื•ืจ ืžืฉื—ืง ืœื“ื•ื’ืžื” ื›ื“ื™ ืฉืชื•ื›ืœ ืœืขืงื•ื‘ ืื—ืจื™.

ื”ื“ื•ื’ืžื” ืžืฉืชืžืฉืช ื‘ื“ื‘ืจื™ื ื”ื‘ืื™ื:

  • ืืงืกืคืจืก ื”ื™ื ืžืกื’ืจืช ื”ืื™ื ื˜ืจื ื˜ ื”ืคื•ืคื•ืœืจื™ืช ื‘ื™ื•ืชืจ ืฉืœ Node.js ื”ืžื ื”ืœืช ืืช ืฉืจืช ื”ืื™ื ื˜ืจื ื˜ ืฉืœ ื”ืžืฉื—ืง.
  • socket.io - ืกืคืจื™ื™ืช websocket ืœื”ื—ืœืคืช ื ืชื•ื ื™ื ื‘ื™ืŸ ื“ืคื“ืคืŸ ืœืฉืจืช.
  • Webpack - ืžื ื”ืœ ืžื•ื“ื•ืœ. ืืชื” ื™ื›ื•ืœ ืœืงืจื•ื ืขืœ ืœืžื” ืœื”ืฉืชืžืฉ ื‘-Webpack. ื›ืืŸ.

ื›ืš ื ืจืื” ืžื‘ื ื” ืกืคืจื™ื•ืช ื”ืคืจื•ื™ืงื˜:

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. ื‘ื•ืื• ื ืกืชื›ืœ ืขืœ ืชืฆื•ืจืช 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 build ืฉืœื ื• ื™ื”ื™ื” ืžืžื•ืงื ื‘ืกืคืจื™ื™ื” dist/. ืื ื™ ืืงืจื ืœืงื•ื‘ืฅ ื”ื–ื” ืฉืœื ื• ื—ื‘ื™ืœืช js.
  • ืื ื• ืžืฉืชืžืฉื™ื ื‘ื‘ืœ, ื•ื‘ืคืจื˜ ื”ืชืฆื•ืจื” @babel/preset-env ืœื”ืžืจืช ืงื•ื“ JS ืฉืœื ื• ืœื“ืคื“ืคื ื™ื ื™ืฉื ื™ื ื™ื•ืชืจ.
  • ืื ื• ืžืฉืชืžืฉื™ื ื‘ืชื•ืกืฃ ื›ื“ื™ ืœื—ืœืฅ ืืช ื›ืœ ื”-CSS ืืœื™ื• ืžืชื™ื™ื—ืกื™ื ืงื‘ืฆื™ JS ื•ืœืฉืœื‘ ืื•ืชื ื‘ืžืงื•ื ืื—ื“. ืื ื™ ืืงืจื ืœื• ืฉืœื ื• ื—ื‘ื™ืœืช css.

ืื•ืœื™ ืฉืžืช ืœื‘ ืœืฉืžื•ืช ืงื‘ืฆื™ื ืžื•ื–ืจื™ื ืฉืœ ื—ื‘ื™ืœื•ืช '[name].[contenthash].ext'. ื”ื ืžื›ื™ืœื™ื ื”ื—ืœืคื•ืช ืฉืžื•ืช ืงื‘ืฆื™ื ื—ื‘ื™ืœืช ืื™ื ื˜ืจื ื˜: [name] ื™ื•ื—ืœืฃ ื‘ืฉื ืฉืœ ื ืงื•ื“ืช ื”ืงืœื˜ (ื‘ืžืงืจื” ืฉืœื ื•, ื–ื” game), ื• [contenthash] ื™ื•ื—ืœืฃ ื‘-hash ืฉืœ ืชื•ื›ืŸ ื”ืงื•ื‘ืฅ. ืื ื—ื ื• ืขื•ืฉื™ื ืืช ื–ื” ื›ื“ื™ ืžื˜ื‘ ืืช ื”ืคืจื•ื™ืงื˜ ืขื‘ื•ืจ 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 ืœื“ื•ื’ืžื”  ืœึฐืฉื‚ึทื—ึตืง

ื“ื•ื’ืžื” ื–ื• ืฉืœ ื”ืงื•ื“ ืคื•ืฉื˜ื” ืžืขื˜ ืœืฆื•ืจืš ื”ื‘ื”ื™ืจื•ืช, ื•ืื ื™ ืืขืฉื” ืืช ืื•ืชื• ื”ื“ื‘ืจ ืขื ืจื‘ื•ืช ืžื“ื•ื’ืžืื•ืช ื”ืคื•ืกื˜ื™ื ื”ืื—ืจื•ืช. ืชืžื™ื“ ื ื™ืชืŸ ืœืจืื•ืช ืืช ื”ืงื•ื“ ื”ืžืœื ื‘ื›ืชื•ื‘ืช GitHub.

ื™ืฉ ืœื ื•:

  • ืจื›ื™ื‘ ืงื ื‘ืก HTML5 (<canvas>) ืฉื‘ื• ื ืฉืชืžืฉ ืœืขื™ื‘ื•ื“ ื”ืžืฉื—ืง.
  • <link> ื›ื“ื™ ืœื”ื•ืกื™ืฃ ืืช ื—ื‘ื™ืœืช ื”-CSS ืฉืœื ื•.
  • <script> ื›ื“ื™ ืœื”ื•ืกื™ืฃ ืืช ื—ื‘ื™ืœืช ื”-Javascript ืฉืœื ื•.
  • ืชืคืจื™ื˜ ืจืืฉื™ ืขื ืฉื ืžืฉืชืžืฉ <input> ื•ื›ืคืชื•ืจ ื”-PLAY (<button>).

ืœืื—ืจ ื˜ืขื™ื ืช ื“ืฃ ื”ื‘ื™ืช, ื”ื“ืคื“ืคืŸ ื™ืชื—ื™ืœ ืœื”ืคืขื™ืœ ืงื•ื“ Javascript, ื”ื—ืœ ืžืงื•ื‘ืฅ 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 ื™ืฉ ืชืžื™ื›ื” ืžืงื•ืจื™ืช 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.

ืœืื—ืจ ื”ื•ืจื“ืช ื”ืžืฉืื‘ื™ื, ืชื•ื›ืœ ืœื”ืชื—ื™ืœ ื‘ืขื™ื‘ื•ื“. ื›ืคื™ ืฉื ืืžืจ ืงื•ื“ื ืœื›ืŸ, ื›ื“ื™ ืœืฆื™ื™ืจ ืขืœ ื“ืฃ ืื™ื ื˜ืจื ื˜, ืื ื• ืžืฉืชืžืฉื™ื 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: ืžื™ื“ืข ืขืœ ื”ื ื’ืŸ ืฉืžืงื‘ืœ ืขื“ื›ื•ืŸ ื–ื”.
  • ืื—ืจื™ื: ืžืขืจืš ืžื™ื“ืข ืขืœ ืฉื—ืงื ื™ื ืื—ืจื™ื ื”ืžืฉืชืชืคื™ื ื‘ืื•ืชื• ืžืฉื—ืง.
  • ื›ื“ื•ืจื™ื: ืžืขืจืš ืžื™ื“ืข ืขืœ ืงืœื™ืขื™ื ื‘ืžืฉื—ืง.
  • leaderboard: ื ืชื•ื ื™ Leaderboard ื ื•ื›ื—ื™ื™ื. ื‘ืคื•ืกื˜ ื–ื”, ืœื ื ืฉืงื•ืœ ืื•ืชื.

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
ื”ื™ื™ืฉื•ื ื”ื ืื™ื‘ื™ ื”ื•ื ืœืžืขืฉื” ื”ืžืงืจื” ื”ื’ืจื•ืข ื‘ื™ื•ืชืจ ื‘ื›ืœ ื”ื ื•ื’ืข ืœ-latency. ืื ืขื“ื›ื•ืŸ ืžืฉื—ืง ืžืชืงื‘ืœ ื‘ืื™ื—ื•ืจ ืฉืœ 50ms, ืื– ื“ื•ื›ื ื™ ืœืงื•ื—ื•ืช ืชื•ืกืคืช ืฉืœ 50 ืืœืคื™ื•ืช ื”ืฉื ื™ื™ื” ืžื›ื™ื•ื•ืŸ ืฉื”ื™ื ืขื“ื™ื™ืŸ ืžืฆื™ื’ื” ืืช ืžืฆื‘ ื”ืžืฉื—ืง ืžื”ืขื“ื›ื•ืŸ ื”ืงื•ื“ื. ืืชื ื™ื›ื•ืœื™ื ืœืชืืจ ืœืขืฆืžื›ื ื›ืžื” ื–ื” ืœื ื ื•ื— ืœืฉื—ืงืŸ: ื‘ืœื™ืžื” ืฉืจื™ืจื•ืชื™ืช ืชื’ืจื•ื ืœืžืฉื—ืง ืœื”ืจื’ื™ืฉ ืงื•ืคืฆื ื™ ื•ืœื ื™ืฆื™ื‘.

7.2 ืžืฆื‘ ืœืงื•ื— ืžืฉื•ืคืจ

ื ื‘ืฆืข ื›ืžื” ืฉื™ืคื•ืจื™ื ื‘ื™ื™ืฉื•ื ื”ื ืื™ื‘ื™. ืจืืฉื™ืช, ืื ื• ืžืฉืชืžืฉื™ื ืขื™ื›ื•ื‘ ื‘ืขื™ื‘ื•ื“ ืœืžืฉืš 100 ืืœืคื™ื•ืช ื”ืฉื ื™ื™ื”. ื–ื” ืื•ืžืจ ืฉื”ืžืฆื‘ ื”"ื ื•ื›ื—ื™" ืฉืœ ื”ืœืงื•ื— ืชืžื™ื“ ื™ืคื’ืจ ืื—ืจื™ ืžืฆื‘ ื”ืžืฉื—ืง ื‘ืฉืจืช ื‘-100ms. ืœื“ื•ื’ืžื”, ืื ื”ืฉืขื” ื‘ืฉืจืช ื”ื™ื 150, ืื– ื”ืœืงื•ื— ื™ืฆื™ื’ ืืช ื”ืžืฆื‘ ืฉื‘ื• ื”ื™ื” ื”ืฉืจืช ื‘ืื•ืชื• ื–ืžืŸ 50:

ื™ืฆื™ืจืช ืžืฉื—ืง ืื™ื ื˜ืจื ื˜ ืžืจื•ื‘ื” ืžืฉืชืชืคื™ื .io
ื–ื” ื ื•ืชืŸ ืœื ื• ืžืื’ืจ ืฉืœ 100ms ื›ื“ื™ ืœืฉืจื•ื“ ื–ืžื ื™ ืขื“ื›ื•ืŸ ื‘ืœืชื™ ืฆืคื•ื™ื™ื ืฉืœ ื”ืžืฉื—ืง:

ื™ืฆื™ืจืช ืžืฉื—ืง ืื™ื ื˜ืจื ื˜ ืžืจื•ื‘ื” ืžืฉืชืชืคื™ื .io
ื”ืชืžื•ืจื” ืขื‘ื•ืจ ื–ื” ืชื”ื™ื” ืงื‘ื•ืขื” ืคื™ื’ื•ืจ ืงืœื˜ ืœืžืฉืš 100 ืืœืคื™ื•ืช ื”ืฉื ื™ื™ื”. ื–ื•ื”ื™ ื”ืงืจื‘ื” ืงื˜ื ื” ืœืžืฉื—ืง ื—ืœืง - ืจื•ื‘ ื”ืฉื—ืงื ื™ื (ื‘ืžื™ื•ื—ื“ ืฉื—ืงื ื™ื ืžื–ื“ืžื ื™ื) ืืคื™ืœื• ืœื ื™ืฉื™ืžื• ืœื‘ ืœืขื™ื›ื•ื‘ ื”ื–ื”. ื”ืจื‘ื” ื™ื•ืชืจ ืงืœ ืœืื ืฉื™ื ืœื”ืกืชื’ืœ ืœื–ืžืŸ ื”ืฉื”ื™ื™ื” ืงื‘ื•ืข ืฉืœ 100ms ืžืืฉืจ ืœืฉื—ืง ืขื ื—ื‘ื™ื•ืŸ ื‘ืœืชื™ ืฆืคื•ื™.

ืื ื—ื ื• ื™ื›ื•ืœื™ื ื’ื ืœื”ืฉืชืžืฉ ื‘ื˜ื›ื ื™ืงื” ืื—ืจืช ืฉื ืงืจืืช ื—ื™ื–ื•ื™ ืžืฆื“ ื”ืœืงื•ื—, ืฉืขื•ืฉื” ืขื‘ื•ื“ื” ื˜ื•ื‘ื” ื‘ื”ืคื—ืชืช ื”ืฉื”ื™ื™ื” ื”ื ืชืคืกืช, ืืš ืœื ื™ืกื•ืงืจ ื‘ืคื•ืกื˜ ื”ื–ื”.

ืฉื™ืคื•ืจ ื ื•ืกืฃ ืฉืื ื• ืžืฉืชืžืฉื™ื ื‘ื• ื”ื•ื ืื™ื ื˜ืจืคื•ืœืฆื™ื” ืœื™ื ืืจื™ืช. ืขืงื‘ ืคื™ื’ื•ืจ ื‘ืขื™ื‘ื•ื“, ื‘ื“ืจืš ื›ืœืœ ืื ื—ื ื• ืžืงื“ื™ืžื™ื ืœืคื—ื•ืช ืขื“ื›ื•ืŸ ืื—ื“ ืžื”ื–ืžืŸ ื”ื ื•ื›ื—ื™ ื‘ืœืงื•ื—. ื›ืฉืžืชืงืฉืจื™ื getCurrentState(), ืื ื—ื ื• ื™ื›ื•ืœื™ื ืœื‘ืฆืข ืื™ื ื˜ืจืคื•ืœืฆื™ื” ืœื™ื ืืจื™ืช ื‘ื™ืŸ ืขื“ื›ื•ื ื™ ื”ืžืฉื—ืง ืžืžืฉ ืœืคื ื™ ื•ืื—ืจื™ ื”ืฉืขื” ื”ื ื•ื›ื—ื™ืช ื‘ืœืงื•ื—:

ื™ืฆื™ืจืช ืžืฉื—ืง ืื™ื ื˜ืจื ื˜ ืžืจื•ื‘ื” ืžืฉืชืชืคื™ื .io
ื–ื” ืคื•ืชืจ ืืช ื‘ืขื™ื™ืช ืงืฆื‘ ื”ืคืจื™ื™ืžื™ื: ื›ืขืช ื ื•ื›ืœ ืœื”ืฆื™ื’ ืคืจื™ื™ืžื™ื ื™ื™ื—ื•ื“ื™ื™ื ื‘ื›ืœ ืงืฆื‘ ืคืจื™ื™ืžื™ื ืฉื ืจืฆื”!

7.3 ื™ื™ืฉื•ื ืžืฆื‘ ืœืงื•ื— ืžืฉื•ืคืจ

ื“ื•ื’ืžื” ืœื™ื™ืฉื•ื ื‘ src/client/state.js ืžืฉืชืžืฉ ื’ื ื‘-Rend lag ื•ื’ื ื‘ืื™ื ื˜ืจืคื•ืœืฆื™ื” ืœื™ื ื™ืืจื™ืช, ืื‘ืœ ืœื ืœืื•ืจืš ื–ืžืŸ. ื‘ื•ืื• ื ืฉื‘ื•ืจ ืืช ื”ืงื•ื“ ืœืฉื ื™ ื—ืœืงื™ื. ื”ื ื” ื”ืจืืฉื•ืŸ:

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
ืขื“ื›ื•ืŸ ื”ืžืฉื—ืง ื™ืฉื™ืจื•ืช ืžืฉืžืืœ ืœ-"Client Render Time" ื”ื•ื ืขื“ื›ื•ืŸ ื”ื‘ืกื™ืก.

ืœืžื” ืžืฉืžืฉ ืขื“ื›ื•ืŸ ื”ื‘ืกื™ืก? ืœืžื” ืื ื—ื ื• ื™ื›ื•ืœื™ื ืœื”ื•ืจื™ื“ ืขื“ื›ื•ื ื™ื ืœืงื• ื”ื‘ืกื™ืก? ื›ื“ื™ ืœื”ื‘ื™ืŸ ืืช ื–ื”, ื‘ื•ืื• ะะฐะบะพะฝะตั†-ั‚ะพ ืœืฉืงื•ืœ ืืช ื”ื™ื™ืฉื•ื 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ืฉืžืชื—ื‘ืจ ืจืง ืœืฉืจืช ื”ืืงืกืคืจืก:

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ืื– ืื ื—ื ื• ืœื ืฆืจื™ื›ื™ื ืœื“ืื•ื’ ื‘ืงืฉืจ ืœื–ื”. ืื ื™ ืืชืงืฉืจ ืืœื™ื• ืžื–ื”ื” ืฉื—ืงืŸ.

ืขื ื–ื” ื‘ื—ืฉื‘ื•ืŸ, ื‘ื•ืื• ื ื—ืงื•ืจ ืžืฉืชื ื™ ืžื•ืคืข ื‘ืžื—ืœืงื” 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:

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

ื”ื•ืกืคืช ืชื’ื•ื‘ื”