io рд╡рд┐рдзрд╛рдорд╛ рдорд▓реНрдЯрд┐рдкреНрд▓реЗрдпрд░ рд╡реЗрдм рдЧреЗрдо рд╕рд┐рд░реНрдЬрдирд╛ рдЧрд░реНрджреИ

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

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

рдпрд╕ рдкреЛрд╖реНрдЯрдорд╛ рд╣рд╛рдореА рдХрд╕рд░реА рдкрддреНрддрд╛ рд▓рдЧрд╛рдЙрдиреЗрдЫреМрдВ рд╕реНрдХреНрд░реНрдпрд╛рдЪрдмрд╛рдЯ io рдЦреЗрд▓ рд╕рд┐рд░реНрдЬрдирд╛ рдЧрд░реНрдиреБрд╣реЛрд╕реНред рдпреЛ рдЧрд░реНрдирдХреЛ рд▓рд╛рдЧрд┐, рдЬрд╛рднрд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯрдХреЛ рдорд╛рддреНрд░ рдЬреНрдЮрд╛рди рдкрд░реНрдпрд╛рдкреНрдд рд╣реБрдиреЗрдЫ: рддрдкрд╛рдИрдВрд▓реЗ рд╕рд┐рдиреНрдЯреНрдпрд╛рдХреНрд╕ рдЬрд╕реНрддрд╛ рдЪреАрдЬрд╣рд░реВ рдмреБрдЭреНрди рдЖрд╡рд╢реНрдпрдХ рдЫ ES6, рдХреАрд╡рд░реНрдб this ╨╕ рдкреНрд░рддрд┐рдЬреНрдЮрд╛рд╣рд░реВред рдпрджрд┐ рддрдкрд╛рдЗрдБ рдЬрд╛рднрд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯрд▓рд╛рдИ рдкреВрд░реНрдг рд░реВрдкрдорд╛ рдерд╛рд╣рд╛ рдЫреИрди рднрдиреЗ, рддрдкрд╛рдЗрдБ рдЕрдЭреИ рдкрдирд┐ рдзреЗрд░реИ рдкреЛрд╕реНрдЯ рдмреБрдЭреНрди рд╕рдХреНрдиреБрд╣реБрдиреНрдЫред

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

рдкреНрд░рд╢рд┐рдХреНрд╖рдг рд╕рд╣рдпреЛрдЧрдХреЛ рд▓рд╛рдЧрд┐ рд╣рд╛рдореА рд╕рдиреНрджрд░реНрдн рдЧрд░реНрдиреЗрдЫреМрдВ рдЙрджрд╛рд╣рд░рдг рдЦреЗрд▓ .ioред рдпрд╕рд▓рд╛рдИ рдЦреЗрд▓реНрди рдкреНрд░рдпрд╛рд╕ рдЧрд░реНрдиреБрд╣реЛрд╕реН!

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

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

рдо рд╕рд┐рдлрд╛рд░рд┐рд╢ рдЧрд░реНрдЫреБ рд╕реНрд░реЛрдд рдХреЛрдб рдбрд╛рдЙрдирд▓реЛрдб рдЧрд░реНрдиреБрд╣реЛрд╕реН рдЙрджрд╛рд╣рд░рдг рдЦреЗрд▓ рддрд╛рдХрд┐ рддрдкрд╛рдИрдВ рдорд▓рд╛рдИ рдкрдЫреНрдпрд╛рдЙрди рд╕рдХреНрдиреБрд╣реБрдиреНрдЫред

рдЙрджрд╛рд╣рд░рдг рдирд┐рдореНрди рдкреНрд░рдпреЛрдЧ рдЧрд░реНрджрдЫ:

  • рд╡реНрдпрдХреНрдд рдЦреЗрд▓рдХреЛ рд╡реЗрдм рд╕рд░реНрднрд░ рд╡реНрдпрд╡рд╕реНрдерд╛рдкрди рдЧрд░реНрдиреЗ Node.js рдХреЛ рд▓рд╛рдЧрд┐ рд╕рдмреИрднрдиреНрджрд╛ рд▓реЛрдХрдкреНрд░рд┐рдп рд╡реЗрдм рдлреНрд░реЗрдорд╡рд░реНрдХ рд╣реЛред
  • socket.io - рдмреНрд░рд╛рдЙрдЬрд░ рд░ рд╕рд░реНрднрд░ рдмреАрдЪ рдбрд╛рдЯрд╛ рдЖрджрд╛рди рдкреНрд░рджрд╛рдирдХреЛ рд▓рд╛рдЧрд┐ рд╡реЗрдмрд╕рдХреЗрдЯ рдкреБрд╕реНрддрдХрд╛рд▓рдпред
  • рд╡реЗрдмрдкреНрдпрд╛рдХ - рдореЛрдбреНрдпреБрд▓ рдкреНрд░рдмрдиреНрдзрдХред рддрдкрд╛рдЗрдБ рдХрд┐рди Webpack рдкреНрд░рдпреЛрдЧ рдЧрд░реНрдиреЗ рдмрд╛рд░реЗ рдкрдвреНрди рд╕рдХреНрдиреБрд╣реБрдиреНрдЫ рдпрд╣рд╛рдБ.

рдпреЛ рдкрд░рд┐рдпреЛрдЬрдирд╛ рдирд┐рд░реНрджреЗрд╢рд┐рдХрд╛ рд╕рдВрд░рдЪрдирд╛ рдЬрд╕реНрддреЛ рджреЗрдЦрд┐рдиреНрдЫ:

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

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

рд╕рдмреИ рдлреЛрд▓реНрдбрд░рдорд╛ рдЫ public/ рд╕рд░реНрднрд░ рджреНрд╡рд╛рд░рд╛ рд╕реНрдерд┐рд░ рд░реВрдкрдорд╛ рдкреНрд░рд╕рд╛рд░рд┐рдд рд╣реБрдиреЗрдЫред IN public/assets/ рд╣рд╛рдореНрд░реЛ рдкрд░рд┐рдпреЛрдЬрдирд╛ рджреНрд╡рд╛рд░рд╛ рдкреНрд░рдпреЛрдЧ рдЧрд░рд┐рдПрдХреЛ рдЫрд╡рд┐рд╣рд░реВ рд╕рдорд╛рд╡реЗрд╢ рдЧрд░реНрджрдЫред

src /

рд╕рдмреИ рд╕реНрд░реЛрдд рдХреЛрдб рдлреЛрд▓реНрдбрд░рдорд╛ рдЫ src/ред рд╢реАрд░реНрд╖рдХрд╣рд░реВ client/ ╨╕ server/ рдЖрдлреНрдиреЛ рд▓рд╛рдЧрд┐ рдмреЛрд▓реНрдиреБрд╣реЛрд╕реН рд░ shared/ рджреБрдмреИ рдЧреНрд░рд╛рд╣рдХ рд░ рд╕рд░реНрднрд░ рджреНрд╡рд╛рд░рд╛ рдЖрдпрд╛рдд рдЧрд░рд┐рдПрдХреЛ рд╕реНрдерд┐рд░ рдлрд╛рдЗрд▓ рд╕рдорд╛рд╡реЗрд╢ рдЧрд░реНрджрдЫред

2. рд╕рдореНрдореЗрд▓рдирд╣рд░реВ/рдкрд░рд┐рдпреЛрдЬрдирд╛ рдкреНрдпрд╛рд░рд╛рдорд┐рдЯрд░рд╣рд░реВ

рдорд╛рдерд┐ рдЙрд▓реНрд▓реЗрдЦ рдЧрд░рд┐рдП рдЕрдиреБрд╕рд╛рд░, рд╣рд╛рдореА рдкрд░рд┐рдпреЛрдЬрдирд╛ рдирд┐рд░реНрдорд╛рдг рдЧрд░реНрди рдореЛрдбреНрдпреБрд▓ рдкреНрд░рдмрдиреНрдзрдХ рдкреНрд░рдпреЛрдЧ рдЧрд░реНрдЫреМрдВ рд╡реЗрдмрдкреНрдпрд╛рдХред рд╣рд╛рдореНрд░реЛ рд╡реЗрдмрдкреНрдпрд╛рдХ рдХрдиреНрдлрд┐рдЧрд░реЗрд╕рдирдорд╛ рдПрдХ рдирдЬрд░ рд░рд╛рдЦреМрдВ:

webpack.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 рдЦреЗрд▓  рдЦреЗрд▓реНрдиреБ

рдпреЛ рдХреЛрдб рдЙрджрд╛рд╣рд░рдг рд╕реНрдкрд╖реНрдЯрддрд╛рдХреЛ рд▓рд╛рдЧрд┐ рдереЛрд░реИ рд╕рд░рд▓реАрдХреГрдд рдЧрд░рд┐рдПрдХреЛ рдЫ, рд░ рдо рдкреЛрд╖реНрдЯрдорд╛ рдЕрдиреНрдп рдзреЗрд░реИ рдЙрджрд╛рд╣рд░рдгрд╣рд░реВрд╕рдБрдЧ рддреНрдпрд╕реНрддреИ рдЧрд░реНрдиреЗрдЫреБред рддрдкрд╛рдИрдВ рд╕рдзреИрдВ рдорд╛ рдкреВрд░реНрдг рдХреЛрдб рд╣реЗрд░реНрди рд╕рдХреНрдиреБрд╣реБрдиреНрдЫ Github.

рд╣рд╛рдореА рд╕рдВрдЧ рдЫ:

  • 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 рдЖрдпрд╛рдд рдЧрд░реНрдиреБрд╣реЛрд╕реН (рддреНрдпрд╕реИрд▓реЗ Webpack рд▓рд╛рдИ рд╣рд╛рдореНрд░реЛ 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 рднрдиреНрджрд╛ рдмрдвреА рд╣реБрди рд╕рдХреНрджреИрди рдХрд┐рдирднрдиреЗ рд╣рд╛рдореА рд╕рд░реНрднрд░рдмрд╛рдЯ рдкреНрд░рддрд┐ рд╕реЗрдХреЗрдиреНрдб 30 рднрдиреНрджрд╛ рдмрдвреА рдЕрджреНрдпрд╛рд╡рдзрд┐рдХрд╣рд░реВ рдкреНрд░рд╛рдкреНрдд рдЧрд░реНрджреИрдиреМрдВред рдмреЛрд▓рд╛рдП рдкрдирд┐ render() рдкреНрд░рддрд┐ рд╕реЗрдХреЗрдиреНрдб 60 рдкрдЯрдХ, рддреНрдпрд╕рдкрдЫрд┐ рдпреА рдХрд▓рд╣рд░реВ рдордзреНрдпреЗ рдЖрдзрд╛рд▓реЗ рдПрдЙрдЯреИ рдХреБрд░рд╛рд▓рд╛рдИ рдкреБрди: рдЪрд┐рддреНрд░рд┐рдд рдЧрд░реНрдиреЗрдЫ, рдЕрдирд┐рд╡рд╛рд░реНрдп рд░реВрдкрдорд╛ рдХреЗрд╣рд┐ рдЧрд░реНрджреИрдиред рд╕рд░рд▓ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрдирдХреЛ рд╕рд╛рде рдЕрд░реНрдХреЛ рд╕рдорд╕реНрдпрд╛ рдпреЛ рд╣реЛ рдврд┐рд▓рд╛рдЗрдХреЛ рд╡рд┐рд╖рдп рд╣реЛред рдЖрджрд░реНрд╢ рдЗрдиреНрдЯрд░рдиреЗрдЯ рдЧрддрд┐рдорд╛, рдЧреНрд░рд╛рд╣рдХрд▓реЗ рдареНрдпрд╛рдХреНрдХреИ рд╣рд░реЗрдХ 33 ms (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. рд╣рд╛рдореАрд╕рдБрдЧ рд╣рд╛рд▓рдХреЛ рд░реЗрдиреНрдбрд░ рд╕рдордп рдЕрдШрд┐ рд░ рдкрдЫрд┐ рджреБрдмреИ рдЕрдкрдбреЗрдЯ рдЫ, рддреНрдпрд╕реИрд▓реЗ рд╣рд╛рдореА рд╕рдХреНрдЫреМрдВ interpolate!

рд╕рдмреИ рдмрд╛рдБрдХреА рдЫ state.js рд╕рд░рд▓ (рддрд░ рдмреЛрд░рд┐рдВрдЧ) рдЧрдгрд┐рддрдХреЛ рд░реЗрдЦреАрдп рдкреНрд░рдХреНрд╖реЗрдкрдгрдХреЛ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рд╣реЛред рдпрджрд┐ рддрдкрд╛рдИрдВ рдпрд╕рд▓рд╛рдИ рдЖрдлреИрдВ рдЕрдиреНрд╡реЗрд╖рдг рдЧрд░реНрди рдЪрд╛рд╣рдиреБрд╣реБрдиреНрдЫ рднрдиреЗ, рддреНрдпрд╕рдкрдЫрд┐ рдЦреЛрд▓реНрдиреБрд╣реЛрд╕реН state.js рдорд╛ Github.

рднрд╛рдЧ реиред рдмреНрдпрд╛рдХрдПрдиреНрдб рд╕рд░реНрднрд░

рдпрд╕ рднрд╛рдЧрдорд╛ рд╣рд╛рдореА 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-middleware рд╣рд╛рдореНрд░реЛ рд╡рд┐рдХрд╛рд╕ рдкреНрдпрд╛рдХреЗрдЬрд╣рд░реВ рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рд░реВрдкрдорд╛ рдкреБрдирд░реНрдирд┐рд░реНрдорд╛рдг рдЧрд░реНрди, рд╡рд╛
  • рд╕реНрдерд┐рд░ рд░реВрдкрдорд╛ рдлреЛрд▓реНрдбрд░ рд╕реНрдерд╛рдирд╛рдиреНрддрд░рдг рдЧрд░реНрдиреБрд╣реЛрд╕реН 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 рдПрдХ рд╡рд╕реНрддреБ рд╣реЛ рдЬрд╕рд▓реЗ рдЦреЗрд▓рд╛рдбреА ID рд▓рд╛рдИ рдЦреЗрд▓рд╛рдбреАрд╕рдБрдЧ рд╕рдореНрдмрдиреНрдзрд┐рдд рд╕рдХреЗрдЯрдорд╛ рдмрд╛рдБрдзреНрдЫред рдпрд╕рд▓реЗ рд╣рд╛рдореАрд▓рд╛рдИ рд╕рдордпрд╕рдБрдЧреИ рддрд┐рдиреАрд╣рд░реВрдХреЛ рдкреНрд▓реЗрдпрд░ рдЖрдИрдбреАрд╣рд░реВрджреНрд╡рд╛рд░рд╛ рд╕рдХреЗрдЯрд╣рд░реВ рдкрд╣реБрдБрдЪ рдЧрд░реНрди рдЕрдиреБрдорддрд┐ рджрд┐рдиреНрдЫред
  • players рдПрдЙрдЯрд╛ рд╡рд╕реНрддреБ рд╣реЛ рдЬрд╕рд▓реЗ рдкреНрд▓реЗрдпрд░ ID рд▓рд╛рдИ рдХреЛрдб>рдкреНрд▓реЗрдпрд░ рд╡рд╕реНрддреБрдорд╛ рдмрд╛рдБрдзреНрдЫ

bullets рд╡рд╕реНрддреБрд╣рд░реВрдХреЛ рдПрд░реЗ рд╣реЛ Bullet, рдПрдХ рд╡рд┐рд╢реЗрд╖ рдЖрджреЗрд╢ рдЫреИрдиред
lastUpdateTime - рдпреЛ рдЕрдиреНрддрд┐рдо рдЦреЗрд▓ рдЕрдкрдбреЗрдЯрдХреЛ рдЯрд╛рдЗрдорд╕реНрдЯреНрдпрд╛рдореНрдк рд╣реЛред рд╣рд╛рдореА рдпрд╕рд▓рд╛рдИ рдЪрд╛рдБрдбреИ рдХрд╕рд░реА рдкреНрд░рдпреЛрдЧ рдЧрд░рд┐рдиреНрдЫ рд╣реЗрд░реНрдиреЗрдЫреМрдВред
shouldSendUpdate рдПрдХ рд╕рд╣рд╛рдпрдХ рдЪрд░ рд╣реЛред рд╣рд╛рдореА рдЪрд╛рдБрдбреИ рдпрд╕рдХреЛ рдкреНрд░рдпреЛрдЧ рдкрдирд┐ рджреЗрдЦреНрдиреЗрдЫреМрдВред
рд╡рд┐рдзрд┐рд╣рд░реВ addPlayer(), removePlayer() ╨╕ handleInput() рд╡реНрдпрд╛рдЦреНрдпрд╛ рдЧрд░реНрди рдЖрд╡рд╢реНрдпрдХ рдЫреИрди, рддрд┐рдиреАрд╣рд░реВ рдкреНрд░рдпреЛрдЧ рдЧрд░рд┐рдиреНрдЫ server.jsред рдпрджрд┐ рддрдкрд╛рдИрдВрд▓рд╛рдИ рд░рд┐рдлреНрд░реЗрд╕рд░ рдЪрд╛рд╣рд┐рдиреНрдЫ рднрдиреЗ, рдЕрд▓рд┐ рдорд╛рдерд┐ рдЬрд╛рдиреБрд╣реЛрд╕реНред

рдЕрдиреНрддрд┐рдо рд▓рд╛рдЗрди constructor() рд╕реБрд░реБ рд╣реБрдиреНрдЫ рдЕрдкрдбреЗрдЯ рдЪрдХреНрд░ рдЦреЗрд▓рд╣рд░реВ (ремреж рдЕрдкрдбреЗрдЯ/рд╕реЗрдХреЗрдиреНрдбрдХреЛ рдлреНрд░рд┐рдХреНрд╡реЗрдиреНрд╕реАрдХреЛ рд╕рд╛рде):

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()), рд░ рддреНрдпрд╕рдкрдЫрд┐ array рдмрд╛рдЯ рдкреНрд░реЛрдЬреЗрдХреНрдЯрд╛рдЗрд▓ рд╣рдЯрд╛рдЙрдиреБрд╣реЛрд╕реН 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:

bullet.js

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

class Bullet extends ObjectClass {
  constructor(parentID, x, y, dir) {
    super(shortid(), x, y, dir, Constants.BULLET_SPEED);
    this.parentID = parentID;
  }

  // Returns true if the bullet should be destroyed
  update(dt) {
    super.update(dt);
    return this.x < 0 || this.x > Constants.MAP_SIZE || this.y < 0 || this.y > Constants.MAP_SIZE;
  }
}

рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди Bullet рдзреЗрд░реИ рдЫреЛрдЯреЛ! рдорд╛ рдердкреЗрдХрд╛ рдЫреМрдВ Object рдХреЗрд╡рд▓ рдирд┐рдореНрди рд╡рд┐рд╕реНрддрд╛рд░рд╣рд░реВ:

  • рдкреНрдпрд╛рдХреЗрдЬ рдкреНрд░рдпреЛрдЧ рдЧрд░реНрджреИ рдЫреЛрдЯреЛ рдЕрдирд┐рдпрдорд┐рдд рдкреБрд╕реНрддрд╛рдХреЛ рд▓рд╛рдЧрд┐ id рдкреНрд░рдХреНрд╖реЗрдкрдгред
  • рдлрд┐рд▓реНрдб рдердкреНрджреИ parentID, рддрд╛рдХрд┐ рддрдкрд╛рдИрдВ рдпреЛ рдкреНрд░реЛрдЬреЗрдХреНрдЯрд╛рдЗрд▓ рд╕рд┐рд░реНрдЬрдирд╛ рдЧрд░реНрдиреЗ рдЦреЗрд▓рд╛рдбреА рдЯреНрд░реНрдпрд╛рдХ рдЧрд░реНрди рд╕рдХреНрдиреБрд╣реБрдиреНрдЫред
  • рдлрд┐рд░реНрддрд╛ рдорд╛рди рдердкреНрджреИ update(), рдЬреБрди рдмрд░рд╛рдмрд░ рдЫ true, рдпрджрд┐ рдкреНрд░рдХреНрд╖реЗрдкрдг рдХреНрд╖реЗрддреНрд░ рдмрд╛рд╣рд┐рд░ рдЫ рднрдиреЗ (рд╣рд╛рдореАрд▓реЗ рдЕрдиреНрддрд┐рдо рдЦрдгреНрдбрдорд╛ рдпрд╕ рдмрд╛рд░реЗ рдХреБрд░рд╛ рдЧрд░реНрдпреМрдВ?)ред

рддрд┐рд░ рд▓рд╛рдЧреМрдВ Player:

player.js

const ObjectClass = require('./object');
const Bullet = require('./bullet');
const Constants = require('../shared/constants');

class Player extends ObjectClass {
  constructor(id, username, x, y) {
    super(id, x, y, Math.random() * 2 * Math.PI, Constants.PLAYER_SPEED);
    this.username = username;
    this.hp = Constants.PLAYER_MAX_HP;
    this.fireCooldown = 0;
    this.score = 0;
  }

  // Returns a newly created bullet, or null.
  update(dt) {
    super.update(dt);

    // Update score
    this.score += dt * Constants.SCORE_PER_SECOND;

    // Make sure the player stays in bounds
    this.x = Math.max(0, Math.min(Constants.MAP_SIZE, this.x));
    this.y = Math.max(0, Math.min(Constants.MAP_SIZE, this.y));

    // Fire a bullet, if needed
    this.fireCooldown -= dt;
    if (this.fireCooldown <= 0) {
      this.fireCooldown += Constants.PLAYER_FIRE_COOLDOWN;
      return new Bullet(this.id, this.x, this.y, this.direction);
    }
    return null;
  }

  takeBulletDamage() {
    this.hp -= Constants.BULLET_DAMAGE;
  }

  onDealtDamage() {
    this.score += Constants.SCORE_BULLET_HIT;
  }

  serializeForUpdate() {
    return {
      ...(super.serializeForUpdate()),
      direction: this.direction,
      hp: this.hp,
    };
  }
}

рдЦреЗрд▓рд╛рдбреАрд╣рд░реВ рдкреНрд░реЛрдЬреЗрдХреНрдЯрд╛рдЗрд▓рд╣рд░реВ рднрдиреНрджрд╛ рдмрдвреА рдЬрдЯрд┐рд▓ рд╣реБрдиреНрдЫрдиреН, рддреНрдпрд╕реИрд▓реЗ рдпреЛ рд╡рд░реНрдЧрд▓реЗ рдХреЗрд╣реА рдердк рдХреНрд╖реЗрддреНрд░рд╣рд░реВ рднрдгреНрдбрд╛рд░ рдЧрд░реНрдиреБрдкрд░реНрдЫред рдЙрдирдХреЛ рд╡рд┐рдзрд┐ update() рдердк рдХрд╛рдо рдЧрд░реНрджрдЫ, рд╡рд┐рд╢реЗрд╖ рдЧрд░реА рдирдпрд╛рдБ рд╕рд┐рд░реНрдЬрдирд╛ рдЧрд░рд┐рдПрдХреЛ рдкреНрд░реЛрдЬреЗрдХреНрдЯрд╛рдЗрд▓ рдлрд┐рд░реНрддрд╛ рдЧрд░реНрджреИ рдпрджрд┐ рддреНрдпрд╣рд╛рдБ рдХреБрдиреИ рдкрдирд┐ рдмрд╛рдБрдХреА рдЫреИрди fireCooldown (рд╣рд╛рдореАрд▓реЗ рдЕрдШрд┐рд▓реНрд▓реЛ рдЦрдгреНрдбрдорд╛ рдпрд╕ рдмрд╛рд░реЗ рдХреБрд░рд╛ рдЧрд░реЗрдХрд╛ рдерд┐рдпреМрдВ?) рдпрд╕рд▓реЗ рдкрдирд┐ рд╡рд┐рдзрд┐ рд╡рд┐рд╕реНрддрд╛рд░ рдЧрд░реНрджрдЫ serializeForUpdate(), рдХрд┐рдирдХрд┐ рд╣рд╛рдореАрд▓реЗ рдЦреЗрд▓ рдЕрдкрдбреЗрдЯрдорд╛ рдЦреЗрд▓рд╛рдбреАрдХрд╛ рд▓рд╛рдЧрд┐ рдЕрддрд┐рд░рд┐рдХреНрдд рдХреНрд╖реЗрддреНрд░рд╣рд░реВ рд╕рдорд╛рд╡реЗрд╢ рдЧрд░реНрди рдЖрд╡рд╢реНрдпрдХ рдЫред

рдЖрдзрд╛рд░ рд╡рд░реНрдЧрдХреЛ рдЙрдкрд▓рдмреНрдзрддрд╛ Object - рдХреЛрдб рдкреБрдирд░рд╛рд╡реГрддреНрддрд┐рдмрд╛рдЯ рдмрдЪреНрдирдХреЛ рд▓рд╛рдЧрд┐ рдорд╣рддреНрддреНрд╡рдкреВрд░реНрдг рдХрджрдоред рдЙрджрд╛рд╣рд░рдгрдХрд╛ рд▓рд╛рдЧрд┐, рдХрдХреНрд╖рд╛ рдмрд┐рдирд╛ Object рдкреНрд░рддреНрдпреЗрдХ рдЦреЗрд▓ рд╡рд╕реНрддреБ рд╕рдорд╛рди рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рд╣реБрдиреБрдкрд░реНрдЫ distanceTo(), рд░ рдзреЗрд░реИ рдлрд╛рдЗрд▓рд╣рд░реВрдорд╛ рдпреА рд╕рдмреИ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрдирд╣рд░реВ рдкреНрд░рддрд┐рд▓рд┐рдкрд┐ рдЯрд╛рдБрд╕реНрдиреБ рдПрдХ рджреБрдГрд╕реНрд╡рдкреНрди рд╣реБрдиреЗрдЫред рдпреЛ рд╡рд┐рд╢реЗрд╖ рдЧрд░реА рдареВрд▓рд╛ рдкрд░рд┐рдпреЛрдЬрдирд╛рд╣рд░реВрдХреЛ рд▓рд╛рдЧрд┐ рдорд╣рддреНрддреНрд╡рдкреВрд░реНрдг рд╣реБрдиреНрдЫ, рдЬрдм рд╡рд┐рд╕реНрддрд╛рд░ рдХреЛ рд╕рдВрдЦреНрдпрд╛ Object рдХрдХреНрд╖рд╛рд╣рд░реВ рдмрдвреНрджреИ рдЫрдиреНред

4. рдЯрдХреНрдХрд░ рдкрддреНрддрд╛ рд▓рдЧрд╛рдЙрдиреЗ

рдкреНрд░рдХреНрд╖реЗрдкрдгрд╣рд░реВрд▓реЗ рдЦреЗрд▓рд╛рдбреАрд╣рд░реВрд▓рд╛рдИ рд╣рд┐рд░реНрдХрд╛рдЙрдБрджрд╛ рд╣рд╛рдореАрд▓рд╛рдИ рдкрд╣рд┐рдЪрд╛рди рдЧрд░реНрди рдорд╛рддреНрд░ рдмрд╛рдБрдХреА рдЫ! рд╡рд┐рдзрд┐рдмрд╛рдЯ рдпреЛ рдХреЛрдб рд╕реНрдирд┐рдкреЗрдЯ рд╕рдореНрдЭрдиреБрд╣реЛрд╕реН update() рдХрдХреНрд╖рд╛рдорд╛ Game:

game.js

const applyCollisions = require('./collisions');

class Game {
  // ...

  update() {
    // ...

    // Apply collisions, give players score for hitting bullets
    const destroyedBullets = applyCollisions(
      Object.values(this.players),
      this.bullets,
    );
    destroyedBullets.forEach(b => {
      if (this.players[b.parentID]) {
        this.players[b.parentID].onDealtDamage();
      }
    });
    this.bullets = this.bullets.filter(
      bullet => !destroyedBullets.includes(bullet),
    );

    // ...
  }
}

рд╣рд╛рдореАрд▓реЗ рдкрджреНрдзрддрд┐ рд▓рд╛рдЧреВ рдЧрд░реНрдиреБрдкрд░реНрдЫ applyCollisions(), рдЬрд╕рд▓реЗ рдЦреЗрд▓рд╛рдбреАрд╣рд░реВрд▓рд╛рдИ рд╣рд┐рдЯ рдЧрд░реНрдиреЗ рд╕рдмреИ рдкреНрд░реЛрдЬреЗрдХреНрдЯрд╛рдЗрд▓рд╣рд░реВ рдлрд░реНрдХрд╛рдЙрдБрдЫред рд╕реМрднрд╛рдЧреНрдп рджреЗрдЦрд┐, рдпреЛ рдЧрд░реНрди рдЧрд╛рд╣реНрд░реЛ рдЫреИрди рдХрд┐рдирднрдиреЗ

  • рд╕рдмреИ рдЯрдХреНрдХрд░ рдЧрд░реНрдиреЗ рд╡рд╕реНрддреБрд╣рд░реВ рд╕рд░реНрдХрд▓рд╣рд░реВ рд╣реБрдиреН, рд░ рдпреЛ рдЯрдХреНрдХрд░ рдкрддреНрддрд╛ рд▓рдЧрд╛рдЙрдиреЗ рд╕рдмреИрднрдиреНрджрд╛ рд╕рд░рд▓ рдЖрдХрд╛рд░ рд╣реЛред
  • рд╣рд╛рдореАрд╕рдБрдЧ рдкрд╣рд┐рд▓реЗ рдиреИ рд╡рд┐рдзрд┐ рдЫ distanceTo(), рдЬреБрди рд╣рд╛рдореАрд▓реЗ рдЕрдШрд┐рд▓реНрд▓реЛ рдЦрдгреНрдбрдорд╛ рдХрдХреНрд╖рд╛рдорд╛ рд▓рд╛рдЧреВ рдЧрд░реНрдпреМрдВ Object.

рдЯрдХреНрдХрд░ рдкрддреНрддрд╛ рд▓рдЧрд╛рдЙрдиреЗ рд╣рд╛рдореНрд░реЛ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдпрд╕реНрддреЛ рджреЗрдЦрд┐рдиреНрдЫ:

collisions.js

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

// Returns an array of bullets to be destroyed.
function applyCollisions(players, bullets) {
  const destroyedBullets = [];
  for (let i = 0; i < bullets.length; i++) {
    // Look for a player (who didn't create the bullet) to collide each bullet with.
    // As soon as we find one, break out of the loop to prevent double counting a bullet.
    for (let j = 0; j < players.length; j++) {
      const bullet = bullets[i];
      const player = players[j];
      if (
        bullet.parentID !== player.id &&
        player.distanceTo(bullet) <= Constants.PLAYER_RADIUS + Constants.BULLET_RADIUS
      ) {
        destroyedBullets.push(bullet);
        player.takeBulletDamage();
        break;
      }
    }
  }
  return destroyedBullets;
}

рдпреЛ рд╕рд╛рдзрд╛рд░рдг рдЯрдХреНрдХрд░ рдкрддреНрддрд╛ рд▓рдЧрд╛рдЙрдиреЗ рддрдереНрдпрдорд╛ рдЖрдзрд╛рд░рд┐рдд рдЫ рдпрджрд┐ рддрд┐рдиреАрд╣рд░реВрдХреЛ рдХреЗрдиреНрджреНрд░рд╣рд░реВ рдмреАрдЪрдХреЛ рджреВрд░реА рддрд┐рдиреАрд╣рд░реВрдХреЛ рддреНрд░рд┐рдЬреНрдпрд╛рдХреЛ рдпреЛрдЧрдлрд▓рднрдиреНрджрд╛ рдХрдо рдЫ рднрдиреЗ рджреБрдИрд╡рдЯрд╛ рд╡реГрддреНрддрд╣рд░реВ рдареЛрдХреНрдХрд┐рдиреНрдЫрдиреНред рдпрд╣рд╛рдБ рдПрдЙрдЯрд╛ рдХреЗрд╕ рдЫ рдЬрд╣рд╛рдБ рджреБрдИ рд╡реГрддреНрддрд╣рд░реВрдХрд╛ рдХреЗрдиреНрджреНрд░рд╣рд░реВ рдмреАрдЪрдХреЛ рджреВрд░реА рддрд┐рдиреАрд╣рд░реВрдХреЛ рддреНрд░рд┐рдЬреНрдпрд╛рдХреЛ рдпреЛрдЧрдлрд▓ рдмрд░рд╛рдмрд░ рдЫ:

io рд╡рд┐рдзрд╛рдорд╛ рдорд▓реНрдЯрд┐рдкреНрд▓реЗрдпрд░ рд╡реЗрдм рдЧреЗрдо рд╕рд┐рд░реНрдЬрдирд╛ рдЧрд░реНрджреИ
рдпрд╣рд╛рдБ рддрдкрд╛рдИрдВрд▓реЗ рдХреЗрд╣реА рдердк рдкрдХреНрд╖рд╣рд░реВрдорд╛ рдзреНрдпрд╛рди рджрд┐рди рдЖрд╡рд╢реНрдпрдХ рдЫ:

  • рдкреНрд░реЛрдЬреЗрдХреНрдЯрд╛рдЗрд▓рд▓реЗ рдпрд╕рд▓рд╛рдИ рд╕рд┐рд░реНрдЬрдирд╛ рдЧрд░реНрдиреЗ рдЦреЗрд▓рд╛рдбреАрд▓рд╛рдИ рд╣рд┐рдЯ рдЧрд░реНрдиреБ рд╣реБрдБрджреИрдиред рдпреЛ рддреБрд▓рдирд╛ рдЧрд░реЗрд░ рд╣рд╛рд╕рд┐рд▓ рдЧрд░реНрди рд╕рдХрд┐рдиреНрдЫ bullet.parentID ╤Б player.id.
  • рдПрдХреИ рд╕рдордпрдорд╛ рдзреЗрд░реИ рдЦреЗрд▓рд╛рдбреАрд╣рд░реВрд▓рд╛рдИ рд╣рд┐рдЯ рдЧрд░реНрдиреЗ рдЪрд░рдо рдЕрд╡рд╕реНрдерд╛рдорд╛ рдкреНрд░реЛрдЬреЗрдХреНрдЯрд╛рдЗрд▓рд▓реЗ рдПрдХ рдкрдЯрдХ рдорд╛рддреНрд░ рд╣рд┐рдЯ рдЧрд░реНрдиреБрдкрд░реНрдЫред рд╣рд╛рдореА рдЕрдкрд░реЗрдЯрд░ рдкреНрд░рдпреЛрдЧ рдЧрд░реЗрд░ рдпреЛ рд╕рдорд╕реНрдпрд╛ рд╕рдорд╛рдзрд╛рди рдЧрд░реНрдиреЗрдЫреМрдВ break: рдПрдХ рдкрдЯрдХ рдкреНрд░реЛрдЬреЗрдХреНрдЯрд╛рдЗрд▓рд╕рдБрдЧ рдЯрдХреНрдХрд░ рднрдПрдХреЛ рдЦреЗрд▓рд╛рдбреА рдлреЗрд▓рд╛ рдкрд░реЗрдкрдЫрд┐, рд╣рд╛рдореА рдЦреЛрдЬреА рдЧрд░реНрди рд░реЛрдХреНрдЫреМрдВ рд░ рдЕрд░реНрдХреЛ рдкреНрд░рдХреНрд╖реЗрдкрдгрдорд╛ рдЬрд╛рдиреНрдЫреМрдВред

╨Ъ╨╛╨╜╨╡╤Ж

рдпрддрд┐ рдиреИ! рд╣рд╛рдореАрд▓реЗ .io рд╡реЗрдм рдЧреЗрдо рд╕рд┐рд░реНрдЬрдирд╛ рдЧрд░реНрдирдХрд╛ рд▓рд╛рдЧрд┐ рддрдкрд╛рдИрдВрд▓реЗ рдЬрд╛рдиреНрдиреБрдкрд░реНрдиреЗ рд╕рдмреИ рдХреБрд░рд╛рд╣рд░реВ рд╕рдорд╛рд╡реЗрд╢ рдЧрд░реЗрдХрд╛ рдЫреМрдВред рдЕрдм рдХреЗ? рдЖрдлреНрдиреЛ рдЖрдлреНрдиреИ .io рдЦреЗрд▓ рдмрдирд╛рдЙрдиреБрд╣реЛрд╕реН!

рд╕рдмреИ рдЙрджрд╛рд╣рд░рдг рдХреЛрдб рдЦреБрд▓рд╛ рд╕реНрд░реЛрдд рд╣реЛ рд░ рдкреЛрд╕реНрдЯ рдЧрд░рд┐рдПрдХреЛ рдЫ Github.

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

рдПрдХ рдЯрд┐рдкреНрдкрдгреА рдердкреНрди