2015 рдореЗрдВ рд░рд┐рд▓реАрдЬрд╝ рд╣реБрдИ
рдпрджрд┐ рдЖрдкрдиреЗ рдкрд╣рд▓реЗ рдХрднреА рдЗрди рдЦреЗрд▓реЛрдВ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдирд╣реАрдВ рд╕реБрдирд╛ рд╣реИ, рддреЛ рдпреЗ рдореБрдлрд╝реНрдд рдорд▓реНрдЯреАрдкреНрд▓реЗрдпрд░ рд╡реЗрдм рдЧреЗрдо рд╣реИрдВ рдЬрд┐рдиреНрд╣реЗрдВ рдЦреЗрд▓рдирд╛ рдЖрд╕рд╛рди рд╣реИ (рдХрд┐рд╕реА рдЦрд╛рддреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдирд╣реАрдВ рд╣реИ)ред рд╡реЗ рдЖрдо рддреМрд░ рдкрд░ рдПрдХ рд╣реА рдореИрджрд╛рди рдореЗрдВ рдХрдИ рд╡рд┐рд░реЛрдзреА рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ рдХрд╛ рд╕рд╛рдордирд╛ рдХрд░рддреЗ рд╣реИрдВред рдЕрдиреНрдп рдкреНрд░рд╕рд┐рджреНрдз .io рдЦреЗрд▓:
рдЗрд╕ рдкреЛрд╕реНрдЯ рдореЗрдВ рд╣рдо рдЬрд╛рдиреЗрдВрдЧреЗ рдХрд┐ рдХреИрд╕реЗ рд╕реНрдХреНрд░реИрдЪ рд╕реЗ рдПрдХ .io рдЧреЗрдо рдмрдирд╛рдПрдВ. рдЗрд╕рдХреЗ рд▓рд┐рдП рдХреЗрд╡рд▓ рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдХрд╛ рдЬреНрдЮрд╛рди рд╣реА рдХрд╛рдлреА рд╣реЛрдЧрд╛: рдЖрдкрдХреЛ рд╕рд┐рдВрдЯреИрдХреНрд╕ рдЬреИрд╕реА рдЪреАрдЬреЛрдВ рдХреЛ рд╕рдордЭрдиреЗ рдХреА рдЬрд░реВрд░рдд рд╣реИ this
╨╕
.io рдЦреЗрд▓ рдЙрджрд╛рд╣рд░рдг
рд╕реАрдЦрдиреЗ рдореЗрдВ рд╕рд╣рд╛рдпрддрд╛ рдХреЗ рд▓рд┐рдП, рд╣рдо рдЗрд╕рдХрд╛ рдЙрд▓реНрд▓реЗрдЦ рдХрд░реЗрдВрдЧреЗ
рдЦреЗрд▓ рдХрд╛рдлреА рд╕рд░рд▓ рд╣реИ: рдЖрдк рдПрдХ рдЬрд╣рд╛рдЬрд╝ рдХреЛ рдРрд╕реЗ рдХреНрд╖реЗрддреНрд░ рдореЗрдВ рдирд┐рдпрдВрддреНрд░рд┐рдд рдХрд░рддреЗ рд╣реИрдВ рдЬрд╣рд╛рдБ рдЕрдиреНрдп рдЦрд┐рд▓рд╛рдбрд╝реА рд╣реЛрддреЗ рд╣реИрдВред рдЖрдкрдХрд╛ рдЬрд╣рд╛рдЬ рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рд░реВрдк рд╕реЗ рдкреНрд░рдХреНрд╖реЗрдкреНрдп рджрд╛рдЧрддрд╛ рд╣реИ рдФрд░ рдЖрдк рдЕрдиреНрдп рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ рдХреЗ рдкреНрд░рдХреНрд╖реЗрдкреНрдп рд╕реЗ рдмрдЪрддреЗ рд╣реБрдП рдЙрди рдкрд░ рдкреНрд░рд╣рд╛рд░ рдХрд░рдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░рддреЗ рд╣реИрдВред
1. рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХрд╛ рд╕рдВрдХреНрд╖рд┐рдкреНрдд рдЕрд╡рд▓реЛрдХрди/рд╕рдВрд░рдЪрдирд╛
рдХреА рд╕рд┐рдлрд╛рд░рд┐рд╢
рд╕реНрд░реЛрдд рдХреЛрдб рдбрд╛рдЙрдирд▓реЛрдб рдХрд░реЗрдВ рдЙрджрд╛рд╣рд░рдг рдЦреЗрд▓ рддрд╛рдХрд┐ рдЖрдк рдореЗрд░рд╛ рдЕрдиреБрд╕рд░рдг рдХрд░ рд╕рдХреЗрдВред
рдЙрджрд╛рд╣рд░рдг рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реИ:
рд╡реНрдпрдХреНрдд рд╕рдмрд╕реЗ рд▓реЛрдХрдкреНрд░рд┐рдп Node.js рд╡реЗрдм рдлреНрд░реЗрдорд╡рд░реНрдХ рд╣реИ рдЬреЛ рдЧреЗрдо рдХреЗ рд╡реЗрдм рд╕рд░реНрд╡рд░ рдХреЛ рдкреНрд░рдмрдВрдзрд┐рдд рдХрд░рддрд╛ рд╣реИредрд╕реЙрдХреЗрдЯ.рдЖрдИрдУ - рдмреНрд░рд╛рдЙрдЬрд╝рд░ рдФрд░ рд╕рд░реНрд╡рд░ рдХреЗ рдмреАрдЪ рдбреЗрдЯрд╛ рдХреЗ рдЖрджрд╛рди-рдкреНрд░рджрд╛рди рдХреЗ рд▓рд┐рдП рдПрдХ рд╡реЗрдмрд╕реЙрдХреЗрдЯ рд▓рд╛рдЗрдмреНрд░реЗрд░реАредwebpack - рдореЙрдбреНрдпреВрд▓ рдкреНрд░рдмрдВрдзрдХред рдЖрдк рд╡реЗрдмрдкреИрдХ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХреНрдпреЛрдВ рдХрд░реЗрдВ рдЗрд╕рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдкрдврд╝ рд╕рдХрддреЗ рд╣реИрдВредрдпрд╣рд╛рдВ .
рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдирд┐рд░реНрджреЗрд╢рд┐рдХрд╛ рд╕рдВрд░рдЪрдирд╛ рдЗрд╕ рдкреНрд░рдХрд╛рд░ рджрд┐рдЦрддреА рд╣реИ:
public/
assets/
...
src/
client/
css/
...
html/
index.html
index.js
...
server/
server.js
...
shared/
constants.js
рдЬрдирддрд╛/
рдПрдХ рдлрд╝реЛрд▓реНрдбрд░ рдореЗрдВ рд╕рдм рдХреБрдЫ public/
рд╕рд░реНрд╡рд░ рджреНрд╡рд╛рд░рд╛ рд╕реНрдерд┐рд░ рд░реВрдк рд╕реЗ рд╕рдмрдорд┐рдЯ рдХрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛ред рдореЗрдВ public/assets/
рдЗрд╕рдореЗрдВ рд╣рдорд╛рд░реЗ рдкреНрд░реЛрдЬреЗрдХреНрдЯ рджреНрд╡рд╛рд░рд╛ рдЙрдкрдпреЛрдЧ рдХреА рдЧрдИ рдЫрд╡рд┐рдпрд╛рдВ рд╢рд╛рдорд┐рд▓ рд╣реИрдВред
src /
рд╕рднреА рд╕реНрд░реЛрдд рдХреЛрдб рдлрд╝реЛрд▓реНрдбрд░ рдореЗрдВ рд╣реИ src/
. рдЯрд╛рдЗрдЯрд▓ client/
╨╕ server/
рдЕрдкрдиреЗ рд▓рд┐рдП рдмреЛрд▓реЗрдВ рдФрд░ shared/
рдЗрд╕рдореЗрдВ рдПрдХ рд╕реНрдерд┐рд░рд╛рдВрдХ рдлрд╝рд╛рдЗрд▓ рд╣реЛрддреА рд╣реИ рдЬрд┐рд╕реЗ рдХреНрд▓рд╛рдЗрдВрдЯ рдФрд░ рд╕рд░реНрд╡рд░ рджреЛрдиреЛрдВ рджреНрд╡рд╛рд░рд╛ рдЖрдпрд╛рдд рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред
2. рдЕрд╕реЗрдВрдмрд▓реА/рдкреНрд░реЛрдЬреЗрдХреНрдЯ рд╕реЗрдЯрд┐рдВрдЧреНрд╕
рдЬреИрд╕рд╛ рдХрд┐ рдКрдкрд░ рдмрддрд╛рдпрд╛ рдЧрдпрд╛ рд╣реИ, рд╣рдо рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдореЙрдбреНрдпреВрд▓ рдореИрдиреЗрдЬрд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВред
рд╡реЗрдмрдкреИрдХ.рдХреЙрдорди.рдЬреЗрдПрд╕:
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: {
game: './src/client/index.js',
},
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ['@babel/preset-env'],
},
},
},
{
test: /.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
'css-loader',
],
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
}),
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'src/client/html/index.html',
}),
],
};
рдпрд╣рд╛рдБ рд╕рдмрд╕реЗ рдорд╣рддреНрд╡рдкреВрд░реНрдг рдкрдВрдХреНрддрд┐рдпрд╛рдБ рд╣реИрдВ:
src/client/index.js
рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ (JS) рдХреНрд▓рд╛рдЗрдВрдЯ рдХрд╛ рдкреНрд░рд╡реЗрд╢ рдмрд┐рдВрджреБ рд╣реИред рд╡реЗрдмрдкреИрдХ рдпрд╣рд╛рдВ рд╕реЗ рд╢реБрд░реВ рд╣реЛрдЧрд╛ рдФрд░ рдЕрдиреНрдп рдЖрдпрд╛рддрд┐рдд рдлрд╝рд╛рдЗрд▓реЛрдВ рдХреЗ рд▓рд┐рдП рдкреБрдирд░рд╛рд╡рд░реНрддреА рд░реВрдк рд╕реЗ рдЦреЛрдЬ рдХрд░реЗрдЧрд╛ред- рд╣рдорд╛рд░реЗ рд╡реЗрдмрдкреИрдХ рдмрд┐рд▓реНрдб рдХрд╛ рдЖрдЙрдЯрдкреБрдЯ рдЬреЗрдПрд╕ рдирд┐рд░реНрджреЗрд╢рд┐рдХрд╛ рдореЗрдВ рд╕реНрдерд┐рдд рд╣реЛрдЧрд╛
dist/
. рдореИрдВ рдЗрд╕ рдлрд╛рдЗрд▓ рдХреЛ рд╣рдорд╛рд░рд╛ рдХрд╣реВрдВрдЧрд╛ рдЬреЗрдПрд╕ рдкреИрдХреЗрдЬ. - рд╣рдо рдкреНрд░рдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВ
рдХреЛрд▓рд╛рд╣рд▓ , рдФрд░ рд╡рд┐рд╢реЗрд╖ рд░реВрдк рд╕реЗ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди@babel/preset-env рдкреБрд░рд╛рдиреЗ рдмреНрд░рд╛рдЙрдЬрд╝рд░реЛрдВ рдХреЗ рд▓рд┐рдП рд╣рдорд╛рд░реЗ рдЬреЗрдПрд╕ рдХреЛрдб рдХреЛ рдЯреНрд░рд╛рдВрд╕рдкрд┐рд▓рд┐рдВрдЧ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдПред - рд╣рдо рдЬреЗрдПрд╕ рдлрд╛рдЗрд▓реЛрдВ рджреНрд╡рд╛рд░рд╛ рд╕рдВрджрд░реНрднрд┐рдд рд╕рднреА рд╕реАрдПрд╕рдПрд╕ рдХреЛ рдирд┐рдХрд╛рд▓рдиреЗ рдФрд░ рдЙрдиреНрд╣реЗрдВ рдПрдХ рд╕реНрдерд╛рди рдкрд░ рд╕рдВрдпреЛрдЬрд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдкреНрд▓рдЧрдЗрди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд░рд╣реЗ рд╣реИрдВред рдореИрдВ рдЙрд╕реЗ рдЕрдкрдирд╛ рдХрд╣реВрдБрдЧрд╛ рд╕реАрдПрд╕рдПрд╕ рдкреИрдХреЗрдЬ.
рдЖрдкрдиреЗ рдЕрдЬреАрдм рдкреИрдХреЗрдЬ рдлрд╝рд╛рдЗрд▓ рдирд╛рдо рджреЗрдЦреЗ рд╣реЛрдВрдЧреЗ '[name].[contenthash].ext'
. рд╡реЗ рд╣реЛрддреЗ рд╣реИрдВ [name]
рдЗрдирдкреБрдЯ рдмрд┐рдВрджреБ рдХреЗ рдирд╛рдо рд╕реЗ рдкреНрд░рддрд┐рд╕реНрдерд╛рдкрд┐рдд рдХрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛ (рд╣рдорд╛рд░реЗ рдорд╛рдорд▓реЗ рдореЗрдВ, рдпрд╣ game
), рдФрд░ [contenthash]
рдлрд╝рд╛рдЗрд▓ рдХреА рд╕рд╛рдордЧреНрд░реА рдХреЗ рд╣реИрд╢ рд╕реЗ рдкреНрд░рддрд┐рд╕реНрдерд╛рдкрд┐рдд рдХрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛ред рд╣рдо рдпрд╣ рдХрд░рддреЗ рд╣реИрдВ 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
рдФрд░ рд╡реЗрдм рдмреНрд░рд╛рдЙрдЬрд░ рдкрд░ рдЬрд╛рдПрдВ
3. рдЧреНрд░рд╛рд╣рдХ рдкреНрд░рд╡реЗрд╢ рдмрд┐рдВрджреБ
рдЪрд▓рд┐рдП рдЧреЗрдо рдХреЛрдб рдкрд░ рд╣реА рдЖрддреЗ рд╣реИрдВред рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ рд╣рдореЗрдВ рдПрдХ рдкреЗрдЬ рдЪрд╛рд╣рд┐рдП index.html
, рд╕рд╛рдЗрдЯ рдкрд░ рдЬрд╛рдиреЗ рдкрд░ рдмреНрд░рд╛рдЙрдЬрд╝рд░ рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ рдЗрд╕реЗ рд▓реЛрдб рдХрд░реЗрдЧрд╛ред рд╣рдорд╛рд░рд╛ рдкреЗрдЬ рдХрд╛рдлреА рд╕рд░рд▓ рд╣реЛрдЧрд╛:
рд╕реВрдЪрдХрд╛рдВрдХ
рдПрдХ рдЙрджрд╛рд╣рд░рдг .io рдЧреЗрдо рдЦреЗрд▓
рдЗрд╕ рдХреЛрдб рдЙрджрд╛рд╣рд░рдг рдХреЛ рд╕реНрдкрд╖реНрдЯрддрд╛ рдХреЗ рд▓рд┐рдП рдереЛрдбрд╝рд╛ рд╕рд░рд▓ рдмрдирд╛рдпрд╛ рдЧрдпрд╛ рд╣реИ, рдФрд░ рдореИрдВ рдХрдИ рдЕрдиреНрдп рдкреЛрд╕реНрдЯ рдЙрджрд╛рд╣рд░рдгреЛрдВ рдХреЗ рд╕рд╛рде рднреА рдРрд╕рд╛ рд╣реА рдХрд░реВрдВрдЧрд╛ред рдкреВрд░рд╛ рдХреЛрдб рд╣рдореЗрд╢рд╛ рдпрд╣рд╛рдВ рджреЗрдЦрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ
рдЕрдкрдиреЗ рдкрд╛рд╕:
HTML5 рдХреИрдирд╡рд╛рд╕ рддрддреНрд╡ (<canvas>
) рдЬрд┐рд╕рдХрд╛ рдЙрдкрдпреЛрдЧ рд╣рдо рдЧреЗрдо рдХреЛ рд░реЗрдВрдбрд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХрд░реЗрдВрдЧреЗред<link>
рд╣рдорд╛рд░реЗ рд╕реАрдПрд╕рдПрд╕ рдкреИрдХреЗрдЬ рдХреЛ рдЬреЛрдбрд╝рдиреЗ рдХреЗ рд▓рд┐рдПред<script>
рд╣рдорд╛рд░рд╛ рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдкреИрдХреЗрдЬ рдЬреЛрдбрд╝рдиреЗ рдХреЗ рд▓рд┐рдПред- рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдирд╛рдо рдХреЗ рд╕рд╛рде рдореБрдЦреНрдп рдореЗрдиреВ
<input>
рдФрд░ рдкреНрд▓реЗ рдмрдЯрди (<button>
).
рд╣реЛрдо рдкреЗрдЬ рд▓реЛрдб рдХрд░рдиреЗ рдХреЗ рдмрд╛рдж, рдмреНрд░рд╛рдЙрдЬрд╝рд░ рдкреНрд░рд╡реЗрд╢ рдмрд┐рдВрджреБ рдЬреЗрдПрд╕ рдлрд╝рд╛рдЗрд▓ рд╕реЗ рд╢реБрд░реВ рдХрд░рдХреЗ рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдХреЛрдб рдирд┐рд╖реНрдкрд╛рджрд┐рдд рдХрд░рдирд╛ рд╢реБрд░реВ рдХрд░ рджреЗрдЧрд╛: src/client/index.js
.
index.js
import { connect, play } from './networking';
import { startRendering, stopRendering } from './render';
import { startCapturingInput, stopCapturingInput } from './input';
import { downloadAssets } from './assets';
import { initState } from './state';
import { setLeaderboardHidden } from './leaderboard';
import './css/main.css';
const playMenu = document.getElementById('play-menu');
const playButton = document.getElementById('play-button');
const usernameInput = document.getElementById('username-input');
Promise.all([
connect(),
downloadAssets(),
]).then(() => {
playMenu.classList.remove('hidden');
usernameInput.focus();
playButton.onclick = () => {
// Play!
play(usernameInput.value);
playMenu.classList.add('hidden');
initState();
startCapturingInput();
startRendering();
setLeaderboardHidden(false);
};
});
рдпрд╣ рдЬрдЯрд┐рд▓ рд▓рдЧ рд╕рдХрддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдпрд╣рд╛рдВ рдмрд╣реБрдд рдХреБрдЫ рдирд╣реАрдВ рдЪрд▓ рд░рд╣рд╛ рд╣реИ:
- рдХрдИ рдЕрдиреНрдп JS рдлрд╝рд╛рдЗрд▓реЗрдВ рдЖрдпрд╛рдд рдХрд░рдирд╛ред
- рд╕реАрдПрд╕рдПрд╕ рдЖрдпрд╛рдд (рдЗрд╕рд▓рд┐рдП рд╡реЗрдмрдкреИрдХ рдЙрдиреНрд╣реЗрдВ рд╣рдорд╛рд░реЗ рд╕реАрдПрд╕рдПрд╕ рдкреИрдХреЗрдЬ рдореЗрдВ рд╢рд╛рдорд┐рд▓ рдХрд░рдирд╛ рдЬрд╛рдирддрд╛ рд╣реИ)ред
- ╨Ч╨░╨┐╤Г╤Б╨║
connect()
рд╕рд░реНрд╡рд░ рдХреЗ рд╕рд╛рде рдХрдиреЗрдХреНрд╢рди рд╕реНрдерд╛рдкрд┐рдд рдХрд░рдиреЗ рдФрд░ рдЪрд▓рд╛рдиреЗ рдХреЗ рд▓рд┐рдПdownloadAssets()
рдЧреЗрдо рдХреЛ рд░реЗрдВрдбрд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЖрд╡рд╢реНрдпрдХ рдЫрд╡рд┐рдпреЛрдВ рдХреЛ рдбрд╛рдЙрдирд▓реЛрдб рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдПред - рдЪрд░рдг 3 рдкреВрд░рд╛ рд╣реЛрдиреЗ рдХреЗ рдмрд╛рдж рдореБрдЦреНрдп рдореЗрдиреВ рдкреНрд░рджрд░реНрд╢рд┐рдд рд╣реЛрддрд╛ рд╣реИ (
playMenu
). - "рдкреНрд▓реЗ" рдмрдЯрди рджрдмрд╛рдиреЗ рдХреЗ рд▓рд┐рдП рд╣реИрдВрдбрд▓рд░ рд╕реЗрдЯ рдХрд░рдирд╛ред рдЬрдм рдмрдЯрди рджрдмрд╛рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рддреЛ рдХреЛрдб рдЧреЗрдо рдХреЛ рдЗрдирд┐рд╢рд┐рдпрд▓рд╛рдЗрдЬрд╝ рдХрд░рддрд╛ рд╣реИ рдФрд░ рд╕рд░реНрд╡рд░ рдХреЛ рдмрддрд╛рддрд╛ рд╣реИ рдХрд┐ рд╣рдо рдЦреЗрд▓рдиреЗ рдХреЗ рд▓рд┐рдП рддреИрдпрд╛рд░ рд╣реИрдВред
рд╣рдорд╛рд░реЗ рдХреНрд▓рд╛рдЗрдВрдЯ-рд╕рд░реНрд╡рд░ рд▓реЙрдЬрд┐рдХ рдХрд╛ рдореБрдЦреНрдп "рдореАрдЯ" рдЙрди рдлрд╝рд╛рдЗрд▓реЛрдВ рдореЗрдВ рд╣реИ рдЬрд┐рдиреНрд╣реЗрдВ рдлрд╝рд╛рдЗрд▓ рджреНрд╡рд╛рд░рд╛ рдЖрдпрд╛рдд рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛ index.js
. рдЕрдм рд╣рдо рдЙрди рд╕рднреА рдкрд░ рдХреНрд░рдо рд╕реЗ рд╡рд┐рдЪрд╛рд░ рдХрд░реЗрдВрдЧреЗред
4. рдЧреНрд░рд╛рд╣рдХ рдбреЗрдЯрд╛ рдХрд╛ рдЖрджрд╛рди-рдкреНрд░рджрд╛рди
рдЗрд╕ рдЧреЗрдо рдореЗрдВ, рд╣рдо рд╕рд░реНрд╡рд░ рдХреЗ рд╕рд╛рде рд╕рдВрдЪрд╛рд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдкреНрд░рд╕рд┐рджреНрдз рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВ
рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдПрдХ рдлрд╝рд╛рдЗрд▓ рд╣реЛрдЧреА src/client/networking.js
рдХреМрди рджреЗрдЦрднрд╛рд▓ рдХрд░реЗрдЧрд╛ рд╕рд╛рд░реЗ рд╕рд░реНрд╡рд░ рдХреЗ рд╕рд╛рде рд╕рдВрдЪрд╛рд░:
рдиреЗрдЯрд╡рд░реНрдХрд┐рдВрдЧ.рдЬреЗ.рдПрд╕
import io from 'socket.io-client';
import { processGameUpdate } from './state';
const Constants = require('../shared/constants');
const socket = io(`ws://${window.location.host}`);
const connectedPromise = new Promise(resolve => {
socket.on('connect', () => {
console.log('Connected to server!');
resolve();
});
});
export const connect = onGameOver => (
connectedPromise.then(() => {
// Register callbacks
socket.on(Constants.MSG_TYPES.GAME_UPDATE, processGameUpdate);
socket.on(Constants.MSG_TYPES.GAME_OVER, onGameOver);
})
);
export const play = username => {
socket.emit(Constants.MSG_TYPES.JOIN_GAME, username);
};
export const updateDirection = dir => {
socket.emit(Constants.MSG_TYPES.INPUT, dir);
};
рд╕реНрдкрд╖реНрдЯрддрд╛ рдХреЗ рд▓рд┐рдП рдЗрд╕ рдХреЛрдб рдХреЛ рдереЛрдбрд╝рд╛ рдЫреЛрдЯрд╛ рднреА рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИред
рдЗрд╕ рдлрд╝рд╛рдЗрд▓ рдореЗрдВ рддреАрди рдореБрдЦреНрдп рдХреНрд░рд┐рдпрд╛рдПрдБ рд╣реИрдВ:
- рд╣рдо рд╕рд░реНрд╡рд░ рд╕реЗ рдЬреБрдбрд╝рдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░ рд░рд╣реЗ рд╣реИрдВред
connectedPromise
рдХреЗрд╡рд▓ рддрднреА рдЕрдиреБрдорддрд┐ рджреА рдЬрд╛рддреА рд╣реИ рдЬрдм рд╣рдордиреЗ рдХреЛрдИ рдХрдиреЗрдХреНрд╢рди рд╕реНрдерд╛рдкрд┐рдд рдХрд░ рд▓рд┐рдпрд╛ рд╣реЛред - рдпрджрд┐ рдХрдиреЗрдХреНрд╢рди рд╕рдлрд▓ рд╣реЛрддрд╛ рд╣реИ, рддреЛ рд╣рдо рдХреЙрд▓рдмреИрдХ рдлрд╝рдВрдХреНрд╢рди рдкрдВрдЬреАрдХреГрдд рдХрд░рддреЗ рд╣реИрдВ (
processGameUpdate()
╨╕onGameOver()
) рдЙрди рд╕рдВрджреЗрд╢реЛрдВ рдХреЗ рд▓рд┐рдП рдЬрд┐рдиреНрд╣реЗрдВ рд╣рдо рд╕рд░реНрд╡рд░ рд╕реЗ рдкреНрд░рд╛рдкреНрдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред - рд╣рдо рдирд┐рд░реНрдпрд╛рдд рдХрд░рддреЗ рд╣реИрдВ
play()
╨╕updateDirection()
рддрд╛рдХрд┐ рдЕрдиреНрдп рдлрд╝рд╛рдЗрд▓реЗрдВ рдЙрдирдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд╕рдХреЗрдВ.
5. рдХреНрд▓рд╛рдЗрдВрдЯ рд░реЗрдВрдбрд░рд┐рдВрдЧ
рд╕реНрдХреНрд░реАрди рдкрд░ рдЪрд┐рддреНрд░ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рдиреЗ рдХрд╛ рд╕рдордп рдЖ рдЧрдпрд╛ рд╣реИ!
...рд▓реЗрдХрд┐рди рдЗрд╕рд╕реЗ рдкрд╣рд▓реЗ рдХрд┐ рд╣рдо рдРрд╕рд╛ рдХрд░ рд╕рдХреЗрдВ, рд╣рдореЗрдВ рдЗрд╕рдХреЗ рд▓рд┐рдП рдЖрд╡рд╢реНрдпрдХ рд╕рднреА рдЫрд╡рд┐рдпреЛрдВ (рд╕рдВрд╕рд╛рдзрдиреЛрдВ) рдХреЛ рдбрд╛рдЙрдирд▓реЛрдб рдХрд░рдирд╛ рд╣реЛрдЧрд╛ред рдЖрдЗрдП рдПрдХ рд╕рдВрд╕рд╛рдзрди рдкреНрд░рдмрдВрдзрдХ рд▓рд┐рдЦреЗрдВ:
рд╕рдВрдкрддреНрддрд┐.рдЬреЗ.рдПрд╕
const ASSET_NAMES = ['ship.svg', 'bullet.svg'];
const assets = {};
const downloadPromise = Promise.all(ASSET_NAMES.map(downloadAsset));
function downloadAsset(assetName) {
return new Promise(resolve => {
const asset = new Image();
asset.onload = () => {
console.log(`Downloaded ${assetName}`);
assets[assetName] = asset;
resolve();
};
asset.src = `/assets/${assetName}`;
});
}
export const downloadAssets = () => downloadPromise;
export const getAsset = assetName => assets[assetName];
рд╕рдВрд╕рд╛рдзрди рдкреНрд░рдмрдВрдзрди рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдирд╛ рдЙрддрдирд╛ рдХрдард┐рди рдирд╣реАрдВ рд╣реИ! рдореБрдЦреНрдп рд╡рд┐рдЪрд╛рд░ рдХрд┐рд╕реА рд╡рд╕реНрддреБ рдХреЛ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд░рдирд╛ рд╣реИ assets
, рдЬреЛ рдлрд╝рд╛рдЗрд▓ рдирд╛рдо рдХреА рдХреБрдВрдЬреА рдХреЛ рдСрдмреНрдЬреЗрдХреНрдЯ рдХреЗ рдорд╛рди рд╕реЗ рдмрд╛рдВрдз рджреЗрдЧрд╛ Image
. рдЬрдм рд╕рдВрд╕рд╛рдзрди рд▓реЛрдб рд╣реЛрддрд╛ рд╣реИ, рддреЛ рд╣рдо рдЗрд╕реЗ рдХрд┐рд╕реА рдСрдмреНрдЬреЗрдХреНрдЯ рдореЗрдВ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд░рддреЗ рд╣реИрдВ assets
рднрд╡рд┐рд╖реНрдп рдореЗрдВ рддреНрд╡рд░рд┐рдд рдкрд╣реБрдВрдЪ рдХреЗ рд▓рд┐рдП. рдкреНрд░рддреНрдпреЗрдХ рд╡реНрдпрдХреНрддрд┐рдЧрдд рд╕рдВрд╕рд╛рдзрди рдХреЛ рдбрд╛рдЙрдирд▓реЛрдб рдХрд░рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рдХрдм рджреА рдЬрд╛рдПрдЧреА (рдЕрд░реНрдерд╛рддреН, рд╕рдм рд╕рдВрд╕рд╛рдзрди), рд╣рдо рдЕрдиреБрдорддрд┐ рджреЗрддреЗ рд╣реИрдВ downloadPromise
.
рд╕рдВрд╕рд╛рдзрдиреЛрдВ рдХреЛ рдбрд╛рдЙрдирд▓реЛрдб рдХрд░рдиреЗ рдХреЗ рдмрд╛рдж, рдЖрдк рд░реЗрдВрдбрд░ рдХрд░рдирд╛ рд╢реБрд░реВ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред рдЬреИрд╕рд╛ рдХрд┐ рдкрд╣рд▓реЗ рдХрд╣рд╛ рдЧрдпрд╛ рд╣реИ, рд╡реЗрдм рдкреЗрдЬ рдкрд░ рдЪрд┐рддреНрд░ рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП рд╣рдо рдЗрд╕рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВ <canvas>
). рд╣рдорд╛рд░рд╛ рдЦреЗрд▓ рдХрд╛рдлреА рд╕рд░рд▓ рд╣реИ, рдЗрд╕рд▓рд┐рдП рд╣рдореЗрдВ рдХреЗрд╡рд▓ рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдбреНрд░рд╛ рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ:
- рдкреГрд╖реНрдарднреВрдорд┐
- рдЦрд┐рд▓рд╛рдбрд╝реА рдЬрд╣рд╛рдЬ
- рдЦреЗрд▓ рдореЗрдВ рдЕрдиреНрдп рдЦрд┐рд▓рд╛рдбрд╝реА
- рдЧреЛрд▓рд╛рдмрд╛рд░реВрдж
рдпрд╣рд╛рдВ рдорд╣рддреНрд╡рдкреВрд░реНрдг рдЕрдВрд╢ рджрд┐рдП рдЧрдП рд╣реИрдВ src/client/render.js
, рдЬреЛ рдКрдкрд░ рд╕реВрдЪреАрдмрджреНрдз рдЪрд╛рд░ рд╡рд╕реНрддреБрдУрдВ рдХреЛ рдмрд┐рд▓реНрдХреБрд▓ рдкреНрд░рд╕реНрддреБрдд рдХрд░рддрд╛ рд╣реИ:
рд░реЗрдВрдбрд░.рдЬреЗ.рдПрд╕
import { getAsset } from './assets';
import { getCurrentState } from './state';
const Constants = require('../shared/constants');
const { PLAYER_RADIUS, PLAYER_MAX_HP, BULLET_RADIUS, MAP_SIZE } = Constants;
// Get the canvas graphics context
const canvas = document.getElementById('game-canvas');
const context = canvas.getContext('2d');
// Make the canvas fullscreen
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
function render() {
const { me, others, bullets } = getCurrentState();
if (!me) {
return;
}
// Draw background
renderBackground(me.x, me.y);
// Draw all bullets
bullets.forEach(renderBullet.bind(null, me));
// Draw all players
renderPlayer(me, me);
others.forEach(renderPlayer.bind(null, me));
}
// ... Helper functions here excluded
let renderInterval = null;
export function startRendering() {
renderInterval = setInterval(render, 1000 / 60);
}
export function stopRendering() {
clearInterval(renderInterval);
}
рд╕реНрдкрд╖реНрдЯрддрд╛ рдХреЗ рд▓рд┐рдП рдЗрд╕ рдХреЛрдб рдХреЛ рднреА рдЫреЛрдЯрд╛ рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИред
render()
рдЗрд╕ рдлрд╝рд╛рдЗрд▓ рдХрд╛ рдореБрдЦреНрдп рдХрд╛рд░реНрдп рд╣реИ. startRendering()
╨╕ stopRendering()
60 рдПрдлрдкреАрдПрд╕ рдкрд░ рд░реЗрдВрдбрд░ рд▓реВрдк рдХреЗ рд╕рдХреНрд░рд┐рдпрдг рдХреЛ рдирд┐рдпрдВрддреНрд░рд┐рдд рдХрд░реЗрдВред
рд╡реНрдпрдХреНрддрд┐рдЧрдд рдкреНрд░рддрд┐рдкрд╛рджрди рд╕рд╣рд╛рдпрдХ рдХрд╛рд░реНрдпреЛрдВ рдХрд╛ рдареЛрд╕ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди (рдЙрджрд╛. renderBullet()
) рдЙрддрдиреЗ рдорд╣рддреНрд╡рдкреВрд░реНрдг рдирд╣реАрдВ рд╣реИрдВ, рд▓реЗрдХрд┐рди рдпрд╣рд╛рдВ рдПрдХ рд╕рд░рд▓ рдЙрджрд╛рд╣рд░рдг рджрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ:
рд░реЗрдВрдбрд░.рдЬреЗ.рдПрд╕
function renderBullet(me, bullet) {
const { x, y } = bullet;
context.drawImage(
getAsset('bullet.svg'),
canvas.width / 2 + x - me.x - BULLET_RADIUS,
canvas.height / 2 + y - me.y - BULLET_RADIUS,
BULLET_RADIUS * 2,
BULLET_RADIUS * 2,
);
}
рдзреНрдпрд╛рди рджреЗрдВ рдХрд┐ рд╣рдо рд╡рд┐рдзрд┐ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд░рд╣реЗ рд╣реИрдВ getAsset()
, рдЬреЛ рдкрд╣рд▓реЗ рджреЗрдЦрд╛ рдЧрдпрд╛ рдерд╛ asset.js
!
рдпрджрд┐ рдЖрдк рдЕрдиреНрдп рд░реЗрдВрдбрд░рд┐рдВрдЧ рд╕рд╣рд╛рдпрдХреЛрдВ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЬрд╛рдирдиреЗ рдореЗрдВ рд░реБрдЪрд┐ рд░рдЦрддреЗ рд╣реИрдВ, рддреЛ рдмрд╛рдХреА рдкрдврд╝реЗрдВред
src/client/render.js .
6. рдЧреНрд░рд╛рд╣рдХ рдЗрдирдкреБрдЯ
рдпрд╣ рдПрдХ рдЧреЗрдо рдмрдирд╛рдиреЗ рдХрд╛ рд╕рдордп рд╣реИ рдмрдЬрд╛рдиреЗ! рдирд┐рдпрдВрддреНрд░рдг рдпреЛрдЬрдирд╛ рдмрд╣реБрдд рд╕рд░рд▓ рд╣реЛрдЧреА: рдЧрддрд┐ рдХреА рджрд┐рд╢рд╛ рдмрджрд▓рдиреЗ рдХреЗ рд▓рд┐рдП, рдЖрдк рдорд╛рдЙрд╕ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ (рдХрдВрдкреНрдпреВрдЯрд░ рдкрд░) рдпрд╛ рд╕реНрдХреНрд░реАрди рдХреЛ рдЫреВ рд╕рдХрддреЗ рд╣реИрдВ (рдореЛрдмрд╛рдЗрд▓ рдбрд┐рд╡рд╛рдЗрд╕ рдкрд░)ред рдЗрд╕реЗ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╣рдо рд░рдЬрд┐рд╕реНрдЯреНрд░реЗрд╢рди рдХрд░рд╛рдПрдВрдЧреЗ
рдЗрди рд╕рдмрдХрд╛ рдЦреНрдпрд╛рд▓ рд░рдЦреВрдВрдЧрд╛ src/client/input.js
:
рдЗрдирдкреБрдЯ.рдЬреЗ.рдПрд╕
import { updateDirection } from './networking';
function onMouseInput(e) {
handleInput(e.clientX, e.clientY);
}
function onTouchInput(e) {
const touch = e.touches[0];
handleInput(touch.clientX, touch.clientY);
}
function handleInput(x, y) {
const dir = Math.atan2(x - window.innerWidth / 2, window.innerHeight / 2 - y);
updateDirection(dir);
}
export function startCapturingInput() {
window.addEventListener('mousemove', onMouseInput);
window.addEventListener('touchmove', onTouchInput);
}
export function stopCapturingInput() {
window.removeEventListener('mousemove', onMouseInput);
window.removeEventListener('touchmove', onTouchInput);
}
onMouseInput()
╨╕ onTouchInput()
рдЗрд╡реЗрдВрдЯ рд╢реНрд░реЛрддрд╛ рд╣реИрдВ рдЬреЛ рдХреЙрд▓ рдХрд░рддреЗ рд╣реИрдВ updateDirection()
(рд╕реЗ networking.js
) рдЬрдм рдХреЛрдИ рдЗрдирдкреБрдЯ рдИрд╡реЗрдВрдЯ рдШрдЯрд┐рдд рд╣реЛрддрд╛ рд╣реИ (рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдЬрдм рдорд╛рдЙрд╕ рдХреЛ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ)ред updateDirection()
рд╕рд░реНрд╡рд░ рдХреЗ рд╕рд╛рде рдореИрд╕реЗрдЬрд┐рдВрдЧ рдХреЛ рд╕рдВрднрд╛рд▓рддрд╛ рд╣реИ, рдЬреЛ рдЗрдирдкреБрдЯ рдЗрд╡реЗрдВрдЯ рдХреЛ рд╕рдВрднрд╛рд▓рддрд╛ рд╣реИ рдФрд░ рддрджрдиреБрд╕рд╛рд░ рдЧреЗрдо рд╕реНрдерд┐рддрд┐ рдХреЛ рдЕрдкрдбреЗрдЯ рдХрд░рддрд╛ рд╣реИред
7. рдЧреНрд░рд╛рд╣рдХ рд╕реНрдерд┐рддрд┐
рдпрд╣ рд╕реЗрдХреНрд╢рди рдкреЛрд╕реНрдЯ рдХреЗ рдкрд╣рд▓реЗ рднрд╛рдЧ рдореЗрдВ рд╕рдмрд╕реЗ рдХрдард┐рди рд╣реИред рдпрджрд┐ рдЖрдк рдЗрд╕реЗ рдкрд╣рд▓реА рдмрд╛рд░ рдкрдврд╝рдиреЗ рдкрд░ рд╕рдордЭ рдирд╣реАрдВ рдкрд╛рддреЗ рд╣реИрдВ рддреЛ рдирд┐рд░рд╛рд╢ рди рд╣реЛрдВ! рдЖрдк рдЗрд╕реЗ рдЫреЛрдбрд╝ рднреА рд╕рдХрддреЗ рд╣реИрдВ рдФрд░ рдмрд╛рдж рдореЗрдВ рдЗрд╕ рдкрд░ рд╡рд╛рдкрд╕ рдЖ рд╕рдХрддреЗ рд╣реИрдВред
рдХреНрд▓рд╛рдЗрдВрдЯ/рд╕рд░реНрд╡рд░ рдХреЛрдб рдХреЛ рдкреВрд░рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЖрд╡рд╢реНрдпрдХ рдкрд╣реЗрд▓реА рдХрд╛ рдЕрдВрддрд┐рдо рднрд╛рдЧ рд╣реИ рд░рд╛рдЬреНрдп. рдХреНрд▓рд╛рдЗрдВрдЯ рд░реЗрдВрдбрд░рд┐рдВрдЧ рдЕрдиреБрднрд╛рдЧ рд╕реЗ рдХреЛрдб рд╕реНрдирд┐рдкреЗрдЯ рдпрд╛рдж рд╣реИ?
рд░реЗрдВрдбрд░.рдЬреЗ.рдПрд╕
import { getCurrentState } from './state';
function render() {
const { me, others, bullets } = getCurrentState();
// Do the rendering
// ...
}
getCurrentState()
рд╣рдореЗрдВ рдХреНрд▓рд╛рдЗрдВрдЯ рдореЗрдВ рдЧреЗрдо рдХреА рд╡рд░реНрддрдорд╛рди рд╕реНрдерд┐рддрд┐ рдмрддрд╛рдиреЗ рдореЗрдВ рд╕рдХреНрд╖рдо рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдП рдХрд┐рд╕реА рднреА рд╕рдордп рд╕рд░реНрд╡рд░ рд╕реЗ рдкреНрд░рд╛рдкреНрдд рдЕрдкрдбреЗрдЯ рдХреЗ рдЖрдзрд╛рд░ рдкрд░ред рдпрд╣рд╛рдВ рдЧреЗрдо рдЕрдкрдбреЗрдЯ рдХрд╛ рдПрдХ рдЙрджрд╛рд╣рд░рдг рджрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ рдЬрд┐рд╕реЗ рд╕рд░реНрд╡рд░ рднреЗрдЬ рд╕рдХрддрд╛ рд╣реИ:
{
"t": 1555960373725,
"me": {
"x": 2213.8050880413657,
"y": 1469.370893425012,
"direction": 1.3082443894581433,
"id": "AhzgAtklgo2FJvwWAADO",
"hp": 100
},
"others": [],
"bullets": [
{
"id": "RUJfJ8Y18n",
"x": 2354.029197099604,
"y": 1431.6848318262666
},
{
"id": "ctg5rht5s",
"x": 2260.546457727445,
"y": 1456.8088728920968
}
],
"leaderboard": [
{
"username": "Player",
"score": 3
}
]
}
рдкреНрд░рддреНрдпреЗрдХ рдЧреЗрдо рдЕрдкрдбреЗрдЯ рдореЗрдВ рдкрд╛рдБрдЪ рд╕рдорд╛рди рдлрд╝реАрд▓реНрдб рд╣реЛрддреЗ рд╣реИрдВ:
- t: рд╕рд░реНрд╡рд░ рдЯрд╛рдЗрдорд╕реНрдЯреИрдореНрдк рдпрд╣ рджрд░реНрд╢рд╛рддрд╛ рд╣реИ рдХрд┐ рдпрд╣ рдЕрджреНрдпрддрди рдХрдм рдмрдирд╛рдпрд╛ рдЧрдпрд╛ рдерд╛ред
- me: рдЗрд╕ рдЕрджреНрдпрддрди рдХреЛ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рд╡рд╛рд▓реЗ рдЦрд┐рд▓рд╛рдбрд╝реА рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЬрд╛рдирдХрд╛рд░реАред
- рджреВрд╕рд░реЛрдВ: рдПрдХ рд╣реА рдЦреЗрд▓ рдореЗрдВ рднрд╛рдЧ рд▓реЗрдиреЗ рд╡рд╛рд▓реЗ рдЕрдиреНрдп рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЬрд╛рдирдХрд╛рд░реА рдХреА рдПрдХ рд╢реНрд░реГрдВрдЦрд▓рд╛ред
- рдЧреЛрд▓рд┐рдпреЛрдВ: рдЦреЗрд▓ рдореЗрдВ рдкреНрд░реЛрдЬреЗрдХреНрдЯрд╛рдЗрд▓ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЬрд╛рдирдХрд╛рд░реА рдХреА рдПрдХ рд╢реНрд░реГрдВрдЦрд▓рд╛ред
- рд▓реАрдбрд░рдмреЛрд░реНрдб: рд╡рд░реНрддрдорд╛рди рд▓реАрдбрд░рдмреЛрд░реНрдб рдбреЗрдЯрд╛ред рдЗрд╕ рдкреЛрд╕реНрдЯ рдореЗрдВ рд╣рдо рдЙрди рдкрд░ рд╡рд┐рдЪрд╛рд░ рдирд╣реАрдВ рдХрд░реЗрдВрдЧреЗ.
7.1 рдЕрдиреБрднрд╡рд╣реАрди рдЧреНрд░рд╛рд╣рдХ рд╕реНрдерд┐рддрд┐
рдЕрдиреБрднрд╡рд╣реАрди рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди getCurrentState()
рдХреЗрд╡рд▓ рд╕рдмрд╕реЗ рд╣рд╛рд▓ рд╣реА рдореЗрдВ рдкреНрд░рд╛рдкреНрдд рдЧреЗрдо рдЕрдкрдбреЗрдЯ рдХрд╛ рдбреЗрдЯрд╛ рд╕реАрдзреЗ рд▓реМрдЯрд╛ рд╕рдХрддрд╛ рд╣реИред
рдЕрдиреБрднрд╡рд╣реАрди-рд░рд╛рдЬреНрдп.рдЬреЗ.рдПрд╕
let lastGameUpdate = null;
// Handle a newly received game update.
export function processGameUpdate(update) {
lastGameUpdate = update;
}
export function getCurrentState() {
return lastGameUpdate;
}
рдЕрдЪреНрдЫрд╛ рдФрд░ рд╕реНрдкрд╖реНрдЯ! рд▓реЗрдХрд┐рди рдХрд╛рд╢ рдпрд╣ рдЗрддрдирд╛ рдЖрд╕рд╛рди рд╣реЛрддрд╛. рдЗрд╕ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХреЗ рд╕рдорд╕реНрдпрд╛рдЧреНрд░рд╕реНрдд рд╣реЛрдиреЗ рдХреЗ рдХрд╛рд░рдгреЛрдВ рдореЗрдВ рд╕реЗ рдПрдХ: рдпрд╣ рд░реЗрдВрдбрд░рд┐рдВрдЧ рдлрд╝реНрд░реЗрдо рджрд░ рдХреЛ рд╕рд░реНрд╡рд░ рдХреНрд▓реЙрдХ рджрд░ рддрдХ рд╕реАрдорд┐рдд рдХрд░рддрд╛ рд╣реИ.
рдлреНрд░реЗрдо рд░реЗрдЯ: рдлрд╝реНрд░реЗрдо рдХреА рд╕рдВрдЦреНрдпрд╛ (рдЕрд░реНрдерд╛рдд рдХреЙрд▓
render()
) рдкреНрд░рддрд┐ рд╕реЗрдХрдВрдб, рдпрд╛ рдПрдлрдкреАрдПрд╕ред рдЦреЗрд▓ рдЖрдорддреМрд░ рдкрд░ рдХрдо рд╕реЗ рдХрдо 60 рдПрдлрдкреАрдПрд╕ рд╣рд╛рд╕рд┐рд▓ рдХрд░рдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░рддреЗ рд╣реИрдВред
рджрд░ рдкрд░ рдЯрд┐рдХ рдХрд░реЗрдВ: рд╡рд╣ рдЖрд╡реГрддреНрддрд┐ рдЬрд┐рд╕ рдкрд░ рд╕рд░реНрд╡рд░ рдХреНрд▓рд╛рдЗрдВрдЯ рдХреЛ рдЧреЗрдо рдЕрдкрдбреЗрдЯ рднреЗрдЬрддрд╛ рд╣реИред рдпрд╣ рдЕрдХреНрд╕рд░ рдлрд╝реНрд░реЗрдо рджрд░ рд╕реЗ рдХрдо рд╣реЛрддрд╛ рд╣реИ. рд╣рдорд╛рд░реЗ рдЧреЗрдо рдореЗрдВ, рд╕рд░реНрд╡рд░ 30 рдЪрдХреНрд░ рдкреНрд░рддрд┐ рд╕реЗрдХрдВрдб рдХреА рдЖрд╡реГрддреНрддрд┐ рдкрд░ рдЪрд▓рддрд╛ рд╣реИред
рдпрджрд┐ рд╣рдо рдЧреЗрдо рдХреЗ рдирд╡реАрдирддрдо рдЕрдкрдбреЗрдЯ рдХреЛ рдкреНрд░рд╕реНрддреБрдд рдХрд░рддреЗ рд╣реИрдВ, рддреЛ рдПрдлрдкреАрдПрд╕ рдЕрдирд┐рд╡рд╛рд░реНрдп рд░реВрдк рд╕реЗ рдХрднреА рднреА 30 рд╕реЗ рдЕрдзрд┐рдХ рдирд╣реАрдВ рд╣реЛрдЧрд╛, рдХреНрдпреЛрдВрдХрд┐ рд╣рдореЗрдВ рд╕рд░реНрд╡рд░ рд╕реЗ рдХрднреА рднреА рдкреНрд░рддрд┐ рд╕реЗрдХрдВрдб 30 рд╕реЗ рдЕрдзрд┐рдХ рдЕрдкрдбреЗрдЯ рдирд╣реАрдВ рдорд┐рд▓рддреЗ. рднрд▓реЗ рд╣реА рд╣рдо рдХреЙрд▓ рдХрд░реЗрдВ render()
рдкреНрд░рддрд┐ рд╕реЗрдХрдВрдб 60 рдмрд╛рд░, рддреЛ рдЗрдирдореЗрдВ рд╕реЗ рдЖрдзреА рдХреЙрд▓реЗрдВ рдмрд╕ рдЙрд╕реА рдЪреАрдЬрд╝ рдХреЛ рдлрд┐рд░ рд╕реЗ рддреИрдпрд╛рд░ рдХрд░реЗрдВрдЧреА, рдЕрдирд┐рд╡рд╛рд░реНрдп рд░реВрдк рд╕реЗ рдХреБрдЫ рднреА рдирд╣реАрдВ рдХрд░реЗрдВрдЧреАред рд╕рд░рд▓ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХреЗ рд╕рд╛рде рдПрдХ рдФрд░ рд╕рдорд╕реНрдпрд╛ рдпрд╣ рд╣реИ рдХрд┐ рдпрд╣ рджреЗрд░реА рдХреА рд╕рдВрднрд╛рд╡рдирд╛. рдЖрджрд░реНрд╢ рдЗрдВрдЯрд░рдиреЗрдЯ рд╕реНрдкреАрдб рдХреЗ рд╕рд╛рде, рдХреНрд▓рд╛рдЗрдВрдЯ рдХреЛ рд╣рд░ 33ms (30 рдкреНрд░рддрд┐ рд╕реЗрдХрдВрдб) рдкрд░ рдПрдХ рдЧреЗрдо рдЕрдкрдбреЗрдЯ рдкреНрд░рд╛рдкреНрдд рд╣реЛрдЧрд╛:
рджреБрд░реНрднрд╛рдЧреНрдп рд╕реЗ, рдХреБрдЫ рднреА рдкреВрд░реНрдг рдирд╣реАрдВ рд╣реИред рдПрдХ рдЕрдзрд┐рдХ рдпрдерд╛рд░реНрдерд╡рд╛рджреА рдЪрд┐рддреНрд░ рд╣реЛрдЧрд╛:
рдЬрдм рд╡рд┐рд▓рдВрдмрддрд╛ рдХреА рдмрд╛рдд рдЖрддреА рд╣реИ рддреЛ рдЕрдиреБрднрд╡рд╣реАрди рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рд╡реНрдпрд╛рд╡рд╣рд╛рд░рд┐рдХ рд░реВрдк рд╕реЗ рд╕рдмрд╕реЗ рдЦрд░рд╛рдм рд╕реНрдерд┐рддрд┐ рд╣реИред рдпрджрд┐ рдХреЛрдИ рдЧреЗрдо рдЕрдкрдбреЗрдЯ 50ms рдХреА рджреЗрд░реА рд╕реЗ рдкреНрд░рд╛рдкреНрдд рд╣реЛрддрд╛ рд╣реИ, рддреЛ рдЧреНрд░рд╛рд╣рдХ рд╕реНрдЯреЙрд▓ рдЕрддрд┐рд░рд┐рдХреНрдд 50 рдПрдордПрд╕ рдХреНрдпреЛрдВрдХрд┐ рдпрд╣ рдЕрднреА рднреА рдкрд┐рдЫрд▓реЗ рдЕрдкрдбреЗрдЯ рд╕реЗ рдЧреЗрдо рд╕реНрдерд┐рддрд┐ рдкреНрд░рд╕реНрддреБрдд рдХрд░ рд░рд╣рд╛ рд╣реИред рдЖрдк рдХрд▓реНрдкрдирд╛ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ рдХрд┐ рдпрд╣ рдЦрд┐рд▓рд╛рдбрд╝реА рдХреЗ рд▓рд┐рдП рдХрд┐рддрдирд╛ рдЕрд╕реБрд╡рд┐рдзрд╛рдЬрдирдХ рд╣реИ: рдордирдорд╛рдиреЗ рдврдВрдЧ рд╕реЗ рдмреНрд░реЗрдХ рд▓рдЧрд╛рдиреЗ рд╕реЗ рдЦреЗрд▓ рдЭрдЯрдХреЗрджрд╛рд░ рдФрд░ рдЕрд╕реНрдерд┐рд░ рд▓рдЧреЗрдЧрд╛ред
7.2 рдмреЗрд╣рддрд░ рдЧреНрд░рд╛рд╣рдХ рд╕реНрдерд┐рддрд┐
рд╣рдо рд╕рд░рд▓ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдореЗрдВ рдХреБрдЫ рд╕реБрдзрд╛рд░ рдХрд░реЗрдВрдЧреЗред рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рд╣рдо рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВ рджреЗрд░реА рдкреНрд░рджрд╛рди рдХрд░рдирд╛ 100 рдПрдордПрд╕ рдХреЗ рд▓рд┐рдП. рдЗрд╕рдХрд╛ рдорддрд▓рдм рдпрд╣ рд╣реИ рдХрд┐ рдХреНрд▓рд╛рдЗрдВрдЯ рдХреА "рд╡рд░реНрддрдорд╛рди" рд╕реНрдерд┐рддрд┐ рд╕рд░реНрд╡рд░ рдкрд░ рдЧреЗрдо рдХреА рд╕реНрдерд┐рддрд┐ рд╕реЗ рд╣рдореЗрд╢рд╛ 100ms рдкреАрдЫреЗ рд░рд╣реЗрдЧреАред рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдпрджрд┐ рд╕рд░реНрд╡рд░ рдкрд░ рд╕рдордп рд╣реИ 150, рддреЛ рдХреНрд▓рд╛рдЗрдВрдЯ рдЙрд╕ рд╕реНрдерд┐рддрд┐ рдХреЛ рдкреНрд░рд╕реНрддреБрдд рдХрд░реЗрдЧрд╛ рдЬрд┐рд╕рдореЗрдВ рд╕рд░реНрд╡рд░ рдЙрд╕ рд╕рдордп рдерд╛ 50:
рдпрд╣ рд╣рдореЗрдВ рдЕрдкреНрд░рддреНрдпрд╛рд╢рд┐рдд рдЧреЗрдо рдЕрдкрдбреЗрдЯ рд╕рдордп рд╕реЗ рдмрдЪрдиреЗ рдХреЗ рд▓рд┐рдП 100ms рдХрд╛ рдмрдлрд░ рджреЗрддрд╛ рд╣реИ:
рдЗрд╕рдХреЗ рд▓рд┐рдП рднреБрдЧрддрд╛рди рд╕реНрдерд╛рдпреА рд╣реЛрдЧрд╛
рд╣рдо рдирд╛рдордХ рдПрдХ рдЕрдиреНрдп рддрдХрдиреАрдХ рдХрд╛ рднреА рдЙрдкрдпреЛрдЧ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ
рдЧреНрд░рд╛рд╣рдХ-рдкрдХреНрд╖ рднрд╡рд┐рд╖реНрдпрд╡рд╛рдгреА , рдЬреЛ рдХрдерд┐рдд рд╡рд┐рд▓рдВрдмрддрд╛ рдХреЛ рдХрдо рдХрд░рдиреЗ рдХрд╛ рдЕрдЪреНрдЫрд╛ рдХрд╛рдо рдХрд░рддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдЗрд╕ рдкреЛрд╕реНрдЯ рдореЗрдВ рдЗрд╕реЗ рдХрд╡рд░ рдирд╣реАрдВ рдХрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛ред
рдПрдХ рдФрд░ рд╕реБрдзрд╛рд░ рдЬреЛ рд╣рдо рдЙрдкрдпреЛрдЧ рдХрд░ рд░рд╣реЗ рд╣реИрдВ рд╡рд╣ рд╣реИ рд░реЗрдЦрд┐рдХ рдЖрдВрддрд░рд┐рдХ. рд░реЗрдВрдбрд░рд┐рдВрдЧ рд▓реИрдЧ рдХреЗ рдХрд╛рд░рдг, рд╣рдо рдЖрдо рддреМрд░ рдкрд░ рдХреНрд▓рд╛рдЗрдВрдЯ рдореЗрдВ рд╡рд░реНрддрдорд╛рди рд╕рдордп рд╕реЗ рдХрдо рд╕реЗ рдХрдо рдПрдХ рдЕрдкрдбреЗрдЯ рдЖрдЧреЗ рд░рдЦрддреЗ рд╣реИрдВред рдЬрдм рдмреБрд▓рд╛рдпрд╛ рдЧрдпрд╛ getCurrentState()
, рд╣рдо рдирд┐рд╖реНрдкрд╛рджрд┐рдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ
рдЗрд╕рд╕реЗ рдлрд╝реНрд░реЗрдо рджрд░ рдХреА рд╕рдорд╕реНрдпрд╛ рд╣рд▓ рд╣реЛ рдЬрд╛рддреА рд╣реИ: рдЕрдм рд╣рдо рдЕрдкрдиреА рдЗрдЪреНрдЫрд╛рдиреБрд╕рд╛рд░ рдХрд┐рд╕реА рднреА рдлрд╝реНрд░реЗрдо рджрд░ рдкрд░ рдЕрджреНрд╡рд┐рддреАрдп рдлрд╝реНрд░реЗрдо рдкреНрд░рд╕реНрддреБрдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ!
7.3 рдЙрдиреНрдирдд рдЧреНрд░рд╛рд╣рдХ рд╕реНрдерд┐рддрд┐ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдирд╛
рдореЗрдВ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдЙрджрд╛рд╣рд░рдг src/client/state.js
рд░реЗрдВрдбрд░ рд▓реИрдЧ рдФрд░ рд▓реАрдирд┐рдпрд░ рдЗрдВрдЯрд░рдкреЛрд▓реЗрд╢рди рджреЛрдиреЛрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рд▓рдВрдмреЗ рд╕рдордп рддрдХ рдирд╣реАрдВред рдЖрдЗрдП рдХреЛрдб рдХреЛ рджреЛ рднрд╛рдЧреЛрдВ рдореЗрдВ рддреЛрдбрд╝реЗрдВред рдпрд╣ рдкрд╣рд▓рд╛ рд╡рд┐рдХрд▓реНрдк рд╣реИ:
рд░рд╛рдЬреНрдп.рдЬреЗрдПрд╕ рднрд╛рдЧ 1
const RENDER_DELAY = 100;
const gameUpdates = [];
let gameStart = 0;
let firstServerTimestamp = 0;
export function initState() {
gameStart = 0;
firstServerTimestamp = 0;
}
export function processGameUpdate(update) {
if (!firstServerTimestamp) {
firstServerTimestamp = update.t;
gameStart = Date.now();
}
gameUpdates.push(update);
// Keep only one game update before the current server time
const base = getBaseUpdate();
if (base > 0) {
gameUpdates.splice(0, base);
}
}
function currentServerTime() {
return firstServerTimestamp + (Date.now() - gameStart) - RENDER_DELAY;
}
// Returns the index of the base update, the first game update before
// current server time, or -1 if N/A.
function getBaseUpdate() {
const serverTime = currentServerTime();
for (let i = gameUpdates.length - 1; i >= 0; i--) {
if (gameUpdates[i].t <= serverTime) {
return i;
}
}
return -1;
}
рдкрд╣рд▓рд╛ рдХрджрдо рдпрд╣ рдкрддрд╛ рд▓рдЧрд╛рдирд╛ рд╣реИ рдХрд┐ рдХреНрдпрд╛ рд╣реИ currentServerTime()
. рдЬреИрд╕рд╛ рдХрд┐ рд╣рдордиреЗ рдкрд╣рд▓реЗ рджреЗрдЦрд╛, рдкреНрд░рддреНрдпреЗрдХ рдЧреЗрдо рдЕрдкрдбреЗрдЯ рдореЗрдВ рдПрдХ рд╕рд░реНрд╡рд░ рдЯрд╛рдЗрдорд╕реНрдЯреИрдореНрдк рд╢рд╛рдорд┐рд▓ рд╣реЛрддрд╛ рд╣реИред рд╣рдо рдЫрд╡рд┐ рдХреЛ рд╕рд░реНрд╡рд░ рд╕реЗ 100 рдПрдордПрд╕ рдкреАрдЫреЗ рд░реЗрдВрдбрд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд░реЗрдВрдбрд░ рд╡рд┐рд▓рдВрдмрддрд╛ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВ, рд▓реЗрдХрд┐рди рд╣рдо рд╕рд░реНрд╡рд░ рдкрд░ рд╡рд░реНрддрдорд╛рди рд╕рдордп рдХрднреА рдирд╣реАрдВ рдЬрд╛рди рдкрд╛рдПрдВрдЧреЗ, рдХреНрдпреЛрдВрдХрд┐ рд╣рдо рдирд╣реАрдВ рдЬрд╛рди рд╕рдХрддреЗ рдХрд┐ рдХрд┐рд╕реА рднреА рдЕрдкрдбреЗрдЯ рдХреЛ рд╣рдо рддрдХ рдкрд╣реБрдВрдЪрдиреЗ рдореЗрдВ рдХрд┐рддрдирд╛ рд╕рдордп рд▓рдЧрд╛ред рдЗрдВрдЯрд░рдиреЗрдЯ рдЕрдкреНрд░рддреНрдпрд╛рд╢рд┐рдд рд╣реИ рдФрд░ рдЗрд╕рдХреА рдЧрддрд┐ рдмрд╣реБрдд рднрд┐рдиреНрди рд╣реЛ рд╕рдХрддреА рд╣реИ!
рдЗрд╕ рд╕рдорд╕реНрдпрд╛ рд╕реЗ рдирд┐рдЬрд╛рдд рдкрд╛рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдо рдПрдХ рдЙрдЪрд┐рдд рдЕрдиреБрдорд╛рди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ: рд╣рдо рджрд┐рдЦрд╛рд╡рд╛ рдХрд░реЗрдВ рдХрд┐ рдкрд╣рд▓рд╛ рдЕрдкрдбреЗрдЯ рддреБрд░рдВрдд рдЖ рдЧрдпрд╛. рдпрджрд┐ рдпрд╣ рд╕рдЪ рд╣реЛрддрд╛, рддреЛ рд╣рдореЗрдВ рдЗрд╕ рд╡рд┐рд╢реЗрд╖ рдХреНрд╖рдг рдореЗрдВ рд╕рд░реНрд╡рд░ рдХрд╛ рд╕рдордп рдкрддрд╛ рд╣реЛрддрд╛! рд╣рдо рд╕рд░реНрд╡рд░ рдХрд╛ рдЯрд╛рдЗрдорд╕реНрдЯреИрдореНрдк рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд░рддреЗ рд╣реИрдВ firstServerTimestamp
рдФрд░ рд╣рдорд╛рд░реЗ рд░рдЦреЛ рд╕реНрдерд╛рдиреАрдп (рдХреНрд▓рд╛рдЗрдВрдЯ) рдЙрд╕реА рдХреНрд╖рдг рдЯрд╛рдЗрдорд╕реНрдЯреИрдореНрдк gameStart
.
рдЕрд░реЗ рд░реБрдХреЛред рдХреНрдпрд╛ рдпрд╣ рд╕рд░реНрд╡рд░ рд╕рдордп = рдХреНрд▓рд╛рдЗрдВрдЯ рд╕рдордп рдирд╣реАрдВ рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдП? рд╣рдо "рд╕рд░реНрд╡рд░ рдЯрд╛рдЗрдорд╕реНрдЯреИрдореНрдк" рдФрд░ "рдХреНрд▓рд╛рдЗрдВрдЯ рдЯрд╛рдЗрдорд╕реНрдЯреИрдореНрдк" рдХреЗ рдмреАрдЪ рдЕрдВрддрд░ рдХреНрдпреЛрдВ рдХрд░рддреЗ рд╣реИрдВ? рдпрд╣ рдПрдХ рдмрдбрд╝рд╛ рд╕рд╡рд╛рд▓ рд╣реИ! рдЗрд╕рд╕реЗ рдкрддрд╛ рдЪрд▓рддрд╛ рд╣реИ рдХрд┐ рд╡реЗ рдПрдХ рд╣реА рдЪреАрдЬрд╝ рдирд╣реАрдВ рд╣реИрдВред Date.now()
рдХреНрд▓рд╛рдЗрдВрдЯ рдФрд░ рд╕рд░реНрд╡рд░ рдореЗрдВ рдЕрд▓рдЧ-рдЕрд▓рдЧ рдЯрд╛рдЗрдорд╕реНрдЯреИрдореНрдк рд▓реМрдЯрд╛рдПрдЧрд╛, рдФрд░ рдпрд╣ рдЗрди рдорд╢реАрдиреЛрдВ рдХреЗ рд╕реНрдерд╛рдиреАрдп рдХрд╛рд░рдХреЛрдВ рдкрд░ рдирд┐рд░реНрднрд░ рдХрд░рддрд╛ рд╣реИред рдпрд╣ рдХрднреА рди рдорд╛рдиреЗрдВ рдХрд┐ рдЯрд╛рдЗрдорд╕реНрдЯреИрдореНрдк рд╕рднреА рдорд╢реАрдиреЛрдВ рдкрд░ рд╕рдорд╛рди рд╣реЛрдВрдЧреЗред
рдЕрдм рд╣рдо рд╕рдордЭрддреЗ рд╣реИрдВ рдХрд┐ рдХреНрдпрд╛ рдХрд░рддрд╛ рд╣реИ currentServerTime()
: рдпрд╣ рд╡рд╛рдкрд╕ рдЖрддрд╛ рд╣реИ рд╡рд░реНрддрдорд╛рди рд░реЗрдВрдбрд░ рд╕рдордп рдХрд╛ рд╕рд░реНрд╡рд░ рдЯрд╛рдЗрдорд╕реНрдЯреИрдореНрдк. рджреВрд╕рд░реЗ рд╢рдмреНрджреЛрдВ рдореЗрдВ, рдпрд╣ рд╕рд░реНрд╡рд░ рдХрд╛ рд╡рд░реНрддрдорд╛рди рд╕рдордп рд╣реИ (firstServerTimestamp <+ (Date.now() - gameStart)
) рдорд╛рдЗрдирд╕ рд░реЗрдВрдбрд░ рд╡рд┐рд▓рдВрдм (RENDER_DELAY
).
рдЖрдЗрдП рдЕрдм рджреЗрдЦреЗрдВ рдХрд┐ рд╣рдо рдЧреЗрдо рдЕрдкрдбреЗрдЯ рдХреЛ рдХреИрд╕реЗ рдкреНрд░рдмрдВрдзрд┐рдд рдХрд░рддреЗ рд╣реИрдВред рдЕрджреНрдпрддрди рд╕рд░реНрд╡рд░ рд╕реЗ рдкреНрд░рд╛рдкреНрдд рд╣реЛрдиреЗ рдкрд░, рдЗрд╕реЗ рдХреЙрд▓ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ processGameUpdate()
рдФрд░ рд╣рдо рдирдП рдЕрдкрдбреЗрдЯ рдХреЛ рдПрдХ рдРрд░реЗ рдореЗрдВ рд╕рд╣реЗрдЬрддреЗ рд╣реИрдВ gameUpdates
. рдлрд┐рд░, рдореЗрдореЛрд░реА рдЙрдкрдпреЛрдЧ рдХреА рдЬрд╛рдВрдЪ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдо рдкрд╣рд▓реЗ рдХреЗ рд╕рднреА рдкреБрд░рд╛рдиреЗ рдЕрдкрдбреЗрдЯ рд╣рдЯрд╛ рджреЗрддреЗ рд╣реИрдВ рдЖрдзрд╛рд░ рдЕрджреНрдпрддрдирдХреНрдпреЛрдВрдХрд┐ рдЕрдм рд╣рдореЗрдВ рдЙрдирдХреА рдЬрд░реВрд░рдд рдирд╣реАрдВ рд╣реИ.
"рдмреБрдирд┐рдпрд╛рджреА рдЕрджреНрдпрддрди" рдХреНрдпрд╛ рд╣реИ? рдпрд╣ рдкрд╣рд▓рд╛ рдЕрдкрдбреЗрдЯ рд╣рдореЗрдВ рд╕рд░реНрд╡рд░ рдХреЗ рд╡рд░реНрддрдорд╛рди рд╕рдордп рд╕реЗ рдкреАрдЫреЗ рдХреА рдУрд░ рдЬрд╛рдХрд░ рдорд┐рд▓рддрд╛ рд╣реИ. рдпрд╣ рдЖрд░реЗрдЦ рдпрд╛рдж рд╣реИ?
рдЧреЗрдо рдЕрдкрдбреЗрдЯ рд╕реАрдзреЗ "рдХреНрд▓рд╛рдЗрдВрдЯ рд░реЗрдВрдбрд░ рдЯрд╛рдЗрдо" рдХреЗ рдмрд╛рдИрдВ рдУрд░ рдмреЗрд╕ рдЕрдкрдбреЗрдЯ рд╣реИред
рдЖрдзрд╛рд░ рдЕрджреНрдпрддрди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд┐рд╕ рд▓рд┐рдП рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ? рд╣рдо рдЕрдкрдбреЗрдЯ рдХреЛ рдмреЗрд╕рд▓рд╛рдЗрди рдкрд░ рдХреНрдпреЛрдВ рдЫреЛрдбрд╝ рд╕рдХрддреЗ рд╣реИрдВ? рдЗрд╕реЗ рдЬрд╛рдирдиреЗ рдХреЗ рд▓рд┐рдП, рдЖрдЗрдП рдЕрдВрдд рдореЗрдВ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдкрд░ рд╡рд┐рдЪрд╛рд░ рдХрд░реЗрдВ getCurrentState()
:
рд░рд╛рдЬреНрдп.рдЬреЗрдПрд╕ рднрд╛рдЧ 2
export function getCurrentState() {
if (!firstServerTimestamp) {
return {};
}
const base = getBaseUpdate();
const serverTime = currentServerTime();
// If base is the most recent update we have, use its state.
// Else, interpolate between its state and the state of (base + 1).
if (base < 0) {
return gameUpdates[gameUpdates.length - 1];
} else if (base === gameUpdates.length - 1) {
return gameUpdates[base];
} else {
const baseUpdate = gameUpdates[base];
const next = gameUpdates[base + 1];
const r = (serverTime - baseUpdate.t) / (next.t - baseUpdate.t);
return {
me: interpolateObject(baseUpdate.me, next.me, r),
others: interpolateObjectArray(baseUpdate.others, next.others, r),
bullets: interpolateObjectArray(baseUpdate.bullets, next.bullets, r),
};
}
}
рд╣рдо рддреАрди рдорд╛рдорд▓реЗ рд╕рдВрднрд╛рд▓рддреЗ рд╣реИрдВ:
base < 0
рдЗрд╕рдХрд╛ рдорддрд▓рдм рд╣реИ рдХрд┐ рд╡рд░реНрддрдорд╛рди рд░реЗрдВрдбрд░ рд╕рдордп рддрдХ рдХреЛрдИ рдЕрдкрдбреЗрдЯ рдирд╣реАрдВ рд╣реИ (рдЙрдкрд░реЛрдХреНрдд рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рджреЗрдЦреЗрдВ)редgetBaseUpdate()
). рд░реЗрдВрдбрд░рд┐рдВрдЧ рд▓реИрдЧ рдХреЗ рдХрд╛рд░рдг рдЧреЗрдо рдХреА рд╢реБрд░реБрдЖрдд рдореЗрдВ рд╣реА рдРрд╕рд╛ рд╣реЛ рд╕рдХрддрд╛ рд╣реИред рдЗрд╕ рдорд╛рдорд▓реЗ рдореЗрдВ, рд╣рдо рдкреНрд░рд╛рдкреНрдд рдирд╡реАрдирддрдо рдЕрдкрдбреЗрдЯ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВредbase
рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдирд╡реАрдирддрдо рдЕрдкрдбреЗрдЯ рд╣реИред рдпрд╣ рдиреЗрдЯрд╡рд░реНрдХ рд╡рд┐рд▓рдВрдм рдпрд╛ рдЦрд╝рд░рд╛рдм рдЗрдВрдЯрд░рдиреЗрдЯ рдХрдиреЗрдХреНрд╢рди рдХреЗ рдХрд╛рд░рдг рд╣реЛ рд╕рдХрддрд╛ рд╣реИред рдРрд╕реЗ рдореЗрдВ рд╣рдо рдЕрдкрдиреЗ рдкрд╛рд╕ рдореМрдЬреВрдж рдирд╡реАрдирддрдо рдЕрдкрдбреЗрдЯ рдХрд╛ рднреА рдЙрдкрдпреЛрдЧ рдХрд░ рд░рд╣реЗ рд╣реИрдВред- рд╡рд░реНрддрдорд╛рди рд░реЗрдВрдбрд░ рд╕рдордп рд╕реЗ рдкрд╣рд▓реЗ рдФрд░ рдмрд╛рдж рдореЗрдВ рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдЕрдкрдбреЗрдЯ рд╣реИ, рдЗрд╕рд▓рд┐рдП рд╣рдо рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ рдмреИрдард╛рдирд╛!
рд╡рд╣ рд╕рдм рдХреБрдЫ рдмрдЪрд╛ рд╣реБрдЖ рд╣реИ state.js
рд░реИрдЦрд┐рдХ рдкреНрд░рдХреНрд╖реЗрдк рдХрд╛ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рд╣реИ рдЬреЛ рд╕рд░рд▓ (рд▓реЗрдХрд┐рди рдЙрдмрд╛рдК) рдЧрдгрд┐рдд рд╣реИред рдЕрдЧрд░ рдЖрдк рдЗрд╕реЗ рдЦреБрдж рдПрдХреНрд╕реНрдкреНрд▓реЛрд░ рдХрд░рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВ рддреЛ рдУрдкрди рдХрд░реЗрдВ state.js
рдкрд░
рднрд╛рдЧ 2. рдмреИрдХрдПрдВрдб рд╕рд░реНрд╡рд░
рдЗрд╕ рднрд╛рдЧ рдореЗрдВ, рд╣рдо Node.js рдмреИрдХрдПрдВрдб рдкрд░ рдПрдХ рдирдЬрд╝рд░ рдбрд╛рд▓реЗрдВрдЧреЗ рдЬреЛ рд╣рдореЗрдВ рдирд┐рдпрдВрддреНрд░рд┐рдд рдХрд░рддрд╛ рд╣реИ
1. рд╕рд░реНрд╡рд░ рдкреНрд░рд╡реЗрд╢ рдмрд┐рдВрджреБ
рд╡реЗрдм рд╕рд░реНрд╡рд░ рдХреЛ рдкреНрд░рдмрдВрдзрд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдо Node.js рдирд╛рдордХ рдПрдХ рд▓реЛрдХрдкреНрд░рд┐рдп рд╡реЗрдм рдлреНрд░реЗрдорд╡рд░реНрдХ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВрдЧреЗ src/server/server.js
:
рд╕рд░реНрд╡рд░.рдЬреЗрдПрд╕ рднрд╛рдЧ 1
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackConfig = require('../../webpack.dev.js');
// Setup an Express server
const app = express();
app.use(express.static('public'));
if (process.env.NODE_ENV === 'development') {
// Setup Webpack for development
const compiler = webpack(webpackConfig);
app.use(webpackDevMiddleware(compiler));
} else {
// Static serve the dist/ folder in production
app.use(express.static('dist'));
}
// Listen on port
const port = process.env.PORT || 3000;
const server = app.listen(port);
console.log(`Server listening on port ${port}`);
рдпрд╛рдж рд░рдЦреЗрдВ рдХрд┐ рдкрд╣рд▓реЗ рднрд╛рдЧ рдореЗрдВ рд╣рдордиреЗ рд╡реЗрдмрдкреИрдХ рдкрд░ рдЪрд░реНрдЪрд╛ рдХреА рдереА? рдпрд╣реАрдВ рдкрд░ рд╣рдо рдЕрдкрдиреЗ рд╡реЗрдмрдкреИрдХ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВрдЧреЗред рд╣рдо рдЙрдирдХрд╛ рджреЛ рддрд░рд╣ рд╕реЗ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВрдЧреЗ:
- рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП
рд╡реЗрдмрдкреИрдХ-рдбреЗрд╡-рдорд┐рдбрд▓рд╡реЗрдпрд░ рд╣рдорд╛рд░реЗ рд╡рд┐рдХрд╛рд╕ рдкреИрдХреЗрдЬреЛрдВ рдХреЛ рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рд░реВрдк рд╕реЗ рдкреБрдирд░реНрдирд┐рд░реНрдорд╛рдг рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдпрд╛ - рдлрд╝реЛрд▓реНрдбрд░ рдХреЛ рд╕реНрдерд┐рд░ рд░реВрдк рд╕реЗ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд░реЗрдВ
dist/
, рдЬрд┐рд╕рдореЗрдВ рд╡реЗрдмрдкреИрдХ рдЙрддреНрдкрд╛рджрди рдирд┐рд░реНрдорд╛рдг рдХреЗ рдмрд╛рдж рд╣рдорд╛рд░реА рдлрд╝рд╛рдЗрд▓реЗрдВ рд▓рд┐рдЦреЗрдЧрд╛ред
рдПрдХ рдФрд░ рдорд╣рддреНрд╡рдкреВрд░реНрдг рдХрд╛рд░реНрдп server.js
рд╕рд░реНрд╡рд░ рд╕реЗрдЯ рдХрд░рдирд╛ рд╣реИ
рд╕рд░реНрд╡рд░.рдЬреЗрдПрд╕ рднрд╛рдЧ 2
const socketio = require('socket.io');
const Constants = require('../shared/constants');
// Setup Express
// ...
const server = app.listen(port);
console.log(`Server listening on port ${port}`);
// Setup socket.io
const io = socketio(server);
// Listen for socket.io connections
io.on('connection', socket => {
console.log('Player connected!', socket.id);
socket.on(Constants.MSG_TYPES.JOIN_GAME, joinGame);
socket.on(Constants.MSG_TYPES.INPUT, handleInput);
socket.on('disconnect', onDisconnect);
});
рд╕рд░реНрд╡рд░ рд╕реЗ рд╕реЙрдХреЗрдЯ.рдЖрдИрдУ рдХрдиреЗрдХреНрд╢рди рд╕рдлрд▓рддрд╛рдкреВрд░реНрд╡рдХ рд╕реНрдерд╛рдкрд┐рдд рдХрд░рдиреЗ рдХреЗ рдмрд╛рдж, рд╣рдо рдирдП рд╕реЙрдХреЗрдЯ рдХреЗ рд▓рд┐рдП рдЗрд╡реЗрдВрдЯ рд╣реИрдВрдбрд▓рд░ рд╕реЗрдЯ рдХрд░рддреЗ рд╣реИрдВред рдЗрд╡реЗрдВрдЯ рд╣реИрдВрдбрд▓рд░ рд╕рд┐рдВрдЧрд▓рдЯрди рдСрдмреНрдЬреЗрдХреНрдЯ рдХреЛ рд╕реМрдВрдкрдХрд░ рдХреНрд▓рд╛рдЗрдВрдЯ рд╕реЗ рдкреНрд░рд╛рдкреНрдд рд╕рдВрджреЗрд╢реЛрдВ рдХреЛ рд╕рдВрднрд╛рд▓рддреЗ рд╣реИрдВ game
:
рд╕рд░реНрд╡рд░.рдЬреЗрдПрд╕ рднрд╛рдЧ 3
const Game = require('./game');
// ...
// Setup the Game
const game = new Game();
function joinGame(username) {
game.addPlayer(this, username);
}
function handleInput(dir) {
game.handleInput(this, dir);
}
function onDisconnect() {
game.removePlayer(this);
}
рд╣рдо рдПрдХ .io рдЧреЗрдо рдмрдирд╛ рд░рд╣реЗ рд╣реИрдВ, рдЗрд╕рд▓рд┐рдП рд╣рдореЗрдВ рдХреЗрд╡рд▓ рдПрдХ рдкреНрд░рддрд┐ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ Game
("рдЧреЗрдо") - рд╕рднреА рдЦрд┐рд▓рд╛рдбрд╝реА рдПрдХ рд╣реА рдореИрджрд╛рди рдореЗрдВ рдЦреЗрд▓рддреЗ рд╣реИрдВ! рдЕрдЧрд▓реЗ рднрд╛рдЧ рдореЗрдВ рд╣рдо рджреЗрдЦреЗрдВрдЧреЗ рдХрд┐ рдпрд╣ рдХреНрд▓рд╛рд╕ рдХреИрд╕реЗ рдХрд╛рдо рдХрд░рддреА рд╣реИред Game
.
2. рдЧреЗрдо рд╕рд░реНрд╡рд░
рд╡рд░реНрдЧ Game
рд╕рд░реНрд╡рд░ рд╕рд╛рдЗрдб рдкрд░ рд╕рдмрд╕реЗ рдорд╣рддреНрд╡рдкреВрд░реНрдг рддрд░реНрдХ рд╢рд╛рдорд┐рд▓ рд╣реИред рдЗрд╕рдХреЗ рджреЛ рдореБрдЦреНрдп рдХрд╛рд░реНрдп рд╣реИрдВ: рдЦрд┐рд▓рд╛рдбрд╝реА рдкреНрд░рдмрдВрдзрди ╨╕ рдЦреЗрд▓ рдЕрдиреБрдХрд░рдг.
рдЖрдЗрдП рдкрд╣рд▓реЗ рдХрд╛рд░реНрдп, рдЦрд┐рд▓рд╛рдбрд╝реА рдкреНрд░рдмрдВрдзрди рд╕реЗ рд╢реБрд░реБрдЖрдд рдХрд░реЗрдВред
рдЧреЗрдо.рдЬреЗрдПрд╕ рднрд╛рдЧ 1
const Constants = require('../shared/constants');
const Player = require('./player');
class Game {
constructor() {
this.sockets = {};
this.players = {};
this.bullets = [];
this.lastUpdateTime = Date.now();
this.shouldSendUpdate = false;
setInterval(this.update.bind(this), 1000 / 60);
}
addPlayer(socket, username) {
this.sockets[socket.id] = socket;
// Generate a position to start this player at.
const x = Constants.MAP_SIZE * (0.25 + Math.random() * 0.5);
const y = Constants.MAP_SIZE * (0.25 + Math.random() * 0.5);
this.players[socket.id] = new Player(socket.id, username, x, y);
}
removePlayer(socket) {
delete this.sockets[socket.id];
delete this.players[socket.id];
}
handleInput(socket, dir) {
if (this.players[socket.id]) {
this.players[socket.id].setDirection(dir);
}
}
// ...
}
рдЗрд╕ рдЧреЗрдо рдореЗрдВ рд╣рдо рдореИрджрд╛рди рдХреЗ рдЖрдзрд╛рд░ рдкрд░ рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ рдХреА рдкрд╣рдЪрд╛рди рдХрд░реЗрдВрдЧреЗ id
рдЙрдирдХрд╛ рд╕реЙрдХреЗрдЯ.рдЖрдИрдУ рд╕реЙрдХреЗрдЯ (рдпрджрд┐ рдЖрдк рднреНрд░рдорд┐рдд рд╣реЛ рдЬрд╛рддреЗ рд╣реИрдВ, рддреЛ рд╡рд╛рдкрд╕ рдЬрд╛рдПрдВ server.js
). Socket.io рд╕реНрд╡рдпрдВ рдкреНрд░рддреНрдпреЗрдХ рд╕реЙрдХреЗрдЯ рдХреЛ рдПрдХ рдЕрджреНрд╡рд┐рддреАрдп рдирд┐рд░реНрджрд┐рд╖реНрдЯ рдХрд░рддрд╛ рд╣реИ id
рдЗрд╕рд▓рд┐рдП рд╣рдореЗрдВ рдЗрд╕рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЪрд┐рдВрддрд╛ рдХрд░рдиреЗ рдХреА рдЬрд╝рд░реВрд░рдд рдирд╣реАрдВ рд╣реИред рдореИрдВ рдЙрд╕реЗ рдлреЛрди рдХрд░реБрдВрдЧрд╛ рдкреНрд▓реЗрдпрд░ рдЖрдИрдбреА.
рдЗрд╕реЗ рдзреНрдпрд╛рди рдореЗрдВ рд░рдЦрддреЗ рд╣реБрдП, рдЖрдЗрдП рдПрдХ рдХрдХреНрд╖рд╛ рдореЗрдВ рдЗрдВрд╕реНрдЯреЗрдВрд╕ рд╡реЗрд░рд┐рдПрдмрд▓реНрд╕ рдХрд╛ рдкрддрд╛ рд▓рдЧрд╛рдПрдВ Game
:
sockets
рдПрдХ рдСрдмреНрдЬреЗрдХреНрдЯ рд╣реИ рдЬреЛ рдкреНрд▓реЗрдпрд░ рдЖрдИрдбреА рдХреЛ рдкреНрд▓реЗрдпрд░ рд╕реЗ рдЬреБрдбрд╝реЗ рд╕реЙрдХреЗрдЯ рд╕реЗ рдмрд╛рдВрдзрддрд╛ рд╣реИред рдпрд╣ рд╣рдореЗрдВ рдирд┐рд░рдВрддрд░ рд╕рдордп рдореЗрдВ рдЙрдирдХреЗ рдкреНрд▓реЗрдпрд░ рдЖрдИрдбреА рджреНрд╡рд╛рд░рд╛ рд╕реЙрдХреЗрдЯ рддрдХ рдкрд╣реБрдВрдЪрдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддрд╛ рд╣реИредplayers
рдПрдХ рдСрдмреНрдЬреЗрдХреНрдЯ рд╣реИ рдЬреЛ рдкреНрд▓реЗрдпрд░ рдЖрдИрдбреА рдХреЛ рдХреЛрдб>рдкреНрд▓реЗрдпрд░ рдСрдмреНрдЬреЗрдХреНрдЯ рд╕реЗ рдмрд╛рдВрдзрддрд╛ рд╣реИ
bullets
рд╡рд╕реНрддреБрдУрдВ рдХреА рдПрдХ рд╕рд░рдгреА рд╣реИ Bullet
, рдЬрд┐рд╕рдХрд╛ рдХреЛрдИ рдирд┐рд╢реНрдЪрд┐рдд рдХреНрд░рдо рдирд╣реАрдВ рд╣реИред
lastUpdateTime
рдпрд╣ рдЙрд╕ рд╕рдордп рдХрд╛ рдЯрд╛рдЗрдорд╕реНрдЯреИрдореНрдк рд╣реИ рдЬрдм рдЧреЗрдо рдХреЛ рдЖрдЦрд┐рд░реА рдмрд╛рд░ рдЕрдкрдбреЗрдЯ рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛ред рд╣рдо рд╢реАрдШреНрд░ рд╣реА рджреЗрдЦреЗрдВрдЧреЗ рдХрд┐ рдЗрд╕рдХрд╛ рдЙрдкрдпреЛрдЧ рдХреИрд╕реЗ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред
shouldSendUpdate
рдПрдХ рд╕рд╣рд╛рдпрдХ рдЪрд░ рд╣реИ. рд╣рдо рд╢реАрдШреНрд░ рд╣реА рдЗрд╕рдХрд╛ рдЙрдкрдпреЛрдЧ рднреА рджреЗрдЦреЗрдВрдЧреЗред
рддрд░реАрдХреЛрдВ addPlayer()
, removePlayer()
╨╕ handleInput()
рд╕рдордЭрд╛рдиреЗ рдХреА рдЬрд░реВрд░рдд рдирд╣реАрдВ, рдЗрдирдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ server.js
. рдпрджрд┐ рдЖрдкрдХреЛ рдЕрдкрдиреА рдпрд╛рджрджрд╛рд╢реНрдд рддрд╛рдЬрд╝рд╛ рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ, рддреЛ рдереЛрдбрд╝рд╛ рдКрдкрд░ рдЬрд╛рдПрдБред
рдЕрдВрддрд┐рдо рдкрдВрдХреНрддрд┐ constructor()
рд╢реБрд░реВрдЖрдд рдЕрджреНрдпрддрди рдЪрдХреНрд░ рдЧреЗрдо (60 рдЕрдкрдбреЗрдЯ/рд╕реЗрдХреЗрдВрдб рдХреА рдЖрд╡реГрддреНрддрд┐ рдХреЗ рд╕рд╛рде):
рдЧреЗрдо.рдЬреЗрдПрд╕ рднрд╛рдЧ 2
const Constants = require('../shared/constants');
const applyCollisions = require('./collisions');
class Game {
// ...
update() {
// Calculate time elapsed
const now = Date.now();
const dt = (now - this.lastUpdateTime) / 1000;
this.lastUpdateTime = now;
// Update each bullet
const bulletsToRemove = [];
this.bullets.forEach(bullet => {
if (bullet.update(dt)) {
// Destroy this bullet
bulletsToRemove.push(bullet);
}
});
this.bullets = this.bullets.filter(
bullet => !bulletsToRemove.includes(bullet),
);
// Update each player
Object.keys(this.sockets).forEach(playerID => {
const player = this.players[playerID];
const newBullet = player.update(dt);
if (newBullet) {
this.bullets.push(newBullet);
}
});
// Apply collisions, give players score for hitting bullets
const destroyedBullets = applyCollisions(
Object.values(this.players),
this.bullets,
);
destroyedBullets.forEach(b => {
if (this.players[b.parentID]) {
this.players[b.parentID].onDealtDamage();
}
});
this.bullets = this.bullets.filter(
bullet => !destroyedBullets.includes(bullet),
);
// Check if any players are dead
Object.keys(this.sockets).forEach(playerID => {
const socket = this.sockets[playerID];
const player = this.players[playerID];
if (player.hp <= 0) {
socket.emit(Constants.MSG_TYPES.GAME_OVER);
this.removePlayer(socket);
}
});
// Send a game update to each player every other time
if (this.shouldSendUpdate) {
const leaderboard = this.getLeaderboard();
Object.keys(this.sockets).forEach(playerID => {
const socket = this.sockets[playerID];
const player = this.players[playerID];
socket.emit(
Constants.MSG_TYPES.GAME_UPDATE,
this.createUpdate(player, leaderboard),
);
});
this.shouldSendUpdate = false;
} else {
this.shouldSendUpdate = true;
}
}
// ...
}
рд╡рд┐рдзрд┐ update()
рдЗрд╕рдореЗрдВ рд╕рдВрднрд╡рддрдГ рд╕рд░реНрд╡рд░-рд╕рд╛рдЗрдб рд▓реЙрдЬрд┐рдХ рдХрд╛ рд╕рдмрд╕реЗ рдорд╣рддреНрд╡рдкреВрд░реНрдг рднрд╛рдЧ рд╢рд╛рдорд┐рд▓ рд╣реИред рдпрд╣рд╛рдВ рдмрддрд╛рдпрд╛ рдЧрдпрд╛ рд╣реИ рдХрд┐ рдпрд╣ рдХреНрд░рдо рдореЗрдВ рдХреНрдпрд╛ рдХрд░рддрд╛ рд╣реИ:
- рдЧрдгрдирд╛ рдХрд░рддрд╛ рд╣реИ рдХрд┐ рдХрдм рддрдХ
dt
рдЖрдЦрд┐рд░реА рд╕реЗ рдЧреБрдЬрд░ рдЧрдпрд╛update()
. - рдкреНрд░рддреНрдпреЗрдХ рдкреНрд░рдХреНрд╖реЗрдкреНрдп рдХреЛ рддрд╛рдЬрд╝рд╛ рдХрд░рддрд╛ рд╣реИ рдФрд░ рдпрджрд┐ рдЖрд╡рд╢реНрдпрдХ рд╣реЛ рддреЛ рдЙрдиреНрд╣реЗрдВ рдирд╖реНрдЯ рдХрд░ рджреЗрддрд╛ рд╣реИред рд╣рдо рдЗрд╕ рдХрд╛рд░реНрдпрдХреНрд╖рдорддрд╛ рдХрд╛ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдмрд╛рдж рдореЗрдВ рджреЗрдЦреЗрдВрдЧреЗред рдлрд┐рд▓рд╣рд╛рд▓ рд╣рдорд╛рд░реЗ рд▓рд┐рдП рдЗрддрдирд╛ рдЬрд╛рдирдирд╛ рд╣реА рдХрд╛рдлреА рд╣реИ
bullet.update()
рд░рд┐рдЯрд░реНрдиtrue
рдпрджрд┐ рдкреНрд░рдХреНрд╖реЗрдкреНрдп рдХреЛ рдирд╖реНрдЯ рдХрд░ рджреЗрдирд╛ рдЪрд╛рд╣рд┐рдП (рд╡рд╣ рдореИрджрд╛рди рд╕реЗ рдмрд╛рд╣рд░ рдЪрд▓рд╛ рдЧрдпрд╛)ред - рдкреНрд░рддреНрдпреЗрдХ рдЦрд┐рд▓рд╛рдбрд╝реА рдХреЛ рдЕрджреНрдпрддрди рдХрд░рддрд╛ рд╣реИ рдФрд░ рдпрджрд┐ рдЖрд╡рд╢реНрдпрдХ рд╣реЛ рддреЛ рдПрдХ рдкреНрд░рдХреНрд╖реЗрдкреНрдп рдЙрддреНрдкрдиреНрди рдХрд░рддрд╛ рд╣реИред рд╣рдо рдЗрд╕ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХреЛ рдмрд╛рдж рдореЗрдВ рднреА рджреЗрдЦреЗрдВрдЧреЗ -
player.update()
рдХрд┐рд╕реА рд╡рд╕реНрддреБ рдХреЛ рд╡рд╛рдкрд╕ рдХрд░ рд╕рдХрддрд╛ рд╣реИBullet
. - рдкреНрд░реЛрдЬреЗрдХреНрдЯрд╛рдЗрд▓ рдФрд░ рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ рдХреЗ рдмреАрдЪ рдЯрдХрд░рд╛рд╡ рдХреА рдЬрд╛рдБрдЪ рдХрд░рддрд╛ рд╣реИ
applyCollisions()
, рдЬреЛ рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ рдХреЛ рд╣рд┐рдЯ рдХрд░рдиреЗ рд╡рд╛рд▓реЗ рдкреНрд░реЛрдЬреЗрдХреНрдЯрд╛рдЗрд▓ рдХреА рдПрдХ рд╢реНрд░реГрдВрдЦрд▓рд╛ рд▓реМрдЯрд╛рддрд╛ рд╣реИред рд▓реМрдЯрд╛рдП рдЧрдП рдкреНрд░рддреНрдпреЗрдХ рдкреНрд░рдХреНрд╖реЗрдкреНрдп рдХреЗ рд▓рд┐рдП, рд╣рдо рдЙрд╕ рдЦрд┐рд▓рд╛рдбрд╝реА рдХреЗ рдЕрдВрдХ рдмрдврд╝рд╛рддреЗ рд╣реИрдВ рдЬрд┐рд╕рдиреЗ рдЗрд╕реЗ рдЪрд▓рд╛рдпрд╛ (рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ)редplayer.onDealtDamage()
) рдФрд░ рдлрд┐рд░ рдкреНрд░рдХреНрд╖реЗрдкреНрдп рдХреЛ рд╕рд░рдгреА рд╕реЗ рд╣рдЯрд╛ рджреЗрдВbullets
. - рд╕рднреА рдорд╛рд░реЗ рдЧрдП рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ рдХреЛ рд╕реВрдЪрд┐рдд рдХрд░рддрд╛ рд╣реИ рдФрд░ рдирд╖реНрдЯ рдХрд░ рджреЗрддрд╛ рд╣реИред
- рд╕рднреА рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ рдХреЛ рдЧреЗрдо рдЕрдкрдбреЗрдЯ рднреЗрдЬрддрд╛ рд╣реИ рд╣рд░ рдкрд▓ рдХрдИ рдмрд╛рд░ рдЬрдм рдмреБрд▓рд╛рдпрд╛ рдЧрдпрд╛
update()
. рдЗрд╕рд╕реЗ рд╣рдореЗрдВ рдКрдкрд░ рдЙрд▓реНрд▓рд┐рдЦрд┐рдд рд╕рд╣рд╛рдпрдХ рдЪрд░ рдХрд╛ рдЯреНрд░реИрдХ рд░рдЦрдиреЗ рдореЗрдВ рдорджрдж рдорд┐рд▓рддреА рд╣реИредshouldSendUpdate
. рдХреНрдпреЛрдВрдХрд┐update()
60 рдмрд╛рд░/рд╕реЗрдХрдВрдб рдкрд░ рдХреЙрд▓ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рд╣рдо рдЧреЗрдо рдЕрдкрдбреЗрдЯ 30 рдмрд╛рд░/рд╕реЗрдХрдВрдб рднреЗрдЬрддреЗ рд╣реИрдВред рдЗрд╕ рдкреНрд░рдХрд╛рд░, рдШрдбрд╝реА рдХреА рдЖрд╡реГрддреНрддрд┐ рд╕рд░реНрд╡рд░ рдХреНрд▓реЙрдХ 30 рдХреНрд▓реЙрдХ/рд╕реЗрдХреЗрдВрдб рд╣реИ (рд╣рдордиреЗ рдкрд╣рд▓реЗ рднрд╛рдЧ рдореЗрдВ рдХреНрд▓реЙрдХ рд░реЗрдЯ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдмрд╛рдд рдХреА рдереА)ред
рдХреЗрд╡рд▓ рдЧреЗрдо рдЕрдкрдбреЗрдЯ рд╣реА рдХреНрдпреЛрдВ рднреЗрдЬреЗрдВ рд╕рдордп рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ ? рдЪреИрдирд▓ рдХреЛ рдмрдЪрд╛рдиреЗ рдХреЗ рд▓рд┐рдП. рдкреНрд░рддрд┐ рд╕реЗрдХрдВрдб 30 рдЧреЗрдо рдЕрдкрдбреЗрдЯ рдмрд╣реБрдд рд╣реИ!
рдХреЙрд▓ рд╣реА рдХреНрдпреЛрдВ рдирд╣реАрдВ рдХрд░рддреЗ
update()
рдкреНрд░рддрд┐ рд╕реЗрдХрдВрдб 30 рдмрд╛рд░? рдЧреЗрдо рд╕рд┐рдореБрд▓реЗрд╢рди рдХреЛ рдмреЗрд╣рддрд░ рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдПред рдЕрдзрд┐рдХ рдмрд╛рд░ рдмреБрд▓рд╛рдпрд╛ рдЬрд╛рддрд╛ рд╣реИupdate()
, рдЧреЗрдо рд╕рд┐рдореБрд▓реЗрд╢рди рдЙрддрдирд╛ рд╣реА рд╕рдЯреАрдХ рд╣реЛрдЧрд╛ред рд▓реЗрдХрд┐рди рдЪреБрдиреМрддрд┐рдпреЛрдВ рдХреА рд╕рдВрдЦреНрдпрд╛ рд╕реЗ рдмрд╣реБрдд рдЕрдзрд┐рдХ рдкреНрд░рднрд╛рд╡рд┐рдд рди рд╣реЛрдВредupdate()
, рдХреНрдпреЛрдВрдХрд┐ рдпрд╣ рдПрдХ рдХрдореНрдкреНрдпреВрдЯреЗрд╢рдирд▓ рд░реВрдк рд╕реЗ рдорд╣рдВрдЧрд╛ рдХрд╛рд░реНрдп рд╣реИ - 60 рдкреНрд░рддрд┐ рд╕реЗрдХрдВрдб рдкрд░реНрдпрд╛рдкреНрдд рд╣реИред
рдмрд╛рдХреА рдХрдХреНрд╖рд╛ Game
рдЗрд╕рдореЗрдВ рдкреНрд░рдпреБрдХреНрдд рд╕рд╣рд╛рдпрдХ рд╡рд┐рдзрд┐рдпрд╛рдБ рд╢рд╛рдорд┐рд▓ рд╣реИрдВ update()
:
рдЧреЗрдо.рдЬреЗрдПрд╕ рднрд╛рдЧ 3
class Game {
// ...
getLeaderboard() {
return Object.values(this.players)
.sort((p1, p2) => p2.score - p1.score)
.slice(0, 5)
.map(p => ({ username: p.username, score: Math.round(p.score) }));
}
createUpdate(player, leaderboard) {
const nearbyPlayers = Object.values(this.players).filter(
p => p !== player && p.distanceTo(player) <= Constants.MAP_SIZE / 2,
);
const nearbyBullets = this.bullets.filter(
b => b.distanceTo(player) <= Constants.MAP_SIZE / 2,
);
return {
t: Date.now(),
me: player.serializeForUpdate(),
others: nearbyPlayers.map(p => p.serializeForUpdate()),
bullets: nearbyBullets.map(b => b.serializeForUpdate()),
leaderboard,
};
}
}
getLeaderboard()
рдмрд╣реБрдд рд╕рд░рд▓ - рдпрд╣ рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ рдХреЛ рд╕реНрдХреЛрд░ рдХреЗ рдЖрдзрд╛рд░ рдкрд░ рдХреНрд░рдордмрджреНрдз рдХрд░рддрд╛ рд╣реИ, рд╢реАрд░реНрд╖ рдкрд╛рдВрдЪ рдореЗрдВ рд▓реЗрддрд╛ рд╣реИ, рдФрд░ рдкреНрд░рддреНрдпреЗрдХ рдХреЗ рд▓рд┐рдП рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдирд╛рдо рдФрд░ рд╕реНрдХреЛрд░ рд▓реМрдЯрд╛рддрд╛ рд╣реИред
createUpdate()
рдореЗрдВ рдЗрд╕реНрддреЗрдорд╛рд▓ рдХрд┐рдпрд╛ update()
рдЧреЗрдо рдЕрдкрдбреЗрдЯ рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдЬреЛ рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ рдХреЛ рд╡рд┐рддрд░рд┐рдд рдХрд┐рдП рдЬрд╛рддреЗ рд╣реИрдВред рдЗрд╕рдХрд╛ рдореБрдЦреНрдп рдХрд╛рд░реНрдп рдореЗрдердбреНрд╕ рдХреЛ рдХреЙрд▓ рдХрд░рдирд╛ рд╣реИ serializeForUpdate()
рдХрдХреНрд╖рд╛рдУрдВ рдХреЗ рд▓рд┐рдП рд▓рд╛рдЧреВ рдХрд┐рдпрд╛ рдЧрдпрд╛ Player
╨╕ Bullet
. рдзреНрдпрд╛рди рджреЗрдВ рдХрд┐ рдпрд╣ рдкреНрд░рддреНрдпреЗрдХ рдЦрд┐рд▓рд╛рдбрд╝реА рдХреЛ рдХреЗрд╡рд▓ рдбреЗрдЯрд╛ рд╣реА рднреЗрдЬрддрд╛ рд╣реИ рдирд┐рдХрдЯрддрдо рдЦрд┐рд▓рд╛рдбрд╝реА рдФрд░ рдкреНрд░реЛрдЬреЗрдХреНрдЯрд╛рдЗрд▓ - рдЦрд┐рд▓рд╛рдбрд╝реА рд╕реЗ рджреВрд░ рдЦреЗрд▓ рдХреА рд╡рд╕реНрддреБрдУрдВ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЬрд╛рдирдХрд╛рд░реА рдкреНрд░рд╕рд╛рд░рд┐рдд рдХрд░рдиреЗ рдХреА рдХреЛрдИ рдЖрд╡рд╢реНрдпрдХрддрд╛ рдирд╣реАрдВ рд╣реИ!
3. рд╕рд░реНрд╡рд░ рдкрд░ рдЧреЗрдо рдСрдмреНрдЬреЗрдХреНрдЯ
рд╣рдорд╛рд░реЗ рдЦреЗрд▓ рдореЗрдВ, рдкреНрд░реЛрдЬреЗрдХреНрдЯрд╛рдЗрд▓ рдФрд░ рдЦрд┐рд▓рд╛рдбрд╝реА рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рдмрд╣реБрдд рд╕рдорд╛рди рд╣реИрдВ: рд╡реЗ рдЕрдореВрд░реНрдд, рдЧреЛрд▓, рдЪрд▓ рдЧреЗрдо рдСрдмреНрдЬреЗрдХреНрдЯ рд╣реИрдВред рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ рдФрд░ рдкреНрд░реЛрдЬреЗрдХреНрдЯрд╛рдЗрд▓ рдХреЗ рдмреАрдЪ рдЗрд╕ рд╕рдорд╛рдирддрд╛ рдХрд╛ рд▓рд╛рдн рдЙрдард╛рдиреЗ рдХреЗ рд▓рд┐рдП, рдЖрдЗрдП рдмреЗрд╕ рдХреНрд▓рд╛рд╕ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдХреЗ рд╢реБрд░реБрдЖрдд рдХрд░реЗрдВ Object
:
рдСрдмреНрдЬреЗрдХреНрдЯ.рдЬреЗ.рдПрд╕
class Object {
constructor(id, x, y, dir, speed) {
this.id = id;
this.x = x;
this.y = y;
this.direction = dir;
this.speed = speed;
}
update(dt) {
this.x += dt * this.speed * Math.sin(this.direction);
this.y -= dt * this.speed * Math.cos(this.direction);
}
distanceTo(object) {
const dx = this.x - object.x;
const dy = this.y - object.y;
return Math.sqrt(dx * dx + dy * dy);
}
setDirection(dir) {
this.direction = dir;
}
serializeForUpdate() {
return {
id: this.id,
x: this.x,
y: this.y,
};
}
}
рдпрд╣рд╛рдВ рдХреБрдЫ рднреА рдЬрдЯрд┐рд▓ рдирд╣реАрдВ рдЪрд▓ рд░рд╣рд╛ рд╣реИ. рдпрд╣ рдХрдХреНрд╖рд╛ рд╡рд┐рд╕реНрддрд╛рд░ рдХреЗ рд▓рд┐рдП рдПрдХ рдЕрдЪреНрдЫрд╛ рдЖрдзрд╛рд░ рдмрд┐рдВрджреБ рд╣реЛрдЧреАред рджреЗрдЦрддреЗ рд╣реИрдВ рдХреНрд▓рд╛рд╕ рдХреИрд╕реА рд╣реЛрддреА рд╣реИ Bullet
рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реИ Object
:
рдмреБрд▓реЗрдЯ.рдЬреЗ.рдПрд╕
const shortid = require('shortid');
const ObjectClass = require('./object');
const Constants = require('../shared/constants');
class Bullet extends ObjectClass {
constructor(parentID, x, y, dir) {
super(shortid(), x, y, dir, Constants.BULLET_SPEED);
this.parentID = parentID;
}
// Returns true if the bullet should be destroyed
update(dt) {
super.update(dt);
return this.x < 0 || this.x > Constants.MAP_SIZE || this.y < 0 || this.y > Constants.MAP_SIZE;
}
}
рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди Bullet
рдмрд╣реБрдд рдЫреЛрдЯрд╛! рд╣рдордиреЗ рдЬреЛрдбрд╝ рджрд┐рдпрд╛ рд╣реИ Object
рдХреЗрд╡рд▓ рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдПрдХреНрд╕рдЯреЗрдВрд╢рди:
- рдкреИрдХреЗрдЬ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛
рд╢реЙрд░реНрдЯрд┐рдб рдпрд╛рджреГрдЪреНрдЫрд┐рдХ рдкреАрдврд╝реА рдХреЗ рд▓рд┐рдПid
рдлреЗрдВрдХрдиреЗред - рдПрдХ рдлрд╝реАрд▓реНрдб рдЬреЛрдбрд╝рдирд╛
parentID
рддрд╛рдХрд┐ рдЖрдк рдЙрд╕ рдЦрд┐рд▓рд╛рдбрд╝реА рдХреЛ рдЯреНрд░реИрдХ рдХрд░ рд╕рдХреЗрдВ рдЬрд┐рд╕рдиреЗ рдпрд╣ рдкреНрд░реЛрдЬреЗрдХреНрдЯрд╛рдЗрд▓ рдмрдирд╛рдпрд╛ рд╣реИред - рдореЗрдВ рд╡рд╛рдкрд╕реА рдорд╛рди рдЬреЛрдбрд╝рдирд╛
update()
, рдЬреЛ рдХреЗ рдмрд░рд╛рдмрд░ рд╣реИtrue
рдпрджрд┐ рдкреНрд░рдХреНрд╖реЗрдкреНрдп рдореИрджрд╛рди рдХреЗ рдмрд╛рд╣рд░ рд╣реИ (рдпрд╛рдж рд░рдЦреЗрдВ рдХрд┐ рд╣рдордиреЗ рдкрд┐рдЫрд▓реЗ рднрд╛рдЧ рдореЗрдВ рдЗрд╕ рдмрд╛рд░реЗ рдореЗрдВ рдмрд╛рдд рдХреА рдереА?)ред
рдЪрд▓рд┐рдП рдЖрдЧреЗ рдмрдврд╝рддреЗ рд╣реИрдВ Player
:
рдкреНрд▓реЗрдпрд░.рдЬреЗ.рдПрд╕
const ObjectClass = require('./object');
const Bullet = require('./bullet');
const Constants = require('../shared/constants');
class Player extends ObjectClass {
constructor(id, username, x, y) {
super(id, x, y, Math.random() * 2 * Math.PI, Constants.PLAYER_SPEED);
this.username = username;
this.hp = Constants.PLAYER_MAX_HP;
this.fireCooldown = 0;
this.score = 0;
}
// Returns a newly created bullet, or null.
update(dt) {
super.update(dt);
// Update score
this.score += dt * Constants.SCORE_PER_SECOND;
// Make sure the player stays in bounds
this.x = Math.max(0, Math.min(Constants.MAP_SIZE, this.x));
this.y = Math.max(0, Math.min(Constants.MAP_SIZE, this.y));
// Fire a bullet, if needed
this.fireCooldown -= dt;
if (this.fireCooldown <= 0) {
this.fireCooldown += Constants.PLAYER_FIRE_COOLDOWN;
return new Bullet(this.id, this.x, this.y, this.direction);
}
return null;
}
takeBulletDamage() {
this.hp -= Constants.BULLET_DAMAGE;
}
onDealtDamage() {
this.score += Constants.SCORE_BULLET_HIT;
}
serializeForUpdate() {
return {
...(super.serializeForUpdate()),
direction: this.direction,
hp: this.hp,
};
}
}
рдЦрд┐рд▓рд╛рдбрд╝реА рдкреНрд░реЛрдЬреЗрдХреНрдЯрд╛рдЗрд▓ рдХреА рддреБрд▓рдирд╛ рдореЗрдВ рдЕрдзрд┐рдХ рдЬрдЯрд┐рд▓ рд╣реЛрддреЗ рд╣реИрдВ, рдЗрд╕рд▓рд┐рдП рдЗрд╕ рд╡рд░реНрдЧ рдореЗрдВ рдХреБрдЫ рдФрд░ рдлрд╝реАрд▓реНрдб рд╕рдВрдЧреНрд░рд╣реАрдд рдХреА рдЬрд╛рдиреА рдЪрд╛рд╣рд┐рдПред рдЙрд╕рдХреА рд╡рд┐рдзрд┐ update()
рдмрд╣реБрдд рд╕рд╛рд░рд╛ рдХрд╛рдо рдХрд░рддрд╛ рд╣реИ, рд╡рд┐рд╢реЗрд╖ рд░реВрдк рд╕реЗ, рдпрджрд┐ рдХреЛрдИ рдирд╣реАрдВ рдмрдЪрд╛ рд╣реИ рддреЛ рдирд╡ рдирд┐рд░реНрдорд┐рдд рдкреНрд░рдХреНрд╖реЗрдкреНрдп рдХреЛ рд╡рд╛рдкрд╕ рдХрд░ рджреЗрддрд╛ рд╣реИ fireCooldown
(рдпрд╛рдж рд░рдЦреЗрдВ рд╣рдордиреЗ рдкрд┐рдЫрд▓реЗ рднрд╛рдЧ рдореЗрдВ рдЗрд╕ рдмрд╛рд░реЗ рдореЗрдВ рдмрд╛рдд рдХреА рдереА?)ред рд╕рд╛рде рд╣реА рдпрд╣ рд╡рд┐рдзрд┐ рдХрд╛ рд╡рд┐рд╕реНрддрд╛рд░ рднреА рдХрд░рддрд╛ рд╣реИ serializeForUpdate()
, рдХреНрдпреЛрдВрдХрд┐ рд╣рдореЗрдВ рдЧреЗрдо рдЕрдкрдбреЗрдЯ рдореЗрдВ рдкреНрд▓реЗрдпрд░ рдХреЗ рд▓рд┐рдП рдЕрддрд┐рд░рд┐рдХреНрдд рдлрд╝реАрд▓реНрдб рд╢рд╛рдорд┐рд▓ рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред
рдмреЗрд╕ рдХреНрд▓рд╛рд╕ рд╣реЛрдирд╛ Object
- рдХреЛрдб рдХреЛ рджреЛрд╣рд░рд╛рдиреЗ рд╕реЗ рдмрдЪрдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдорд╣рддреНрд╡рдкреВрд░реНрдг рдХрджрдо. рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдХреЛрдИ рдХрдХреНрд╖рд╛ рдирд╣реАрдВ Object
рдкреНрд░рддреНрдпреЗрдХ рдЧреЗрдо рдСрдмреНрдЬреЗрдХреНрдЯ рдХрд╛ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рд╕рдорд╛рди рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдП distanceTo()
, рдФрд░ рдЗрди рд╕рднреА рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрдиреЛрдВ рдХреЛ рдХрдИ рдлрд╝рд╛рдЗрд▓реЛрдВ рдореЗрдВ рдХреЙрдкреА-рдкреЗрд╕реНрдЯ рдХрд░рдирд╛ рдПрдХ рджреБрдГрд╕реНрд╡рдкреНрди рд╣реЛрдЧрд╛ред рдпрд╣ рдмрдбрд╝реА рдкрд░рд┐рдпреЛрдЬрдирд╛рдУрдВ рдХреЗ рд▓рд┐рдП рд╡рд┐рд╢реЗрд╖ рд░реВрдк рд╕реЗ рдорд╣рддреНрд╡рдкреВрд░реНрдг рд╣реЛ рдЬрд╛рддрд╛ рд╣реИредрдЬрдм рд╡рд┐рд╕реНрддрд╛рд░ рдХреА рд╕рдВрдЦреНрдпрд╛ Object
рдХрдХреНрд╖рд╛рдПрдВ рдмрдврд╝ рд░рд╣реА рд╣реИрдВ.
4. рдЯрдХрд░рд╛рд╡ рдХрд╛ рдкрддрд╛ рд▓рдЧрд╛рдирд╛
рд╣рдорд╛рд░реЗ рд▓рд┐рдП рдХреЗрд╡рд▓ рдПрдХ рдЪреАрдЬ рдмрдЪреА рд╣реИ рд╡рд╣ рд╣реИ рдпрд╣ рдкрд╣рдЪрд╛рдирдирд╛ рдХрд┐ рдХрдм рдкреНрд░рдХреНрд╖реЗрдкреНрдп рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ рд╕реЗ рдЯрдХрд░рд╛рдП! рд╡рд┐рдзрд┐ рд╕реЗ рдХреЛрдб рдХрд╛ рдпрд╣ рдЯреБрдХрдбрд╝рд╛ рдпрд╛рдж рд░рдЦреЗрдВ update()
рдХрдХреНрд╖рд╛ рдореЗрдВ Game
:
рдЧреЗрдо.рдЬреЗ.рдПрд╕
const applyCollisions = require('./collisions');
class Game {
// ...
update() {
// ...
// Apply collisions, give players score for hitting bullets
const destroyedBullets = applyCollisions(
Object.values(this.players),
this.bullets,
);
destroyedBullets.forEach(b => {
if (this.players[b.parentID]) {
this.players[b.parentID].onDealtDamage();
}
});
this.bullets = this.bullets.filter(
bullet => !destroyedBullets.includes(bullet),
);
// ...
}
}
рд╣рдореЗрдВ рдЗрд╕ рдкрджреНрдзрддрд┐ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреА рдЬрд░реВрд░рдд рд╣реИ applyCollisions()
, рдЬреЛ рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ рд╕реЗ рдЯрдХрд░рд╛рдиреЗ рд╡рд╛рд▓реЗ рд╕рднреА рдкреНрд░реЛрдЬреЗрдХреНрдЯрд╛рдЗрд▓ рдХреЛ рд▓реМрдЯрд╛ рджреЗрддрд╛ рд╣реИред рд╕реМрднрд╛рдЧреНрдп рд╕реЗ, рдРрд╕рд╛ рдХрд░рдирд╛ рдЙрддрдирд╛ рдХрдард┐рди рдирд╣реАрдВ рд╣реИ рдХреНрдпреЛрдВрдХрд┐
- рд╕рднреА рдЯрдХрд░рд╛рдиреЗ рд╡рд╛рд▓реА рд╡рд╕реНрддреБрдПрдБ рд╡реГрддреНрдд рд╣реИрдВ, рдЬреЛ рдЯрдХрд░рд╛рд╡ рдХрд╛ рдкрддрд╛ рд▓рдЧрд╛рдиреЗ рдХреЗ рд▓рд┐рдП рд╕рдмрд╕реЗ рд╕рд░рд▓ рдЖрдХрд╛рд░ рд╣реИред
- рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдПрдХ рддрд░реАрдХрд╛ рд╣реИ
distanceTo()
, рдЬрд┐рд╕реЗ рд╣рдордиреЗ рдХрдХреНрд╖рд╛ рдореЗрдВ рдкрд┐рдЫрд▓реЗ рдЕрдиреБрднрд╛рдЧ рдореЗрдВ рд▓рд╛рдЧреВ рдХрд┐рдпрд╛ рдерд╛Object
.
рдЯрдХрд░рд╛рд╡ рдХрд╛ рдкрддрд╛ рд▓рдЧрд╛рдиреЗ рдХрд╛ рд╣рдорд╛рд░рд╛ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдЗрд╕ рдкреНрд░рдХрд╛рд░ рд╣реИ:
рдЯрдХрд░рд╛рд╡.рдЬреЗ.рдПрд╕
const Constants = require('../shared/constants');
// Returns an array of bullets to be destroyed.
function applyCollisions(players, bullets) {
const destroyedBullets = [];
for (let i = 0; i < bullets.length; i++) {
// Look for a player (who didn't create the bullet) to collide each bullet with.
// As soon as we find one, break out of the loop to prevent double counting a bullet.
for (let j = 0; j < players.length; j++) {
const bullet = bullets[i];
const player = players[j];
if (
bullet.parentID !== player.id &&
player.distanceTo(bullet) <= Constants.PLAYER_RADIUS + Constants.BULLET_RADIUS
) {
destroyedBullets.push(bullet);
player.takeBulletDamage();
break;
}
}
}
return destroyedBullets;
}
рдпрд╣ рд╕рд░рд▓ рдЯрдХрд░рд╛рд╡ рдХрд╛ рдкрддрд╛ рд▓рдЧрд╛рдирд╛ рдЗрд╕ рддрдереНрдп рдкрд░ рдЖрдзрд╛рд░рд┐рдд рд╣реИ рджреЛ рд╡реГрддреНрдд рдЯрдХрд░рд╛рддреЗ рд╣реИрдВ рдпрджрд┐ рдЙрдирдХреЗ рдХреЗрдВрджреНрд░реЛрдВ рдХреЗ рдмреАрдЪ рдХреА рджреВрд░реА рдЙрдирдХреА рддреНрд░рд┐рдЬреНрдпрд╛рдУрдВ рдХреЗ рдпреЛрдЧ рд╕реЗ рдХрдо рд╣реИ. рдпрд╣рд╛рдВ рд╡рд╣ рдорд╛рдорд▓рд╛ рд╣реИ рдЬрд╣рд╛рдВ рджреЛ рд╡реГрддреНрддреЛрдВ рдХреЗ рдХреЗрдВрджреНрд░реЛрдВ рдХреЗ рдмреАрдЪ рдХреА рджреВрд░реА рдЙрдирдХреА рддреНрд░рд┐рдЬреНрдпрд╛рдУрдВ рдХреЗ рдпреЛрдЧ рдХреЗ рдмрд┐рд▓реНрдХреБрд▓ рдмрд░рд╛рдмрд░ рд╣реИ:
рдпрд╣рд╛рдВ рд╡рд┐рдЪрд╛рд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХреБрдЫ рдФрд░ рдкрд╣рд▓реВ рд╣реИрдВ:
- рдкреНрд░рдХреНрд╖реЗрдкреНрдп рдХреЛ рдЙрд╕ рдЦрд┐рд▓рд╛рдбрд╝реА рдкрд░ рдирд╣реАрдВ рд▓рдЧрдирд╛ рдЪрд╛рд╣рд┐рдП рдЬрд┐рд╕рдиреЗ рдЗрд╕реЗ рдмрдирд╛рдпрд╛ рд╣реИред рдЗрд╕реЗ рддреБрд▓рдирд╛ рдХрд░рдХреЗ рд╣рд╛рд╕рд┐рд▓ рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ
bullet.parentID
╤Бplayer.id
. - рдПрдХ рд╣реА рд╕рдордп рдореЗрдВ рдХрдИ рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ рдХреЗ рдЯрдХрд░рд╛рдиреЗ рдХреА рд╕реАрдорд┐рдд рд╕реНрдерд┐рддрд┐ рдореЗрдВ рдкреНрд░рдХреНрд╖реЗрдкреНрдп рдХреЛ рдХреЗрд╡рд▓ рдПрдХ рдмрд╛рд░ рд╣реА рд╣рд┐рдЯ рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдПред рд╣рдо рдСрдкрд░реЗрдЯрд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдЗрд╕ рд╕рдорд╕реНрдпрд╛ рдХрд╛ рд╕рдорд╛рдзрд╛рди рдХрд░реЗрдВрдЧреЗ
break
: рдЬреИрд╕реЗ рд╣реА рдкреНрд░рдХреНрд╖реЗрдкреНрдп рд╕реЗ рдЯрдХрд░рд╛рдиреЗ рд╡рд╛рд▓рд╛ рдЦрд┐рд▓рд╛рдбрд╝реА рдорд┐рд▓ рдЬрд╛рддрд╛ рд╣реИ, рд╣рдо рдЦреЛрдЬ рдмрдВрдж рдХрд░ рджреЗрддреЗ рд╣реИрдВ рдФрд░ рдЕрдЧрд▓реЗ рдкреНрд░рдХреНрд╖реЗрдкреНрдп рдХреА рдУрд░ рдмрдврд╝ рдЬрд╛рддреЗ рд╣реИрдВред
рдЕрдВрдд
рдмрд╕ рдЗрддрдирд╛ рд╣реА! .io рд╡реЗрдм рдЧреЗрдо рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдЖрдкрдХреЛ рдЬреЛ рдХреБрдЫ рдЬрд╛рдирдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ, рд╣рдордиреЗ рдЙрд╕реЗ рд╢рд╛рдорд┐рд▓ рдХрд░ рд▓рд┐рдпрд╛ рд╣реИред рдЖрдЧреЗ рдХреНрдпрд╛ рд╣реЛрдЧрд╛? рдЕрдкрдирд╛ рдЦреБрдж рдХрд╛ .io рдЧреЗрдо рдмрдирд╛рдПрдВ!
рд╕рднреА рдирдореВрдирд╛ рдХреЛрдб рдЦреБрд▓рд╛ рд╕реНрд░реЛрдд рд╣реИрдВ рдФрд░ рдЗрд╕ рдкрд░ рдкреЛрд╕реНрдЯ рдХрд┐рдП рдЧрдП рд╣реИрдВ
рд╕реНрд░реЛрдд: www.habr.com