рдПрдХ рдорд▓реНрдЯреАрдкреНрд▓реЗрдпрд░ .io рд╡реЗрдм рдЧреЗрдо рдмрдирд╛рдирд╛

рдПрдХ рдорд▓реНрдЯреАрдкреНрд▓реЗрдпрд░ .io рд╡реЗрдм рдЧреЗрдо рдмрдирд╛рдирд╛
2015 рдореЗрдВ рд░рд┐рд▓реАрдЬрд╝ рд╣реБрдИ Agar.io рдПрдХ рдирдИ рд╢реИрд▓реА рдХреЗ рдЬрдирдХ рдмрдиреЗ рдЧреЗрдореНрд╕ .ioрдЬреЛ рддрдм рд╕реЗ рд▓реЛрдХрдкреНрд░рд┐рдпрддрд╛ рдореЗрдВ рд╡реГрджреНрдзрд┐ рд╣реБрдИ рд╣реИред рдореИрдВрдиреЗ рд╡реНрдпрдХреНрддрд┐рдЧрдд рд░реВрдк рд╕реЗ .io рдЧреЗрдореНрд╕ рдХреА рд▓реЛрдХрдкреНрд░рд┐рдпрддрд╛ рдореЗрдВ рд╡реГрджреНрдзрд┐ рдХрд╛ рдЕрдиреБрднрд╡ рдХрд┐рдпрд╛ рд╣реИ: рдкрд┐рдЫрд▓реЗ рддреАрди рд╡рд░реНрд╖реЛрдВ рдореЗрдВ, рдореИрдВрдиреЗ рдРрд╕рд╛ рдХрд┐рдпрд╛ рд╣реИ рдЗрд╕ рд╢реИрд▓реА рдХреЗ рджреЛ рдЧреЗрдо рдмрдирд╛рдП рдФрд░ рдмреЗрдЪреЗред.

рдпрджрд┐ рдЖрдкрдиреЗ рдкрд╣рд▓реЗ рдХрднреА рдЗрди рдЦреЗрд▓реЛрдВ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдирд╣реАрдВ рд╕реБрдирд╛ рд╣реИ, рддреЛ рдпреЗ рдореБрдлрд╝реНрдд рдорд▓реНрдЯреАрдкреНрд▓реЗрдпрд░ рд╡реЗрдм рдЧреЗрдо рд╣реИрдВ рдЬрд┐рдиреНрд╣реЗрдВ рдЦреЗрд▓рдирд╛ рдЖрд╕рд╛рди рд╣реИ (рдХрд┐рд╕реА рдЦрд╛рддреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдирд╣реАрдВ рд╣реИ)ред рд╡реЗ рдЖрдо рддреМрд░ рдкрд░ рдПрдХ рд╣реА рдореИрджрд╛рди рдореЗрдВ рдХрдИ рд╡рд┐рд░реЛрдзреА рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ рдХрд╛ рд╕рд╛рдордирд╛ рдХрд░рддреЗ рд╣реИрдВред рдЕрдиреНрдп рдкреНрд░рд╕рд┐рджреНрдз .io рдЦреЗрд▓: Slither.io ╨╕ Diep.io.

рдЗрд╕ рдкреЛрд╕реНрдЯ рдореЗрдВ рд╣рдо рдЬрд╛рдиреЗрдВрдЧреЗ рдХрд┐ рдХреИрд╕реЗ рд╕реНрдХреНрд░реИрдЪ рд╕реЗ рдПрдХ .io рдЧреЗрдо рдмрдирд╛рдПрдВ. рдЗрд╕рдХреЗ рд▓рд┐рдП рдХреЗрд╡рд▓ рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдХрд╛ рдЬреНрдЮрд╛рди рд╣реА рдХрд╛рдлреА рд╣реЛрдЧрд╛: рдЖрдкрдХреЛ рд╕рд┐рдВрдЯреИрдХреНрд╕ рдЬреИрд╕реА рдЪреАрдЬреЛрдВ рдХреЛ рд╕рдордЭрдиреЗ рдХреА рдЬрд░реВрд░рдд рд╣реИ ES6, рдХреАрд╡рд░реНрдб this ╨╕ рд╡рд╛рджреЗ. рднрд▓реЗ рд╣реА рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЖрдкрдХрд╛ рдЬреНрдЮрд╛рди рд╕рд╣реА рди рд╣реЛ, рдлрд┐рд░ рднреА рдЖрдк рдЕрдзрд┐рдХрд╛рдВрд╢ рдкреЛрд╕реНрдЯ рдХреЛ рд╕рдордЭ рд╕рдХрддреЗ рд╣реИрдВред

.io рдЦреЗрд▓ рдЙрджрд╛рд╣рд░рдг

рд╕реАрдЦрдиреЗ рдореЗрдВ рд╕рд╣рд╛рдпрддрд╛ рдХреЗ рд▓рд┐рдП, рд╣рдо рдЗрд╕рдХрд╛ рдЙрд▓реНрд▓реЗрдЦ рдХрд░реЗрдВрдЧреЗ .io рдЦреЗрд▓ рдЙрджрд╛рд╣рд░рдг. рдЗрд╕реЗ рдЦреЗрд▓рдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░реЗрдВ!

рдПрдХ рдорд▓реНрдЯреАрдкреНрд▓реЗрдпрд░ .io рд╡реЗрдм рдЧреЗрдо рдмрдирд╛рдирд╛
рдЦреЗрд▓ рдХрд╛рдлреА рд╕рд░рд▓ рд╣реИ: рдЖрдк рдПрдХ рдЬрд╣рд╛рдЬрд╝ рдХреЛ рдРрд╕реЗ рдХреНрд╖реЗрддреНрд░ рдореЗрдВ рдирд┐рдпрдВрддреНрд░рд┐рдд рдХрд░рддреЗ рд╣реИрдВ рдЬрд╣рд╛рдБ рдЕрдиреНрдп рдЦрд┐рд▓рд╛рдбрд╝реА рд╣реЛрддреЗ рд╣реИрдВред рдЖрдкрдХрд╛ рдЬрд╣рд╛рдЬ рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рд░реВрдк рд╕реЗ рдкреНрд░рдХреНрд╖реЗрдкреНрдп рджрд╛рдЧрддрд╛ рд╣реИ рдФрд░ рдЖрдк рдЕрдиреНрдп рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ рдХреЗ рдкреНрд░рдХреНрд╖реЗрдкреНрдп рд╕реЗ рдмрдЪрддреЗ рд╣реБрдП рдЙрди рдкрд░ рдкреНрд░рд╣рд╛рд░ рдХрд░рдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░рддреЗ рд╣реИрдВред

1. рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХрд╛ рд╕рдВрдХреНрд╖рд┐рдкреНрдд рдЕрд╡рд▓реЛрдХрди/рд╕рдВрд░рдЪрдирд╛

рдХреА рд╕рд┐рдлрд╛рд░рд┐рд╢ рд╕реНрд░реЛрдд рдХреЛрдб рдбрд╛рдЙрдирд▓реЛрдб рдХрд░реЗрдВ рдЙрджрд╛рд╣рд░рдг рдЦреЗрд▓ рддрд╛рдХрд┐ рдЖрдк рдореЗрд░рд╛ рдЕрдиреБрд╕рд░рдг рдХрд░ рд╕рдХреЗрдВред

рдЙрджрд╛рд╣рд░рдг рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реИ:

  • рд╡реНрдпрдХреНрдд рд╕рдмрд╕реЗ рд▓реЛрдХрдкреНрд░рд┐рдп Node.js рд╡реЗрдм рдлреНрд░реЗрдорд╡рд░реНрдХ рд╣реИ рдЬреЛ рдЧреЗрдо рдХреЗ рд╡реЗрдм рд╕рд░реНрд╡рд░ рдХреЛ рдкреНрд░рдмрдВрдзрд┐рдд рдХрд░рддрд╛ рд╣реИред
  • рд╕реЙрдХреЗрдЯ.рдЖрдИрдУ - рдмреНрд░рд╛рдЙрдЬрд╝рд░ рдФрд░ рд╕рд░реНрд╡рд░ рдХреЗ рдмреАрдЪ рдбреЗрдЯрд╛ рдХреЗ рдЖрджрд╛рди-рдкреНрд░рджрд╛рди рдХреЗ рд▓рд┐рдП рдПрдХ рд╡реЗрдмрд╕реЙрдХреЗрдЯ рд▓рд╛рдЗрдмреНрд░реЗрд░реАред
  • 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. рдЖрдЗрдП рд╣рдорд╛рд░реЗ рд╡реЗрдмрдкреИрдХ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди рдкрд░ рдПрдХ рдирдЬрд╝рд░ рдбрд╛рд▓реЗрдВ:

рд╡реЗрдмрдкреИрдХ.рдХреЙрдорди.рдЬреЗрдПрд╕:

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) рдХреНрд▓рд╛рдЗрдВрдЯ рдХрд╛ рдкреНрд░рд╡реЗрд╢ рдмрд┐рдВрджреБ рд╣реИред рд╡реЗрдмрдкреИрдХ рдпрд╣рд╛рдВ рд╕реЗ рд╢реБрд░реВ рд╣реЛрдЧрд╛ рдФрд░ рдЕрдиреНрдп рдЖрдпрд╛рддрд┐рдд рдлрд╝рд╛рдЗрд▓реЛрдВ рдХреЗ рд▓рд┐рдП рдкреБрдирд░рд╛рд╡рд░реНрддреА рд░реВрдк рд╕реЗ рдЦреЛрдЬ рдХрд░реЗрдЧрд╛ред
  • рд╣рдорд╛рд░реЗ рд╡реЗрдмрдкреИрдХ рдмрд┐рд▓реНрдб рдХрд╛ рдЖрдЙрдЯрдкреБрдЯ рдЬреЗрдПрд╕ рдирд┐рд░реНрджреЗрд╢рд┐рдХрд╛ рдореЗрдВ рд╕реНрдерд┐рдд рд╣реЛрдЧрд╛ dist/. рдореИрдВ рдЗрд╕ рдлрд╛рдЗрд▓ рдХреЛ рд╣рдорд╛рд░рд╛ рдХрд╣реВрдВрдЧрд╛ рдЬреЗрдПрд╕ рдкреИрдХреЗрдЬ.
  • рд╣рдо рдкреНрд░рдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВ рдХреЛрд▓рд╛рд╣рд▓, рдФрд░ рд╡рд┐рд╢реЗрд╖ рд░реВрдк рд╕реЗ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди @babel/preset-env рдкреБрд░рд╛рдиреЗ рдмреНрд░рд╛рдЙрдЬрд╝рд░реЛрдВ рдХреЗ рд▓рд┐рдП рд╣рдорд╛рд░реЗ рдЬреЗрдПрд╕ рдХреЛрдб рдХреЛ рдЯреНрд░рд╛рдВрд╕рдкрд┐рд▓рд┐рдВрдЧ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдПред
  • рд╣рдо рдЬреЗрдПрд╕ рдлрд╛рдЗрд▓реЛрдВ рджреНрд╡рд╛рд░рд╛ рд╕рдВрджрд░реНрднрд┐рдд рд╕рднреА рд╕реАрдПрд╕рдПрд╕ рдХреЛ рдирд┐рдХрд╛рд▓рдиреЗ рдФрд░ рдЙрдиреНрд╣реЗрдВ рдПрдХ рд╕реНрдерд╛рди рдкрд░ рд╕рдВрдпреЛрдЬрд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдкреНрд▓рдЧрдЗрди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд░рд╣реЗ рд╣реИрдВред рдореИрдВ рдЙрд╕реЗ рдЕрдкрдирд╛ рдХрд╣реВрдБрдЧрд╛ рд╕реАрдПрд╕рдПрд╕ рдкреИрдХреЗрдЬ.

рдЖрдкрдиреЗ рдЕрдЬреАрдм рдкреИрдХреЗрдЬ рдлрд╝рд╛рдЗрд▓ рдирд╛рдо рджреЗрдЦреЗ рд╣реЛрдВрдЧреЗ '[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. рдХреЛрдб рдмрджрд▓рддреЗ рд╣реА рдбреЗрд╡рд▓рдкрдореЗрдВрдЯ рд╕рд░реНрд╡рд░ рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рд░реВрдк рд╕реЗ рдЬреЗрдПрд╕ рдФрд░ рд╕реАрдПрд╕рдПрд╕ рдкреИрдХреЗрдЬреЛрдВ рдХрд╛ рдкреБрдирд░реНрдирд┐рд░реНрдорд╛рдг рдХрд░реЗрдЧрд╛ - рд╕рднреА рдкрд░рд┐рд╡рд░реНрддрдиреЛрдВ рдХреЛ рджреЗрдЦрдиреЗ рдХреЗ рд▓рд┐рдП рдмрд╕ рдкреЗрдЬ рдХреЛ рд░реАрдлреНрд░реЗрд╢ рдХрд░реЗрдВ!

3. рдЧреНрд░рд╛рд╣рдХ рдкреНрд░рд╡реЗрд╢ рдмрд┐рдВрджреБ

рдЪрд▓рд┐рдП рдЧреЗрдо рдХреЛрдб рдкрд░ рд╣реА рдЖрддреЗ рд╣реИрдВред рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ рд╣рдореЗрдВ рдПрдХ рдкреЗрдЬ рдЪрд╛рд╣рд┐рдП index.html, рд╕рд╛рдЗрдЯ рдкрд░ рдЬрд╛рдиреЗ рдкрд░ рдмреНрд░рд╛рдЙрдЬрд╝рд░ рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ рдЗрд╕реЗ рд▓реЛрдб рдХрд░реЗрдЧрд╛ред рд╣рдорд╛рд░рд╛ рдкреЗрдЬ рдХрд╛рдлреА рд╕рд░рд▓ рд╣реЛрдЧрд╛:

рд╕реВрдЪрдХрд╛рдВрдХ

рдПрдХ рдЙрджрд╛рд╣рд░рдг .io рдЧреЗрдо  рдЦреЗрд▓

рдЗрд╕ рдХреЛрдб рдЙрджрд╛рд╣рд░рдг рдХреЛ рд╕реНрдкрд╖реНрдЯрддрд╛ рдХреЗ рд▓рд┐рдП рдереЛрдбрд╝рд╛ рд╕рд░рд▓ рдмрдирд╛рдпрд╛ рдЧрдпрд╛ рд╣реИ, рдФрд░ рдореИрдВ рдХрдИ рдЕрдиреНрдп рдкреЛрд╕реНрдЯ рдЙрджрд╛рд╣рд░рдгреЛрдВ рдХреЗ рд╕рд╛рде рднреА рдРрд╕рд╛ рд╣реА рдХрд░реВрдВрдЧрд╛ред рдкреВрд░рд╛ рдХреЛрдб рд╣рдореЗрд╢рд╛ рдпрд╣рд╛рдВ рджреЗрдЦрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ Github.

рдЕрдкрдиреЗ рдкрд╛рд╕:

  • HTML5 рдХреИрдирд╡рд╛рд╕ рддрддреНрд╡ (<canvas>) рдЬрд┐рд╕рдХрд╛ рдЙрдкрдпреЛрдЧ рд╣рдо рдЧреЗрдо рдХреЛ рд░реЗрдВрдбрд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХрд░реЗрдВрдЧреЗред
  • <link> рд╣рдорд╛рд░реЗ рд╕реАрдПрд╕рдПрд╕ рдкреИрдХреЗрдЬ рдХреЛ рдЬреЛрдбрд╝рдиреЗ рдХреЗ рд▓рд┐рдПред
  • <script> рд╣рдорд╛рд░рд╛ рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдкреИрдХреЗрдЬ рдЬреЛрдбрд╝рдиреЗ рдХреЗ рд▓рд┐рдПред
  • рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдирд╛рдо рдХреЗ рд╕рд╛рде рдореБрдЦреНрдп рдореЗрдиреВ <input> рдФрд░ рдкреНрд▓реЗ рдмрдЯрди (<button>).

рд╣реЛрдо рдкреЗрдЬ рд▓реЛрдб рдХрд░рдиреЗ рдХреЗ рдмрд╛рдж, рдмреНрд░рд╛рдЙрдЬрд╝рд░ рдкреНрд░рд╡реЗрд╢ рдмрд┐рдВрджреБ рдЬреЗрдПрд╕ рдлрд╝рд╛рдЗрд▓ рд╕реЗ рд╢реБрд░реВ рдХрд░рдХреЗ рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдХреЛрдб рдирд┐рд╖реНрдкрд╛рджрд┐рдд рдХрд░рдирд╛ рд╢реБрд░реВ рдХрд░ рджреЗрдЧрд╛: 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. рд╕реАрдПрд╕рдПрд╕ рдЖрдпрд╛рдд (рдЗрд╕рд▓рд┐рдП рд╡реЗрдмрдкреИрдХ рдЙрдиреНрд╣реЗрдВ рд╣рдорд╛рд░реЗ рд╕реАрдПрд╕рдПрд╕ рдкреИрдХреЗрдЬ рдореЗрдВ рд╢рд╛рдорд┐рд▓ рдХрд░рдирд╛ рдЬрд╛рдирддрд╛ рд╣реИ)ред
  3. ╨Ч╨░╨┐╤Г╤Б╨║ connect() рд╕рд░реНрд╡рд░ рдХреЗ рд╕рд╛рде рдХрдиреЗрдХреНрд╢рди рд╕реНрдерд╛рдкрд┐рдд рдХрд░рдиреЗ рдФрд░ рдЪрд▓рд╛рдиреЗ рдХреЗ рд▓рд┐рдП downloadAssets() рдЧреЗрдо рдХреЛ рд░реЗрдВрдбрд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЖрд╡рд╢реНрдпрдХ рдЫрд╡рд┐рдпреЛрдВ рдХреЛ рдбрд╛рдЙрдирд▓реЛрдб рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдПред
  4. рдЪрд░рдг 3 рдкреВрд░рд╛ рд╣реЛрдиреЗ рдХреЗ рдмрд╛рдж рдореБрдЦреНрдп рдореЗрдиреВ рдкреНрд░рджрд░реНрд╢рд┐рдд рд╣реЛрддрд╛ рд╣реИ (playMenu).
  5. "рдкреНрд▓реЗ" рдмрдЯрди рджрдмрд╛рдиреЗ рдХреЗ рд▓рд┐рдП рд╣реИрдВрдбрд▓рд░ рд╕реЗрдЯ рдХрд░рдирд╛ред рдЬрдм рдмрдЯрди рджрдмрд╛рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рддреЛ рдХреЛрдб рдЧреЗрдо рдХреЛ рдЗрдирд┐рд╢рд┐рдпрд▓рд╛рдЗрдЬрд╝ рдХрд░рддрд╛ рд╣реИ рдФрд░ рд╕рд░реНрд╡рд░ рдХреЛ рдмрддрд╛рддрд╛ рд╣реИ рдХрд┐ рд╣рдо рдЦреЗрд▓рдиреЗ рдХреЗ рд▓рд┐рдП рддреИрдпрд╛рд░ рд╣реИрдВред

рд╣рдорд╛рд░реЗ рдХреНрд▓рд╛рдЗрдВрдЯ-рд╕рд░реНрд╡рд░ рд▓реЙрдЬрд┐рдХ рдХрд╛ рдореБрдЦреНрдп "рдореАрдЯ" рдЙрди рдлрд╝рд╛рдЗрд▓реЛрдВ рдореЗрдВ рд╣реИ рдЬрд┐рдиреНрд╣реЗрдВ рдлрд╝рд╛рдЗрд▓ рджреНрд╡рд╛рд░рд╛ рдЖрдпрд╛рдд рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛ index.js. рдЕрдм рд╣рдо рдЙрди рд╕рднреА рдкрд░ рдХреНрд░рдо рд╕реЗ рд╡рд┐рдЪрд╛рд░ рдХрд░реЗрдВрдЧреЗред

4. рдЧреНрд░рд╛рд╣рдХ рдбреЗрдЯрд╛ рдХрд╛ рдЖрджрд╛рди-рдкреНрд░рджрд╛рди

рдЗрд╕ рдЧреЗрдо рдореЗрдВ, рд╣рдо рд╕рд░реНрд╡рд░ рдХреЗ рд╕рд╛рде рд╕рдВрдЪрд╛рд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдкреНрд░рд╕рд┐рджреНрдз рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВ рд╕реЙрдХреЗрдЯ.рдЖрдИрдУ. Socket.io рдХреЛ рдореВрд▓ рд╕рдорд░реНрдерди рдкреНрд░рд╛рдкреНрдд рд╣реИ WebSockets, рдЬреЛ рджреЛрддрд░рдлрд╛ рд╕рдВрдЪрд╛рд░ рдХреЗ рд▓рд┐рдП рдЙрдкрдпреБрдХреНрдд рд╣реИрдВ: рд╣рдо рд╕рд░реНрд╡рд░ рдХреЛ рд╕рдВрджреЗрд╢ рднреЗрдЬ рд╕рдХрддреЗ рд╣реИрдВ ╨╕ рд╕рд░реНрд╡рд░ рд╣рдореЗрдВ рдЙрд╕реА рдХрдиреЗрдХреНрд╢рди рдкрд░ рд╕рдВрджреЗрд╢ рднреЗрдЬ рд╕рдХрддрд╛ рд╣реИред

рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдПрдХ рдлрд╝рд╛рдЗрд▓ рд╣реЛрдЧреА src/client/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. рдХреНрд▓рд╛рдЗрдВрдЯ рд░реЗрдВрдбрд░рд┐рдВрдЧ

рд╕реНрдХреНрд░реАрди рдкрд░ рдЪрд┐рддреНрд░ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рдиреЗ рдХрд╛ рд╕рдордп рдЖ рдЧрдпрд╛ рд╣реИ!

...рд▓реЗрдХрд┐рди рдЗрд╕рд╕реЗ рдкрд╣рд▓реЗ рдХрд┐ рд╣рдо рдРрд╕рд╛ рдХрд░ рд╕рдХреЗрдВ, рд╣рдореЗрдВ рдЗрд╕рдХреЗ рд▓рд┐рдП рдЖрд╡рд╢реНрдпрдХ рд╕рднреА рдЫрд╡рд┐рдпреЛрдВ (рд╕рдВрд╕рд╛рдзрдиреЛрдВ) рдХреЛ рдбрд╛рдЙрдирд▓реЛрдб рдХрд░рдирд╛ рд╣реЛрдЧрд╛ред рдЖрдЗрдП рдПрдХ рд╕рдВрд╕рд╛рдзрди рдкреНрд░рдмрдВрдзрдХ рд▓рд┐рдЦреЗрдВ:

рд╕рдВрдкрддреНрддрд┐.рдЬреЗ.рдПрд╕

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, рдЬреЛ рдКрдкрд░ рд╕реВрдЪреАрдмрджреНрдз рдЪрд╛рд░ рд╡рд╕реНрддреБрдУрдВ рдХреЛ рдмрд┐рд▓реНрдХреБрд▓ рдкреНрд░рд╕реНрддреБрдд рдХрд░рддрд╛ рд╣реИ:

рд░реЗрдВрдбрд░.рдЬреЗ.рдПрд╕

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 рдПрдлрдкреАрдПрд╕ рдкрд░ рд░реЗрдВрдбрд░ рд▓реВрдк рдХреЗ рд╕рдХреНрд░рд┐рдпрдг рдХреЛ рдирд┐рдпрдВрддреНрд░рд┐рдд рдХрд░реЗрдВред

рд╡реНрдпрдХреНрддрд┐рдЧрдд рдкреНрд░рддрд┐рдкрд╛рджрди рд╕рд╣рд╛рдпрдХ рдХрд╛рд░реНрдпреЛрдВ рдХрд╛ рдареЛрд╕ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди (рдЙрджрд╛. renderBullet()) рдЙрддрдиреЗ рдорд╣рддреНрд╡рдкреВрд░реНрдг рдирд╣реАрдВ рд╣реИрдВ, рд▓реЗрдХрд┐рди рдпрд╣рд╛рдВ рдПрдХ рд╕рд░рд▓ рдЙрджрд╛рд╣рд░рдг рджрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ:

рд░реЗрдВрдбрд░.рдЬреЗ.рдПрд╕

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:

рдЗрдирдкреБрдЯ.рдЬреЗ.рдПрд╕

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. рдЧреНрд░рд╛рд╣рдХ рд╕реНрдерд┐рддрд┐

рдпрд╣ рд╕реЗрдХреНрд╢рди рдкреЛрд╕реНрдЯ рдХреЗ рдкрд╣рд▓реЗ рднрд╛рдЧ рдореЗрдВ рд╕рдмрд╕реЗ рдХрдард┐рди рд╣реИред рдпрджрд┐ рдЖрдк рдЗрд╕реЗ рдкрд╣рд▓реА рдмрд╛рд░ рдкрдврд╝рдиреЗ рдкрд░ рд╕рдордЭ рдирд╣реАрдВ рдкрд╛рддреЗ рд╣реИрдВ рддреЛ рдирд┐рд░рд╛рд╢ рди рд╣реЛрдВ! рдЖрдк рдЗрд╕реЗ рдЫреЛрдбрд╝ рднреА рд╕рдХрддреЗ рд╣реИрдВ рдФрд░ рдмрд╛рдж рдореЗрдВ рдЗрд╕ рдкрд░ рд╡рд╛рдкрд╕ рдЖ рд╕рдХрддреЗ рд╣реИрдВред

рдХреНрд▓рд╛рдЗрдВрдЯ/рд╕рд░реНрд╡рд░ рдХреЛрдб рдХреЛ рдкреВрд░рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЖрд╡рд╢реНрдпрдХ рдкрд╣реЗрд▓реА рдХрд╛ рдЕрдВрддрд┐рдо рднрд╛рдЧ рд╣реИ рд░рд╛рдЬреНрдп. рдХреНрд▓рд╛рдЗрдВрдЯ рд░реЗрдВрдбрд░рд┐рдВрдЧ рдЕрдиреБрднрд╛рдЧ рд╕реЗ рдХреЛрдб рд╕реНрдирд┐рдкреЗрдЯ рдпрд╛рдж рд╣реИ?

рд░реЗрдВрдбрд░.рдЬреЗ.рдПрд╕

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() рдХреЗрд╡рд▓ рд╕рдмрд╕реЗ рд╣рд╛рд▓ рд╣реА рдореЗрдВ рдкреНрд░рд╛рдкреНрдд рдЧреЗрдо рдЕрдкрдбреЗрдЯ рдХрд╛ рдбреЗрдЯрд╛ рд╕реАрдзреЗ рд▓реМрдЯрд╛ рд╕рдХрддрд╛ рд╣реИред

рдЕрдиреБрднрд╡рд╣реАрди-рд░рд╛рдЬреНрдп.рдЬреЗ.рдПрд╕

let lastGameUpdate = null;

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

export function getCurrentState() {
  return lastGameUpdate;
}

рдЕрдЪреНрдЫрд╛ рдФрд░ рд╕реНрдкрд╖реНрдЯ! рд▓реЗрдХрд┐рди рдХрд╛рд╢ рдпрд╣ рдЗрддрдирд╛ рдЖрд╕рд╛рди рд╣реЛрддрд╛. рдЗрд╕ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХреЗ рд╕рдорд╕реНрдпрд╛рдЧреНрд░рд╕реНрдд рд╣реЛрдиреЗ рдХреЗ рдХрд╛рд░рдгреЛрдВ рдореЗрдВ рд╕реЗ рдПрдХ: рдпрд╣ рд░реЗрдВрдбрд░рд┐рдВрдЧ рдлрд╝реНрд░реЗрдо рджрд░ рдХреЛ рд╕рд░реНрд╡рд░ рдХреНрд▓реЙрдХ рджрд░ рддрдХ рд╕реАрдорд┐рдд рдХрд░рддрд╛ рд╣реИ.

рдлреНрд░реЗрдо рд░реЗрдЯ: рдлрд╝реНрд░реЗрдо рдХреА рд╕рдВрдЦреНрдпрд╛ (рдЕрд░реНрдерд╛рдд рдХреЙрд▓ render()) рдкреНрд░рддрд┐ рд╕реЗрдХрдВрдб, рдпрд╛ рдПрдлрдкреАрдПрд╕ред рдЦреЗрд▓ рдЖрдорддреМрд░ рдкрд░ рдХрдо рд╕реЗ рдХрдо 60 рдПрдлрдкреАрдПрд╕ рд╣рд╛рд╕рд┐рд▓ рдХрд░рдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░рддреЗ рд╣реИрдВред

рджрд░ рдкрд░ рдЯрд┐рдХ рдХрд░реЗрдВ: рд╡рд╣ рдЖрд╡реГрддреНрддрд┐ рдЬрд┐рд╕ рдкрд░ рд╕рд░реНрд╡рд░ рдХреНрд▓рд╛рдЗрдВрдЯ рдХреЛ рдЧреЗрдо рдЕрдкрдбреЗрдЯ рднреЗрдЬрддрд╛ рд╣реИред рдпрд╣ рдЕрдХреНрд╕рд░ рдлрд╝реНрд░реЗрдо рджрд░ рд╕реЗ рдХрдо рд╣реЛрддрд╛ рд╣реИ. рд╣рдорд╛рд░реЗ рдЧреЗрдо рдореЗрдВ, рд╕рд░реНрд╡рд░ 30 рдЪрдХреНрд░ рдкреНрд░рддрд┐ рд╕реЗрдХрдВрдб рдХреА рдЖрд╡реГрддреНрддрд┐ рдкрд░ рдЪрд▓рддрд╛ рд╣реИред

рдпрджрд┐ рд╣рдо рдЧреЗрдо рдХреЗ рдирд╡реАрдирддрдо рдЕрдкрдбреЗрдЯ рдХреЛ рдкреНрд░рд╕реНрддреБрдд рдХрд░рддреЗ рд╣реИрдВ, рддреЛ рдПрдлрдкреАрдПрд╕ рдЕрдирд┐рд╡рд╛рд░реНрдп рд░реВрдк рд╕реЗ рдХрднреА рднреА 30 рд╕реЗ рдЕрдзрд┐рдХ рдирд╣реАрдВ рд╣реЛрдЧрд╛, рдХреНрдпреЛрдВрдХрд┐ рд╣рдореЗрдВ рд╕рд░реНрд╡рд░ рд╕реЗ рдХрднреА рднреА рдкреНрд░рддрд┐ рд╕реЗрдХрдВрдб 30 рд╕реЗ рдЕрдзрд┐рдХ рдЕрдкрдбреЗрдЯ рдирд╣реАрдВ рдорд┐рд▓рддреЗ. рднрд▓реЗ рд╣реА рд╣рдо рдХреЙрд▓ рдХрд░реЗрдВ render() рдкреНрд░рддрд┐ рд╕реЗрдХрдВрдб 60 рдмрд╛рд░, рддреЛ рдЗрдирдореЗрдВ рд╕реЗ рдЖрдзреА рдХреЙрд▓реЗрдВ рдмрд╕ рдЙрд╕реА рдЪреАрдЬрд╝ рдХреЛ рдлрд┐рд░ рд╕реЗ рддреИрдпрд╛рд░ рдХрд░реЗрдВрдЧреА, рдЕрдирд┐рд╡рд╛рд░реНрдп рд░реВрдк рд╕реЗ рдХреБрдЫ рднреА рдирд╣реАрдВ рдХрд░реЗрдВрдЧреАред рд╕рд░рд▓ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХреЗ рд╕рд╛рде рдПрдХ рдФрд░ рд╕рдорд╕реНрдпрд╛ рдпрд╣ рд╣реИ рдХрд┐ рдпрд╣ рджреЗрд░реА рдХреА рд╕рдВрднрд╛рд╡рдирд╛. рдЖрджрд░реНрд╢ рдЗрдВрдЯрд░рдиреЗрдЯ рд╕реНрдкреАрдб рдХреЗ рд╕рд╛рде, рдХреНрд▓рд╛рдЗрдВрдЯ рдХреЛ рд╣рд░ 33ms (30 рдкреНрд░рддрд┐ рд╕реЗрдХрдВрдб) рдкрд░ рдПрдХ рдЧреЗрдо рдЕрдкрдбреЗрдЯ рдкреНрд░рд╛рдкреНрдд рд╣реЛрдЧрд╛:

рдПрдХ рдорд▓реНрдЯреАрдкреНрд▓реЗрдпрд░ .io рд╡реЗрдм рдЧреЗрдо рдмрдирд╛рдирд╛
рджреБрд░реНрднрд╛рдЧреНрдп рд╕реЗ, рдХреБрдЫ рднреА рдкреВрд░реНрдг рдирд╣реАрдВ рд╣реИред рдПрдХ рдЕрдзрд┐рдХ рдпрдерд╛рд░реНрдерд╡рд╛рджреА рдЪрд┐рддреНрд░ рд╣реЛрдЧрд╛:
рдПрдХ рдорд▓реНрдЯреАрдкреНрд▓реЗрдпрд░ .io рд╡реЗрдм рдЧреЗрдо рдмрдирд╛рдирд╛
рдЬрдм рд╡рд┐рд▓рдВрдмрддрд╛ рдХреА рдмрд╛рдд рдЖрддреА рд╣реИ рддреЛ рдЕрдиреБрднрд╡рд╣реАрди рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рд╡реНрдпрд╛рд╡рд╣рд╛рд░рд┐рдХ рд░реВрдк рд╕реЗ рд╕рдмрд╕реЗ рдЦрд░рд╛рдм рд╕реНрдерд┐рддрд┐ рд╣реИред рдпрджрд┐ рдХреЛрдИ рдЧреЗрдо рдЕрдкрдбреЗрдЯ 50ms рдХреА рджреЗрд░реА рд╕реЗ рдкреНрд░рд╛рдкреНрдд рд╣реЛрддрд╛ рд╣реИ, рддреЛ рдЧреНрд░рд╛рд╣рдХ рд╕реНрдЯреЙрд▓ рдЕрддрд┐рд░рд┐рдХреНрдд 50 рдПрдордПрд╕ рдХреНрдпреЛрдВрдХрд┐ рдпрд╣ рдЕрднреА рднреА рдкрд┐рдЫрд▓реЗ рдЕрдкрдбреЗрдЯ рд╕реЗ рдЧреЗрдо рд╕реНрдерд┐рддрд┐ рдкреНрд░рд╕реНрддреБрдд рдХрд░ рд░рд╣рд╛ рд╣реИред рдЖрдк рдХрд▓реНрдкрдирд╛ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ рдХрд┐ рдпрд╣ рдЦрд┐рд▓рд╛рдбрд╝реА рдХреЗ рд▓рд┐рдП рдХрд┐рддрдирд╛ рдЕрд╕реБрд╡рд┐рдзрд╛рдЬрдирдХ рд╣реИ: рдордирдорд╛рдиреЗ рдврдВрдЧ рд╕реЗ рдмреНрд░реЗрдХ рд▓рдЧрд╛рдиреЗ рд╕реЗ рдЦреЗрд▓ рдЭрдЯрдХреЗрджрд╛рд░ рдФрд░ рдЕрд╕реНрдерд┐рд░ рд▓рдЧреЗрдЧрд╛ред

7.2 рдмреЗрд╣рддрд░ рдЧреНрд░рд╛рд╣рдХ рд╕реНрдерд┐рддрд┐

рд╣рдо рд╕рд░рд▓ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдореЗрдВ рдХреБрдЫ рд╕реБрдзрд╛рд░ рдХрд░реЗрдВрдЧреЗред рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рд╣рдо рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВ рджреЗрд░реА рдкреНрд░рджрд╛рди рдХрд░рдирд╛ 100 рдПрдордПрд╕ рдХреЗ рд▓рд┐рдП. рдЗрд╕рдХрд╛ рдорддрд▓рдм рдпрд╣ рд╣реИ рдХрд┐ рдХреНрд▓рд╛рдЗрдВрдЯ рдХреА "рд╡рд░реНрддрдорд╛рди" рд╕реНрдерд┐рддрд┐ рд╕рд░реНрд╡рд░ рдкрд░ рдЧреЗрдо рдХреА рд╕реНрдерд┐рддрд┐ рд╕реЗ рд╣рдореЗрд╢рд╛ 100ms рдкреАрдЫреЗ рд░рд╣реЗрдЧреАред рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдпрджрд┐ рд╕рд░реНрд╡рд░ рдкрд░ рд╕рдордп рд╣реИ 150, рддреЛ рдХреНрд▓рд╛рдЗрдВрдЯ рдЙрд╕ рд╕реНрдерд┐рддрд┐ рдХреЛ рдкреНрд░рд╕реНрддреБрдд рдХрд░реЗрдЧрд╛ рдЬрд┐рд╕рдореЗрдВ рд╕рд░реНрд╡рд░ рдЙрд╕ рд╕рдордп рдерд╛ 50:

рдПрдХ рдорд▓реНрдЯреАрдкреНрд▓реЗрдпрд░ .io рд╡реЗрдм рдЧреЗрдо рдмрдирд╛рдирд╛
рдпрд╣ рд╣рдореЗрдВ рдЕрдкреНрд░рддреНрдпрд╛рд╢рд┐рдд рдЧреЗрдо рдЕрдкрдбреЗрдЯ рд╕рдордп рд╕реЗ рдмрдЪрдиреЗ рдХреЗ рд▓рд┐рдП 100ms рдХрд╛ рдмрдлрд░ рджреЗрддрд╛ рд╣реИ:

рдПрдХ рдорд▓реНрдЯреАрдкреНрд▓реЗрдпрд░ .io рд╡реЗрдм рдЧреЗрдо рдмрдирд╛рдирд╛
рдЗрд╕рдХреЗ рд▓рд┐рдП рднреБрдЧрддрд╛рди рд╕реНрдерд╛рдпреА рд╣реЛрдЧрд╛ рдЗрдирдкреБрдЯ рдЕрдВрддрд░рд╛рд▓ 100 рдПрдордПрд╕ рдХреЗ рд▓рд┐рдП. рд╕рд╣рдЬ рдЧреЗрдордкреНрд▓реЗ рдХреЗ рд▓рд┐рдП рдпрд╣ рдПрдХ рдЫреЛрдЯрд╛ рд╕рд╛ рддреНрдпрд╛рдЧ рд╣реИ - рдЕрдзрд┐рдХрд╛рдВрд╢ рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ (рд╡рд┐рд╢реЗрд╖ рд░реВрдк рд╕реЗ рдЖрдХрд╕реНрдорд┐рдХ рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ) рдХреЛ рдЗрд╕ рджреЗрд░реА рдХрд╛ рдкрддрд╛ рднреА рдирд╣реАрдВ рдЪрд▓реЗрдЧрд╛ред рдЕрдкреНрд░рддреНрдпрд╛рд╢рд┐рдд рд╡рд┐рд▓рдВрдмрддрд╛ рдХреЗ рд╕рд╛рде рдЦреЗрд▓рдиреЗ рдХреА рддреБрд▓рдирд╛ рдореЗрдВ рд▓реЛрдЧреЛрдВ рдХреЗ рд▓рд┐рдП рдирд┐рд░рдВрддрд░ 100ms рд╡рд┐рд▓рдВрдмрддрд╛ рдХреЛ рд╕рдорд╛рдпреЛрдЬрд┐рдд рдХрд░рдирд╛ рдмрд╣реБрдд рдЖрд╕рд╛рди рд╣реИред

рд╣рдо рдирд╛рдордХ рдПрдХ рдЕрдиреНрдп рддрдХрдиреАрдХ рдХрд╛ рднреА рдЙрдкрдпреЛрдЧ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ рдЧреНрд░рд╛рд╣рдХ-рдкрдХреНрд╖ рднрд╡рд┐рд╖реНрдпрд╡рд╛рдгреА, рдЬреЛ рдХрдерд┐рдд рд╡рд┐рд▓рдВрдмрддрд╛ рдХреЛ рдХрдо рдХрд░рдиреЗ рдХрд╛ рдЕрдЪреНрдЫрд╛ рдХрд╛рдо рдХрд░рддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдЗрд╕ рдкреЛрд╕реНрдЯ рдореЗрдВ рдЗрд╕реЗ рдХрд╡рд░ рдирд╣реАрдВ рдХрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛ред

рдПрдХ рдФрд░ рд╕реБрдзрд╛рд░ рдЬреЛ рд╣рдо рдЙрдкрдпреЛрдЧ рдХрд░ рд░рд╣реЗ рд╣реИрдВ рд╡рд╣ рд╣реИ рд░реЗрдЦрд┐рдХ рдЖрдВрддрд░рд┐рдХ. рд░реЗрдВрдбрд░рд┐рдВрдЧ рд▓реИрдЧ рдХреЗ рдХрд╛рд░рдг, рд╣рдо рдЖрдо рддреМрд░ рдкрд░ рдХреНрд▓рд╛рдЗрдВрдЯ рдореЗрдВ рд╡рд░реНрддрдорд╛рди рд╕рдордп рд╕реЗ рдХрдо рд╕реЗ рдХрдо рдПрдХ рдЕрдкрдбреЗрдЯ рдЖрдЧреЗ рд░рдЦрддреЗ рд╣реИрдВред рдЬрдм рдмреБрд▓рд╛рдпрд╛ рдЧрдпрд╛ getCurrentState(), рд╣рдо рдирд┐рд╖реНрдкрд╛рджрд┐рдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ рд░реЗрдЦрд┐рдХ рдЖрдВрддрд░рд┐рдХ рдХреНрд▓рд╛рдЗрдВрдЯ рдореЗрдВ рд╡рд░реНрддрдорд╛рди рд╕рдордп рдХреЗ рдареАрдХ рдкрд╣рд▓реЗ рдФрд░ рдмрд╛рдж рдореЗрдВ рдЧреЗрдо рдЕрдкрдбреЗрдЯ рдХреЗ рдмреАрдЪ:

рдПрдХ рдорд▓реНрдЯреАрдкреНрд▓реЗрдпрд░ .io рд╡реЗрдм рдЧреЗрдо рдмрдирд╛рдирд╛
рдЗрд╕рд╕реЗ рдлрд╝реНрд░реЗрдо рджрд░ рдХреА рд╕рдорд╕реНрдпрд╛ рд╣рд▓ рд╣реЛ рдЬрд╛рддреА рд╣реИ: рдЕрдм рд╣рдо рдЕрдкрдиреА рдЗрдЪреНрдЫрд╛рдиреБрд╕рд╛рд░ рдХрд┐рд╕реА рднреА рдлрд╝реНрд░реЗрдо рджрд░ рдкрд░ рдЕрджреНрд╡рд┐рддреАрдп рдлрд╝реНрд░реЗрдо рдкреНрд░рд╕реНрддреБрдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ!

7.3 рдЙрдиреНрдирдд рдЧреНрд░рд╛рд╣рдХ рд╕реНрдерд┐рддрд┐ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдирд╛

рдореЗрдВ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдЙрджрд╛рд╣рд░рдг src/client/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(). рдЬреИрд╕рд╛ рдХрд┐ рд╣рдордиреЗ рдкрд╣рд▓реЗ рджреЗрдЦрд╛, рдкреНрд░рддреНрдпреЗрдХ рдЧреЗрдо рдЕрдкрдбреЗрдЯ рдореЗрдВ рдПрдХ рд╕рд░реНрд╡рд░ рдЯрд╛рдЗрдорд╕реНрдЯреИрдореНрдк рд╢рд╛рдорд┐рд▓ рд╣реЛрддрд╛ рд╣реИред рд╣рдо рдЫрд╡рд┐ рдХреЛ рд╕рд░реНрд╡рд░ рд╕реЗ 100 рдПрдордПрд╕ рдкреАрдЫреЗ рд░реЗрдВрдбрд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд░реЗрдВрдбрд░ рд╡рд┐рд▓рдВрдмрддрд╛ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВ, рд▓реЗрдХрд┐рди рд╣рдо рд╕рд░реНрд╡рд░ рдкрд░ рд╡рд░реНрддрдорд╛рди рд╕рдордп рдХрднреА рдирд╣реАрдВ рдЬрд╛рди рдкрд╛рдПрдВрдЧреЗ, рдХреНрдпреЛрдВрдХрд┐ рд╣рдо рдирд╣реАрдВ рдЬрд╛рди рд╕рдХрддреЗ рдХрд┐ рдХрд┐рд╕реА рднреА рдЕрдкрдбреЗрдЯ рдХреЛ рд╣рдо рддрдХ рдкрд╣реБрдВрдЪрдиреЗ рдореЗрдВ рдХрд┐рддрдирд╛ рд╕рдордп рд▓рдЧрд╛ред рдЗрдВрдЯрд░рдиреЗрдЯ рдЕрдкреНрд░рддреНрдпрд╛рд╢рд┐рдд рд╣реИ рдФрд░ рдЗрд╕рдХреА рдЧрддрд┐ рдмрд╣реБрдд рднрд┐рдиреНрди рд╣реЛ рд╕рдХрддреА рд╣реИ!

рдЗрд╕ рд╕рдорд╕реНрдпрд╛ рд╕реЗ рдирд┐рдЬрд╛рдд рдкрд╛рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдо рдПрдХ рдЙрдЪрд┐рдд рдЕрдиреБрдорд╛рди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ: рд╣рдо рджрд┐рдЦрд╛рд╡рд╛ рдХрд░реЗрдВ рдХрд┐ рдкрд╣рд▓рд╛ рдЕрдкрдбреЗрдЯ рддреБрд░рдВрдд рдЖ рдЧрдпрд╛. рдпрджрд┐ рдпрд╣ рд╕рдЪ рд╣реЛрддрд╛, рддреЛ рд╣рдореЗрдВ рдЗрд╕ рд╡рд┐рд╢реЗрд╖ рдХреНрд╖рдг рдореЗрдВ рд╕рд░реНрд╡рд░ рдХрд╛ рд╕рдордп рдкрддрд╛ рд╣реЛрддрд╛! рд╣рдо рд╕рд░реНрд╡рд░ рдХрд╛ рдЯрд╛рдЗрдорд╕реНрдЯреИрдореНрдк рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд░рддреЗ рд╣реИрдВ firstServerTimestamp рдФрд░ рд╣рдорд╛рд░реЗ рд░рдЦреЛ рд╕реНрдерд╛рдиреАрдп (рдХреНрд▓рд╛рдЗрдВрдЯ) рдЙрд╕реА рдХреНрд╖рдг рдЯрд╛рдЗрдорд╕реНрдЯреИрдореНрдк gameStart.

рдЕрд░реЗ рд░реБрдХреЛред рдХреНрдпрд╛ рдпрд╣ рд╕рд░реНрд╡рд░ рд╕рдордп = рдХреНрд▓рд╛рдЗрдВрдЯ рд╕рдордп рдирд╣реАрдВ рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдП? рд╣рдо "рд╕рд░реНрд╡рд░ рдЯрд╛рдЗрдорд╕реНрдЯреИрдореНрдк" рдФрд░ "рдХреНрд▓рд╛рдЗрдВрдЯ рдЯрд╛рдЗрдорд╕реНрдЯреИрдореНрдк" рдХреЗ рдмреАрдЪ рдЕрдВрддрд░ рдХреНрдпреЛрдВ рдХрд░рддреЗ рд╣реИрдВ? рдпрд╣ рдПрдХ рдмрдбрд╝рд╛ рд╕рд╡рд╛рд▓ рд╣реИ! рдЗрд╕рд╕реЗ рдкрддрд╛ рдЪрд▓рддрд╛ рд╣реИ рдХрд┐ рд╡реЗ рдПрдХ рд╣реА рдЪреАрдЬрд╝ рдирд╣реАрдВ рд╣реИрдВред Date.now() рдХреНрд▓рд╛рдЗрдВрдЯ рдФрд░ рд╕рд░реНрд╡рд░ рдореЗрдВ рдЕрд▓рдЧ-рдЕрд▓рдЧ рдЯрд╛рдЗрдорд╕реНрдЯреИрдореНрдк рд▓реМрдЯрд╛рдПрдЧрд╛, рдФрд░ рдпрд╣ рдЗрди рдорд╢реАрдиреЛрдВ рдХреЗ рд╕реНрдерд╛рдиреАрдп рдХрд╛рд░рдХреЛрдВ рдкрд░ рдирд┐рд░реНрднрд░ рдХрд░рддрд╛ рд╣реИред рдпрд╣ рдХрднреА рди рдорд╛рдиреЗрдВ рдХрд┐ рдЯрд╛рдЗрдорд╕реНрдЯреИрдореНрдк рд╕рднреА рдорд╢реАрдиреЛрдВ рдкрд░ рд╕рдорд╛рди рд╣реЛрдВрдЧреЗред

рдЕрдм рд╣рдо рд╕рдордЭрддреЗ рд╣реИрдВ рдХрд┐ рдХреНрдпрд╛ рдХрд░рддрд╛ рд╣реИ currentServerTime(): рдпрд╣ рд╡рд╛рдкрд╕ рдЖрддрд╛ рд╣реИ рд╡рд░реНрддрдорд╛рди рд░реЗрдВрдбрд░ рд╕рдордп рдХрд╛ рд╕рд░реНрд╡рд░ рдЯрд╛рдЗрдорд╕реНрдЯреИрдореНрдк. рджреВрд╕рд░реЗ рд╢рдмреНрджреЛрдВ рдореЗрдВ, рдпрд╣ рд╕рд░реНрд╡рд░ рдХрд╛ рд╡рд░реНрддрдорд╛рди рд╕рдордп рд╣реИ (firstServerTimestamp <+ (Date.now() - gameStart)) рдорд╛рдЗрдирд╕ рд░реЗрдВрдбрд░ рд╡рд┐рд▓рдВрдм (RENDER_DELAY).

рдЖрдЗрдП рдЕрдм рджреЗрдЦреЗрдВ рдХрд┐ рд╣рдо рдЧреЗрдо рдЕрдкрдбреЗрдЯ рдХреЛ рдХреИрд╕реЗ рдкреНрд░рдмрдВрдзрд┐рдд рдХрд░рддреЗ рд╣реИрдВред рдЕрджреНрдпрддрди рд╕рд░реНрд╡рд░ рд╕реЗ рдкреНрд░рд╛рдкреНрдд рд╣реЛрдиреЗ рдкрд░, рдЗрд╕реЗ рдХреЙрд▓ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ processGameUpdate()рдФрд░ рд╣рдо рдирдП рдЕрдкрдбреЗрдЯ рдХреЛ рдПрдХ рдРрд░реЗ рдореЗрдВ рд╕рд╣реЗрдЬрддреЗ рд╣реИрдВ gameUpdates. рдлрд┐рд░, рдореЗрдореЛрд░реА рдЙрдкрдпреЛрдЧ рдХреА рдЬрд╛рдВрдЪ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдо рдкрд╣рд▓реЗ рдХреЗ рд╕рднреА рдкреБрд░рд╛рдиреЗ рдЕрдкрдбреЗрдЯ рд╣рдЯрд╛ рджреЗрддреЗ рд╣реИрдВ рдЖрдзрд╛рд░ рдЕрджреНрдпрддрдирдХреНрдпреЛрдВрдХрд┐ рдЕрдм рд╣рдореЗрдВ рдЙрдирдХреА рдЬрд░реВрд░рдд рдирд╣реАрдВ рд╣реИ.

"рдмреБрдирд┐рдпрд╛рджреА рдЕрджреНрдпрддрди" рдХреНрдпрд╛ рд╣реИ? рдпрд╣ рдкрд╣рд▓рд╛ рдЕрдкрдбреЗрдЯ рд╣рдореЗрдВ рд╕рд░реНрд╡рд░ рдХреЗ рд╡рд░реНрддрдорд╛рди рд╕рдордп рд╕реЗ рдкреАрдЫреЗ рдХреА рдУрд░ рдЬрд╛рдХрд░ рдорд┐рд▓рддрд╛ рд╣реИ. рдпрд╣ рдЖрд░реЗрдЦ рдпрд╛рдж рд╣реИ?

рдПрдХ рдорд▓реНрдЯреАрдкреНрд▓реЗрдпрд░ .io рд╡реЗрдм рдЧреЗрдо рдмрдирд╛рдирд╛
рдЧреЗрдо рдЕрдкрдбреЗрдЯ рд╕реАрдзреЗ "рдХреНрд▓рд╛рдЗрдВрдЯ рд░реЗрдВрдбрд░ рдЯрд╛рдЗрдо" рдХреЗ рдмрд╛рдИрдВ рдУрд░ рдмреЗрд╕ рдЕрдкрдбреЗрдЯ рд╣реИред

рдЖрдзрд╛рд░ рдЕрджреНрдпрддрди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд┐рд╕ рд▓рд┐рдП рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ? рд╣рдо рдЕрдкрдбреЗрдЯ рдХреЛ рдмреЗрд╕рд▓рд╛рдЗрди рдкрд░ рдХреНрдпреЛрдВ рдЫреЛрдбрд╝ рд╕рдХрддреЗ рд╣реИрдВ? рдЗрд╕реЗ рдЬрд╛рдирдиреЗ рдХреЗ рд▓рд┐рдП, рдЖрдЗрдП рдЕрдВрдд рдореЗрдВ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдкрд░ рд╡рд┐рдЪрд╛рд░ рдХрд░реЗрдВ getCurrentState():

рд░рд╛рдЬреНрдп.рдЬреЗрдПрд╕ рднрд╛рдЧ 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:

рд╕рд░реНрд╡рд░.рдЬреЗрдПрд╕ рднрд╛рдЧ 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}`);

рдпрд╛рдж рд░рдЦреЗрдВ рдХрд┐ рдкрд╣рд▓реЗ рднрд╛рдЧ рдореЗрдВ рд╣рдордиреЗ рд╡реЗрдмрдкреИрдХ рдкрд░ рдЪрд░реНрдЪрд╛ рдХреА рдереА? рдпрд╣реАрдВ рдкрд░ рд╣рдо рдЕрдкрдиреЗ рд╡реЗрдмрдкреИрдХ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВрдЧреЗред рд╣рдо рдЙрдирдХрд╛ рджреЛ рддрд░рд╣ рд╕реЗ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВрдЧреЗ:

  • рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╡реЗрдмрдкреИрдХ-рдбреЗрд╡-рдорд┐рдбрд▓рд╡реЗрдпрд░ рд╣рдорд╛рд░реЗ рд╡рд┐рдХрд╛рд╕ рдкреИрдХреЗрдЬреЛрдВ рдХреЛ рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рд░реВрдк рд╕реЗ рдкреБрдирд░реНрдирд┐рд░реНрдорд╛рдг рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдпрд╛
  • рдлрд╝реЛрд▓реНрдбрд░ рдХреЛ рд╕реНрдерд┐рд░ рд░реВрдк рд╕реЗ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд░реЗрдВ dist/, рдЬрд┐рд╕рдореЗрдВ рд╡реЗрдмрдкреИрдХ рдЙрддреНрдкрд╛рджрди рдирд┐рд░реНрдорд╛рдг рдХреЗ рдмрд╛рдж рд╣рдорд╛рд░реА рдлрд╝рд╛рдЗрд▓реЗрдВ рд▓рд┐рдЦреЗрдЧрд╛ред

рдПрдХ рдФрд░ рдорд╣рддреНрд╡рдкреВрд░реНрдг рдХрд╛рд░реНрдп 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);
});

рд╕рд░реНрд╡рд░ рд╕реЗ рд╕реЙрдХреЗрдЯ.рдЖрдИрдУ рдХрдиреЗрдХреНрд╢рди рд╕рдлрд▓рддрд╛рдкреВрд░реНрд╡рдХ рд╕реНрдерд╛рдкрд┐рдд рдХрд░рдиреЗ рдХреЗ рдмрд╛рдж, рд╣рдо рдирдП рд╕реЙрдХреЗрдЯ рдХреЗ рд▓рд┐рдП рдЗрд╡реЗрдВрдЯ рд╣реИрдВрдбрд▓рд░ рд╕реЗрдЯ рдХрд░рддреЗ рд╣реИрдВред рдЗрд╡реЗрдВрдЯ рд╣реИрдВрдбрд▓рд░ рд╕рд┐рдВрдЧрд▓рдЯрди рдСрдмреНрдЬреЗрдХреНрдЯ рдХреЛ рд╕реМрдВрдкрдХрд░ рдХреНрд▓рд╛рдЗрдВрдЯ рд╕реЗ рдкреНрд░рд╛рдкреНрдд рд╕рдВрджреЗрд╢реЛрдВ рдХреЛ рд╕рдВрднрд╛рд▓рддреЗ рд╣реИрдВ game:

рд╕рд░реНрд╡рд░.рдЬреЗрдПрд╕ рднрд╛рдЧ 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 рд╕рд░реНрд╡рд░ рд╕рд╛рдЗрдб рдкрд░ рд╕рдмрд╕реЗ рдорд╣рддреНрд╡рдкреВрд░реНрдг рддрд░реНрдХ рд╢рд╛рдорд┐рд▓ рд╣реИред рдЗрд╕рдХреЗ рджреЛ рдореБрдЦреНрдп рдХрд╛рд░реНрдп рд╣реИрдВ: рдЦрд┐рд▓рд╛рдбрд╝реА рдкреНрд░рдмрдВрдзрди ╨╕ рдЦреЗрд▓ рдЕрдиреБрдХрд░рдг.

рдЖрдЗрдП рдкрд╣рд▓реЗ рдХрд╛рд░реНрдп, рдЦрд┐рд▓рд╛рдбрд╝реА рдкреНрд░рдмрдВрдзрди рд╕реЗ рд╢реБрд░реБрдЖрдд рдХрд░реЗрдВред

рдЧреЗрдо.рдЬреЗрдПрд╕ рднрд╛рдЧ 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 рдЙрдирдХрд╛ рд╕реЙрдХреЗрдЯ.рдЖрдИрдУ рд╕реЙрдХреЗрдЯ (рдпрджрд┐ рдЖрдк рднреНрд░рдорд┐рдд рд╣реЛ рдЬрд╛рддреЗ рд╣реИрдВ, рддреЛ рд╡рд╛рдкрд╕ рдЬрд╛рдПрдВ server.js). Socket.io рд╕реНрд╡рдпрдВ рдкреНрд░рддреНрдпреЗрдХ рд╕реЙрдХреЗрдЯ рдХреЛ рдПрдХ рдЕрджреНрд╡рд┐рддреАрдп рдирд┐рд░реНрджрд┐рд╖реНрдЯ рдХрд░рддрд╛ рд╣реИ idрдЗрд╕рд▓рд┐рдП рд╣рдореЗрдВ рдЗрд╕рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЪрд┐рдВрддрд╛ рдХрд░рдиреЗ рдХреА рдЬрд╝рд░реВрд░рдд рдирд╣реАрдВ рд╣реИред рдореИрдВ рдЙрд╕реЗ рдлреЛрди рдХрд░реБрдВрдЧрд╛ рдкреНрд▓реЗрдпрд░ рдЖрдИрдбреА.

рдЗрд╕реЗ рдзреНрдпрд╛рди рдореЗрдВ рд░рдЦрддреЗ рд╣реБрдП, рдЖрдЗрдП рдПрдХ рдХрдХреНрд╖рд╛ рдореЗрдВ рдЗрдВрд╕реНрдЯреЗрдВрд╕ рд╡реЗрд░рд┐рдПрдмрд▓реНрд╕ рдХрд╛ рдкрддрд╛ рд▓рдЧрд╛рдПрдВ Game:

  • sockets рдПрдХ рдСрдмреНрдЬреЗрдХреНрдЯ рд╣реИ рдЬреЛ рдкреНрд▓реЗрдпрд░ рдЖрдИрдбреА рдХреЛ рдкреНрд▓реЗрдпрд░ рд╕реЗ рдЬреБрдбрд╝реЗ рд╕реЙрдХреЗрдЯ рд╕реЗ рдмрд╛рдВрдзрддрд╛ рд╣реИред рдпрд╣ рд╣рдореЗрдВ рдирд┐рд░рдВрддрд░ рд╕рдордп рдореЗрдВ рдЙрдирдХреЗ рдкреНрд▓реЗрдпрд░ рдЖрдИрдбреА рджреНрд╡рд╛рд░рд╛ рд╕реЙрдХреЗрдЯ рддрдХ рдкрд╣реБрдВрдЪрдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддрд╛ рд╣реИред
  • players рдПрдХ рдСрдмреНрдЬреЗрдХреНрдЯ рд╣реИ рдЬреЛ рдкреНрд▓реЗрдпрд░ рдЖрдИрдбреА рдХреЛ рдХреЛрдб>рдкреНрд▓реЗрдпрд░ рдСрдмреНрдЬреЗрдХреНрдЯ рд╕реЗ рдмрд╛рдВрдзрддрд╛ рд╣реИ

bullets рд╡рд╕реНрддреБрдУрдВ рдХреА рдПрдХ рд╕рд░рдгреА рд╣реИ Bullet, рдЬрд┐рд╕рдХрд╛ рдХреЛрдИ рдирд┐рд╢реНрдЪрд┐рдд рдХреНрд░рдо рдирд╣реАрдВ рд╣реИред
lastUpdateTime рдпрд╣ рдЙрд╕ рд╕рдордп рдХрд╛ рдЯрд╛рдЗрдорд╕реНрдЯреИрдореНрдк рд╣реИ рдЬрдм рдЧреЗрдо рдХреЛ рдЖрдЦрд┐рд░реА рдмрд╛рд░ рдЕрдкрдбреЗрдЯ рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛ред рд╣рдо рд╢реАрдШреНрд░ рд╣реА рджреЗрдЦреЗрдВрдЧреЗ рдХрд┐ рдЗрд╕рдХрд╛ рдЙрдкрдпреЛрдЧ рдХреИрд╕реЗ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред
shouldSendUpdate рдПрдХ рд╕рд╣рд╛рдпрдХ рдЪрд░ рд╣реИ. рд╣рдо рд╢реАрдШреНрд░ рд╣реА рдЗрд╕рдХрд╛ рдЙрдкрдпреЛрдЧ рднреА рджреЗрдЦреЗрдВрдЧреЗред
рддрд░реАрдХреЛрдВ addPlayer(), removePlayer() ╨╕ handleInput() рд╕рдордЭрд╛рдиреЗ рдХреА рдЬрд░реВрд░рдд рдирд╣реАрдВ, рдЗрдирдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ server.js. рдпрджрд┐ рдЖрдкрдХреЛ рдЕрдкрдиреА рдпрд╛рджрджрд╛рд╢реНрдд рддрд╛рдЬрд╝рд╛ рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ, рддреЛ рдереЛрдбрд╝рд╛ рдКрдкрд░ рдЬрд╛рдПрдБред

рдЕрдВрддрд┐рдо рдкрдВрдХреНрддрд┐ constructor() рд╢реБрд░реВрдЖрдд рдЕрджреНрдпрддрди рдЪрдХреНрд░ рдЧреЗрдо (60 рдЕрдкрдбреЗрдЯ/рд╕реЗрдХреЗрдВрдб рдХреА рдЖрд╡реГрддреНрддрд┐ рдХреЗ рд╕рд╛рде):

рдЧреЗрдо.рдЬреЗрдПрд╕ рднрд╛рдЧ 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():

рдЧреЗрдо.рдЬреЗрдПрд╕ рднрд╛рдЧ 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:

рдСрдмреНрдЬреЗрдХреНрдЯ.рдЬреЗ.рдПрд╕

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:

рдмреБрд▓реЗрдЯ.рдЬреЗ.рдПрд╕

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:

рдкреНрд▓реЗрдпрд░.рдЬреЗ.рдПрд╕

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:

рдЧреЗрдо.рдЬреЗ.рдПрд╕

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.

рдЯрдХрд░рд╛рд╡ рдХрд╛ рдкрддрд╛ рд▓рдЧрд╛рдиреЗ рдХрд╛ рд╣рдорд╛рд░рд╛ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдЗрд╕ рдкреНрд░рдХрд╛рд░ рд╣реИ:

рдЯрдХрд░рд╛рд╡.рдЬреЗ.рдПрд╕

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

рдПрдХ рдЯрд┐рдкреНрдкрдгреА рдЬреЛрдбрд╝реЗрдВ