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

рдорд▓реНрдЯреАрдкреНрд▓реЗрдЕрд░ .io рд╡реЗрдм рдЧреЗрдо рддрдпрд╛рд░ рдХрд░рдгреЗ
2015 рдордзреНрдпреЗ рд░рд┐рд▓реАрдЬ рдЭрд╛рд▓рд╛ рдЕрдЧрд░.рдЖрдпрдУ рдирд╡реАрди рд╢реИрд▓реАрдЪрд╛ рдкреВрд░реНрд╡рдЬ рдмрдирд▓рд╛ рдЦреЗрд│ .ioрдЬреНрдпрд╛рдЪреА рддреЗрд╡реНрд╣рд╛рдкрд╛рд╕реВрди рд▓реЛрдХрдкреНрд░рд┐рдпрддрд╛ рд╡рд╛рдврд▓реА рдЖрд╣реЗ. рдореА рд╡реИрдпрдХреНрддрд┐рдХрд░рд┐рддреНрдпрд╛ .io рдЧреЗрдордЪреНрдпрд╛ рд▓реЛрдХрдкреНрд░рд┐рдпрддреЗрдд рд╡рд╛рдв рдЕрдиреБрднрд╡рд▓реА рдЖрд╣реЗ: рдЧреЗрд▓реНрдпрд╛ рддреАрди рд╡рд░реНрд╖рд╛рдВрдд, рдорд╛рдЭреНрдпрд╛рдХрдбреЗ рдЖрд╣реЗ рдпрд╛ рд╢реИрд▓реАрдЪреЗ рджреЛрди рдЧреЗрдо рддрдпрд╛рд░ рдХреЗрд▓реЗ рдЖрдгрд┐ рд╡рд┐рдХрд▓реЗ..

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

рдпрд╛ рдкреЛрд╕реНрдЯрдордзреНрдпреЗ, рдЖрдореНрд╣реА рдХрд╕реЗ рддреЗ рд╢реЛрдзреВ рд╕реБрд░рд╡рд╛рддреАрдкрд╛рд╕реВрди рдПрдХ .io рдЧреЗрдо рддрдпрд╛рд░ рдХрд░рд╛. рдпрд╛рд╕рд╛рдареА, рдлрдХреНрдд Javascript рдЪреЗ рдЬреНрдЮрд╛рди рдкреБрд░реЗрд╕реЗ рдЕрд╕реЗрд▓: рддреБрдореНрд╣рд╛рд▓рд╛ рд╕рд┐рдВрдЯреЕрдХреНрд╕ рд╕рд╛рд░рдЦреНрдпрд╛ рдЧреЛрд╖реНрдЯреА рд╕рдордЬреВрди рдШреЗрдгреЗ рдЖрд╡рд╢реНрдпрдХ рдЖрд╣реЗ ES6, рдХреАрд╡рд░реНрдб this ╨╕ рд╡рдЪрди. рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯрдЪреЗ рддреБрдордЪреЗ рдЬреНрдЮрд╛рди рдкрд░рд┐рдкреВрд░реНрдг рдирд╕рд▓реЗ рддрд░реАрд╣реА рддреБрдореНрд╣реА рдмрд╣реБрддрд╛рдВрд╢ рдкреЛрд╕реНрдЯ рд╕рдордЬреВ рд╢рдХрддрд╛.

io рдЧреЗрдордЪреЗ рдЙрджрд╛рд╣рд░рдг

рд╢рд┐рдХрдгреНрдпрд╛рдЪреНрдпрд╛ рд╕рд╣рд╛рдпреНрдпрд╛рд╕рд╛рдареА, рдЖрдореНрд╣реА рд╕рдВрджрд░реНрдн рдШреЗрдК io рдЧреЗрдордЪреЗ рдЙрджрд╛рд╣рд░рдг. рддреЗ рдЦреЗрд│рдгреНрдпрд╛рдЪрд╛ рдкреНрд░рдпрддреНрди рдХрд░рд╛!

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

1. рдкреНрд░рдХрд▓реНрдкрд╛рдЪреЗ рд╕рдВрдХреНрд╖рд┐рдкреНрдд рд╡рд┐рд╣рдВрдЧрд╛рд╡рд▓реЛрдХрди / рд░рдЪрдирд╛

рдореА рд╢рд┐рдлрд╛рд░рд╕ рдХрд░рддреЛ рд╕реНрд░реЛрдд рдХреЛрдб рдбрд╛рдЙрдирд▓реЛрдб рдХрд░рд╛ рдЙрджрд╛рд╣рд░рдг рдЧреЗрдо рдЬреЗрдгреЗрдХрд░реВрди рддреБрдореНрд╣реА рдорд╛рдЭреЗ рдЕрдиреБрд╕рд░рдг рдХрд░реВ рд╢рдХрддрд╛.

рдЙрджрд╛рд╣рд░рдг рдЦрд╛рд▓реАрд▓ рд╡рд╛рдкрд░рддреЗ:

  • рд╡реНрдпрдХреНрдд рдЧреЗрдордЪреЗ рд╡реЗрдм рд╕рд░реНрд╡реНрд╣рд░ рд╡реНрдпрд╡рд╕реНрдерд╛рдкрд┐рдд рдХрд░рдгрд╛рд░реЗ рд╕рд░реНрд╡рд╛рдд рд▓реЛрдХрдкреНрд░рд┐рдп Node.js рд╡реЗрдм рдлреНрд░реЗрдорд╡рд░реНрдХ рдЖрд╣реЗ.
  • socket.io - рдмреНрд░рд╛рдЙрдЭрд░ рдЖрдгрд┐ рд╕рд░реНрд╡реНрд╣рд░ рджрд░рдореНрдпрд╛рди рдбреЗрдЯрд╛рдЪреА рджреЗрд╡рд╛рдгрдШреЗрд╡рд╛рдг рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рд╡реЗрдмрд╕реЙрдХреЗрдЯ рд▓рд╛рдпрдмреНрд░рд░реА.
  • рд╡реЗрдмрдкреЕрдХ - рдореЙрдбреНрдпреВрд▓ рд╡реНрдпрд╡рд╕реНрдерд╛рдкрдХ. рд╡реЗрдмрдкреЕрдХ рдХрд╛ рд╡рд╛рдкрд░рд╛рдпрдЪреЗ рдпрд╛рдмрджреНрджрд▓ рддреБрдореНрд╣реА рд╡рд╛рдЪреВ рд╢рдХрддрд╛. рдпреЗрдереЗ.

рдкреНрд░рдХрд▓реНрдк рдирд┐рд░реНрджреЗрд╢рд┐рдХрд╛ рд░рдЪрдирд╛ рдХрд╢реА рджрд┐рд╕рддреЗ рддреЗ рдпреЗрдереЗ рдЖрд╣реЗ:

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

рд╕рд╛рд░реНрд╡рдЬрдирд┐рдХ/

рдлреЛрд▓реНрдбрд░рдордзреНрдпреЗ рд╕рд░реНрд╡ рдХрд╛рд╣реА public/ рд╕рд░реНрд╡реНрд╣рд░рджреНрд╡рд╛рд░реЗ рд╕реНрдерд┐рд░рдкрдгреЗ рд╕рдмрдорд┐рдЯ рдХреЗрд▓реЗ рдЬрд╛рдИрд▓. IN public/assets/ рдЖрдордЪреНрдпрд╛ рдкреНрд░рдХрд▓реНрдкрд╛рджреНрд╡рд╛рд░реЗ рд╡рд╛рдкрд░рд▓реЗрд▓реНрдпрд╛ рдкреНрд░рддрд┐рдорд╛ рдЖрд╣реЗрдд.

src /

рд╕рд░реНрд╡ рд╕реНрддреНрд░реЛрдд рдХреЛрдб рдлреЛрд▓реНрдбрд░рдордзреНрдпреЗ рдЖрд╣реЗ src/. рд╢реАрд░реНрд╖рдХреЗ client/ ╨╕ server/ рд╕реНрд╡рдд: рд╕рд╛рдареА рдмреЛрд▓рд╛ рдЖрдгрд┐ shared/ рдПрдХ рд╕реНрдерд┐рд░ рдлрд╛рдЗрд▓ рдЖрд╣реЗ рдЬреА рдХреНрд▓рд╛рдпрдВрдЯ рдЖрдгрд┐ рд╕рд░реНрд╡реНрд╣рд░ рджреЛрдиреНрд╣реАрджреНрд╡рд╛рд░реЗ рдЖрдпрд╛рдд рдХреЗрд▓реА рдЬрд╛рддреЗ.

2. рдЕрд╕реЗрдВрдмреНрд▓реА/рдкреНрд░реЛрдЬреЗрдХреНрдЯ рд╕реЗрдЯрд┐рдВрдЧреНрдЬ

рд╡рд░ рдирдореВрдж рдХреЗрд▓реНрдпрд╛рдкреНрд░рдорд╛рдгреЗ, рдЖрдореНрд╣реА рдкреНрд░рдХрд▓реНрдк рддрдпрд╛рд░ рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рдореЙрдбреНрдпреВрд▓ рд╡реНрдпрд╡рд╕реНрдерд╛рдкрдХ рд╡рд╛рдкрд░рддреЛ. рд╡реЗрдмрдкреЕрдХ. рдЪрд▓рд╛ рдЖрдордЪреНрдпрд╛ рд╡реЗрдмрдкреЕрдХ рдХреЙрдиреНрдлрд┐рдЧрд░реЗрд╢рдирд╡рд░ рдПрдХ рдирдЬрд░ рдЯрд╛рдХреВрдпрд╛:

webpack.common.js:

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  entry: {
    game: './src/client/index.js',
  },
  output: {
    filename: '[name].[contenthash].js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
        test: /.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            presets: ['@babel/preset-env'],
          },
        },
      },
      {
        test: /.css$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
          },
          'css-loader',
        ],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash].css',
    }),
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'src/client/html/index.html',
    }),
  ],
};

рдпреЗрдереЗ рд╕рд░реНрд╡рд╛рдд рдорд╣рддреНрд╡рд╛рдЪреНрдпрд╛ рдУрд│реА рдЖрд╣реЗрдд:

  • src/client/index.js Javascript (JS) рдХреНрд▓рд╛рдпрдВрдЯрдЪрд╛ рдкреНрд░рд╡реЗрд╢ рдмрд┐рдВрджреВ рдЖрд╣реЗ. рд╡реЗрдмрдкреЕрдХ рдпреЗрдереВрди рд╕реБрд░реВ рд╣реЛрдИрд▓ рдЖрдгрд┐ рдЗрддрд░ рдЖрдпрд╛рдд рдХреЗрд▓реЗрд▓реНрдпрд╛ рдлрд╛рдпрд▓реАрдВрд╕рд╛рдареА рд╡рд╛рд░рдВрд╡рд╛рд░ рд╢реЛрдзрд╛.
  • рдЖрдордЪреНрдпрд╛ рд╡реЗрдмрдкреЕрдХ рдмрд┐рд▓реНрдбрдЪреЗ рдЖрдЙрдЯрдкреБрдЯ JS рдирд┐рд░реНрджреЗрд╢рд┐рдХреЗрдд рд╕реНрдерд┐рдд рдЕрд╕реЗрд▓ dist/. рдореА рдпрд╛ рдлрд╛рдЗрд▓рд▓рд╛ рдЖрдордЪреА рдореНрд╣рдгреЗрди js рдкреЕрдХреЗрдЬ.
  • рдЖрдореНрд╣реА рд╡рд╛рдкрд░рддреЛ рдЧреЛрдВрдЧрд╛рдЯ, рдЖрдгрд┐ рд╡рд┐рд╢реЗрд╖рддрдГ рдХреЙрдиреНрдлрд┐рдЧрд░реЗрд╢рди @babel/preset-env рдЬреБрдиреНрдпрд╛ рдмреНрд░рд╛рдЙрдЭрд░рд╕рд╛рдареА рдЖрдордЪрд╛ JS рдХреЛрдб рдмрджрд▓рдгреНрдпрд╛рд╕рд╛рдареА.
  • JS рдлрд╛рдпрд▓реАрдВрджреНрд╡рд╛рд░реЗ рд╕рдВрджрд░реНрднрд┐рдд рд╕рд░реНрд╡ CSS рдХрд╛рдврдгреНрдпрд╛рд╕рд╛рдареА рдЖрдгрд┐ рддреНрдпрд╛рдВрдирд╛ рдПрдХрд╛рдЪ рдард┐рдХрд╛рдгреА рдПрдХрддреНрд░ рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рдЖрдореНрд╣реА рдкреНрд▓рдЧрдЗрди рд╡рд╛рдкрд░рдд рдЖрд╣реЛрдд. рдореА рддреНрдпрд╛рд▓рд╛ рдЖрдордЪрд╛ рдореНрд╣рдгреЗрди css рдкреЕрдХреЗрдЬ.

рддреБрдореНрд╣рд╛рд▓рд╛ рдХрджрд╛рдЪрд┐рдд рд╡рд┐рдЪрд┐рддреНрд░ рдкреЕрдХреЗрдЬ рдлрд╛рдЗрд▓рдирд╛рд╡реЗ рд▓рдХреНрд╖рд╛рдд рдЖрд▓реА рдЕрд╕рддреАрд▓ '[name].[contenthash].ext'. рддреЗ рдЕрд╕рддрд╛рдд рдлрд╛рдЗрд▓рдирд╛рд╡ рдкреНрд░рддрд┐рд╕реНрдерд╛рдкрди рд╡реЗрдмрдкреЕрдХ: [name] рдЗрдирдкреБрдЯ рдкреЙрдЗрдВрдЯрдЪреНрдпрд╛ рдирд╛рд╡рд╛рдиреЗ рдмрджрд▓рд▓реЗ рдЬрд╛рдИрд▓ (рдЖрдордЪреНрдпрд╛ рдмрд╛рдмрддреАрдд, рд╣реЗ game), рдЖрдгрд┐ [contenthash] рдлрд╛рдЗрд▓рдЪреНрдпрд╛ рд╕рд╛рдордЧреНрд░реАрдЪреНрдпрд╛ рд╣реЕрд╢рдиреЗ рдмрджрд▓рд▓реЗ рдЬрд╛рдИрд▓. рдЖрдореНрд╣реА рддреЗ рдХрд░рддреЛ рд╣реЕрд╢рд┐рдВрдЧрд╕рд╛рдареА рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдСрдкреНрдЯрд┐рдорд╛рдЗрдЭ рдХрд░рд╛ - рддреБрдореНрд╣реА рдмреНрд░рд╛рдЙрдЭрд░рдирд╛ рдЖрдордЪреА JS рдкреЕрдХреЗрдЬреЗрд╕ рдЕрдирд┐рд╢реНрдЪрд┐рдд рдХрд╛рд│рд╛рд╕рд╛рдареА рдХреЕрд╢реЗ рдХрд░рдгреНрдпрд╛рд╕ рд╕рд╛рдВрдЧреВ рд╢рдХрддрд╛, рдХрд╛рд░рдг рдЬрд░ рдкреЕрдХреЗрдЬ рдмрджрд▓рд▓реЗ рддрд░ рддреНрдпрд╛рдЪреЗ рдлрд╛рдЗрд▓ рдирд╛рд╡ рджреЗрдЦреАрд▓ рдмрджрд▓рддреЗ (рдмрджрд▓ contenthash). рдЕрдВрддрд┐рдо рдкрд░рд┐рдгрд╛рдо рджреГрд╢реНрдп рдлрд╛рдЗрд▓рдЪреЗ рдирд╛рд╡ рдЕрд╕реЗрд▓ game.dbeee76e91a97d0c7207.js.

рдлрд╛рдЗрд▓ webpack.common.js рд╣реА рдмреЗрд╕ рдХреЙрдиреНрдлрд┐рдЧрд░реЗрд╢рди рдлрд╛рдЗрд▓ рдЖрд╣реЗ рдЬреА рдЖрдореНрд╣реА рдбреЗрд╡реНрд╣рд▓рдкрдореЗрдВрдЯ рдЖрдгрд┐ рдкреВрд░реНрдг рдЭрд╛рд▓реЗрд▓реНрдпрд╛ рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдХреЙрдиреНрдлрд┐рдЧрд░реЗрд╢рдирдордзреНрдпреЗ рдЖрдпрд╛рдд рдХрд░рддреЛ. рдпреЗрдереЗ рдПрдХ рдЙрджрд╛рд╣рд░рдг рд╡рд┐рдХрд╛рд╕ рдХреЙрдиреНрдлрд┐рдЧрд░реЗрд╢рди рдЖрд╣реЗ:

webpack.dev.js

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

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

рдХрд╛рд░реНрдпрдХреНрд╖рдорддреЗрд╕рд╛рдареА, рдЖрдореНрд╣реА рд╡рд┐рдХрд╛рд╕ рдкреНрд░рдХреНрд░рд┐рдпреЗрдд рд╡рд╛рдкрд░рддреЛ webpack.dev.js, рдЖрдгрд┐ рд╡рд░ рд╕реНрд╡рд┐рдЪ рдХрд░рддреЗ webpack.prod.jsрдЙрддреНрдкрд╛рджрдирд╛рд╕рд╛рдареА рддреИрдирд╛рдд рдХрд░рддрд╛рдирд╛ рдкреЕрдХреЗрдЬ рдЖрдХрд╛рд░ рдСрдкреНрдЯрд┐рдорд╛рдЗрдЭ рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА.

рд╕реНрдерд╛рдирд┐рдХ рд╕реЗрдЯрд┐рдВрдЧ

рдореА рд╕реНрдерд╛рдирд┐рдХ рдорд╢реАрдирд╡рд░ рдкреНрд░рдХрд▓реНрдк рд╕реНрдерд╛рдкрд┐рдд рдХрд░рдгреНрдпрд╛рдЪреА рд╢рд┐рдлрд╛рд░рд╕ рдХрд░рддреЛ рдЬреЗрдгреЗрдХрд░реВрди рдЖрдкрдг рдпрд╛ рдкреЛрд╕реНрдЯрдордзреНрдпреЗ рд╕реВрдЪреАрдмрджреНрдз рдХреЗрд▓реЗрд▓реНрдпрд╛ рдЪрд░рдгрд╛рдВрдЪреЗ рдЕрдиреБрд╕рд░рдг рдХрд░реВ рд╢рдХрддрд╛. рд╕реЗрдЯрдЕрдк рд╕реЛрдкреЗ рдЖрд╣реЗ: рдкреНрд░рдердо, рд╕рд┐рд╕реНрдЯрдо рд╕реНрдерд╛рдкрд┐рдд рдХрд░рдгреЗ рдЖрд╡рд╢реНрдпрдХ рдЖрд╣реЗ рдиреЛрдб ╨╕ рдПрдирдкреАрдПрдо. рдкреБрдвреЗ рдЖрдкрдг рдХрд░рдгреЗ рдЖрд╡рд╢реНрдпрдХ рдЖрд╣реЗ

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

рдЖрдгрд┐ рдЖрдкрдг рдЬрд╛рдгреНрдпрд╛рд╕рд╛рдареА рддрдпрд╛рд░ рдЖрд╣рд╛рдд! рд╡рд┐рдХрд╛рд╕ рд╕рд░реНрд╡реНрд╣рд░ рд╕реБрд░реВ рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА, рдлрдХреНрдд рдЪрд╛рд▓рд╡рд╛

$ npm run develop

рдЖрдгрд┐ рд╡реЗрдм рдмреНрд░рд╛рдЙрдЭрд░ рд╡рд░ рдЬрд╛ рд▓реЛрдХрд╣реЛрд╕реНрдЯрдГ 3000. рдХреЛрдб рдмрджрд▓рд▓реНрдпрд╛рд╡рд░ рдбреЗрд╡реНрд╣рд▓рдкрдореЗрдВрдЯ рд╕рд░реНрд╡реНрд╣рд░ рдЖрдкреЛрдЖрдк JS рдЖрдгрд┐ CSS рдкреЕрдХреЗрдЬреЗрд╕ рдкреБрдиреНрд╣рд╛ рддрдпрд╛рд░ рдХрд░реЗрд▓ - рд╕рд░реНрд╡ рдмрджрд▓ рдкрд╛рд╣рдгреНрдпрд╛рд╕рд╛рдареА рдлрдХреНрдд рдкреЗрдЬ рд░рд┐рдлреНрд░реЗрд╢ рдХрд░рд╛!

3. рдХреНрд▓рд╛рдпрдВрдЯ рдПрдВрдЯреНрд░реА рдкреЙрдЗрдВрдЯреНрд╕

рдЪрд▓рд╛ рдЧреЗрдо рдХреЛрдбрд╡рд░рдЪ рдЙрддрд░реВрдпрд╛. рдкреНрд░рдердо рдЖрдкрд▓реНрдпрд╛рд▓рд╛ рдПрдХ рдкреГрд╖реНрда рдЖрд╡рд╢реНрдпрдХ рдЖрд╣реЗ index.html, рд╕рд╛рдЗрдЯрд▓рд╛ рднреЗрдЯ рджреЗрддрд╛рдирд╛, рдмреНрд░рд╛рдЙрдЭрд░ рдкреНрд░рдердо рддреЗ рд▓реЛрдб рдХрд░реЗрд▓. рдЖрдордЪреЗ рдкреГрд╖реНрда рдЦреВрдкрдЪ рд╕реЛрдкреЗ рдЕрд╕реЗрд▓:

Index.html

рдПрдХ рдЙрджрд╛рд╣рд░рдг .io рдЦреЗрд│  рдЦреЗрд│рд╛

рд╣реЗ рдХреЛрдб рдЙрджрд╛рд╣рд░рдг рд╕реНрдкрд╖реНрдЯрддреЗрд╕рд╛рдареА рдереЛрдбреЗрд╕реЗ рд╕реЛрдкреЗ рдХреЗрд▓реЗ рдЖрд╣реЗ рдЖрдгрд┐ рдореА рдЗрддрд░ рдЕрдиреЗрдХ рдкреЛрд╕реНрдЯ рдЙрджрд╛рд╣рд░рдгрд╛рдВрд╕рд╣ рддреЗрдЪ рдХрд░реЗрди. рдкреВрд░реНрдг рдХреЛрдб рдиреЗрд╣рдореА рдпреЗрдереЗ рдкрд╛рд╣рд┐рд▓рд╛ рдЬрд╛рдК рд╢рдХрддреЛ рдЬрд┐рдереВрдм.

рдЖрдордЪреНрдпрд╛рдХрдбреЗ рдЖрд╣реЗ:

  • HTML5 рдХреЕрдирд╡реНрд╣рд╛рд╕ рдШрдЯрдХ (<canvas>) рдЬреНрдпрд╛рдЪрд╛ рд╡рд╛рдкрд░ рдЖрдореНрд╣реА рдЧреЗрдо рд░реЗрдВрдбрд░ рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рдХрд░реВ.
  • <link> рдЖрдордЪреЗ CSS рдкреЕрдХреЗрдЬ рдЬреЛрдбрдгреНрдпрд╛рд╕рд╛рдареА.
  • <script> рдЖрдордЪреЗ Javascript рдкреЕрдХреЗрдЬ рдЬреЛрдбрдгреНрдпрд╛рд╕рд╛рдареА.
  • рд╡рд╛рдкрд░рдХрд░реНрддрд╛рдирд╛рд╡рд╛рд╕рд╣ рдореБрдЦреНрдп рдореЗрдиреВ <input> рдЖрдгрд┐ рдкреНрд▓реЗ рдмрдЯрдг (<button>).

рдореБрдЦреНрдпрдкреГрд╖реНрда рд▓реЛрдб рдХреЗрд▓реНрдпрд╛рдирдВрддрд░, рдмреНрд░рд╛рдЙрдЭрд░ рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдХреЛрдб рдХрд╛рд░реНрдпрд╛рдиреНрд╡рд┐рдд рдХрд░рдгреНрдпрд╛рд╕ рдкреНрд░рд╛рд░рдВрдн рдХрд░реЗрд▓, рдПрдВрдЯреНрд░реА рдкреЙрдЗрдВрдЯ JS рдлрд╛рдЗрд▓рдкрд╛рд╕реВрди рд╕реБрд░реВ рд╣реЛрдИрд▓: src/client/index.js.

index.js

import { connect, play } from './networking';
import { startRendering, stopRendering } from './render';
import { startCapturingInput, stopCapturingInput } from './input';
import { downloadAssets } from './assets';
import { initState } from './state';
import { setLeaderboardHidden } from './leaderboard';

import './css/main.css';

const playMenu = document.getElementById('play-menu');
const playButton = document.getElementById('play-button');
const usernameInput = document.getElementById('username-input');

Promise.all([
  connect(),
  downloadAssets(),
]).then(() => {
  playMenu.classList.remove('hidden');
  usernameInput.focus();
  playButton.onclick = () => {
    // Play!
    play(usernameInput.value);
    playMenu.classList.add('hidden');
    initState();
    startCapturingInput();
    startRendering();
    setLeaderboardHidden(false);
  };
});

рд╣реЗ рдХреНрд▓рд┐рд╖реНрдЯ рд╡рд╛рдЯреВ рд╢рдХрддреЗ, рдкрд░рдВрддреБ рдпреЗрдереЗ рдмрд░реЗрдЪ рдХрд╛рд╣реА рд╣реЛрдд рдирд╛рд╣реА:

  1. рдЗрддрд░ рдЕрдиреЗрдХ JS рдлрд╛рдЗрд▓реНрд╕ рдЗрдВрдкреЛрд░реНрдЯ рдХрд░рдд рдЖрд╣реЗ.
  2. CSS рдЖрдпрд╛рдд (рдореНрд╣рдгреВрди рд╡реЗрдмрдкреЕрдХрд▓рд╛ рддреЗ рдЖрдордЪреНрдпрд╛ CSS рдкреЕрдХреЗрдЬрдордзреНрдпреЗ рд╕рдорд╛рд╡рд┐рд╖реНрдЯ рдХрд░рдгреЗ рдорд╛рд╣рд┐рдд рдЖрд╣реЗ).
  3. рд▓рд╛рдБрдЪ рдХрд░рд╛ connect() рд╕рд░реНрд╡реНрд╣рд░рд╢реА рдХрдиреЗрдХреНрд╢рди рд╕реНрдерд╛рдкрд┐рдд рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рдЖрдгрд┐ рдЪрд╛рд▓рд╡рд╛ downloadAssets() рдЧреЗрдо рд░реЗрдВрдбрд░ рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рдЖрд╡рд╢реНрдпрдХ рдкреНрд░рддрд┐рдорд╛ рдбрд╛рдЙрдирд▓реЛрдб рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА.
  4. рд╕реНрдЯреЗрдЬ 3 рдкреВрд░реНрдг рдЭрд╛рд▓реНрдпрд╛рдирдВрддрд░ рдореБрдЦреНрдп рдореЗрдиреВ рдкреНрд░рджрд░реНрд╢рд┐рдд рд╣реЛрддреЛ (playMenu).
  5. "рдкреНрд▓реЗ" рдмрдЯрдг рджрд╛рдмрдгреНрдпрд╛рд╕рд╛рдареА рд╣рдБрдбрд▓рд░ рд╕реЗрдЯ рдХрд░рдд рдЖрд╣реЗ. рдЬреЗрд╡реНрд╣рд╛ рдмрдЯрдг рджрд╛рдмрд▓реЗ рдЬрд╛рддреЗ, рддреЗрд╡реНрд╣рд╛ рдХреЛрдб рдЧреЗрдо рд╕реБрд░реВ рдХрд░рддреЛ рдЖрдгрд┐ рд╕рд░реНрд╡реНрд╣рд░рд▓рд╛ рд╕рд╛рдВрдЧрддреЛ рдХреА рдЖрдореНрд╣реА рдЦреЗрд│рдгреНрдпрд╛рд╕рд╛рдареА рддрдпрд╛рд░ рдЖрд╣реЛрдд.

рдЖрдордЪреНрдпрд╛ рдХреНрд▓рд╛рдпрдВрдЯ-рд╕рд░реНрд╡реНрд╣рд░ рд▓реЙрдЬрд┐рдХрдЪреЗ рдореБрдЦреНрдп "рдорд╛рдВрд╕" рдлрд╛рдЗрд▓рджреНрд╡рд╛рд░реЗ рдЖрдпрд╛рдд рдХреЗрд▓реЗрд▓реНрдпрд╛ рдлрд╛рдЗрд▓реНрд╕рдордзреНрдпреЗ рдЖрд╣реЗ index.js. рдЖрддрд╛ рдЖрдореНрд╣реА рддреНрдпрд╛ рд╕рд░реНрд╡рд╛рдВрдЪрд╛ рдХреНрд░рдорд╛рдиреЗ рд╡рд┐рдЪрд╛рд░ рдХрд░реВ.

4. рдЧреНрд░рд╛рд╣рдХ рдбреЗрдЯрд╛рдЪреА рджреЗрд╡рд╛рдгрдШреЗрд╡рд╛рдг

рдпрд╛ рдЧреЗрдордордзреНрдпреЗ, рдЖрдореНрд╣реА рд╕рд░реНрд╡реНрд╣рд░рд╢реА рд╕рдВрд╡рд╛рдж рд╕рд╛рдзрдгреНрдпрд╛рд╕рд╛рдареА рдПрдХ рд╕реБрдкреНрд░рд╕рд┐рджреНрдз рд▓рд╛рдпрдмреНрд░рд░реА рд╡рд╛рдкрд░рддреЛ socket.io. Socket.io рд▓рд╛ рдореВрд│ рд╕рдорд░реНрдерди рдЖрд╣реЗ рд╡реЗрдмрд╕реЙрдХреЗрдЯреНрд╕, рдЬреЗ рджреНрд╡рд┐-рдорд╛рд░реНрдЧ рд╕рдВрдкреНрд░реЗрд╖рдгрд╛рд╕рд╛рдареА рдпреЛрдЧреНрдп рдЖрд╣реЗрдд: рдЖрдореНрд╣реА рд╕рд░реНрд╡реНрд╣рд░рд▓рд╛ рд╕рдВрджреЗрд╢ рдкрд╛рдард╡реВ рд╢рдХрддреЛ ╨╕ рд╕рд░реНрд╡реНрд╣рд░ рдЖрдореНрд╣рд╛рд▓рд╛ рддреНрдпрд╛рдЪ рдХрдиреЗрдХреНрд╢рдирд╡рд░ рд╕рдВрджреЗрд╢ рдкрд╛рдард╡реВ рд╢рдХрддреЛ.

рдЖрдордЪреНрдпрд╛рдХрдбреЗ рдПрдХ рдлрд╛рдИрд▓ рдЕрд╕реЗрд▓ src/client/networking.jsрдХреЛрдг рдХрд╛рд│рдЬреА рдШреЗрдИрд▓ рдкреНрд░рддреНрдпреЗрдХрдЬрдг рд╕рд░реНрд╡реНрд╣рд░рд╢реА рд╕рдВрд╡рд╛рдж:

networking.js

import io from 'socket.io-client';
import { processGameUpdate } from './state';

const Constants = require('../shared/constants');

const socket = io(`ws://${window.location.host}`);
const connectedPromise = new Promise(resolve => {
  socket.on('connect', () => {
    console.log('Connected to server!');
    resolve();
  });
});

export const connect = onGameOver => (
  connectedPromise.then(() => {
    // Register callbacks
    socket.on(Constants.MSG_TYPES.GAME_UPDATE, processGameUpdate);
    socket.on(Constants.MSG_TYPES.GAME_OVER, onGameOver);
  })
);

export const play = username => {
  socket.emit(Constants.MSG_TYPES.JOIN_GAME, username);
};

export const updateDirection = dir => {
  socket.emit(Constants.MSG_TYPES.INPUT, dir);
};

рд╕реНрдкрд╖реНрдЯрддреЗрд╕рд╛рдареА рд╣рд╛ рдХреЛрдб рджреЗрдЦреАрд▓ рдереЛрдбрд╛ рд▓рд╣рд╛рди рдХреЗрд▓рд╛ рдЖрд╣реЗ.

рдпрд╛ рдлрд╛рдЗрд▓рдордзреНрдпреЗ рддреАрди рдореБрдЦреНрдп рдХреНрд░рд┐рдпрд╛ рдЖрд╣реЗрдд:

  • рдЖрдореНрд╣реА рд╕рд░реНрд╡реНрд╣рд░рд╢реА рдХрдиреЗрдХреНрдЯ рдХрд░рдгреНрдпрд╛рдЪрд╛ рдкреНрд░рдпрддреНрди рдХрд░рдд рдЖрд╣реЛрдд. connectedPromise рдЖрдореНрд╣реА рдХрдиреЗрдХреНрд╢рди рд╕реНрдерд╛рдкрд┐рдд рдХреЗрд▓реНрдпрд╛рд╡рд░рдЪ рдкрд░рд╡рд╛рдирдЧреА рдЖрд╣реЗ.
  • рдХрдиреЗрдХреНрд╢рди рдпрд╢рд╕реНрд╡реА рдЭрд╛рд▓реНрдпрд╛рд╕, рдЖрдореНрд╣реА рдХреЙрд▓рдмреЕрдХ рдлрдВрдХреНрд╢рдиреНрд╕рдЪреА рдиреЛрдВрджрдгреА рдХрд░рддреЛ (processGameUpdate() ╨╕ onGameOver()) рдЖрдореНрд╣реА рд╕рд░реНрд╡реНрд╣рд░рдХрдбреВрди рдкреНрд░рд╛рдкреНрдд рдХрд░реВ рд╢рдХрдгрд╛рд▒реНрдпрд╛ рд╕рдВрджреЗрд╢рд╛рдВрд╕рд╛рдареА.
  • рдЖрдореНрд╣реА рдирд┐рд░реНрдпрд╛рдд рдХрд░рддреЛ play() ╨╕ updateDirection()рдЬреЗрдгреЗрдХрд░реВрди рдЗрддрд░ рдлрд╛рдпрд▓реА рддреНрдпрд╛рдВрдЪрд╛ рд╡рд╛рдкрд░ рдХрд░реВ рд╢рдХрддреАрд▓.

5. рдХреНрд▓рд╛рдпрдВрдЯ рдкреНрд░рд╕реНрддреБрддреАрдХрд░рдг

рд╕реНрдХреНрд░реАрдирд╡рд░ рдЪрд┐рддреНрд░ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рдгреНрдпрд╛рдЪреА рд╡реЗрд│ рдЖрд▓реА рдЖрд╣реЗ!

тАжрдкрд░рдВрддреБ рдЖрдореНрд╣реА рддреЗ рдХрд░рдгреНрдпрд╛рдкреВрд░реНрд╡реА, рдЖрдореНрд╣рд╛рд▓рд╛ рдпрд╛рд╕рд╛рдареА рдЖрд╡рд╢реНрдпрдХ рдЕрд╕рд▓реЗрд▓реНрдпрд╛ рд╕рд░реНрд╡ рдкреНрд░рддрд┐рдорд╛ (рд╕рдВрд╕рд╛рдзрдиреЗ) рдбрд╛рдЙрдирд▓реЛрдб рдХрд░рдгреЗ рдЖрд╡рд╢реНрдпрдХ рдЖрд╣реЗ. рдЪрд▓рд╛ рд╕рдВрд╕рд╛рдзрди рд╡реНрдпрд╡рд╕реНрдерд╛рдкрдХ рд▓рд┐рд╣реВ:

assets.js

const ASSET_NAMES = ['ship.svg', 'bullet.svg'];

const assets = {};
const downloadPromise = Promise.all(ASSET_NAMES.map(downloadAsset));

function downloadAsset(assetName) {
  return new Promise(resolve => {
    const asset = new Image();
    asset.onload = () => {
      console.log(`Downloaded ${assetName}`);
      assets[assetName] = asset;
      resolve();
    };
    asset.src = `/assets/${assetName}`;
  });
}

export const downloadAssets = () => downloadPromise;
export const getAsset = assetName => assets[assetName];

рд╕рдВрд╕рд╛рдзрди рд╡реНрдпрд╡рд╕реНрдерд╛рдкрди рдЕрдВрдорд▓рдмрдЬрд╛рд╡рдгреА рдХрд░рдгреЗ рдЗрддрдХреЗ рдХрдареАрдг рдирд╛рд╣реА! рдореБрдЦреНрдп рдХрд▓реНрдкрдирд╛ рдореНрд╣рдгрдЬреЗ рд╡рд╕реНрддреВ рд╕рдВрдЧреНрд░рд╣рд┐рдд рдХрд░рдгреЗ assets, рдЬреЗ рдСрдмреНрдЬреЗрдХреНрдЯрдЪреНрдпрд╛ рдореВрд▓реНрдпрд╛рд╢реА рдлрд╛рдЗрд▓ рдирд╛рд╡рд╛рдЪреА рдХреА рдмрд╛рдВрдзреЗрд▓ Image. рдЬреЗрд╡реНрд╣рд╛ рд╕рдВрд╕рд╛рдзрди рд▓реЛрдб рдХреЗрд▓реЗ рдЬрд╛рддреЗ, рддреЗрд╡реНрд╣рд╛ рдЖрдореНрд╣реА рддреЗ рдСрдмреНрдЬреЗрдХреНрдЯрдордзреНрдпреЗ рд╕рд╛рдард╡рддреЛ assets рднрд╡рд┐рд╖реНрдпрд╛рдд рджреНрд░реБрдд рдкреНрд░рд╡реЗрд╢рд╛рд╕рд╛рдареА. рдкреНрд░рддреНрдпреЗрдХ рд╡реИрдпрдХреНрддрд┐рдХ рд╕рдВрд╕рд╛рдзрди рдбрд╛рдЙрдирд▓реЛрдб рдХрд░рдгреНрдпрд╛рдЪреА рдкрд░рд╡рд╛рдирдЧреА рдХрдзреА рджрд┐рд▓реА рдЬрд╛рдИрд▓ (рдореНрд╣рдгрдЬреЗ, рд╕рд░реНрд╡ рд╕рдВрд╕рд╛рдзрдиреЗ), рдЖрдореНрд╣реА рдкрд░рд╡рд╛рдирдЧреА рджреЗрддреЛ downloadPromise.

рд╕рдВрд╕рд╛рдзрдиреЗ рдбрд╛рдЙрдирд▓реЛрдб рдХреЗрд▓реНрдпрд╛рдирдВрддрд░, рддреБрдореНрд╣реА рдкреНрд░рд╕реНрддреБрддреАрдХрд░рдг рд╕реБрд░реВ рдХрд░реВ рд╢рдХрддрд╛. рдкреВрд░реНрд╡реА рдореНрд╣рдЯрд▓реНрдпрд╛рдкреНрд░рдорд╛рдгреЗ, рд╡реЗрдм рдкреГрд╖реНрдард╛рд╡рд░ рдХрд╛рдврдгреНрдпрд╛рд╕рд╛рдареА, рдЖрдореНрд╣реА рд╡рд╛рдкрд░рддреЛ HTML5 рдХреЕрдирд╡реНрд╣рд╛рд╕ (<canvas>). рдЖрдордЪрд╛ рдЦреЗрд│ рдЦреВрдкрдЪ рд╕реЛрдкрд╛ рдЖрд╣реЗ, рдореНрд╣рдгреВрди рдЖрдореНрд╣рд╛рд▓рд╛ рдлрдХреНрдд рдЦрд╛рд▓реАрд▓ рдЧреЛрд╖реНрдЯреА рдХрд╛рдврдгреНрдпрд╛рдЪреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдЖрд╣реЗ:

  1. рдкрд╛рд░реНрд╢реНрд╡рднреВрдореА
  2. рдЦреЗрд│рд╛рдбреВ рдЬрд╣рд╛рдЬ
  3. рдЦреЗрд│рд╛рддреАрд▓ рдЗрддрд░ рдЦреЗрд│рд╛рдбреВ
  4. рдЯрд░рдлрд▓реЗ

рдпреЗрдереЗ рдорд╣рддреНрд╡рд╛рдЪреЗ рд╕реНрдирд┐рдкреЗрдЯреНрд╕ рдЖрд╣реЗрдд src/client/render.js, рдЬреЗ рд╡рд░ рд╕реВрдЪреАрдмрджреНрдз рдХреЗрд▓реЗрд▓реНрдпрд╛ рдЪрд╛рд░ рдЖрдпрдЯрдордЪреЗ рдЕрдЪреВрдХ рд╡рд░реНрдгрди рдХрд░рддрд╛рдд:

render.js

import { getAsset } from './assets';
import { getCurrentState } from './state';

const Constants = require('../shared/constants');
const { PLAYER_RADIUS, PLAYER_MAX_HP, BULLET_RADIUS, MAP_SIZE } = Constants;

// Get the canvas graphics context
const canvas = document.getElementById('game-canvas');
const context = canvas.getContext('2d');

// Make the canvas fullscreen
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

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

  // Draw background
  renderBackground(me.x, me.y);

  // Draw all bullets
  bullets.forEach(renderBullet.bind(null, me));

  // Draw all players
  renderPlayer(me, me);
  others.forEach(renderPlayer.bind(null, me));
}

// ... Helper functions here excluded

let renderInterval = null;
export function startRendering() {
  renderInterval = setInterval(render, 1000 / 60);
}
export function stopRendering() {
  clearInterval(renderInterval);
}

рд╕реНрдкрд╖реНрдЯрддреЗрд╕рд╛рдареА рд╣рд╛ рдХреЛрдб рджреЗрдЦреАрд▓ рд▓рд╣рд╛рди рдХреЗрд▓рд╛ рдЖрд╣реЗ.

render() рдпрд╛ рдлрд╛рдИрд▓рдЪреЗ рдореБрдЦреНрдп рдХрд╛рд░реНрдп рдЖрд╣реЗ. startRendering() ╨╕ stopRendering() 60 FPS рд╡рд░ рд░реЗрдВрдбрд░ рд▓реВрдкрдЪреЗ рд╕рдХреНрд░рд┐рдпрдХрд░рдг рдирд┐рдпрдВрддреНрд░рд┐рдд рдХрд░рд╛.

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

render.js

function renderBullet(me, bullet) {
  const { x, y } = bullet;
  context.drawImage(
    getAsset('bullet.svg'),
    canvas.width / 2 + x - me.x - BULLET_RADIUS,
    canvas.height / 2 + y - me.y - BULLET_RADIUS,
    BULLET_RADIUS * 2,
    BULLET_RADIUS * 2,
  );
}

рд▓рдХреНрд╖рд╛рдд рдШреНрдпрд╛ рдХреА рдЖрдореНрд╣реА рдкрджреНрдзрдд рд╡рд╛рдкрд░рдд рдЖрд╣реЛрдд getAsset(), рдЬреЗ рдкреВрд░реНрд╡реА рджрд┐рд╕рд▓реЗ рд╣реЛрддреЗ asset.js!

рддреБрдореНрд╣рд╛рд▓рд╛ рдЗрддрд░ рд░реЗрдВрдбрд░рд┐рдВрдЧ рд╕рд╣рд╛рдпреНрдпрдХрд╛рдВрдмрджреНрджрд▓ рдЬрд╛рдгреВрди рдШреЗрдгреНрдпрд╛рдд рд╕реНрд╡рд╛рд░рд╕реНрдп рдЕрд╕рд▓реНрдпрд╛рд╕, рдЙрд░реНрд╡рд░рд┐рдд рд╡рд╛рдЪрд╛. src/client/render.js.

6. рдХреНрд▓рд╛рдпрдВрдЯ рдЗрдирдкреБрдЯ

рдЧреЗрдо рдмрдирд╡рдгреНрдпрд╛рдЪреА рд╡реЗрд│ рдЖрд▓реА рдЖрд╣реЗ рдЦреЗрд│рдгреНрдпрд╛рдпреЛрдЧреНрдп! рдирд┐рдпрдВрддреНрд░рдг рдпреЛрдЬрдирд╛ рдЕрдЧрджреА рд╕реЛрдкреА рдЕрд╕реЗрд▓: рд╣рд╛рд▓рдЪрд╛рд▓реАрдЪреА рджрд┐рд╢рд╛ рдмрджрд▓рдгреНрдпрд╛рд╕рд╛рдареА, рдЖрдкрдг рдорд╛рдЙрд╕ (рд╕рдВрдЧрдгрдХрд╛рд╡рд░) рд╡рд╛рдкрд░реВ рд╢рдХрддрд╛ рдХрд┐рдВрд╡рд╛ рд╕реНрдХреНрд░реАрдирд▓рд╛ рд╕реНрдкрд░реНрд╢ рдХрд░реВ рд╢рдХрддрд╛ (рдореЛрдмрд╛рдЗрд▓ рдбрд┐рд╡реНрд╣рд╛рдЗрд╕рд╡рд░). рдпрд╛рдЪреА рдЕрдВрдорд▓рдмрдЬрд╛рд╡рдгреА рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рдЖрдореНрд╣реА рдиреЛрдВрджрдгреА рдХрд░реВ рдХрд╛рд░реНрдпрдХреНрд░рдо рдРрдХрдгрд╛рд░реЗ рдорд╛рдЙрд╕ рдЖрдгрд┐ рдЯрдЪ рдЗрд╡реНрд╣реЗрдВрдЯрд╕рд╛рдареА.
рдпрд╛ рд╕рдЧрд│реНрдпрд╛рдЪреА рдХрд╛рд│рдЬреА рдШреЗрдИрд▓ src/client/input.js:

input.js

import { updateDirection } from './networking';

function onMouseInput(e) {
  handleInput(e.clientX, e.clientY);
}

function onTouchInput(e) {
  const touch = e.touches[0];
  handleInput(touch.clientX, touch.clientY);
}

function handleInput(x, y) {
  const dir = Math.atan2(x - window.innerWidth / 2, window.innerHeight / 2 - y);
  updateDirection(dir);
}

export function startCapturingInput() {
  window.addEventListener('mousemove', onMouseInput);
  window.addEventListener('touchmove', onTouchInput);
}

export function stopCapturingInput() {
  window.removeEventListener('mousemove', onMouseInput);
  window.removeEventListener('touchmove', onTouchInput);
}

onMouseInput() ╨╕ onTouchInput() рдХреЙрд▓ рдХрд░рдгрд╛рд░реЗ рдХрд╛рд░реНрдпрдХреНрд░рдо рд╢реНрд░реЛрддреЗ рдЖрд╣реЗрдд updateDirection() (рдХрдбреВрди networking.js) рдЬреЗрд╡реНрд╣рд╛ рдПрдЦрд╛рджреА рдЗрдирдкреБрдЯ рдШрдЯрдирд╛ рдШрдбрддреЗ (рдЙрджрд╛рд╣рд░рдгрд╛рд░реНрде, рдЬреЗрд╡реНрд╣рд╛ рдорд╛рдЙрд╕ рд╣рд▓рд╡рд┐рд▓рд╛ рдЬрд╛рддреЛ). updateDirection() рд╕рд░реНрд╡реНрд╣рд░рд╕рд╣ рд╕рдВрджреЗрд╢рди рд╣рд╛рддрд╛рд│рддреЗ, рдЬреЗ рдЗрдирдкреБрдЯ рдЗрд╡реНрд╣реЗрдВрдЯ рд╣рд╛рддрд╛рд│рддреЗ рдЖрдгрд┐ рддреНрдпрд╛рдиреБрд╕рд╛рд░ рдЧреЗрдо рд╕реНрдерд┐рддреА рдЕрджреНрдпрддрдирд┐рдд рдХрд░рддреЗ.

7. рдХреНрд▓рд╛рдпрдВрдЯ рд╕реНрдерд┐рддреА

рд╣рд╛ рд╡рд┐рднрд╛рдЧ рдкреЛрд╕реНрдЯрдЪреНрдпрд╛ рдкрд╣рд┐рд▓реНрдпрд╛ рднрд╛рдЧрд╛рдд рд╕рд░реНрд╡рд╛рдд рдХрдареАрдг рдЖрд╣реЗ. рдкрд╣рд┐рд▓реНрдпрд╛рдВрджрд╛ рд╡рд╛рдЪрддрд╛рдирд╛ рддреБрдореНрд╣рд╛рд▓рд╛ рддреЗ рд╕рдордЬрдд рдирд╕реЗрд▓ рддрд░ рдирд┐рд░рд╛рд╢ рд╣реЛрдК рдирдХрд╛! рддреБрдореНрд╣реА рддреЗ рд╡рдЧрд│реВ рд╢рдХрддрд╛ рдЖрдгрд┐ рдирдВрддрд░ рддреНрдпрд╛рд╡рд░ рдкрд░рдд рдпреЗрдК рд╢рдХрддрд╛.

рдХреНрд▓рд╛рдпрдВрдЯ/рд╕рд░реНрд╡реНрд╣рд░ рдХреЛрдб рдкреВрд░реНрдг рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рдЖрд╡рд╢реНрдпрдХ рдЕрд╕рд▓реЗрд▓реНрдпрд╛ рдХреЛрдбреЗрдЪрд╛ рд╢реЗрд╡рдЯрдЪрд╛ рднрд╛рдЧ рдЖрд╣реЗ рд░рд╛рдЬреНрдп. рдХреНрд▓рд╛рдпрдВрдЯ рдкреНрд░рд╕реНрддреБрддреАрдХрд░рдг рд╡рд┐рднрд╛рдЧрд╛рддреАрд▓ рдХреЛрдб рд╕реНрдирд┐рдкреЗрдЯ рд▓рдХреНрд╖рд╛рдд рдареЗрд╡рд╛?

render.js

import { getCurrentState } from './state';

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

  // Do the rendering
  // ...
}

getCurrentState() рдХреНрд▓рд╛рдпрдВрдЯрдордзреАрд▓ рдЧреЗрдордЪреА рд╕рджреНрдп рд╕реНрдерд┐рддреА рдЖрдореНрд╣рд╛рд▓рд╛ рджреЗрдгреНрдпрд╛рд╕ рд╕рдХреНрд╖рдо рдЕрд╕рд╛рд╡реЗ рдХреЛрдгрддреНрдпрд╛рд╣реА рд╡реЗрд│реА рд╕рд░реНрд╡реНрд╣рд░рдХрдбреВрди рдкреНрд░рд╛рдкреНрдд рдЭрд╛рд▓реЗрд▓реНрдпрд╛ рдЕрджреНрдпрддрдирд╛рдВрд╡рд░ рдЖрдзрд╛рд░рд┐рдд. рд╕рд░реНрд╡реНрд╣рд░ рдкрд╛рдард╡реВ рд╢рдХрдгрд╛рд░реНтАНрдпрд╛ рдЧреЗрдо рдЕрдкрдбреЗрдЯрдЪреЗ рдпреЗрдереЗ рдПрдХ рдЙрджрд╛рд╣рд░рдг рдЖрд╣реЗ:

{
  "t": 1555960373725,
  "me": {
    "x": 2213.8050880413657,
    "y": 1469.370893425012,
    "direction": 1.3082443894581433,
    "id": "AhzgAtklgo2FJvwWAADO",
    "hp": 100
  },
  "others": [],
  "bullets": [
    {
      "id": "RUJfJ8Y18n",
      "x": 2354.029197099604,
      "y": 1431.6848318262666
    },
    {
      "id": "ctg5rht5s",
      "x": 2260.546457727445,
      "y": 1456.8088728920968
    }
  ],
  "leaderboard": [
    {
      "username": "Player",
      "score": 3
    }
  ]
}

рдкреНрд░рддреНрдпреЗрдХ рдЧреЗрдо рдЕрдкрдбреЗрдЯрдордзреНрдпреЗ рдкрд╛рдЪ рд╕рдорд╛рди рдлреАрд▓реНрдб рдЕрд╕рддрд╛рдд:

  • t: рд╣реЗ рдЕрдкрдбреЗрдЯ рдХреЗрд╡реНрд╣рд╛ рддрдпрд╛рд░ рдХреЗрд▓реЗ рдЧреЗрд▓реЗ рд╣реЗ рджрд░реНрд╢рд╡рдгрд╛рд░рд╛ рд╕рд░реНрд╡реНрд╣рд░ рдЯрд╛рдЗрдорд╕реНрдЯреЕрдореНрдк.
  • me: рд╣реЗ рдЕрдкрдбреЗрдЯ рдкреНрд░рд╛рдкреНрдд рдХрд░рдгрд╛рд▒реНрдпрд╛ рдЦреЗрд│рд╛рдбреВрдмрджреНрджрд▓ рдорд╛рд╣рд┐рддреА.
  • рдЗрддрд░: рд╕рдорд╛рди рдЧреЗрдордордзреНрдпреЗ рд╕рд╣рднрд╛рдЧреА рд╣реЛрдгрд╛рд░реНтАНрдпрд╛ рдЗрддрд░ рдЦреЗрд│рд╛рдбреВрдВрдмрджреНрджрд▓ рдорд╛рд╣рд┐рддреАрдЪреА рдПрдХ рд╢реНрд░реЗрдгреА.
  • рдЧреЛрд│реНрдпрд╛: рдЧреЗрдордордзреАрд▓ рдкреНрд░реЛрдЬреЗрдХреНрдЯрд╛рдЗрд▓реНрд╕рдмрджреНрджрд▓ рдорд╛рд╣рд┐рддреАрдЪрд╛ рдПрдХ рдЕреЕрд░реЗ.
  • рд▓реАрдбрд░рдмреЛрд░реНрдб: рд╡рд░реНрддрдорд╛рди рд▓реАрдбрд░рдмреЛрд░реНрдб рдбреЗрдЯрд╛. рдпрд╛ рдкреЛрд╕реНрдЯрдордзреНрдпреЗ, рдЖрдореНрд╣реА рддреНрдпрд╛рдВрдЪрд╛ рд╡рд┐рдЪрд╛рд░ рдХрд░рдгрд╛рд░ рдирд╛рд╣реА.

7.1 рдирд┐рд╖реНрдХрд▓рдВрдХ рдХреНрд▓рд╛рдпрдВрдЯрдЪреА рд╕реНрдерд┐рддреА

рднреЛрд│реА рдЕрдВрдорд▓рдмрдЬрд╛рд╡рдгреА getCurrentState() рдлрдХреНрдд рд╕рд░реНрд╡рд╛рдд рдЕрд▓реАрдХрдбреЗ рдкреНрд░рд╛рдкреНрдд рдЭрд╛рд▓реЗрд▓реНрдпрд╛ рдЧреЗрдо рдЕрдкрдбреЗрдЯрдЪрд╛ рдбреЗрдЯрд╛ рдереЗрдЯ рдкрд░рдд рдХрд░реВ рд╢рдХрддреЛ.

naive-state.js

let lastGameUpdate = null;

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

export function getCurrentState() {
  return lastGameUpdate;
}

рдЫрд╛рди рдЖрдгрд┐ рд╕реНрдкрд╖реНрдЯ! рдкрдг рдЬрд░ рддреЗ рдЗрддрдХреЗ рд╕реЛрдкреЗ рдЕрд╕рддреЗ. рдпрд╛ рдЕрдВрдорд▓рдмрдЬрд╛рд╡рдгреАрдЪреЗ рдПрдХ рдХрд╛рд░рдг рд╕рдорд╕реНрдпрд╛рдкреНрд░рдзрд╛рди рдЖрд╣реЗ: рд╣реЗ рдкреНрд░рд╕реНрддреБрддреАрдХрд░рдг рдлреНрд░реЗрдо рджрд░ рд╕рд░реНрд╡реНрд╣рд░ рдШрдбреНрдпрд╛рд│ рджрд░ рдорд░реНрдпрд╛рджрд┐рдд рдХрд░рддреЗ.

рдлреНрд░реЗрдо рджрд░: рдлреНрд░реЗрдореНрд╕рдЪреА рд╕рдВрдЦреНрдпрд╛ (рдореНрд╣рдгрдЬреЗ рдХреЙрд▓ render()) рдкреНрд░рддрд┐ рд╕реЗрдХрдВрдж, рдХрд┐рдВрд╡рд╛ FPS. рдЧреЗрдо рд╕рд╣рд╕рд╛ рдХрд┐рдорд╛рди 60 FPS рдорд┐рд│рд╡рдгреНрдпрд╛рдЪрд╛ рдкреНрд░рдпрддреНрди рдХрд░рддрд╛рдд.

рдЯрд┐рдХ рд░реЗрдЯ: рд╕рд░реНрд╡реНрд╣рд░ рдХреНрд▓рд╛рдпрдВрдЯрд▓рд╛ рдЧреЗрдо рдЕрдкрдбреЗрдЯ рдкрд╛рдард╡рддреЗ рддреА рд╡рд╛рд░рдВрд╡рд╛рд░рддрд╛. рд╣реЗ рдлреНрд░реЗрдо рджрд░рд╛рдкреЗрдХреНрд╖рд╛ рдЕрдиреЗрдХрджрд╛ рдХрдореА рдЕрд╕рддреЗ. рдЖрдордЪреНрдпрд╛ рдЧреЗрдордордзреНрдпреЗ, рд╕рд░реНрд╡реНрд╣рд░ рдкреНрд░рддрд┐ рд╕реЗрдХрдВрдж 30 рдЪрдХреНрд░рд╛рдВрдЪреНрдпрд╛ рд╡рд╛рд░рдВрд╡рд╛рд░рддреЗрдиреЗ рдЪрд╛рд▓рддреЛ.

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

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

7.2 рд╕реБрдзрд╛рд░рд┐рдд рдХреНрд▓рд╛рдпрдВрдЯ рд╕реНрдерд┐рддреА

рдЖрдореНрд╣реА рд╕рд╛рдзреНрдпрд╛ рдЕрдВрдорд▓рдмрдЬрд╛рд╡рдгреАрдд рдХрд╛рд╣реА рд╕реБрдзрд╛рд░рдгрд╛ рдХрд░реВ. рдкреНрд░рдердо, рдЖрдореНрд╣реА рд╡рд╛рдкрд░рддреЛ рдкреНрд░рд╕реНрддреБрддреАрдХрд░рдг рд╡рд┐рд▓рдВрдм 100 ms рд╕рд╛рдареА рдпрд╛рдЪрд╛ рдЕрд░реНрде рдХреНрд▓рд╛рдпрдВрдЯрдЪреА "рд╡рд░реНрддрдорд╛рди" рд╕реНрдерд┐рддреА рдиреЗрд╣рдореА рд╕рд░реНрд╡реНрд╣рд░рд╡рд░реАрд▓ рдЧреЗрдордЪреНрдпрд╛ рд╕реНрдерд┐рддреАрдкреЗрдХреНрд╖рд╛ 100ms рдорд╛рдЧреЗ рд░рд╛рд╣реАрд▓. рдЙрджрд╛рд╣рд░рдгрд╛рд░реНрде, рд╕рд░реНрд╡реНрд╣рд░рд╡рд░ рд╡реЗрд│ рдЕрд╕рд▓реНрдпрд╛рд╕ 150, рдирдВрддрд░ рдХреНрд▓рд╛рдпрдВрдЯ рддреНрдпрд╛ рд╡реЗрд│реА рд╕рд░реНрд╡реНрд╣рд░ рдЬреНрдпрд╛ рд╕реНрдерд┐рддреАрдд рд╣реЛрддрд╛ рддреЗ рд░реЗрдВрдбрд░ рдХрд░реЗрд▓ 50:

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

рдорд▓реНрдЯреАрдкреНрд▓реЗрдЕрд░ .io рд╡реЗрдм рдЧреЗрдо рддрдпрд╛рд░ рдХрд░рдгреЗ
рдпрд╛рд╕рд╛рдареА рдорд┐рд│рдгрд╛рд░рд╛ рдореЛрдмрджрд▓рд╛ рдХрд╛рдпрдорд╕реНрд╡рд░реВрдкреА рдЕрд╕реЗрд▓ рдЗрдирдкреБрдЯ рдЕрдВрддрд░ 100 ms рд╕рд╛рдареА рдЧреБрд│рдЧреБрд│реАрдд рдЧреЗрдордкреНрд▓реЗрд╕рд╛рдареА рд╣рд╛ рдПрдХ рдХрд┐рд░рдХреЛрд│ рддреНрдпрд╛рдЧ рдЖрд╣реЗ - рдмрд╣реБрддреЗрдХ рдЦреЗрд│рд╛рдбреВрдВрдирд╛ (рд╡рд┐рд╢реЗрд╖рдд: рдкреНрд░рд╛рд╕рдВрдЧрд┐рдХ рдЦреЗрд│рд╛рдбреВ) рд╣рд╛ рд╡рд┐рд▓рдВрдм рд▓рдХреНрд╖рд╛рддрд╣реА рдпреЗрдгрд╛рд░ рдирд╛рд╣реА. рдЕрдкреНрд░рддреНрдпрд╛рд╢рд┐рдд рд▓реЗрдЯрдиреНрд╕реАрд╕рд╣ рдЦреЗрд│рдгреНрдпрд╛рдкреЗрдХреНрд╖рд╛ рд▓реЛрдХрд╛рдВрд╕рд╛рдареА рд╕реНрдерд┐рд░ 100ms рд▓реЗрдЯрдиреНрд╕реАрд╢реА рдЬреБрд│рд╡реВрди рдШреЗрдгреЗ рдЦреВрдк рд╕реЛрдкреЗ рдЖрд╣реЗ.

рдЖрдореНрд╣реА рдирд╛рд╡рд╛рдЪреЗ рджреБрд╕рд░реЗ рддрдВрддреНрд░ рджреЗрдЦреАрд▓ рд╡рд╛рдкрд░реВ рд╢рдХрддреЛ рдХреНрд▓рд╛рдпрдВрдЯ-рд╕рд╛рдЗрдб рдЕрдВрджрд╛рдЬ, рдЬреЗ рд╕рдордЬрд▓реЗрд▓реА рд╡рд┐рд▓рдВрдмрддрд╛ рдХрдореА рдХрд░рдгреНрдпрд╛рдЪреЗ рдЪрд╛рдВрдЧрд▓реЗ рдХрд╛рд░реНрдп рдХрд░рддреЗ, рдкрд░рдВрддреБ рдпрд╛ рдкреЛрд╕реНрдЯрдордзреНрдпреЗ рд╕рдорд╛рд╡рд┐рд╖реНрдЯ рдХреЗрд▓реЗ рдЬрд╛рдгрд╛рд░ рдирд╛рд╣реА.

рдЖрдореНрд╣реА рд╡рд╛рдкрд░рдд рдЕрд╕рд▓реЗрд▓реА рдЖрдгрдЦреА рдПрдХ рд╕реБрдзрд╛рд░рдгрд╛ рдЖрд╣реЗ рд░реЗрдЦреАрдп рдкреНрд░рдХреНрд╖реЗрдкрдг. рд░реЗрдВрдбрд░рд┐рдВрдЧ рд▓реЕрдЧрдореБрд│реЗ, рдЖрдореНрд╣реА рдХреНрд▓рд╛рдпрдВрдЯрдордзреАрд▓ рд╕рдзреНрдпрд╛рдЪреНрдпрд╛ рд╡реЗрд│реЗрдкреЗрдХреНрд╖рд╛ рдХрдореАрдд рдХрдореА рдПрдХ рдЕрдкрдбреЗрдЯ рдкреБрдвреЗ рдЕрд╕рддреЛ. рдлреЛрди рдХреЗрд▓рд╛ рдЕрд╕рддрд╛ getCurrentState(), рдЖрдореНрд╣реА рдЕрдВрдорд▓рд╛рдд рдЖрдгреВ рд╢рдХрддреЛ рд░реЗрдЦреАрдп рдкреНрд░рдХреНрд╖реЗрдкрдг рдХреНрд▓рд╛рдпрдВрдЯрдордзреАрд▓ рд╡рд░реНрддрдорд╛рди рд╡реЗрд│реЗрдЪреНрдпрд╛ рдЖрдзреА рдЖрдгрд┐ рдирдВрддрд░ рдЧреЗрдо рдЕрдкрдбреЗрдЯреНрд╕ рджрд░рдореНрдпрд╛рди:

рдорд▓реНрдЯреАрдкреНрд▓реЗрдЕрд░ .io рд╡реЗрдм рдЧреЗрдо рддрдпрд╛рд░ рдХрд░рдгреЗ
рд╣реЗ рдлреНрд░реЗрдо рджрд░ рд╕рдорд╕реНрдпреЗрдЪреЗ рдирд┐рд░рд╛рдХрд░рдг рдХрд░рддреЗ: рдЖрдореНрд╣реА рдЖрддрд╛ рдЖрдореНрд╣рд╛рд▓рд╛ рдкрд╛рд╣рд┐рдЬреЗ рддреНрдпрд╛ рдлреНрд░реЗрдо рджрд░рд╛рдиреЗ рдЕрджреНрд╡рд┐рддреАрдп рдлреНрд░реЗрдореНрд╕ рд░реЗрдВрдбрд░ рдХрд░реВ рд╢рдХрддреЛ!

7.3 рд╡рд░реНрдзрд┐рдд рдХреНрд▓рд╛рдпрдВрдЯ рд╕реНрдерд┐рддреА рд▓рд╛рдЧреВ рдХрд░рдгреЗ

рдордзреНрдпреЗ рдЕрдВрдорд▓рдмрдЬрд╛рд╡рдгреА рдЙрджрд╛рд╣рд░рдг src/client/state.js рд░реЗрдВрдбрд░ рд▓реЕрдЧ рдЖрдгрд┐ рд░реЗрдЦреАрдп рдЗрдВрдЯрд░рдкреЛрд▓реЗрд╢рди рджреЛрдиреНрд╣реА рд╡рд╛рдкрд░рддреЗ, рдкрд░рдВрддреБ рдЬрд╛рд╕реНрдд рдХрд╛рд│ рдирд╛рд╣реА. рдХреЛрдбрдЪреЗ рджреЛрди рднрд╛рдЧ рдХрд░реВ. рдпреЗрдереЗ рдкрд╣рд┐рд▓реЗ рдЖрд╣реЗ:

state.js рднрд╛рдЧ рез

const RENDER_DELAY = 100;

const gameUpdates = [];
let gameStart = 0;
let firstServerTimestamp = 0;

export function initState() {
  gameStart = 0;
  firstServerTimestamp = 0;
}

export function processGameUpdate(update) {
  if (!firstServerTimestamp) {
    firstServerTimestamp = update.t;
    gameStart = Date.now();
  }
  gameUpdates.push(update);

  // Keep only one game update before the current server time
  const base = getBaseUpdate();
  if (base > 0) {
    gameUpdates.splice(0, base);
  }
}

function currentServerTime() {
  return firstServerTimestamp + (Date.now() - gameStart) - RENDER_DELAY;
}

// Returns the index of the base update, the first game update before
// current server time, or -1 if N/A.
function getBaseUpdate() {
  const serverTime = currentServerTime();
  for (let i = gameUpdates.length - 1; i >= 0; i--) {
    if (gameUpdates[i].t <= serverTime) {
      return i;
    }
  }
  return -1;
}

рдкрд╣рд┐рд▓реА рдкрд╛рдпрд░реА рдореНрд╣рдгрдЬреЗ рдХрд╛рдп рд╣реЗ рд╢реЛрдзрдгреЗ currentServerTime(). рдЖрдореНрд╣реА рдЖрдзреА рдкрд╛рд╣рд┐рд▓реНрдпрд╛рдкреНрд░рдорд╛рдгреЗ, рдкреНрд░рддреНрдпреЗрдХ рдЧреЗрдо рдЕрдкрдбреЗрдЯрдордзреНрдпреЗ рд╕рд░реНрд╡реНрд╣рд░ рдЯрд╛рдЗрдорд╕реНрдЯреЕрдореНрдк рд╕рдорд╛рд╡рд┐рд╖реНрдЯ рдЕрд╕рддреЛ. рдЖрдореНрд╣рд╛рд▓рд╛ рд╕рд░реНрд╡реНрд╣рд░рдЪреНрдпрд╛ рдорд╛рдЧреЗ рдЗрдореЗрдЬ 100ms рд░реЗрдВрдбрд░ рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рд░реЗрдВрдбрд░ рд▓реЗрдЯрдиреНрд╕реА рд╡рд╛рдкрд░рд╛рдпрдЪреА рдЖрд╣реЗ, рдкрд░рдВрддреБ рд╕рд░реНрд╡реНрд╣рд░рд╡рд░реАрд▓ рд╡рд░реНрддрдорд╛рди рд╡реЗрд│ рдЖрдореНрд╣рд╛рд▓рд╛ рдХрдзреАрдЪ рдХрд│рдгрд╛рд░ рдирд╛рд╣реА, рдХрд╛рд░рдг рдЖрдореНрд╣рд╛рд▓рд╛ рдХрд│реВ рд╢рдХрдд рдирд╛рд╣реА рдХреА рдХреЛрдгрддреЗрд╣реА рдЕрдкрдбреЗрдЯ рдЖрдордЪреНрдпрд╛рдкрд░реНрдпрдВрдд рдкреЛрд╣реЛрдЪрд╛рдпрд▓рд╛ рдХрд┐рддреА рд╡реЗрд│ рд▓рд╛рдЧрд▓рд╛. рдЗрдВрдЯрд░рдиреЗрдЯ рдЕрдкреНрд░рддреНрдпрд╛рд╢рд┐рдд рдЖрд╣реЗ рдЖрдгрд┐ рддреНрдпрд╛рдЪреА рдЧрддреА рдореЛрдареНрдпрд╛ рдкреНрд░рдорд╛рдгрд╛рдд рдмрджрд▓реВ рд╢рдХрддреЗ!

рдпрд╛ рд╕рдорд╕реНрдпреЗрдЪреЗ рдирд┐рд░рд╛рдХрд░рдг рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА, рдЖрдореНрд╣реА рд╡рд╛рдЬрд╡реА рдЕрдВрджрд╛рдЬреЗ рд╡рд╛рдкрд░реВ рд╢рдХрддреЛ: рдЖрдореНрд╣реА рдкреНрд░рдердо рдЕрджреНрдпрддрди рддреНрд╡рд░рд┐рдд рдЖрд▓реНрдпрд╛рдЪреА рдмрддрд╛рд╡рдгреА рдХрд░рд╛. рдЬрд░ рд╣реЗ рдЦрд░реЗ рдЕрд╕рддреЗ, рддрд░ рдЖрдореНрд╣рд╛рд▓рд╛ рдпрд╛ рд╡рд┐рд╢рд┐рд╖реНрдЯ рдХреНрд╖рдгреА рд╕рд░реНрд╡реНрд╣рд░рдЪреА рд╡реЗрд│ рдорд╛рд╣рд┐рдд рдЕрд╕рддреЗ! рдЖрдореНрд╣реА рд╕рд░реНрд╡реНрд╣рд░рдЪрд╛ рдЯрд╛рдЗрдорд╕реНрдЯреЕрдореНрдк рд╕рдВрдЧреНрд░рд╣рд┐рдд рдХрд░рддреЛ firstServerTimestamp рдЖрдгрд┐ рдЖрдордЪреНрдпрд╛ рдареЗрд╡рд╛ рд╕реНрдерд╛рдирд┐рдХ (рдХреНрд▓рд╛рдпрдВрдЯ) рдЯрд╛рдЗрдорд╕реНрдЯреЕрдореНрдк рдордзреНрдпреЗ рддреНрдпрд╛рдЪ рдХреНрд╖рдгреА gameStart.

рдЕрд░реЗ рдерд╛рдВрдм. рддреЛ рд╕рд░реНрд╡реНрд╣рд░ рд╡реЗрд│ = рдХреНрд▓рд╛рдпрдВрдЯ рд╡реЗрд│ рдЕрд╕реВ рдирдпреЗ? рдЖрдореНрд╣реА "рд╕рд░реНрд╡реНрд╣рд░ рдЯрд╛рдЗрдорд╕реНрдЯреЕрдореНрдк" рдЖрдгрд┐ "рдХреНрд▓рд╛рдпрдВрдЯ рдЯрд╛рдЗрдорд╕реНрдЯреЕрдореНрдк" рдордзреНрдпреЗ рдлрд░рдХ рдХрд╛ рдХрд░рддреЛ? рд╣рд╛ рдПрдХ рдореЛрдард╛ рдкреНрд░рд╢реНрди рдЖрд╣реЗ! рддреЗ рд╕рдорд╛рди рдЧреЛрд╖реНрдЯ рдирд╛рд╣реАрдд рдмрд╛рд╣реЗрд░ рд╡рд│рддреЗ. Date.now() рдХреНрд▓рд╛рдпрдВрдЯ рдЖрдгрд┐ рд╕рд░реНрд╡реНрд╣рд░рдордзреНрдпреЗ рд╡реЗрдЧрд╡реЗрдЧрд│реЗ рдЯрд╛рдЗрдорд╕реНрдЯреЕрдореНрдк рдкрд░рдд рдХрд░рддреАрд▓ рдЖрдгрд┐ рддреЗ рдпрд╛ рдорд╢реАрдирдЪреНрдпрд╛ рд╕реНрдерд╛рдирд┐рдХ рдШрдЯрдХрд╛рдВрд╡рд░ рдЕрд╡рд▓рдВрдмреВрди рдЖрд╣реЗ. рд╕рд░реНрд╡ рдорд╢реАрдирд╡рд░ рдЯрд╛рдЗрдорд╕реНрдЯреЕрдореНрдк рд╕рд╛рд░рдЦреЗрдЪ рдЕрд╕рддреАрд▓ рдЕрд╕реЗ рдХрдзреАрд╣реА рдЧреГрд╣реАрдд рдзрд░реВ рдирдХрд╛.

рдЖрддрд╛ рдЖрдореНрд╣рд╛рд▓рд╛ рд╕рдордЬрд▓реЗ рдХреА рдХрд╛рдп рдХрд░рддреЗ currentServerTime(): рддреЗ рдкрд░рдд рдпреЗрддреЗ рд╡рд░реНрддрдорд╛рди рд░реЗрдВрдбрд░ рд╡реЗрд│реЗрдЪрд╛ рд╕рд░реНрд╡реНрд╣рд░ рдЯрд╛рдЗрдорд╕реНрдЯреЕрдореНрдк. рджреБрд╕рд▒реНрдпрд╛ рд╢рдмреНрджрд╛рдВрдд, рд╣реА рд╕рд░реНрд╡реНрд╣рд░рдЪреА рд╡рд░реНрддрдорд╛рди рд╡реЗрд│ рдЖрд╣реЗ (firstServerTimestamp <+ (Date.now() - gameStart)) рдЙрдгреЗ рдкреНрд░рд╕реНрддреБрдд рд╡рд┐рд▓рдВрдм (RENDER_DELAY).

рдЖрддрд╛ рдЖрдкрдг рдЧреЗрдо рдЕрдкрдбреЗрдЯреНрд╕ рдХрд╕реЗ рд╣рд╛рддрд╛рд│рддреЛ рдпрд╛рд╡рд░ рдПрдХ рдирдЬрд░ рдЯрд╛рдХреВрдпрд╛. рдЕрдкрдбреЗрдЯ рд╕рд░реНрд╡реНрд╣рд░рдХрдбреВрди рдкреНрд░рд╛рдкреНрдд рдЭрд╛рд▓реНрдпрд╛рд╡рд░, рддреНрдпрд╛рд▓рд╛ рдХреЙрд▓ рдХреЗрд▓реЗ рдЬрд╛рддреЗ processGameUpdate()рдЖрдгрд┐ рдЖрдореНрд╣реА рдирд╡реАрди рдЕрдкрдбреЗрдЯ рдЕреЕрд░реЗрдордзреНрдпреЗ рд╕реЗрд╡реНрд╣ рдХрд░рддреЛ gameUpdates. рдирдВрддрд░, рдореЗрдорд░реА рд╡рд╛рдкрд░ рддрдкрд╛рд╕рдгреНрдпрд╛рд╕рд╛рдареА, рдЖрдореНрд╣реА рдЖрдзреАрдЪреЗ рд╕рд░реНрд╡ рдЬреБрдиреЗ рдЕрджреНрдпрддрди рдХрд╛рдвреВрди рдЯрд╛рдХрддреЛ рдмреЗрд╕ рдЕрдкрдбреЗрдЯрдХрд╛рд░рдг рдЖрдореНрд╣рд╛рд▓рд╛ рддреНрдпрд╛рдВрдЪреА рдЖрддрд╛ рдЧрд░рдЬ рдирд╛рд╣реА.

"рдореВрд▓рднреВрдд рдЕрджреНрдпрддрди" рдореНрд╣рдгрдЬреЗ рдХрд╛рдп? рдпрд╛ рд╕рд░реНрд╡реНрд╣рд░рдЪреНрдпрд╛ рд╡рд░реНрддрдорд╛рди рд╡реЗрд│реЗрдкрд╛рд╕реВрди рдорд╛рдЧреЗ рд╕рд░рдХреВрди рдЖрдореНрд╣рд╛рд▓рд╛ рдкрд╣рд┐рд▓реЗ рдЕрдкрдбреЗрдЯ рд╕рд╛рдкрдбрддреЗ. рд╣рд╛ рдЖрдХреГрддреАрдмрдВрдз рдЖрдард╡рддреЛ?

рдорд▓реНрдЯреАрдкреНрд▓реЗрдЕрд░ .io рд╡реЗрдм рдЧреЗрдо рддрдпрд╛рд░ рдХрд░рдгреЗ
"рдХреНрд▓рд╛рдпрдВрдЯ рд░реЗрдВрдбрд░ рдЯрд╛рдЗрдо" рдЪреНрдпрд╛ рдбрд╛рд╡реАрдХрдбреЗ рдереЗрдЯ рдЧреЗрдо рдЕрдкрдбреЗрдЯ рдмреЗрд╕ рдЕрдкрдбреЗрдЯ рдЖрд╣реЗ.

рдмреЗрд╕ рдЕрдкрдбреЗрдЯ рдХрд╢рд╛рд╕рд╛рдареА рд╡рд╛рдкрд░рд▓рд╛ рдЬрд╛рддреЛ? рдЖрдореНрд╣реА рдмреЗрд╕рд▓рд╛рдЗрдирд╡рд░ рдЕрдкрдбреЗрдЯ рдХрд╛ рд╕реЛрдбреВ рд╢рдХрддреЛ? рд╣реЗ рд╢реЛрдзрдгреНрдпрд╛рд╕рд╛рдареА, рдЪрд▓рд╛ рдЕрдЦреЗрд░реАрд╕ рдЕрдВрдорд▓рдмрдЬрд╛рд╡рдгреАрдЪрд╛ рд╡рд┐рдЪрд╛рд░ рдХрд░рд╛ getCurrentState():

state.js рднрд╛рдЧ рез

export function getCurrentState() {
  if (!firstServerTimestamp) {
    return {};
  }

  const base = getBaseUpdate();
  const serverTime = currentServerTime();

  // If base is the most recent update we have, use its state.
  // Else, interpolate between its state and the state of (base + 1).
  if (base < 0) {
    return gameUpdates[gameUpdates.length - 1];
  } else if (base === gameUpdates.length - 1) {
    return gameUpdates[base];
  } else {
    const baseUpdate = gameUpdates[base];
    const next = gameUpdates[base + 1];
    const r = (serverTime - baseUpdate.t) / (next.t - baseUpdate.t);
    return {
      me: interpolateObject(baseUpdate.me, next.me, r),
      others: interpolateObjectArray(baseUpdate.others, next.others, r),
      bullets: interpolateObjectArray(baseUpdate.bullets, next.bullets, r),
    };
  }
}

рдЖрдореНрд╣реА рддреАрди рдкреНрд░рдХрд░рдгреЗ рд╣рд╛рддрд╛рд│рддреЛ:

  1. base < 0 рдпрд╛рдЪрд╛ рдЕрд░реНрде рдЕрд╕рд╛ рдХреА рд╡рд░реНрддрдорд╛рди рд░реЗрдВрдбрд░ рд╡реЗрд│реЗрдкрд░реНрдпрдВрдд рдХреЛрдгрддреАрд╣реА рдЕрджреНрдпрддрдиреЗ рдирд╛рд╣реАрдд (рд╡рд░реАрд▓ рдЕрдВрдорд▓рдмрдЬрд╛рд╡рдгреА рдкрд╣рд╛ getBaseUpdate()). рд╣реЗ рд░реЗрдВрдбрд░рд┐рдВрдЧ рд▓реЕрдЧрдореБрд│реЗ рдЧреЗрдордЪреНрдпрд╛ рд╕реБрд░реБрд╡рд╛рддреАрд▓рд╛рдЪ рд╣реЛрдК рд╢рдХрддреЗ. рдпрд╛ рдкреНрд░рдХрд░рдгрд╛рдд, рдЖрдореНрд╣реА рдкреНрд░рд╛рдкреНрдд рдХреЗрд▓реЗрд▓реЗ рдирд╡реАрдирддрдо рдЕрджреНрдпрддрди рд╡рд╛рдкрд░рддреЛ.
  2. base рдЖрдордЪреНрдпрд╛рдХрдбреЗ рдЕрд╕рд▓реЗрд▓реЗ рдирд╡реАрдирддрдо рдЕрдкрдбреЗрдЯ рдЖрд╣реЗ. рд╣реЗ рдиреЗрдЯрд╡рд░реНрдХ рд╡рд┐рд▓рдВрдм рдХрд┐рдВрд╡рд╛ рдЦрд░рд╛рдм рдЗрдВрдЯрд░рдиреЗрдЯ рдХрдиреЗрдХреНрд╢рдирдореБрд│реЗ рдЕрд╕реВ рд╢рдХрддреЗ. рдпрд╛ рдкреНрд░рдХрд░рдгрд╛рдд, рдЖрдореНрд╣реА рдЖрдордЪреНрдпрд╛рдХрдбреЗ рдЕрд╕рд▓реЗрд▓реЗ рдирд╡реАрдирддрдо рдЕрджреНрдпрддрди рджреЗрдЦреАрд▓ рд╡рд╛рдкрд░рдд рдЖрд╣реЛрдд.
  3. рдЖрдордЪреНрдпрд╛рдХрдбреЗ рд╡рд░реНрддрдорд╛рди рд░реЗрдВрдбрд░ рд╡реЗрд│реЗрдкреВрд░реНрд╡реА рдЖрдгрд┐ рдирдВрддрд░ рджреЛрдиреНрд╣реА рдЕрджреНрдпрддрдиреЗ рдЖрд╣реЗрдд, рдореНрд╣рдгреВрди рдЖрдореНрд╣реА рдХрд░реВ рд╢рдХрддреЛ рдЗрдВрдЯрд░рдкреЛрд▓реЗрдЯ!

рдлрдХреНрдд рдмрд╛рдХреА рдЖрд╣реЗ state.js рд╕рд╛рдзреЗ (рдкрд░рдВрддреБ рдХрдВрдЯрд╛рд│рд╡рд╛рдгреЗ) рдЧрдгрд┐рдд рд░реЗрдЦреАрдп рдЗрдВрдЯрд░рдкреЛрд▓реЗрд╢рдирдЪреА рдЕрдВрдорд▓рдмрдЬрд╛рд╡рдгреА рдЖрд╣реЗ. рдЬрд░ рддреБрдореНрд╣рд╛рд▓рд╛ рддреЗ рд╕реНрд╡рддрдГ рдПрдХреНрд╕рдкреНрд▓реЛрд░ рдХрд░рд╛рдпрдЪреЗ рдЕрд╕реЗрд▓ рддрд░ рдЙрдШрдбрд╛ state.js рд╡рд░ рдЬрд┐рдереВрдм.

рднрд╛рдЧ 2. рдмреЕрдХрдПрдВрдб рд╕рд░реНрд╡реНрд╣рд░

рдпрд╛ рднрд╛рдЧрд╛рдд, рдЖрдореНрд╣реА Node.js рдмреЕрдХрдПрдВрдбрд╡рд░ рдПрдХ рдирдЬрд░ рдЯрд╛рдХреВ рдЬреЛ рдЖрдордЪреЗ рдирд┐рдпрдВрддреНрд░рдг рдХрд░рддреЛ io рдЧреЗрдордЪреЗ рдЙрджрд╛рд╣рд░рдг.

1. рд╕рд░реНрд╡реНрд╣рд░ рдПрдВрдЯреНрд░реА рдкреЙрдЗрдВрдЯ

рд╡реЗрдм рд╕рд░реНрд╡реНрд╣рд░ рд╡реНрдпрд╡рд╕реНрдерд╛рдкрд┐рдд рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА, рдЖрдореНрд╣реА Node.js рдирд╛рд╡рд╛рдЪреНрдпрд╛ рд▓реЛрдХрдкреНрд░рд┐рдп рд╡реЗрдм рдлреНрд░реЗрдорд╡рд░реНрдХрдЪрд╛ рд╡рд╛рдкрд░ рдХрд░реВ рд╡реНрдпрдХреНрдд. рд╣реЗ рдЖрдордЪреНрдпрд╛ рд╕рд░реНрд╡реНрд╣рд░ рдПрдВрдЯреНрд░реА рдкреЙрдЗрдВрдЯ рдлрд╛рдЗрд▓рджреНрд╡рд╛рд░реЗ рдХреЙрдиреНрдлрд┐рдЧрд░ рдХреЗрд▓реЗ рдЬрд╛рдИрд▓ src/server/server.js:

server.js рднрд╛рдЧ рез

const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackConfig = require('../../webpack.dev.js');

// Setup an Express server
const app = express();
app.use(express.static('public'));

if (process.env.NODE_ENV === 'development') {
  // Setup Webpack for development
  const compiler = webpack(webpackConfig);
  app.use(webpackDevMiddleware(compiler));
} else {
  // Static serve the dist/ folder in production
  app.use(express.static('dist'));
}

// Listen on port
const port = process.env.PORT || 3000;
const server = app.listen(port);
console.log(`Server listening on port ${port}`);

рд▓рдХреНрд╖рд╛рдд рдареЗрд╡рд╛ рдХреА рдкрд╣рд┐рд▓реНрдпрд╛ рднрд╛рдЧрд╛рдд рдЖрдореНрд╣реА рд╡реЗрдмрдкреЕрдХрд╡рд░ рдЪрд░реНрдЪрд╛ рдХреЗрд▓реА рд╣реЛрддреА? рдпреЗрдереЗрдЪ рдЖрдореНрд╣реА рдЖрдордЪреЗ рд╡реЗрдмрдкреЕрдХ рдХреЙрдиреНрдлрд┐рдЧрд░реЗрд╢рди рд╡рд╛рдкрд░реВ. рдЖрдореНрд╣реА рддреЗ рджреЛрди рдкреНрд░рдХрд╛рд░реЗ рд╡рд╛рдкрд░реВ:

  • рд╡рд╛рдкрд░рд╛ webpack-dev-midleware рдЖрдордЪреА рд╡рд┐рдХрд╛рд╕ рдкреЕрдХреЗрдЬреЗрд╕ рд╕реНрд╡рдпрдВрдЪрд▓рд┐рддрдкрдгреЗ рдкреБрдирд░реНрдмрд╛рдВрдзрдгреА рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА, рдХрд┐рдВрд╡рд╛
  • рд╕реНрдерд┐рд░рдкрдгреЗ рдлреЛрд▓реНрдбрд░ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд░рд╛ dist/, рдЬреНрдпрд╛рдордзреНрдпреЗ Webpack рдЙрддреНрдкрд╛рджрди рддрдпрд╛рд░ рдХреЗрд▓реНрдпрд╛рдирдВрддрд░ рдЖрдордЪреНрдпрд╛ рдлрд╛рдпрд▓реА рд▓рд┐рд╣реАрд▓.

рдЖрдгрдЦреА рдПрдХ рдорд╣рддреНрддреНрд╡рд╛рдЪрдВ рдХрд╛рдо server.js рд╕рд░реНрд╡реНрд╣рд░ рд╕реЗрдЯ рдХрд░рдгреЗ рдЖрд╣реЗ socket.ioрдЬреЗ рдлрдХреНрдд рдПрдХреНрд╕рдкреНрд░реЗрд╕ рд╕рд░реНрд╡реНрд╣рд░рд╢реА рдХрдиреЗрдХреНрдЯ рд╣реЛрддреЗ:

server.js рднрд╛рдЧ рез

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 рднрд╛рдЧ рез

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 рднрд╛рдЧ рез

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 рдЕрджреНрдпрддрдиреЗ / s рдЪреНрдпрд╛ рд╡рд╛рд░рдВрд╡рд╛рд░рддреЗрд╕рд╣):

game.js рднрд╛рдЧ рез

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 рднрд╛рдЧ рез

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:

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

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 рдЧреЗрдо рддрдпрд╛рд░ рдХрд░рд╛!

рд╕рд░реНрд╡ рдирдореБрдирд╛ рдХреЛрдб рдореБрдХреНрдд рд╕реНрд░реЛрдд рдЖрд╣реЗ рдЖрдгрд┐ рд╡рд░ рдкреЛрд╕реНрдЯ рдХреЗрд▓рд╛ рдЖрд╣реЗ рдЬрд┐рдереВрдм.

рд╕реНрддреНрд░реЛрдд: www.habr.com

рдПрдХ рдЯрд┐рдкреНрдкрдгреА рдЬреЛрдбрд╛