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

рдЫрд╡рд┐

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

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

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

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


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


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

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


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

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

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

рдпрд╣рд╛рдВ рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдбрд╛рдпрд░реЗрдХреНрдЯрд░реА рд╕реНрдЯреНрд░рдХреНрдЪрд░ рдХреИрд╕рд╛ рджрд┐рдЦрддрд╛ рд╣реИ:

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

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


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

src /


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

рдЖрдкрдиреЗ рдЕрдЬреАрдм рдкреИрдХреЗрдЬ рдлрд╝рд╛рдЗрд▓ рдирд╛рдореЛрдВ рдкрд░ рдзреНрдпрд╛рди рджрд┐рдпрд╛ рд╣реЛ рд╕рдХрддрд╛ рд╣реИ '[name].[contenthash].ext' ред рд╡реЗ рд╡реЗрдмрдкреИрдХ рдлрд╝рд╛рдЗрд▓реЛрдВ рдХреЗ рдирд╛рдореЛрдВ рдХрд╛ рдкреНрд░рддрд┐рд╕реНрдерд╛рдкрди рдХрд░рддреЗ рд╣реИрдВ : [name] рдХреЛ рдЗрдирдкреБрдЯ рдмрд┐рдВрджреБ рдХреЗ рдирд╛рдо рд╕реЗ рдмрджрд▓рд╛ рдЬрд╛рдПрдЧрд╛ (рд╣рдорд╛рд░реЗ рдорд╛рдорд▓реЗ рдореЗрдВ, рдпрд╣ рдПрдХ game ), рдФрд░ [contenthash] рдХреЛ рдлрд╝рд╛рдЗрд▓ рд╕рд╛рдордЧреНрд░реА рдХреЗ рд╣реИрд╢ рджреНрд╡рд╛рд░рд╛ рдкреНрд░рддрд┐рд╕реНрдерд╛рдкрд┐рдд рдХрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛ред рд╣рдо рд╣реИрд╢рд┐рдВрдЧ рдХреЗ рд▓рд┐рдП рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреЛ рдЕрдиреБрдХреВрд▓рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдРрд╕рд╛ рдХрд░рддреЗ рд╣реИрдВ - рдЖрдк рдмреНрд░рд╛рдЙрдЬрд╝рд░реЛрдВ рдХреЛ рд╣рдорд╛рд░реЗ рдЬреЗрдПрд╕ рдкреИрдХреЗрдЬреЛрдВ рдХреЛ рдЕрдирд┐рд╢реНрдЪрд┐рдд рдХрд╛рд▓ рдХреЗ рд▓рд┐рдП рдХреИрд╢ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХрд╣ рд╕рдХрддреЗ рд╣реИрдВ, рдХреНрдпреЛрдВрдХрд┐ рдпрджрд┐ рдкреИрдХреЗрдЬ рдмрджрд▓рддрд╛ рд╣реИ, рддреЛ рдЗрд╕рдХрд╛ рдлрд╝рд╛рдЗрд▓ рдирд╛рдо рдмрджрд▓ рдЬрд╛рддрд╛ рд╣реИ ( 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.dev.js рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ 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 ред рдХреЛрдб рдмрджрд▓рддреЗ рд╣реА рдЬреЗрдПрд╕ рдФрд░ рд╕реАрдПрд╕рдПрд╕ рдкреИрдХреЗрдЬреЛрдВ рдХрд╛ рд╡рд┐рдХрд╛рд╕ рд╕рд░реНрд╡рд░ рдЕрдкрдиреЗ рдЖрдк рдлрд┐рд░ рд╕реЗ рддреИрдпрд╛рд░ рд╣реЛ рдЬрд╛рдПрдЧрд╛ - рд╕рднреА рдкрд░рд┐рд╡рд░реНрддрдиреЛрдВ рдХреЛ рджреЗрдЦрдиреЗ рдХреЗ рд▓рд┐рдП рдкреГрд╖реНрда рдХреЛ рддрд╛рдЬрд╝рд╛ рдХрд░реЗрдВ!

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


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

index.html

  <! DOCTYPE html>
 <Html>
 <Head>
   <рд╢реАрд░реНрд╖рдХ> рдПрдХ рдЙрджрд╛рд╣рд░рдг .io рдЦреЗрд▓ </ рд╢реАрд░реНрд╖рдХ>
   <рд▓рд┐рдВрдХ рдкреНрд░рдХрд╛рд░ = "рдЯреЗрдХреНрд╕реНрдЯ / рд╕реАрдПрд╕рдПрд╕" rel = "рд╕реНрдЯрд╛рдЗрд▓рд╢реАрдЯ" href = "/ game.bundle.css">
 </ Head>
 <Body>
   <рдХреИрдирд╡рд╛рд╕ рдЖрдИрдбреА = "рдЧреЗрдо-рдХреИрдирд╡рд╛рд╕"> </ рдХреИрдирд╡рд╛рд╕>
   <script async src = "/ game.bundle.js"> </ script>
   <div id = "play-menu" рд╡рд░реНрдЧ = "рдЫрд┐рдкрд╛ рд╣реБрдЖ">
     <рдЗрдирдкреБрдЯ рдкреНрд░рдХрд╛рд░ = "рдЯреЗрдХреНрд╕реНрдЯ" рдЖрдИрдбреА = "рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдирд╛рдо-рдЗрдирдкреБрдЯ" рдкреНрд▓реЗрд╕рд╣реЛрд▓реНрдбрд░ = "рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдирд╛рдо" />
     <рдмрдЯрди рдЖрдИрдбреА = "рдкреНрд▓реЗ-рдмрдЯрди"> рдкреНрд▓реЗ </ рдмрдЯрди>
   </ Div>
 </ Body>
 </ Html> 

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

рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рд╣реИ:

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

рд╣реЛрдо рдкреЗрдЬ рдХреЛ рд▓реЛрдб рдХрд░рдиреЗ рдХреЗ рдмрд╛рдж, рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдХреЛрдб рдмреНрд░рд╛рдЙрдЬрд╝рд░ рдореЗрдВ рдирд┐рд╖реНрдкрд╛рджрд┐рдд рдХрд░рдирд╛ рд╢реБрд░реВ рдХрд░ рджреЗрдЧрд╛, рдкреНрд░рд╡реЗрд╢ рдмрд┐рдВрджреБ рдХреЗ рдЬреЗрдПрд╕ рдлрд╝рд╛рдЗрд▓ рдХреЗ рд╕рд╛рде рд╢реБрд░реВ рд╣реЛрдЧрд╛: src/client/index.js ред

index.js

 import { connect, play } from './networking'; import { startRendering, stopRendering } from './render'; import { startCapturingInput, stopCapturingInput } from './input'; import { downloadAssets } from './assets'; import { initState } from './state'; import { setLeaderboardHidden } from './leaderboard'; import './css/main.css'; const playMenu = document.getElementById('play-menu'); const playButton = document.getElementById('play-button'); const usernameInput = document.getElementById('username-input'); Promise.all([ connect(), downloadAssets(), ]).then(() => { playMenu.classList.remove('hidden'); usernameInput.focus(); playButton.onclick = () => { // Play! play(usernameInput.value); playMenu.classList.add('hidden'); initState(); startCapturingInput(); startRendering(); setLeaderboardHidden(false); }; }); 

рдпрд╣ рдЬрдЯрд┐рд▓ рд▓рдЧ рд╕рдХрддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рдпрд╣рд╛рдВ рдмрд╣реБрдд рд╕рд╛рд░реА рдХреНрд░рд┐рдпрд╛рдПрдВ рдирд╣реАрдВ рд╣реЛ рд░рд╣реА рд╣реИрдВ:

  1. рдХрдИ рдЕрдиреНрдп рдЬреЗрдПрд╕ рдлрд╛рдЗрд▓реЗрдВ рдЖрдпрд╛рдд рдХрд░реЗрдВред
  2. рдЖрдпрд╛рдд рд╕реАрдПрд╕рдПрд╕ (рддрд╛рдХрд┐ рд╡реЗрдмрдкреИрдХ рд╣рдорд╛рд░реЗ рд╕реАрдПрд╕рдПрд╕ рдкреИрдХреЗрдЬ рдореЗрдВ рдЙрдиреНрд╣реЗрдВ рд╢рд╛рдорд┐рд▓ рдХрд░рдирд╛ рдЬрд╛рдирддрд╛ рд╣реИ)ред
  3. рд╕рд░реНрд╡рд░ рд╕реЗ рдХрдиреЗрдХреНрд╢рди рд╕реНрдерд╛рдкрд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд░рдирд┐рдВрдЧ connect() рдФрд░ рдЧреЗрдо рдХреЛ рд░реЗрдВрдбрд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЖрд╡рд╢реНрдпрдХ рдЫрд╡рд┐рдпрд╛рдВ рдбрд╛рдЙрдирд▓реЛрдб рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдбрд╛рдЙрдирд▓реЛрдбрдЕрд╕реИрдЯ downloadAssets() рдЪрд▓ рд░рд╣рд╛ рд╣реИред
  4. рдЪрд░рдг 3 рдХреЛ рдкреВрд░рд╛ рдХрд░рдиреЗ рдХреЗ рдмрд╛рдж , рдореБрдЦреНрдп рдореЗрдиреВ ( playMenu ) playMenu рдЬрд╛рддрд╛ playMenu ред
  5. PLAY рдмрдЯрди рджрдмрд╛рдиреЗ рдХреЗ рд▓рд┐рдП рд╣реИрдВрдбрд▓рд░ рдХреЛ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░ рдХрд░рдирд╛ред рдЬрдм рдмрдЯрди рджрдмрд╛рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рддреЛ рдХреЛрдб рдЧреЗрдо рдХреЛ рдЗрдирд┐рд╢рд┐рдпрд▓рд╛рдЗрдЬрд╝ рдХрд░рддрд╛ рд╣реИ рдФрд░ рд╕рд░реНрд╡рд░ рдХреЛ рдмрддрд╛рддрд╛ рд╣реИ рдХрд┐ рд╣рдо рдЦреЗрд▓рдиреЗ рдХреЗ рд▓рд┐рдП рддреИрдпрд╛рд░ рд╣реИрдВред

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

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


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

рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдПрдХ src/client/networking.js рдлрд╝рд╛рдЗрд▓ рд╣реЛрдЧреА рдЬреЛ рд╕рд░реНрд╡рд░ рдХреЗ рд╕рд╛рде рд╕рднреА рд╕рдВрдЪрд╛рд░реЛрдВ рдХреЛ рд╕рдВрднрд╛рд▓реЗрдЧреА:

networking.js

 import io from 'socket.io-client'; import { processGameUpdate } from './state'; const Constants = require('../shared/constants'); const socket = io(`ws://${window.location.host}`); const connectedPromise = new Promise(resolve => { socket.on('connect', () => { console.log('Connected to server!'); resolve(); }); }); export const connect = onGameOver => ( connectedPromise.then(() => { // Register callbacks socket.on(Constants.MSG_TYPES.GAME_UPDATE, processGameUpdate); socket.on(Constants.MSG_TYPES.GAME_OVER, onGameOver); }) ); export const play = username => { socket.emit(Constants.MSG_TYPES.JOIN_GAME, username); }; export const updateDirection = dir => { socket.emit(Constants.MSG_TYPES.INPUT, dir); }; 

рд╕реНрдкрд╖реНрдЯрддрд╛ рдХреЗ рд▓рд┐рдП рдпрд╣ рдХреЛрдб рднреА рдереЛрдбрд╝рд╛ рдХрдо рд╣реИред

рдЗрд╕ рдлрд╝рд╛рдЗрд▓ рдореЗрдВ рддреАрди рдореБрдЦреНрдп рдХреНрд░рд┐рдпрд╛рдПрдВ рд╣реИрдВ:

  • рд╣рдо рд╕рд░реНрд╡рд░ рд╕реЗ рдХрдиреЗрдХреНрдЯ рдХрд░рдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░ рд░рд╣реЗ рд╣реИрдВред connectedPromise рдХреА рдЕрдиреБрдорддрд┐ рдХреЗрд╡рд▓ рддрднреА рд╣реИ рдЬрдм рд╣рдордиреЗ рдХрдиреЗрдХреНрд╢рди рд╕реНрдерд╛рдкрд┐рдд рдХрд┐рдпрд╛ рд╣реЛред
  • рдпрджрд┐ рдХрдиреЗрдХреНрд╢рди рд╕рдлрд▓рддрд╛рдкреВрд░реНрд╡рдХ рд╕реНрдерд╛рдкрд┐рдд рд╣реИ, рддреЛ рд╣рдо рд╕рд░реНрд╡рд░ рд╕реЗ рдкреНрд░рд╛рдкреНрдд рд╣реЛрдиреЗ рд╡рд╛рд▓реЗ рд╕рдВрджреЗрд╢реЛрдВ рдХреЗ рд▓рд┐рдП рдХреЙрд▓рдмреИрдХ рдлрд╝рдВрдХреНрд╢рдВрд╕ ( processGameUpdate() рдФрд░ onGameOver() ) рдкрдВрдЬреАрдХреГрдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред
  • рд╣рдо play() рдФрд░ updateDirection() рдирд┐рд░реНрдпрд╛рдд рдХрд░рддреЗ play() рддрд╛рдХрд┐ рдЕрдиреНрдп рдлрд╛рдЗрд▓реЗрдВ рдЙрдирдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд╕рдХреЗрдВред

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 рд╕рдХреНрд╖рдо рдХрд░рддреЗ рд╣реИрдВред

рд╕рдВрд╕рд╛рдзрдиреЛрдВ рдХреЛ рдбрд╛рдЙрдирд▓реЛрдб рдХрд░рдиреЗ рдХреЗ рдмрд╛рдж, рдЖрдк рдкреНрд░рддрд┐рдкрд╛рджрди рд╢реБрд░реВ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред рдЬреИрд╕рд╛ рдХрд┐ рдкрд╣рд▓реЗ рдХрд╣рд╛ рдЧрдпрд╛ рд╣реИ, рд╣рдо рд╡реЗрдм рдкреЗрдЬ рдкрд░ рдЖрдХрд░реНрд╖рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдЪрдЯреАрдПрдордПрд▓ 5 рдХреИрдирд╡рд╕ ( <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 рдореЗрдВ рджреЗрдЦрд╛ рдЧрдпрд╛ рдерд╛ред 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() ( updateDirection() рд╕реЗ updateDirection() рдХреЙрд▓ updateDirection() ред 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 : рдпрд╣ рдЕрджреНрдпрддрди рдмрдирд╛рдпрд╛ рдЧрдпрд╛ рдерд╛ рдЙрд╕ рд╕рдордп рдХреЗ рд▓рд┐рдП рд╕рд░реНрд╡рд░ рдЯрд╛рдЗрдорд╕реНрдЯреИрдореНрдкред
  • рдореБрдЭреЗ : рдЗрд╕ рдЕрдкрдбреЗрдЯ рдХреЛ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рд╡рд╛рд▓реЗ рдЦрд┐рд▓рд╛рдбрд╝реА рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЬрд╛рдирдХрд╛рд░реАред
  • рдЕрдиреНрдп : рдПрдХ рд╣реА рдЦреЗрд▓ рдореЗрдВ рднрд╛рдЧ рд▓реЗрдиреЗ рд╡рд╛рд▓реЗ рдЕрдиреНрдп рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЬрд╛рдирдХрд╛рд░реА рдХреА рдПрдХ рд╕рд░рдгреАред
  • рдЧреЛрд▓рд┐рдпреЛрдВ : рдЦреЗрд▓ рдореЗрдВ рдЧреЛрд▓реЗ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЬрд╛рдирдХрд╛рд░реА рдХреА рдПрдХ рд╕рд░рдгреАред
  • рд▓реАрдбрд░рдмреЛрд░реНрдб : рд╡рд░реНрддрдорд╛рди рд▓реАрдбрд░рдмреЛрд░реНрдб рдбреЗрдЯрд╛ред рдЗрд╕ рдкреЛрд╕реНрдЯ рдореЗрдВ рд╣рдо рдЙрдиреНрд╣реЗрдВ рдзреНрдпрд╛рди рдореЗрдВ рдирд╣реАрдВ рд▓реЗрдВрдЧреЗред

7.1 рдЧреНрд░рд╛рд╣рдХ рдХреА рдЕрдиреБрднрд╡рд╣реАрди рдЕрд╡рд╕реНрдерд╛


getCurrentState() рдХрд╛ рднреЛрд▓реА рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХреЗрд╡рд▓ рд╣рд╛рд▓ рд╣реА рдореЗрдВ рдкреНрд░рд╛рдкреНрдд рдЧреЗрдо рдЕрдкрдбреЗрдЯ рд╕реЗ рд╕реАрдзреЗ рдбреЗрдЯрд╛ рд▓реМрдЯрд╛ рд╕рдХрддрд╛ рд╣реИред

рднреЛрд▓реА-state.js

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

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

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

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

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


рджреБрд░реНрднрд╛рдЧреНрдп рд╕реЗ, рдХреБрдЫ рднреА рд╕рд╣реА рдирд╣реАрдВ рд╣реИред рдПрдХ рдЕрдзрд┐рдХ рдпрдерд╛рд░реНрдерд╡рд╛рджреА рдЪрд┐рддреНрд░ рд╣реЛрдЧрд╛:

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

7.2 рдЧреНрд░рд╛рд╣рдХ рдХреА рд╕реНрдерд┐рддрд┐ рдореЗрдВ рд╕реБрдзрд╛рд░


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


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


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

рд╣рдо "рдХреНрд▓рд╛рдЗрдВрдЯ-рд╕рд╛рдЗрдб рдлреЛрд░рдХрд╛рд╕реНрдЯрд┐рдВрдЧ" рдирд╛рдордХ рдПрдХ рдЕрдиреНрдп рддрдХрдиреАрдХ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ , рдЬреЛ рдХрдерд┐рдд рджреЗрд░реА рдХреЛ рдХрдо рдХрд░рдиреЗ рдХрд╛ рдЕрдЪреНрдЫрд╛ рдХрд╛рдо рдХрд░рддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдЗрд╕ рдкреЛрд╕реНрдЯ рдореЗрдВ рд╡рд┐рдЪрд╛рд░ рдирд╣реАрдВ рдХрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛ред

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


рдпрд╣ рдлреНрд░реЗрдо рджрд░ рдХреЗ рд╕рд╛рде рд╕рдорд╕реНрдпрд╛ рдХреЛ рд╣рд▓ рдХрд░рддрд╛ рд╣реИ: рдЕрдм рд╣рдо рдХрд┐рд╕реА рднреА рдЖрд╡реГрддреНрддрд┐ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдХреЗ рд╕рд╛рде рдЕрджреНрд╡рд┐рддреАрдп рдлреНрд░реЗрдо рдкреНрд░рджрд╛рди рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ!

7.3 рдПрдиреНрд╣рд╛рдВрд╕реНрдб рдХреНрд▓рд╛рдЗрдВрдЯ рд╕реНрдерд┐рддрд┐ рд▓рд╛рдЧреВ рдХрд░рдирд╛


src/client/state.js рдореЗрдВ рдПрдХ рдЙрджрд╛рд╣рд░рдг рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рд░реЗрдВрдбрд░рд┐рдВрдЧ рджреЗрд░реА рдФрд░ рд░реИрдЦрд┐рдХ рдкреНрд░рдХреНрд╖реЗрдк рджреЛрдиреЛрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдпрд╣ рд▓рдВрдмреЗ рд╕рдордп рддрдХ рдирд╣реАрдВ рд╣реИред рдХреЛрдб рдХреЛ рджреЛ рднрд╛рдЧреЛрдВ рдореЗрдВ рддреЛрдбрд╝рддреЗ рд╣реИрдВред рдпрд╣рд╛рдБ рдкрд╣рд▓реЗ рдПрдХ рд╣реИ:

State.js рднрд╛рдЧ 1

 const RENDER_DELAY = 100; const gameUpdates = []; let gameStart = 0; let firstServerTimestamp = 0; export function initState() { gameStart = 0; firstServerTimestamp = 0; } export function processGameUpdate(update) { if (!firstServerTimestamp) { firstServerTimestamp = update.t; gameStart = Date.now(); } gameUpdates.push(update); // Keep only one game update before the current server time const base = getBaseUpdate(); if (base > 0) { gameUpdates.splice(0, base); } } function currentServerTime() { return firstServerTimestamp + (Date.now() - gameStart) - RENDER_DELAY; } // Returns the index of the base update, the first game update before // current server time, or -1 if N/A. function getBaseUpdate() { const serverTime = currentServerTime(); for (let i = gameUpdates.length - 1; i >= 0; i--) { if (gameUpdates[i].t <= serverTime) { return i; } } return -1; } 

рдкрд╣рд▓рд╛ рдХрджрдо рдпрд╣ рдкрддрд╛ рд▓рдЧрд╛рдирд╛ рд╣реИ рдХрд┐ currentServerTime() рдХреНрдпрд╛ рдХрд░рддрд╛ рд╣реИред рдЬреИрд╕рд╛ рдХрд┐ рд╣рдордиреЗ рдкрд╣рд▓реЗ рджреЗрдЦрд╛, рд╣рд░ рдЧреЗрдо рдЕрдкрдбреЗрдЯ рдореЗрдВ рдПрдХ рд╕рд░реНрд╡рд░ рдЯрд╛рдЗрдорд╕реНрдЯреИрдореНрдк рд╢рд╛рдорд┐рд▓ рд╣реЛрддрд╛ рд╣реИред рд╣рдо рд╕рд░реНрд╡рд░ рдХреЗ рдкреАрдЫреЗ рдЫрд╡рд┐ 100 рдПрдордПрд╕ рдХреЛ рд░реЗрдВрдбрд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд░реЗрдВрдбрд░рд┐рдВрдЧ рджреЗрд░реА рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВ, рд▓реЗрдХрд┐рди рд╣рдо рдХрднреА рднреА рд╕рд░реНрд╡рд░ рдкрд░ рд╡рд░реНрддрдорд╛рди рд╕рдордп рдХреЛ рдирд╣реАрдВ рдЬрд╛рди рдкрд╛рдПрдВрдЧреЗ , рдХреНрдпреЛрдВрдХрд┐ рд╣рдо рдпрд╣ рдирд╣реАрдВ рдЬрд╛рди рд╕рдХрддреЗ рдХрд┐ рдХреЛрдИ рднреА рдЕрдкрдбреЗрдЯ рд╣рдореЗрдВ рдХрдм рддрдХ рдорд┐рд▓рд╛ред рдЗрдВрдЯрд░рдиреЗрдЯ рдЕрдкреНрд░рддреНрдпрд╛рд╢рд┐рдд рд╣реИ рдФрд░ рдЗрд╕рдХреА рдЧрддрд┐ рдмрд╣реБрдд рднрд┐рдиреНрди рд╣реЛ рд╕рдХрддреА рд╣реИ!

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

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

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

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

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


рдХреНрд▓рд╛рдЗрдВрдЯ рд░реЗрдВрдбрд░ рдЯрд╛рдЗрдо рдХреЗ рдмрд╛рдИрдВ рдУрд░ рд╕реАрдзреЗ рдЧреЗрдо рдЕрдкрдбреЗрдЯ рдПрдХ рдмреБрдирд┐рдпрд╛рджреА рдЕрдкрдбреЗрдЯ рд╣реИред

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

state.js рднрд╛рдЧ 2

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

рд╣рдо рддреАрди рдорд╛рдорд▓реЛрдВ рдХреЛ рд╕рдВрднрд╛рд▓рддреЗ рд╣реИрдВ:

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

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

рднрд╛рдЧ 2. рдмреИрдХреЗрдВрдб рд╕рд░реНрд╡рд░


рдЗрд╕ рднрд╛рдЧ рдореЗрдВ, рд╣рдо рд╣рдорд╛рд░реЗ .io рдЧреЗрдо рдЙрджрд╛рд╣рд░рдг рдХреЛ рдЪрд▓рд╛рдиреЗ рд╡рд╛рд▓реЗ Node.js рдмреИрдХрдПрдВрдб рдХреЛ рджреЗрдЦреЗрдВрдЧреЗ ред

1. рд╕рд░реНрд╡рд░ рдкреНрд░рд╡рд┐рд╖реНрдЯрд┐ рдмрд┐рдВрджреБ


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

server.js, рднрд╛рдЧ 1

 const express = require('express'); const webpack = require('webpack'); const webpackDevMiddleware = require('webpack-dev-middleware'); const webpackConfig = require('../../webpack.dev.js'); // Setup an Express server const app = express(); app.use(express.static('public')); if (process.env.NODE_ENV === 'development') { // Setup Webpack for development const compiler = webpack(webpackConfig); app.use(webpackDevMiddleware(compiler)); } else { // Static serve the dist/ folder in production app.use(express.static('dist')); } // Listen on port const port = process.env.PORT || 3000; const server = app.listen(port); console.log(`Server listening on port ${port}`); 

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

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

рдПрдХ рдЕрдиреНрдп рдорд╣рддреНрд╡рдкреВрд░реНрдг рдХрд╛рд░реНрдп socket.ioserver.js рд╕рд░реНрд╡рд░ рдХреЛ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░ рдХрд░рдирд╛ рд╣реИ , рдЬреЛ рдмрд╕ рдПрдХреНрд╕рдкреНрд░реЗрд╕ рд╕рд░реНрд╡рд░ рд╕реЗ рдЬреБрдбрд╝рддрд╛ рд╣реИ:

server.js, рднрд╛рдЧ 2

 const socketio = require('socket.io'); const Constants = require('../shared/constants'); // Setup Express // ... const server = app.listen(port); console.log(`Server listening on port ${port}`); // Setup socket.io const io = socketio(server); // Listen for socket.io connections io.on('connection', socket => { console.log('Player connected!', socket.id); socket.on(Constants.MSG_TYPES.JOIN_GAME, joinGame); socket.on(Constants.MSG_TYPES.INPUT, handleInput); socket.on('disconnect', onDisconnect); }); 

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

server.js, рднрд╛рдЧ 3

 const Game = require('./game'); // ... // Setup the Game const game = new Game(); function joinGame(username) { game.addPlayer(this, username); } function handleInput(dir) { game.handleInput(this, dir); } function onDisconnect() { game.removePlayer(this); } 

рд╣рдо .io рд╢реИрд▓реА рдХрд╛ рдПрдХ рдЧреЗрдо рдмрдирд╛рддреЗ рд╣реИрдВ, рдЗрд╕рд▓рд┐рдП рд╣рдореЗрдВ рдХреЗрд╡рд▓ рдПрдХ рдЙрджрд╛рд╣рд░рдг Game("рдЧреЗрдо") рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ - рд╕рднреА рдЦрд┐рд▓рд╛рдбрд╝реА рдПрдХ рд╣реА рдХреНрд╖реЗрддреНрд░ рдореЗрдВ рдЦреЗрд▓рддреЗ рд╣реИрдВ! рдЕрдЧрд▓реЗ рднрд╛рдЧ рдореЗрдВ, рд╣рдо рджреЗрдЦреЗрдВрдЧреЗ рдХрд┐ рдпрд╣ рд╡рд░реНрдЧ рдХреИрд╕реЗ рдХрд╛рдо рдХрд░рддрд╛ рд╣реИ Gameред

2. рдЦреЗрд▓ рд╕рд░реНрд╡рд░


рдХреНрд▓рд╛рд╕ GameрдореЗрдВ рд╕рдмрд╕реЗ рдорд╣рддреНрд╡рдкреВрд░реНрдг рд╕рд░реНрд╡рд░-рд╕рд╛рдЗрдб рд▓реЙрдЬрд┐рдХ рд╣реЛрддрд╛ рд╣реИред рдЗрд╕рдХреЗ рджреЛ рдореБрдЦреНрдп рдХрд╛рд░реНрдп рд╣реИрдВ: рдЦрд┐рд▓рд╛рдбрд╝реА рдкреНрд░рдмрдВрдзрди рдФрд░ рдЦреЗрд▓ рд╕рд┐рдореБрд▓реЗрд╢рди ред

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

game.js, рднрд╛рдЧ 1

 const Constants = require('../shared/constants'); const Player = require('./player'); class Game { constructor() { this.sockets = {}; this.players = {}; this.bullets = []; this.lastUpdateTime = Date.now(); this.shouldSendUpdate = false; setInterval(this.update.bind(this), 1000 / 60); } addPlayer(socket, username) { this.sockets[socket.id] = socket; // Generate a position to start this player at. const x = Constants.MAP_SIZE * (0.25 + Math.random() * 0.5); const y = Constants.MAP_SIZE * (0.25 + Math.random() * 0.5); this.players[socket.id] = new Player(socket.id, username, x, y); } removePlayer(socket) { delete this.sockets[socket.id]; delete this.players[socket.id]; } handleInput(socket, dir) { if (this.players[socket.id]) { this.players[socket.id].setDirection(dir); } } // ... } 

рдЗрд╕ рдЧреЗрдо рдореЗрдВ, рд╣рдо рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ рдХреЛ idрдЙрдирдХреЗ рд╕реЙрдХреЗрдЯ рд╕реЙрдХреЗрдЯ рдХреЗ рдХреНрд╖реЗрддреНрд░ рджреНрд╡рд╛рд░рд╛ рдкрд╣рдЪрд╛рдиреЗрдВрдЧреЗред (рдпрджрд┐ рдЖрдк рднреНрд░рдорд┐рдд рд╣реИрдВ, рддреЛ рд╡рд╛рдкрд╕ рд▓реМрдЯреЗрдВ server.js)ред Socket.io рд╣реА рдкреНрд░рддреНрдпреЗрдХ рд╕реЙрдХреЗрдЯ рдХреЛ рдПрдХ рдЕрдиреЛрдЦрд╛ рдЕрд╕рд╛рдЗрди рдХрд░рддрд╛ рд╣реИ id, рдЗрд╕рд▓рд┐рдП рд╣рдореЗрдВ рдЗрд╕ рдмрд╛рд░реЗ рдореЗрдВ рдЪрд┐рдВрддрд╛ рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдирд╣реАрдВ рд╣реИред рдореИрдВ рдЙрдирдХреА рдЦрд┐рд▓рд╛рдбрд╝реА рдЖрдИрдбреА рдХреЛ рдХреЙрд▓ рдХрд░реВрдВрдЧрд╛ ред

рдЗрд╕реЗ рдзреНрдпрд╛рди рдореЗрдВ рд░рдЦрддреЗ рд╣реБрдП, рдЖрдЗрдП рдХрдХреНрд╖рд╛ рдореЗрдВ рдЙрджрд╛рд╣рд░рдг рдЪрд░ рдХреА рдЬрд╛рдБрдЪ рдХрд░реЗрдВ Game:

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

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

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

game.js, рднрд╛рдЧ 2

 const Constants = require('../shared/constants'); const applyCollisions = require('./collisions'); class Game { // ... update() { // Calculate time elapsed const now = Date.now(); const dt = (now - this.lastUpdateTime) / 1000; this.lastUpdateTime = now; // Update each bullet const bulletsToRemove = []; this.bullets.forEach(bullet => { if (bullet.update(dt)) { // Destroy this bullet bulletsToRemove.push(bullet); } }); this.bullets = this.bullets.filter( bullet => !bulletsToRemove.includes(bullet), ); // Update each player Object.keys(this.sockets).forEach(playerID => { const player = this.players[playerID]; const newBullet = player.update(dt); if (newBullet) { this.bullets.push(newBullet); } }); // Apply collisions, give players score for hitting bullets const destroyedBullets = applyCollisions( Object.values(this.players), this.bullets, ); destroyedBullets.forEach(b => { if (this.players[b.parentID]) { this.players[b.parentID].onDealtDamage(); } }); this.bullets = this.bullets.filter( bullet => !destroyedBullets.includes(bullet), ); // Check if any players are dead Object.keys(this.sockets).forEach(playerID => { const socket = this.sockets[playerID]; const player = this.players[playerID]; if (player.hp <= 0) { socket.emit(Constants.MSG_TYPES.GAME_OVER); this.removePlayer(socket); } }); // Send a game update to each player every other time if (this.shouldSendUpdate) { const leaderboard = this.getLeaderboard(); Object.keys(this.sockets).forEach(playerID => { const socket = this.sockets[playerID]; const player = this.players[playerID]; socket.emit( Constants.MSG_TYPES.GAME_UPDATE, this.createUpdate(player, leaderboard), ); }); this.shouldSendUpdate = false; } else { this.shouldSendUpdate = true; } } // ... } 

рд╡рд┐рдзрд┐ рдореЗрдВ update()рд╕рдВрднрд╡рддрдГ рд╕рд░реНрд╡рд░-рд╕рд╛рдЗрдб рд▓реЙрдЬрд┐рдХ рдХрд╛ рд╕рдмрд╕реЗ рдорд╣рддреНрд╡рдкреВрд░реНрдг рд╣рд┐рд╕реНрд╕рд╛ рд╣реЛрддрд╛ рд╣реИред рдЖрджреЗрд╢ рдореЗрдВ, рд╣рдо рд╡рд╣ рд╕рдм рдХреБрдЫ рд╕реВрдЪреАрдмрджреНрдз рдХрд░рддреЗ рд╣реИрдВ рдЬреЛ рд╡рд╣ рдХрд░рддрд╛ рд╣реИ:

  1. рдЧрдгрдирд╛ рдХрд░рддрд╛ dtрд╣реИ рдХрд┐ рдЖрдЦрд┐рд░реА рдХреЗ рдмрд╛рдж рдХрд┐рддрдирд╛ рд╕рдордп рдмреАрдд рдЪреБрдХрд╛ рд╣реИ update()ред
  2. . . , bullet.update() true , ( ).
  3. . тАФ player.update() Bullet .
  4. applyCollisions() , , . , ( player.onDealtDamage() ), bullets .
  5. .
  6. update() . shouldSendUpdate . update() 60 /, 30 /. , 30 / ( ).

? . 30 тАУ !

рдлрд┐рд░ update()рдкреНрд░рддрд┐ рд╕реЗрдХрдВрдб 30 рдмрд╛рд░ рдХреЙрд▓ рдХреНрдпреЛрдВ рдирд╣реАрдВ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ ? рдЦреЗрд▓ рдХреЗ рд╕рд┐рдореБрд▓реЗрд╢рди рдореЗрдВ рд╕реБрдзрд╛рд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдПред рдЕрдзрд┐рдХ рдмрд╛рд░ рдХрд╣рд╛ рдЬрд╛рддрд╛ рд╣реИ update(), рдЕрдзрд┐рдХ рд╕рдЯреАрдХ рдЦреЗрд▓ рдХрд╛ рдЕрдиреБрдХрд░рдг рд╣реЛрдЧрд╛ред рд▓реЗрдХрд┐рди рдХреЙрд▓ рдХреА рд╕рдВрдЦреНрдпрд╛ рдХреЗ рд╕рд╛рде рдмрд╣реБрдд рджреВрд░ рдордд рдЬрд╛рдУ update(), рдХреНрдпреЛрдВрдХрд┐ рдпрд╣ рдПрдХ рдХрдореНрдкреНрдпреВрдЯреЗрд╢рдирд▓ рд░реВрдк рд╕реЗ рдорд╣рдВрдЧрд╛ рдХрд╛рдо рд╣реИ - 60 рдкреНрд░рддрд┐ рд╕реЗрдХрдВрдб рдХрд╛рдлреА рдкрд░реНрдпрд╛рдкреНрдд рд╣реИред

рдХрдХреНрд╖рд╛ рдореЗрдВ рд╢реЗрд╖ Gameрд╕рд╣рд╛рдпрдХ рд╡рд┐рдзрд┐рдпреЛрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ update():

game.js, рднрд╛рдЧ 3

 class Game { // ... getLeaderboard() { return Object.values(this.players) .sort((p1, p2) => p2.score - p1.score) .slice(0, 5) .map(p => ({ username: p.username, score: Math.round(p.score) })); } createUpdate(player, leaderboard) { const nearbyPlayers = Object.values(this.players).filter( p => p !== player && p.distanceTo(player) <= Constants.MAP_SIZE / 2, ); const nearbyBullets = this.bullets.filter( b => b.distanceTo(player) <= Constants.MAP_SIZE / 2, ); return { t: Date.now(), me: player.serializeForUpdate(), others: nearbyPlayers.map(p => p.serializeForUpdate()), bullets: nearbyBullets.map(b => b.serializeForUpdate()), leaderboard, }; } } 

getLeaderboard()рдХрд╛рдлреА рд╕рд░рд▓ - рдпрд╣ рдЕрдВрдХреЛрдВ рдХреА рд╕рдВрдЦреНрдпрд╛ рд╕реЗ рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ рдХреЛ рдЫрд╛рдБрдЯрддрд╛ рд╣реИ, рдкрд╛рдБрдЪ рд╕рд░реНрд╡рд╢реНрд░реЗрд╖реНрда рд▓реЗрддрд╛ рд╣реИ рдФрд░ рдкреНрд░рддреНрдпреЗрдХ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдирд╛рдо рдФрд░ рд╕реНрдХреЛрд░ рдХреЗ рд▓рд┐рдП рд░рд┐рдЯрд░реНрди рджреЗрддрд╛ рд╣реИред рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ рдХреЛ рдкрд╛рд░рд┐рдд рдХрд░ рджрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ рдХрд┐ рдЦреЗрд▓ рдЕрджреНрдпрддрди рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП

createUpdate()рдЗрд╕реНрддреЗрдорд╛рд▓ рдХрд┐рдпрд╛ update()ред рдЗрд╕рдХрд╛ рдореБрдЦреНрдп рдХрд╛рд░реНрдп serializeForUpdate()рдХрдХреНрд╖рд╛рдУрдВ PlayerрдФрд░ рдХрдХреНрд╖рд╛рдУрдВ рдХреЗ рд▓рд┐рдП рд▓рд╛рдЧреВ рддрд░реАрдХреЛрдВ рдХреЛ рдХреЙрд▓ рдХрд░рдирд╛ рд╣реИ Bulletред рдзреНрдпрд╛рди рджреЗрдВ рдХрд┐ рд╡рд╣ рдкреНрд░рддреНрдпреЗрдХ рдЦрд┐рд▓рд╛рдбрд╝реА рдХреЛ рдХреЗрд╡рд▓ рдирд┐рдХрдЯрддрдо рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ рдФрд░ рдЧреЛрд▓реЗ рдкрд░ рдбреЗрдЯрд╛ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд░рддрд╛ рд╣реИ - рдЦрд┐рд▓рд╛рдбрд╝реА рд╕реЗ рджреВрд░ рд╕реНрдерд┐рдд рдЦреЗрд▓ рд╡рд╕реНрддреБрдУрдВ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЬрд╛рдирдХрд╛рд░реА рдкреНрд░рд╕рд╛рд░рд┐рдд рдХрд░рдиреЗ рдХреА рдХреЛрдИ рдЖрд╡рд╢реНрдпрдХрддрд╛ рдирд╣реАрдВ рд╣реИ!

3. рд╕рд░реНрд╡рд░ рдкрд░ рдЧреЗрдо рдСрдмреНрдЬреЗрдХреНрдЯ


рд╣рдорд╛рд░реЗ рдЦреЗрд▓ рдореЗрдВ, рдЧреЛрд▓реЗ рдФрд░ рдЦрд┐рд▓рд╛рдбрд╝реА рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рдмрд╣реБрдд рд╕рдорд╛рди рд╣реИрдВ: рд╡реЗ рдЕрдореВрд░реНрдд рдЧреЛрд▓ рдЪрд▓рддреА рд╣реБрдИ рдЦреЗрд▓ рд╡рд╕реНрддреБрдПрдВ рд╣реИрдВред рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ рдФрд░ рдЧреЛрд▓реЗ рдХреА рдЗрд╕ рд╕рдорд╛рдирддрд╛ рдХрд╛ рд▓рд╛рдн рдЙрдард╛рдиреЗ рдХреЗ рд▓рд┐рдП, рдЖрдЗрдП рдЖрдзрд╛рд░ рд╡рд░реНрдЧ рдХреЗ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рд╕реЗ рд╢реБрд░реВ рдХрд░реЗрдВ Object:

object.js

 class Object { constructor(id, x, y, dir, speed) { this.id = id; this.x = x; this.y = y; this.direction = dir; this.speed = speed; } update(dt) { this.x += dt * this.speed * Math.sin(this.direction); this.y -= dt * this.speed * Math.cos(this.direction); } distanceTo(object) { const dx = this.x - object.x; const dy = this.y - object.y; return Math.sqrt(dx * dx + dy * dy); } setDirection(dir) { this.direction = dir; } serializeForUpdate() { return { id: this.id, x: this.x, y: this.y, }; } } 

рдпрд╣рд╛рдВ рдХреБрдЫ рднреА рдЬрдЯрд┐рд▓ рдирд╣реАрдВ рд╣реИред рдпрд╣ рд╡рд░реНрдЧ рд╡рд┐рд╕реНрддрд╛рд░ рдХреЗ рд▓рд┐рдП рдПрдХ рдЕрдЪреНрдЫрд╛ рд╕рдВрджрд░реНрдн рдмрд┐рдВрджреБ рд╣реЛрдЧрд╛ред рдЖрдЗрдП рджреЗрдЦреЗрдВ рдХрд┐ рд╡рд░реНрдЧ рдХреИрд╕реЗ BulletрдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реИ Object:

bullet.js

 const shortid = require('shortid'); const ObjectClass = require('./object'); const Constants = require('../shared/constants'); class Bullet extends ObjectClass { constructor(parentID, x, y, dir) { super(shortid(), x, y, dir, Constants.BULLET_SPEED); this.parentID = parentID; } // Returns true if the bullet should be destroyed update(dt) { super.update(dt); return this.x < 0 || this.x > Constants.MAP_SIZE || this.y < 0 || this.y > Constants.MAP_SIZE; } } 

рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди Bulletрдмрд╣реБрдд рдХрдо рд╣реИ! рд╣рдордиреЗ ObjectрдХреЗрд╡рд▓ рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдПрдХреНрд╕рдЯреЗрдВрд╢рдиреЛрдВ рдХреЛ рдЬреЛрдбрд╝рд╛ :

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

рдЪрд▓реЛ рдЖрдЧреЗ рдмрдврд╝рддреЗ рд╣реИрдВ Player:

player.js

 const ObjectClass = require('./object'); const Bullet = require('./bullet'); const Constants = require('../shared/constants'); class Player extends ObjectClass { constructor(id, username, x, y) { super(id, x, y, Math.random() * 2 * Math.PI, Constants.PLAYER_SPEED); this.username = username; this.hp = Constants.PLAYER_MAX_HP; this.fireCooldown = 0; this.score = 0; } // Returns a newly created bullet, or null. update(dt) { super.update(dt); // Update score this.score += dt * Constants.SCORE_PER_SECOND; // Make sure the player stays in bounds this.x = Math.max(0, Math.min(Constants.MAP_SIZE, this.x)); this.y = Math.max(0, Math.min(Constants.MAP_SIZE, this.y)); // Fire a bullet, if needed this.fireCooldown -= dt; if (this.fireCooldown <= 0) { this.fireCooldown += Constants.PLAYER_FIRE_COOLDOWN; return new Bullet(this.id, this.x, this.y, this.direction); } return null; } takeBulletDamage() { this.hp -= Constants.BULLET_DAMAGE; } onDealtDamage() { this.score += Constants.SCORE_BULLET_HIT; } serializeForUpdate() { return { ...(super.serializeForUpdate()), direction: this.direction, hp: this.hp, }; } } 

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

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

4. рд╕рдВрдШрд░реНрд╖ рдорд╛рдиреНрдпрддрд╛


рд╣рдорд╛рд░реЗ рд▓рд┐рдП рдХреЗрд╡рд▓ рдПрдХ рдЪреАрдЬ рдмрдЪреА рд╣реИ, рдЬрдм рдпрд╣ рдкрддрд╛ рдЪрд▓реЗрдЧрд╛ рдХрд┐ рдЧреЛрд▓реЗ рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ рдХреЛ рдХрд┐рд╕ рддрд░рд╣ рдорд╛рд░рддреЗ рд╣реИрдВ! update()рдПрдХ рд╡рд░реНрдЧ рдореЗрдВ рдПрдХ рд╡рд┐рдзрд┐ рд╕реЗ рдХреЛрдб рдХрд╛ рдпрд╣ рдЯреБрдХрдбрд╝рд╛ рдпрд╛рдж рд░рдЦреЗрдВ Game:

game.js

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

рд╣рдореЗрдВ рдПрдХ рдРрд╕рд╛ рддрд░реАрдХрд╛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреА рдЬрд░реВрд░рдд рд╣реИ applyCollisions()рдЬреЛ рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ рдХреЛ рд╣рд┐рдЯ рдХрд░рдиреЗ рд╡рд╛рд▓реЗ рд╕рднреА рдЧреЛрд▓реЗ рд▓реМрдЯрд╛рдПред рд╕реМрднрд╛рдЧреНрдп рд╕реЗ, рдРрд╕рд╛ рдХрд░рдирд╛ рдЗрддрдирд╛ рдореБрд╢реНрдХрд┐рд▓ рдирд╣реАрдВ рд╣реИ, рдХреНрдпреЛрдВрдХрд┐

  • рд╕рднреА рдЯрдХрд░рд╛рдиреЗ рд╡рд╛рд▓реА рд╡рд╕реНрддреБрдПрдВ рдордВрдбрд▓рд┐рдпрд╛рдВ рд╣реИрдВ, рдФрд░ рдЯрдХрд░рд╛рд╡ рдХреА рдорд╛рдиреНрдпрддрд╛ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдпрд╣ рд╕рдмрд╕реЗ рд╕рд░рд▓ рдЖрдВрдХрдбрд╝рд╛ рд╣реИред
  • рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдПрдХ рд╡рд┐рдзрд┐ рд╣реИ distanceTo()рдЬрд┐рд╕реЗ рд╣рдордиреЗ рдХрдХреНрд╖рд╛ рдореЗрдВ рдкрд┐рдЫрд▓реЗ рдЕрдиреБрднрд╛рдЧ рдореЗрдВ рд▓рд╛рдЧреВ рдХрд┐рдпрд╛ рд╣реИ Objectред

рдпрд╣рд╛рдВ рд╣рдорд╛рд░реА рдЯрдХреНрдХрд░ рдорд╛рдиреНрдпрддрд╛ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдЬреИрд╕рд╛ рджрд┐рдЦрддрд╛ рд╣реИ:

collisions.js

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

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


рдпрд╣рд╛рдВ рдЖрдкрдХреЛ рдХреБрдЫ рдкрд╣рд▓реБрдУрдВ рдкрд░ рд╕рд╛рд╡рдзрд╛рдиреА рд╕реЗ рд╡рд┐рдЪрд╛рд░ рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ:

  • рдкреНрд░рдХреНрд╖реЗрдкреНрдп рдЙрд╕ рдЦрд┐рд▓рд╛рдбрд╝реА рдореЗрдВ рдирд╣реАрдВ рдЧрд┐рд░рдирд╛ рдЪрд╛рд╣рд┐рдП рдЬрд┐рд╕рдиреЗ рдЗрд╕реЗ рдмрдирд╛рдпрд╛ рд╣реИред рдпрд╣ рддреБрд▓рдирд╛ рджреНрд╡рд╛рд░рд╛ рдкреНрд░рд╛рдкреНрдд рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ bullet.parentIDрдХреЗ рд╕рд╛рде player.idред
  • рдкреНрд░рдХреНрд╖реЗрдкреНрдп рдХреЗрд╡рд▓ рдПрдХ рдмрд╛рд░ рдХрдИ рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ рдХреЗ рд╕рд╛рде рдПрдХ рд╕рд╛рде рдЯрдХрд░рд╛рд╡ рдХреЗ рдЪрд░рдо рдорд╛рдорд▓реЗ рдореЗрдВ рд╣рд┐рдЯ рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдПред рд╣рдо рдСрдкрд░реЗрдЯрд░ рдХреА рдорджрдж рд╕реЗ рдЗрд╕ рд╕рдорд╕реНрдпрд╛ рдХрд╛ рд╕рдорд╛рдзрд╛рди рдХрд░реЗрдВрдЧреЗ break: рдЬреИрд╕реЗ рд╣реА рдПрдХ рдЦрд┐рд▓рд╛рдбрд╝реА рдХреЛ рдкрддрд╛ рдЪрд▓рддрд╛ рд╣реИ рдХрд┐ рдЙрд╕реЗ рдПрдХ рдкреНрд░реЛрдЬреЗрдХреНрдЯрд╛рдЗрд▓ рдХрд╛ рд╕рд╛рдордирд╛ рдХрд░рдирд╛ рдкрдбрд╝рд╛ рд╣реИ, рд╣рдо рдЦреЛрдЬ рдХреЛ рд░реЛрдХрддреЗ рд╣реИрдВ рдФрд░ рдЕрдЧрд▓реЗ рдкреНрд░реЛрдЬреЗрдХреНрдЯрд╛рдЗрд▓ рдкрд░ рдЬрд╛рддреЗ рд╣реИрдВред

рдЕрдВрдд


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

рд╕рднреА рд╕реИрдВрдкрд▓ рдХреЛрдб рдУрдкрди рд╕реЛрд░реНрд╕ рд╣реИрдВ рдФрд░ рдЗрдиреНрд╣реЗрдВ рдЬреАрдердм рдкрд░ рдкреЛрд╕реНрдЯ рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ ред

Source: https://habr.com/ru/post/hi450574/


All Articles