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