ΠΡΡΠ΅Π΄ΡΠ°Ρ Π² 2015 Π³ΠΎΠ΄Ρ
ΠΠ° ΡΠ»ΡΡΠ°ΠΉ, Π΅ΡΠ»ΠΈ Π²Ρ Π½ΠΈΠΊΠΎΠ³Π΄Π° ΡΠ°Π½ΡΡΠ΅ Π½Π΅ ΡΠ»ΡΡΠ°Π»ΠΈ ΠΎ ΡΠ°ΠΊΠΈΡ
ΠΈΠ³ΡΠ°Ρ
: ΡΡΠΎ Π±Π΅ΡΠΏΠ»Π°ΡΠ½ΡΠ΅ ΠΌΠ½ΠΎΠ³ΠΎΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»ΡΡΠΊΠΈΠ΅ Π²Π΅Π±-ΠΈΠ³ΡΡ, Π² ΠΊΠΎΡΠΎΡΡΡ
Π»Π΅Π³ΠΊΠΎ ΡΡΠ°ΡΡΠ²ΠΎΠ²Π°ΡΡ (Π½Π΅ ΡΡΠ΅Π±ΡΠ΅ΡΡΡ ΡΡΡΡΠ½Π°Ρ Π·Π°ΠΏΠΈΡΡ). ΠΠ±ΡΡΠ½ΠΎ ΠΎΠ½ΠΈ ΡΡΠ°Π»ΠΊΠΈΠ²Π°ΡΡ Π½Π° ΠΎΠ΄Π½ΠΎΠΉ Π°ΡΠ΅Π½Π΅ ΠΌΠ½ΠΎΠΆΠ΅ΡΡΠ²ΠΎ ΠΏΡΠΎΡΠΈΠ²ΠΎΠ±ΠΎΡΡΡΠ²ΡΡΡΠΈΡ
ΠΈΠ³ΡΠΎΠΊΠΎΠ². ΠΡΡΠ³ΠΈΠ΅ Π·Π½Π°ΠΌΠ΅Π½ΠΈΡΡΠ΅ ΠΈΠ³ΡΡ ΠΆΠ°Π½ΡΠ° .io:
Π ΡΡΠΎΠΌ ΠΏΠΎΡΡΠ΅ ΠΌΡ Π±ΡΠ΄Π΅ΠΌ ΡΠ°Π·Π±ΠΈΡΠ°ΡΡΡΡ, ΠΊΠ°ΠΊ Ρ Π½ΡΠ»Ρ ΡΠΎΠ·Π΄Π°ΡΡ ΠΈΠ³ΡΡ .io. ΠΠ»Ρ ΡΡΠΎΠ³ΠΎ Π΄ΠΎΡΡΠ°ΡΠΎΡΠ½ΠΎ Π±ΡΠ΄Π΅Ρ ΡΠΎΠ»ΡΠΊΠΎ Π·Π½Π°Π½ΠΈΡ Javascript: Π²Π°ΠΌ Π½ΡΠΆΠ½ΠΎ ΠΏΠΎΠ½ΠΈΠΌΠ°ΡΡ ΡΠ°ΠΊΠΈΠ΅ Π²Π΅ΡΠΈ, ΠΊΠ°ΠΊ ΡΠΈΠ½ΡΠ°ΠΊΡΠΈΡ this
ΠΈ
ΠΡΠΈΠΌΠ΅Ρ ΠΈΠ³ΡΡ .io
ΠΠ»Ρ ΠΏΠΎΠΌΠΎΡΠΈ Π² ΠΎΠ±ΡΡΠ΅Π½ΠΈΠΈ ΠΌΡ Π±ΡΠ΄Π΅ΠΌ ΡΡΡΠ»Π°ΡΡΡΡ Π½Π°
ΠΠ³ΡΠ° Π΄ΠΎΠ²ΠΎΠ»ΡΠ½ΠΎ ΠΏΡΠΎΡΡΠ°: Π²Ρ ΡΠΏΡΠ°Π²Π»ΡΠ΅ΡΠ΅ ΠΊΠΎΡΠ°Π±Π»ΡΠΌ Π½Π° Π°ΡΠ΅Π½Π΅, Π³Π΄Π΅ Π΅ΡΡΡ Π΄ΡΡΠ³ΠΈΠ΅ ΠΈΠ³ΡΠΎΠΊΠΈ. ΠΠ°Ρ ΠΊΠΎΡΠ°Π±Π»Ρ Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΈ ΡΡΡΠ΅Π»ΡΠ΅Ρ ΡΠ½Π°ΡΡΠ΄Π°ΠΌΠΈ ΠΈ Π²Ρ ΠΏΡΡΠ°Π΅ΡΠ΅ΡΡ ΠΏΠΎΠΏΠ°ΡΡΡ Π² Π΄ΡΡΠ³ΠΈΡ
ΠΈΠ³ΡΠΎΠΊΠΎΠ², Π² ΡΠΎ ΠΆΠ΅ Π²ΡΠ΅ΠΌΡ ΠΈΠ·Π±Π΅Π³Π°Ρ ΠΈΡ
ΡΠ½Π°ΡΡΠ΄ΠΎΠ².
1. ΠΡΠ°ΡΠΊΠΈΠΉ ΠΎΠ±Π·ΠΎΡ/ΡΡΡΡΠΊΡΡΡΠ° ΠΏΡΠΎΠ΅ΠΊΡΠ°
Π Π΅ΠΊΠΎΠΌΠ΅Π½Π΄ΡΡ
ΡΠΊΠ°ΡΠ°ΡΡ ΠΈΡΡ ΠΎΠ΄Π½ΡΠΉ ΠΊΠΎΠ΄ ΠΏΡΠΈΠΌΠ΅ΡΠ° ΠΈΠ³ΡΡ, ΡΡΠΎΠ±Ρ Π²Ρ ΠΌΠΎΠ³Π»ΠΈ ΡΠ»Π΅Π΄ΠΎΠ²Π°ΡΡ Π·Π° ΠΌΠ½ΠΎΠΉ.
Π ΠΏΡΠΈΠΌΠ΅ΡΠ΅ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΡΡΡ ΡΠ»Π΅Π΄ΡΡΡΠ΅Π΅:
Express β ΡΠ°ΠΌΡΠΉ ΠΏΠΎΠΏΡΠ»ΡΡΠ½ΡΠΉ Π²Π΅Π±-ΡΡΠ΅ΠΉΠΌΠ²ΠΎΡΠΊ Π΄Π»Ρ Node.js, ΡΠΏΡΠ°Π²Π»ΡΡΡΠΈΠΉ Π²Π΅Π±-ΡΠ΅ΡΠ²Π΅ΡΠΎΠΌ ΠΈΠ³ΡΡ.socket.io β Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊΠ° websocket Π΄Π»Ρ ΠΎΠ±ΠΌΠ΅Π½Π° Π΄Π°Π½Π½ΡΠΌΠΈ ΠΌΠ΅ΠΆΠ΄Ρ Π±ΡΠ°ΡΠ·Π΅ΡΠΎΠΌ ΠΈ ΡΠ΅ΡΠ²Π΅ΡΠΎΠΌ.Webpack β ΠΌΠ΅Π½Π΅Π΄ΠΆΠ΅Ρ ΠΌΠΎΠ΄ΡΠ»Π΅ΠΉ. Π ΡΠΎΠΌ, Π·Π°ΡΠ΅ΠΌ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ Webpack, ΠΌΠΎΠΆΠ½ΠΎ ΠΏΡΠΎΡΠΈΡΠ°ΡΡΠ·Π΄Π΅ΡΡ .
ΠΠΎΡ ΠΊΠ°ΠΊ Π²ΡΠ³Π»ΡΠ΄ΠΈΡ ΡΡΡΡΠΊΡΡΡΠ° ΠΊΠ°ΡΠ°Π»ΠΎΠ³Π° ΠΏΡΠΎΠ΅ΠΊΡΠ°:
public/ assets/ ... src/ client/ css/ ... html/ index.html index.js ... server/ server.js ... shared/ constants.js
public/
ΠΡΡ Π² ΠΏΠ°ΠΏΠΊΠ΅ public/
Π±ΡΠ΄Π΅Ρ ΡΡΠ°ΡΠΈΡΠ΅ΡΠΊΠΈ ΠΏΠ΅ΡΠ΅Π΄Π°Π²Π°ΡΡΡΡ ΡΠ΅ΡΠ²Π΅ΡΠΎΠΌ. Π 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). Webpack Π±ΡΠ΄Π΅Ρ Π½Π°ΡΠΈΠ½Π°ΡΡ ΠΎΡΡΡΠ΄Π° ΠΈ ΡΡΠ°Π½Π΅Ρ ΡΠ΅ΠΊΡΡΡΠΈΠ²Π½ΠΎ ΠΈΡΠΊΠ°ΡΡ Π΄ΡΡΠ³ΠΈΠ΅ ΠΈΠΌΠΏΠΎΡΡΠΈΡΠΎΠ²Π°Π½Π½ΡΠ΅ ΡΠ°ΠΉΠ»Ρ.- ΠΡΡ
ΠΎΠ΄Π½ΠΎΠΉ JS Π½Π°ΡΠ΅ΠΉ ΡΠ±ΠΎΡΠΊΠΈ Webpack Π±ΡΠ΄Π΅Ρ ΡΠ°ΡΠΏΠΎΠ»Π°Π³Π°ΡΡΡΡ Π² ΠΊΠ°ΡΠ°Π»ΠΎΠ³Π΅
dist/
. Π― Π±ΡΠ΄Ρ Π½Π°Π·ΡΠ²Π°ΡΡ ΡΡΠΎΡ ΡΠ°ΠΉΠ» Π½Π°ΡΠΈΠΌ ΠΏΠ°ΠΊΠ΅ΡΠΎΠΌ JS. - ΠΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌ
Babel , ΠΈ Π² ΡΠ°ΡΡΠ½ΠΎΡΡΠΈ ΠΊΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΡ@babel/preset-env Π΄Π»Ρ ΡΡΠ°Π½ΡΠΏΠΈΠ»ΡΡΠΈΠΈ (transpiling) Π½Π°ΡΠ΅Π³ΠΎ ΠΊΠΎΠ΄Π° JS Π΄Π»Ρ ΡΡΠ°ΡΡΡ Π±ΡΠ°ΡΠ·Π΅ΡΠΎΠ². - ΠΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌ ΠΏΠ»Π°Π³ΠΈΠ½ Π΄Π»Ρ ΠΈΠ·Π²Π»Π΅ΡΠ΅Π½ΠΈΡ Π²ΡΠ΅Ρ CSS, Π½Π° ΠΊΠΎΡΠΎΡΡΠ΅ ΡΡΡΠ»Π°ΡΡΡΡ ΡΠ°ΠΉΠ»Ρ JS, ΠΈ Π΄Π»Ρ ΠΎΠ±ΡΠ΅Π΄ΠΈΠ½Π΅Π½ΠΈΡ ΠΈΡ Π² ΠΎΠ΄Π½ΠΎΠΌ ΠΌΠ΅ΡΡΠ΅. Π― Π±ΡΠ΄Ρ Π½Π°Π·ΡΠ²Π°ΡΡ Π΅Π³ΠΎ Π½Π°ΡΠΈΠΌ ΠΏΠ°ΠΊΠ΅ΡΠΎΠΌ 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
<!DOCTYPE html> <html> <head> <title>An example .io game</title> <link type="text/css" rel="stylesheet" href="/game.bundle.css"> </head> <body> <canvas id="game-canvas"></canvas> <script async src="/game.bundle.js"></script> <div id="play-menu" class="hidden"> <input type="text" id="username-input" placeholder="Username" /> <button id="play-button">PLAY</button> </div> </body> </html>
ΠΡΠΎΡ ΠΏΡΠΈΠΌΠ΅Ρ ΠΊΠΎΠ΄Π° ΡΠ»Π΅Π³ΠΊΠ° ΡΠΏΡΠΎΡΡΠ½ Π΄Π»Ρ ΠΏΠΎΠ½ΡΡΠ½ΠΎΡΡΠΈ, ΡΠΎ ΠΆΠ΅ ΡΠ°ΠΌΠΎΠ΅ Ρ ΡΠ΄Π΅Π»Π°Ρ ΠΈ ΡΠΎ ΠΌΠ½ΠΎΠ³ΠΈΠΌΠΈ Π΄ΡΡΠ³ΠΈΠΌΠΈ ΠΏΡΠΈΠΌΠ΅ΡΠ°ΠΌΠΈ ΠΏΠΎΡΡΠ°. ΠΠΎΠ»Π½ΡΠΉ ΠΊΠΎΠ΄ Π²ΡΠ΅Π³Π΄Π° ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠΎΡΠΌΠΎΡΡΠ΅ΡΡ Π½Π°
Π£ Π½Π°Ρ Π΅ΡΡΡ:
ΠΠ»Π΅ΠΌΠ΅Π½Ρ HTML5 Canvas (<canvas>
), ΠΊΠΎΡΠΎΡΡΠΉ ΠΌΡ Π±ΡΠ΄Π΅ΠΌ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ Π΄Π»Ρ ΡΠ΅Π½Π΄Π΅ΡΠΈΠ½Π³Π° ΠΈΠ³ΡΡ.<link>
Π΄Π»Ρ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΡ Π½Π°ΡΠ΅Π³ΠΎ ΠΏΠ°ΠΊΠ΅ΡΠ° CSS.<script>
Π΄Π»Ρ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΡ Π½Π°ΡΠ΅Π³ΠΎ ΠΏΠ°ΠΊΠ΅ΡΠ° Javascript.- ΠΠ»Π°Π²Π½ΠΎΠ΅ ΠΌΠ΅Π½Ρ Ρ ΠΈΠΌΠ΅Π½Π΅ΠΌ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ
<input>
ΠΈ ΠΊΠ½ΠΎΠΏΠΊΠΎΠΉ Β«PLAYΒ» (<button>
).
ΠΠΎΡΠ»Π΅ Π·Π°Π³ΡΡΠ·ΠΊΠΈ Π΄ΠΎΠΌΠ°ΡΠ½Π΅ΠΉ ΡΡΡΠ°Π½ΠΈΡΡ Π² Π±ΡΠ°ΡΠ·Π΅ΡΠ΅ Π½Π°ΡΠ½ΡΡ Π²ΡΠΏΠΎΠ»Π½ΡΡΡΡΡ Javascript-ΠΊΠΎΠ΄, Π½Π°ΡΠΈΠ½Π°Ρ Ρ ΡΠ°ΠΉΠ»Π° 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
). - ΠΠ°ΡΡΡΠΎΠΉΠΊΠ° ΠΎΠ±ΡΠ°Π±ΠΎΡΡΠΈΠΊΠ° Π½Π°ΠΆΠ°ΡΠΈΡ ΠΊΠ½ΠΎΠΏΠΊΠΈ Β«PLAYΒ». ΠΡΠΈ Π½Π°ΠΆΠ°ΡΠΈΠΈ ΠΊΠ½ΠΎΠΏΠΊΠΈ ΠΊΠΎΠ΄ ΠΈΠ½ΠΈΡΠΈΠ°Π»ΠΈΠ·ΠΈΡΡΠ΅Ρ ΠΈΠ³ΡΡ ΠΈ ΡΠΎΠΎΠ±ΡΠ°Π΅Ρ ΡΠ΅ΡΠ²Π΅ΡΡ, ΡΡΠΎ ΠΌΡ Π³ΠΎΡΠΎΠ²Ρ ΠΈΠ³ΡΠ°ΡΡ.
ΠΡΠ½ΠΎΠ²Π½ΠΎΠ΅ Β«ΠΌΡΡΠΎΒ» Π½Π°ΡΠ΅ΠΉ ΠΊΠ»ΠΈΠ΅Π½Ρ-ΡΠ΅ΡΠ²Π΅ΡΠ½ΠΎΠΉ Π»ΠΎΠ³ΠΈΠΊΠΈ Π½Π°Ρ
ΠΎΠ΄ΠΈΡΡΡ Π² ΡΠ΅Ρ
ΡΠ°ΠΉΠ»Π°Ρ
, ΠΊΠΎΡΠΎΡΡΠ΅ Π±ΡΠ»ΠΈ ΠΈΠΌΠΏΠΎΡΡΠΈΡΠΎΠ²Π°Π½Ρ ΡΠ°ΠΉΠ»ΠΎΠΌ 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
ΡΠ°Π·ΡΠ΅ΡΠ°Π΅ΡΡΡ ΡΠΎΠ»ΡΠΊΠΎ ΡΠΎΠ³Π΄Π°, ΠΊΠΎΠ³Π΄Π° ΠΌΡ ΡΡΡΠ°Π½ΠΎΠ²ΠΈΠ»ΠΈ ΡΠΎΠ΅Π΄ΠΈΠ½Π΅Π½ΠΈΠ΅. - ΠΡΠ»ΠΈ ΡΠΎΠ΅Π΄ΠΈΠ½Π΅Π½ΠΈΠ΅ ΡΡΠΏΠ΅ΡΠ½ΠΎ ΡΡΡΠ°Π½ΠΎΠ²Π»Π΅Π½ΠΎ, ΠΌΡ ΡΠ΅Π³ΠΈΡΡΡΠΈΡΡΠ΅ΠΌ callback-ΡΡΠ½ΠΊΡΠΈΠΈ (
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()
β ΡΡΠΎ Event Listeners, Π²ΡΠ·ΡΠ²Π°ΡΡΠΈΠ΅ updateDirection()
(ΠΈΠ· networking.js
) ΠΏΡΠΈ ΡΠΎΠ²Π΅ΡΡΠ΅Π½ΠΈΠΈ ΡΠΎΠ±ΡΡΠΈΡ Π²Π²ΠΎΠ΄Π° (Π½Π°ΠΏΡΠΈΠΌΠ΅Ρ, ΠΏΡΠΈ ΠΏΠ΅ΡΠ΅ΠΌΠ΅ΡΠ΅Π½ΠΈΠΈ ΠΌΡΡΠΈ). updateDirection()
Π·Π°Π½ΠΈΠΌΠ°Π΅ΡΡΡ ΠΎΠ±ΠΌΠ΅Π½ΠΎΠΌ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΡΠΌΠΈ Ρ ΡΠ΅ΡΠ²Π΅ΡΠΎΠΌ, ΠΊΠΎΡΠΎΡΡΠΉ ΠΎΠ±ΡΠ°Π±Π°ΡΡΠ²Π°Π΅Ρ ΡΠΎΠ±ΡΡΠΈΠ΅ Π²Π²ΠΎΠ΄Π° ΠΈ ΡΠΎΠΎΡΠ²Π΅ΡΡΡΠ²ΡΡΡΠΈΠΌ ΠΎΠ±ΡΠ°Π·ΠΎΠΌ ΠΎΠ±Π½ΠΎΠ²Π»ΡΠ΅Ρ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ ΠΈΠ³ΡΡ.
7. Π‘ΠΎΡΡΠΎΡΠ½ΠΈΠ΅ ΠΊΠ»ΠΈΠ΅Π½ΡΠ°
ΠΡΠΎΡ ΡΠ°Π·Π΄Π΅Π» β ΡΠ°ΠΌΡΠΉ ΡΠ»ΠΎΠΆΠ½ΡΠΉ Π² ΠΏΠ΅ΡΠ²ΠΎΠΉ ΡΠ°ΡΡΠΈ ΠΏΠΎΡΡΠ°. ΠΠ΅ ΡΠ°ΡΡΡΡΠ°ΠΈΠ²Π°ΠΉΡΠ΅ΡΡ, Π΅ΡΠ»ΠΈ Π½Π΅ ΠΏΠΎΠΉΠΌΡΡΠ΅ Π΅Π³ΠΎ Ρ ΠΏΠ΅ΡΠ²ΠΎΠ³ΠΎ ΠΏΡΠΎΡΡΠ΅Π½ΠΈΡ! ΠΠΎΠΆΠ΅ΡΠ΅ Π΄Π°ΠΆΠ΅ ΠΏΡΠΎΠΏΡΡΡΠΈΡΡ Π΅Π³ΠΎ ΠΈ Π²Π΅ΡΠ½ΡΡΡΡΡ ΠΊ Π½Π΅ΠΌΡ ΠΏΠΎΠ·ΠΆΠ΅.
ΠΠΎΡΠ»Π΅Π΄Π½ΠΈΠΉ ΠΊΡΡΠΎΠΊ ΠΏΠ°Π·Π»Π°, ΠΊΠΎΡΠΎΡΡΠΉ Π½ΡΠΆΠ΅Π½ Π΄Π»Ρ Π·Π°Π²Π΅ΡΡΠ΅Π½ΠΈΡ ΠΊΠ»ΠΈΠ΅Π½Ρ-ΡΠ΅ΡΠ²Π΅ΡΠ½ΠΎΠ³ΠΎ ΠΊΠΎΠ΄Π° β ΡΡΠΎ state. ΠΠΎΠΌΠ½ΠΈΡΠ΅ ΡΡΠ°Π³ΠΌΠ΅Π½Ρ ΠΊΠΎΠ΄Π° ΠΈΠ· ΡΠ°Π·Π΄Π΅Π»Π° Β«Π Π΅Π½Π΄Π΅ΡΠΈΠ½Π³ ΠΊΠ»ΠΈΠ΅Π½ΡΠ°Β»?
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: ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΡ ΠΎΠ± ΠΈΠ³ΡΠΎΠΊΠ΅, ΠΏΠΎΠ»ΡΡΠ°ΡΡΠ΅Π³ΠΎ ΡΡΠΎ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅.
- others: ΠΌΠ°ΡΡΠΈΠ² ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΠΈ ΠΎ Π΄ΡΡΠ³ΠΈΡ ΠΈΠ³ΡΠΎΠΊΠ°Ρ , ΡΡΠ°ΡΡΠ²ΡΡΡΠΈΡ Π² ΡΠΎΠΉ ΠΆΠ΅ ΠΈΠ³ΡΠ΅.
- bullets: ΠΌΠ°ΡΡΠΈΠ² ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΠΈ ΠΎ ΡΠ½Π°ΡΡΠ΄Π°Ρ Π² ΠΈΠ³ΡΠ΅.
- leaderboard: ΡΠ΅ΠΊΡΡΠΈΠ΅ Π΄Π°Π½Π½ΡΠ΅ ΡΠ°Π±Π»ΠΈΡΡ Π»ΠΈΠ΄Π΅ΡΠΎΠ². Π ΡΡΠΎΠΌ ΠΏΠΎΡΡΠ΅ ΠΌΡ ΠΈΡ ΡΡΠΈΡΡΠ²Π°ΡΡ Π½Π΅ Π±ΡΠ΄Π΅ΠΌ.
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;
}
ΠΡΠ°ΡΠΈΠ²ΠΎ ΠΈ ΠΏΠΎΠ½ΡΡΠ½ΠΎ! ΠΠΎ Π΅ΡΠ»ΠΈ Π±Ρ Π²ΡΡ Π±ΡΠ»ΠΎ ΡΠ°ΠΊ ΠΏΡΠΎΡΡΠΎ. ΠΠ΄Π½Π° ΠΈΠ· ΠΏΡΠΈΡΠΈΠ½, ΠΏΠΎ ΠΊΠΎΡΠΎΡΡΠΌ ΡΠ°ΠΊΠ°Ρ ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΡ ΠΏΡΠΎΠ±Π»Π΅ΠΌΠ°ΡΠΈΡΠ½Π°: ΠΎΠ½Π° ΠΎΠ³ΡΠ°Π½ΠΈΡΠΈΠ²Π°Π΅Ρ ΡΠ°ΡΡΠΎΡΡ ΠΊΠ°Π΄ΡΠΎΠ² ΡΠ΅Π½Π΄Π΅ΡΠΈΠ½Π³Π° ΡΠ°ΡΡΠΎΡΠΎΠΉ ΡΠ°ΠΊΡΠΎΠ² ΡΠ΅ΡΠ²Π΅ΡΠ°.
Π§Π°ΡΡΠΎΡΠ° ΠΊΠ°Π΄ΡΠΎΠ² (Frame Rate): ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ ΠΊΠ°Π΄ΡΠΎΠ² (Ρ.Π΅. Π²ΡΠ·ΠΎΠ²ΠΎΠ²
render()
) Π² ΡΠ΅ΠΊΡΠ½Π΄Ρ, ΠΈΠ»ΠΈ FPS. Π ΠΈΠ³ΡΠ°Ρ ΠΎΠ±ΡΡΠ½ΠΎ ΡΡΡΠ΅ΠΌΡΡΡΡ Π΄ΠΎΡΡΠΈΡΡ Π½Π΅ ΠΌΠ΅Π½Π΅Π΅ 60 FPS.
Π§Π°ΡΡΠΎΡΠ° ΡΠ°ΠΊΡΠΎΠ² (Tick Rate): ΡΠ°ΡΡΠΎΡΠ°, Ρ ΠΊΠΎΡΠΎΡΠΎΠΉ ΡΠ΅ΡΠ²Π΅Ρ ΠΎΡΠΏΡΠ°Π²Π»ΡΠ΅Ρ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΡ ΠΈΠ³ΡΡ ΠΊΠ»ΠΈΠ΅Π½ΡΠ°ΠΌ. Π§Π°ΡΡΠΎ ΠΎΠ½Π° Π½ΠΈΠΆΠ΅, ΡΠ΅ΠΌ ΡΠ°ΡΡΠΎΡΠ° ΠΊΠ°Π΄ΡΠΎΠ². Π Π½Π°ΡΠ΅ΠΉ ΠΈΠ³ΡΠ΅ ΡΠ΅ΡΠ²Π΅Ρ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ Ρ ΡΠ°ΡΡΠΎΡΠΎΠΉ 30 ΡΠ°ΠΊΡΠΎΠ² Π² ΡΠ΅ΠΊΡΠ½Π΄Ρ.
ΠΡΠ»ΠΈ ΠΌΡ ΠΏΡΠΎΡΡΠΎ Π±ΡΠ΄Π΅ΠΌ ΡΠ΅Π½Π΄Π΅ΡΠΈΡΡ ΠΏΠΎΡΠ»Π΅Π΄Π½Π΅Π΅ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ ΠΈΠ³ΡΡ, ΡΠΎ FPS ΠΏΠΎ ΡΡΡΠΈ Π½ΠΈΠΊΠΎΠ³Π΄Π° Π½Π΅ ΡΠΌΠΎΠΆΠ΅Ρ ΠΏΡΠ΅Π²ΡΡΠΈΡΡ 30, ΠΏΠΎΡΠΎΠΌΡ ΡΡΠΎ ΠΌΡ Π½ΠΈΠΊΠΎΠ³Π΄Π° Π½Π΅ ΠΏΠΎΠ»ΡΡΠ°Π΅ΠΌ ΠΎΡ ΡΠ΅ΡΠ²Π΅ΡΠ° Π±ΠΎΠ»ΡΡΠ΅ 30 ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠΉ Π² ΡΠ΅ΠΊΡΠ½Π΄Ρ. ΠΠ°ΠΆΠ΅ Π΅ΡΠ»ΠΈ ΠΌΡ Π±ΡΠ΄Π΅ΠΌ Π²ΡΠ·ΡΠ²Π°ΡΡ render()
60 ΡΠ°Π· Π² ΡΠ΅ΠΊΡΠ½Π΄Ρ, ΡΠΎ ΠΏΠΎΠ»ΠΎΠ²ΠΈΠ½Π° ΡΡΠΈΡ
Π²ΡΠ·ΠΎΠ²ΠΎΠ² Π±ΡΠ΄Π΅Ρ ΠΏΡΠΎΡΡΠΎ ΠΏΠ΅ΡΠ΅ΡΠΈΡΠΎΠ²ΡΠ²Π°ΡΡ ΡΠΎ ΠΆΠ΅ ΡΠ°ΠΌΠΎΠ΅, ΠΏΠΎ ΡΡΡΠΈ Π½Π΅ Π΄Π΅Π»Π°Ρ Π½ΠΈΡΠ΅Π³ΠΎ. ΠΡΡ ΠΎΠ΄Π½Π° ΠΏΡΠΎΠ±Π»Π΅ΠΌΠ° Π½Π°ΠΈΠ²Π½ΠΎΠΉ ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΠΈ Π·Π°ΠΊΠ»ΡΡΠ°Π΅ΡΡΡ Π² ΡΠΎΠΌ, ΡΡΠΎ ΠΎΠ½Π° ΠΏΠΎΠ΄Π²Π΅ΡΠΆΠ΅Π½Π° Π·Π°Π΄Π΅ΡΠΆΠΊΠ°ΠΌ. ΠΡΠΈ ΠΈΠ΄Π΅Π°Π»ΡΠ½ΠΎΠΉ ΡΠΊΠΎΡΠΎΡΡΠΈ ΠΠ½ΡΠ΅ΡΠ½Π΅ΡΠ° ΠΊΠ»ΠΈΠ΅Π½Ρ Π±ΡΠ΄Π΅Ρ ΠΏΠΎΠ»ΡΡΠ°ΡΡ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ ΠΈΠ³ΡΡ ΡΠΎΠ²Π½ΠΎ ΡΠ΅ΡΠ΅Π· ΠΊΠ°ΠΆΠ΄ΡΠ΅ 33 ΠΌΡ (30 Π² ΡΠ΅ΠΊΡΠ½Π΄Ρ):
Π ΡΠΎΠΆΠ°Π»Π΅Π½ΠΈΡ, Π½ΠΈΡΡΠΎ Π½Π΅ ΠΈΠ΄Π΅Π°Π»ΡΠ½ΠΎ. ΠΠΎΠ»Π΅Π΅ ΡΠ΅Π°Π»ΠΈΡΡΠΈΡΠ½ΠΎΠΉ Π±ΡΠ΄Π΅Ρ ΡΠ°ΠΊΠ°Ρ ΠΊΠ°ΡΡΠΈΠ½Π°:
ΠΠ°ΠΈΠ²Π½Π°Ρ ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΡ β ΡΡΠΎ ΠΏΡΠ°ΠΊΡΠΈΡΠ΅ΡΠΊΠΈ Π½Π°ΠΈΡ
ΡΠ΄ΡΠΈΠΉ ΡΠ»ΡΡΠ°ΠΉ, ΠΊΠΎΠ³Π΄Π° Π΄Π΅Π»ΠΎ Π΄ΠΎΡ
ΠΎΠ΄ΠΈΡ Π΄ΠΎ Π·Π°Π΄Π΅ΡΠΆΠ΅ΠΊ. ΠΡΠ»ΠΈ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ ΠΈΠ³ΡΡ ΠΏΡΠΈΠ½ΠΈΠΌΠ°Π΅ΡΡΡ Ρ Π·Π°Π΄Π΅ΡΠΆΠΊΠΎΠΉ 50 ΠΌΡ, ΡΠΎ ΠΊΠ»ΠΈΠ΅Π½Ρ Π·Π°ΡΠΎΡΠΌΠ°ΠΆΠΈΠ²Π°Π΅ΡΡΡ Π½Π° Π»ΠΈΡΠ½ΠΈΠ΅ 50 ΠΌΡ, ΠΏΠΎΡΠΎΠΌΡ ΡΡΠΎ ΠΎΠ½ ΠΏΠΎ-ΠΏΡΠ΅ΠΆΠ½Π΅ΠΌΡ ΡΠ΅Π½Π΄Π΅ΡΠΈΡ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ ΠΈΠ³ΡΡ ΠΈΠ· ΠΏΡΠ΅Π΄ΡΠ΄ΡΡΠ΅Π³ΠΎ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΡ. ΠΠΎΠΆΠ΅ΡΠ΅ ΠΏΡΠ΅Π΄ΡΡΠ°Π²ΠΈΡΡ, Π½Π°ΡΠΊΠΎΠ»ΡΠΊΠΎ ΡΡΠΎ Π½Π΅ΡΠ΄ΠΎΠ±Π½ΠΎ Π΄Π»Ρ ΠΈΠ³ΡΠΎΠΊΠ°: ΠΈΠ·-Π·Π° ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ»ΡΠ½ΡΡ
ΡΠΎΡΠΌΠΎΠΆΠ΅Π½ΠΈΠΉ ΠΈΠ³ΡΠ° Π±ΡΠ΄Π΅Ρ ΠΊΠ°Π·Π°ΡΡΡΡ Π΄ΡΡΠ³Π°Π½Π½ΠΎΠΉ ΠΈ Π½Π΅ΡΡΠ°Π±ΠΈΠ»ΡΠ½ΠΎΠΉ.
7.2 Π£Π»ΡΡΡΠ΅Π½Π½ΠΎΠ΅ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ ΠΊΠ»ΠΈΠ΅Π½ΡΠ°
ΠΡ Π²Π½Π΅ΡΡΠΌ Π² Π½Π°ΠΈΠ²Π½ΡΡ ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΡ Π½Π΅ΠΊΠΎΡΠΎΡΡΠ΅ ΡΠ»ΡΡΡΠ΅Π½ΠΈΡ. ΠΠΎ-ΠΏΠ΅ΡΠ²ΡΡ , ΠΌΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌ Π·Π°Π΄Π΅ΡΠΆΠΊΡ ΡΠ΅Π½Π΄Π΅ΡΠΈΠ½Π³Π° Π½Π° 100 ΠΌΡ. ΠΡΠΎ ΠΎΠ·Π½Π°ΡΠ°Π΅Ρ, ΡΡΠΎ Β«ΡΠ΅ΠΊΡΡΠ΅Π΅Β» ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ ΠΊΠ»ΠΈΠ΅Π½ΡΠ° Π²ΡΠ΅Π³Π΄Π° Π±ΡΠ΄Π΅Ρ ΠΎΡΡΡΠ°Π²Π°ΡΡ ΠΎΡ ΡΠΎΡΡΠΎΡΠ½ΠΈΡ ΠΈΠ³ΡΡ Π½Π° ΡΠ΅ΡΠ²Π΅ΡΠ΅ Π½Π° 100 ΠΌΡ. ΠΠ°ΠΏΡΠΈΠΌΠ΅Ρ, Π΅ΡΠ»ΠΈ Π½Π° ΡΠ΅ΡΠ²Π΅ΡΠ΅ Π²ΡΠ΅ΠΌΡ ΡΠ°Π²Π½ΠΎ 150, ΡΠΎ Π½Π° ΠΊΠ»ΠΈΠ΅Π½ΡΠ΅ Π±ΡΠ΄Π΅Ρ ΡΠ΅Π½Π΄Π΅ΡΠΈΡΡΡΡ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅, Π² ΠΊΠΎΡΠΎΡΠΎΠΌ Π±ΡΠ» ΡΠ΅ΡΠ²Π΅Ρ Π²ΠΎ Π²ΡΠ΅ΠΌΡ 50:
ΠΡΠΎ Π΄Π°ΡΡ Π½Π°ΠΌ Π±ΡΡΠ΅Ρ Π² 100 ΠΌΡ, ΠΏΠΎΠ·Π²ΠΎΠ»ΡΡΡΠΈΠΉ ΠΏΠ΅ΡΠ΅ΠΆΠΈΡΡ Π½Π΅ΠΏΡΠ΅Π΄ΡΠΊΠ°Π·ΡΠ΅ΠΌΠΎΠ΅ Π²ΡΠ΅ΠΌΡ ΠΏΠΎΠ»ΡΡΠ΅Π½ΠΈΡ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠΉ ΠΈΠ³ΡΡ:
Π Π°ΡΠΏΠ»Π°ΡΠΎΠΉ Π·Π° ΡΡΠΎ Π±ΡΠ΄Π΅Ρ ΠΏΠΎΡΡΠΎΡΠ½Π½Π°Ρ
ΠΡ ΠΌΠΎΠΆΠ΅ΠΌ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ ΠΈ Π΄ΡΡΠ³ΡΡ ΡΠ΅Ρ Π½ΠΈΠΊΡ ΠΏΠΎΠ΄ Π½Π°Π·Π²Π°Π½ΠΈΠ΅ΠΌ
Β«ΠΏΡΠΎΠ³Π½ΠΎΠ·ΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ Π½Π° ΡΡΠΎΡΠΎΠ½Π΅ ΠΊΠ»ΠΈΠ΅Π½ΡΠ°Β» , ΠΊΠΎΡΠΎΡΠ°Ρ Ρ ΠΎΡΠΎΡΠΎ ΡΠΏΡΠ°Π²Π»ΡΠ΅ΡΡΡ ΡΠΎ ΡΠ½ΠΈΠΆΠ΅Π½ΠΈΠ΅ΠΌ Π²ΠΎΡΠΏΡΠΈΠ½ΠΈΠΌΠ°Π΅ΠΌΡΡ Π·Π°Π΄Π΅ΡΠΆΠ΅ΠΊ, Π½ΠΎ Π² ΡΡΠΎΠΌ ΠΏΠΎΡΡΠ΅ ΠΎΠ½Π° ΡΠ°ΡΡΠΌΠ°ΡΡΠΈΠ²Π°ΡΡΡΡ Π½Π΅ Π±ΡΠ΄Π΅Ρ.
ΠΡΡ ΠΎΠ΄Π½ΠΎ ΡΠ»ΡΡΡΠ΅Π½ΠΈΠ΅, ΠΊΠΎΡΠΎΡΠΎΠ΅ ΠΌΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌ β ΡΡΠΎ Π»ΠΈΠ½Π΅ΠΉΠ½Π°Ρ ΠΈΠ½ΡΠ΅ΡΠΏΠΎΠ»ΡΡΠΈΡ. ΠΠ·-Π·Π° Π·Π°Π΄Π΅ΡΠΆΠΊΠΈ ΡΠ΅Π½Π΄Π΅ΡΠΈΠ½Π³Π° ΠΌΡ ΠΎΠ±ΡΡΠ½ΠΎ ΠΊΠ°ΠΊ ΠΌΠΈΠ½ΠΈΠΌΡΠΌ Π½Π° ΠΎΠ΄Π½ΠΎ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ ΠΎΠ±Π³ΠΎΠ½ΡΠ΅ΠΌ ΡΠ΅ΠΊΡΡΠ΅Π΅ Π²ΡΠ΅ΠΌΡ Π² ΠΊΠ»ΠΈΠ΅Π½ΡΠ΅. ΠΠΎΠ³Π΄Π° Π²ΡΠ·ΡΠ²Π°Π΅ΡΡΡ getCurrentState()
, ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ Π²ΡΠΏΠΎΠ»Π½ΠΈΡΡ
ΠΡΠΎ ΡΠ΅ΡΠ°Π΅Ρ ΠΏΡΠΎΠ±Π»Π΅ΠΌΡ Ρ ΡΠ°ΡΡΠΎΡΠΎΠΉ ΠΊΠ°Π΄ΡΠΎΠ²: ΡΠ΅ΠΏΠ΅ΡΡ ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ ΡΠ΅Π½Π΄Π΅ΡΠΈΡΡ ΡΠ½ΠΈΠΊΠ°Π»ΡΠ½ΡΠ΅ ΠΊΠ°Π΄ΡΡ Ρ Π»ΡΠ±ΠΎΠΉ Π½ΡΠΆΠ½ΠΎΠΉ Π½Π°ΠΌ ΡΠ°ΡΡΠΎΡΠΎΠΉ!
7.3 Π Π΅Π°Π»ΠΈΠ·Π°ΡΠΈΡ ΡΠ»ΡΡΡΠ΅Π½Π½ΠΎΠ³ΠΎ ΡΠΎΡΡΠΎΡΠ½ΠΈΡ ΠΊΠ»ΠΈΠ΅Π½ΡΠ°
ΠΡΠΈΠΌΠ΅Ρ ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΠΈ Π² src/client/state.js
ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅Ρ ΠΈ Π·Π°Π΄Π΅ΡΠΆΠΊΡ ΡΠ΅Π½Π΄Π΅ΡΠΈΠ½Π³Π°, ΠΈ Π»ΠΈΠ½Π΅ΠΉΠ½ΡΡ ΠΈΠ½ΡΠ΅ΡΠΏΠΎΠ»ΡΡΠΈΡ, Π½ΠΎ ΡΡΠΎ Π½Π΅Π½Π°Π΄ΠΎΠ»Π³ΠΎ. ΠΠ°Π²Π°ΠΉΡΠ΅ ΡΠ°Π·ΠΎΠ±ΡΡΠΌ ΠΊΠΎΠ΄ Π½Π° Π΄Π²Π΅ ΡΠ°ΡΡΠΈ. ΠΠΎΡ ΠΏΠ΅ΡΠ²Π°Ρ:
state.js, ΡΠ°ΡΡΡ 1
const RENDER_DELAY = 100;
const gameUpdates = [];
let gameStart = 0;
let firstServerTimestamp = 0;
export function initState() {
gameStart = 0;
firstServerTimestamp = 0;
}
export function processGameUpdate(update) {
if (!firstServerTimestamp) {
firstServerTimestamp = update.t;
gameStart = Date.now();
}
gameUpdates.push(update);
// Keep only one game update before the current server time
const base = getBaseUpdate();
if (base > 0) {
gameUpdates.splice(0, base);
}
}
function currentServerTime() {
return firstServerTimestamp + (Date.now() - gameStart) - RENDER_DELAY;
}
// Returns the index of the base update, the first game update before
// current server time, or -1 if N/A.
function getBaseUpdate() {
const serverTime = currentServerTime();
for (let i = gameUpdates.length - 1; i >= 0; i--) {
if (gameUpdates[i].t <= serverTime) {
return i;
}
}
return -1;
}
ΠΠ΅ΡΠ²ΡΠΌ Π΄Π΅Π»ΠΎΠΌ Π½ΡΠΆΠ½ΠΎ ΡΠ°Π·ΠΎΠ±ΡΠ°ΡΡΡΡ Ρ ΡΠ΅ΠΌ, ΡΡΠΎ Π΄Π΅Π»Π°Π΅Ρ currentServerTime()
. ΠΠ°ΠΊ ΠΌΡ Π²ΠΈΠ΄Π΅Π»ΠΈ ΡΠ°Π½Π΅Π΅, Π² ΠΊΠ°ΠΆΠ΄ΠΎΠ΅ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ ΠΈΠ³ΡΡ Π²ΠΊΠ»ΡΡΠ°Π΅ΡΡΡ ΡΠ΅ΡΠ²Π΅ΡΠ½Π°Ρ ΠΌΠ΅ΡΠΊΠ° Π²ΡΠ΅ΠΌΠ΅Π½ΠΈ. ΠΡ Ρ
ΠΎΡΠΈΠΌ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ Π·Π°Π΄Π΅ΡΠΆΠΊΡ ΡΠ΅Π½Π΄Π΅ΡΠΈΠ½Π³Π°, ΡΡΠΎΠ±Ρ ΡΠ΅Π½Π΄Π΅ΡΠΈΡΡ ΠΊΠ°ΡΡΠΈΠ½ΠΊΡ, ΠΎΡΡΡΠ°Π²Π°Ρ ΠΎΡ ΡΠ΅ΡΠ²Π΅ΡΠ° Π½Π° 100 ΠΌΡ, Π½ΠΎ ΠΌΡ Π½ΠΈΠΊΠΎΠ³Π΄Π° Π½Π΅ ΡΠ·Π½Π°Π΅ΠΌ, ΡΠ΅ΠΊΡΡΠ΅Π΅ Π²ΡΠ΅ΠΌΡ Π½Π° ΡΠ΅ΡΠ²Π΅ΡΠ΅, ΠΏΠΎΡΠΎΠΌΡ ΡΡΠΎ Π½Π΅ ΠΌΠΎΠΆΠ΅ΠΌ Π·Π½Π°ΡΡ, ΠΊΠ°ΠΊ Π΄ΠΎΠ»Π³ΠΎ Π΄ΠΎΠ±ΠΈΡΠ°Π»ΠΎΡΡ Π΄ΠΎ Π½Π°Ρ Π»ΡΠ±ΠΎΠ΅ ΠΈΠ· ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠΉ. ΠΠ½ΡΠ΅ΡΠ½Π΅Ρ Π½Π΅ΠΏΡΠ΅Π΄ΡΠΊΠ°Π·ΡΠ΅ΠΌ ΠΈ Π΅Π³ΠΎ ΡΠΊΠΎΡΠΎΡΡΡ ΠΌΠΎΠΆΠ΅Ρ ΠΎΡΠ΅Π½Ρ ΡΠΈΠ»ΡΠ½ΠΎ Π²Π°ΡΡΠΈΡΠΎΠ²Π°ΡΡΡΡ!
Π§ΡΠΎΠ±Ρ ΠΎΠ±ΠΎΠΉΡΠΈ ΡΡΡ ΠΏΡΠΎΠ±Π»Π΅ΠΌΡ, ΠΌΠΎΠΆΠ½ΠΎ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ ΡΠ°Π·ΡΠΌΠ½ΡΡ Π°ΠΏΠΏΡΠΎΠΊΡΠΈΠΌΠ°ΡΠΈΡ: ΠΌΡ ΠΏΡΠΈΡΠ²ΠΎΡΠΈΠΌΡΡ, ΡΡΠΎ ΠΏΠ΅ΡΠ²ΠΎΠ΅ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ ΠΏΡΠΈΠ±ΡΠ»ΠΎ ΠΌΠ³Π½ΠΎΠ²Π΅Π½Π½ΠΎ. ΠΡΠ»ΠΈ Π±Ρ ΡΡΠΎ Π±ΡΠ»ΠΎ Π²Π΅ΡΠ½ΠΎ, ΡΠΎ ΠΌΡ Π±Ρ Π·Π½Π°Π»ΠΈ Π²ΡΠ΅ΠΌΡ ΡΠ΅ΡΠ²Π΅ΡΠ° Π² ΡΡΠΎΡ ΠΊΠΎΠ½ΠΊΡΠ΅ΡΠ½ΡΠΉ ΠΌΠΎΠΌΠ΅Π½Ρ! ΠΡ ΡΠΎΡ
ΡΠ°Π½ΡΠ΅ΠΌ ΠΌΠ΅ΡΠΊΡ Π²ΡΠ΅ΠΌΠ΅Π½ΠΈ ΡΠ΅ΡΠ²Π΅ΡΠ° Π² firstServerTimestamp
ΠΈ ΡΠΎΡ
ΡΠ°Π½ΡΠ΅ΠΌ Π½Π°ΡΡ Π»ΠΎΠΊΠ°Π»ΡΠ½ΡΡ (ΠΊΠ»ΠΈΠ΅Π½ΡΡΠΊΡΡ) ΠΌΠ΅ΡΠΊΡ Π²ΡΠ΅ΠΌΠ΅Π½ΠΈ Π² ΡΠΎΡ ΠΆΠ΅ ΠΌΠΎΠΌΠ΅Π½Ρ Π² gameStart
.
ΠΠΉ, ΠΏΠΎΡΡΠΎΠΉΡΠ΅-ΠΊΠ°. Π Π°Π·Π²Π΅ Π½Π΅ Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π±ΡΡΡ Π²ΡΠ΅ΠΌΡ Π½Π° ΡΠ΅ΡΠ²Π΅ΡΠ΅ = Π²ΡΠ΅ΠΌΠ΅Π½ΠΈ Π² ΠΊΠ»ΠΈΠ΅Π½ΡΠ΅? ΠΠΎΡΠ΅ΠΌΡ ΠΌΡ ΡΠ°Π·Π»ΠΈΡΠ°Π΅ΠΌ Β«ΠΌΠ΅ΡΠΊΡ Π²ΡΠ΅ΠΌΠ΅Π½ΠΈ ΡΠ΅ΡΠ²Π΅ΡΠ°Β» ΠΈ Β«ΠΌΠ΅ΡΠΊΡ Π²ΡΠ΅ΠΌΠ΅Π½ΠΈ ΠΊΠ»ΠΈΠ΅Π½ΡΠ°Β»? ΠΡΠΎ ΠΎΡΠ»ΠΈΡΠ½ΡΠΉ Π²ΠΎΠΏΡΠΎΡ! ΠΠΊΠ°Π·ΡΠ²Π°Π΅ΡΡΡ, ΡΡΠΎ Π½Π΅ ΠΎΠ΄Π½ΠΎ ΠΈ ΡΠΎ ΠΆΠ΅. Date.now()
Π±ΡΠ΄Π΅Ρ Π²ΠΎΠ·Π²ΡΠ°ΡΠ°ΡΡ ΡΠ°Π·Π½ΡΠ΅ ΠΌΠ΅ΡΠΊΠΈ Π²ΡΠ΅ΠΌΠ΅Π½ΠΈ Π² ΠΊΠ»ΠΈΠ΅Π½ΡΠ΅ ΠΈ ΡΠ΅ΡΠ²Π΅ΡΠ° ΠΈ ΡΡΠΎ Π·Π°Π²ΠΈΡΠΈΡ ΠΎΡ Π»ΠΎΠΊΠ°Π»ΡΠ½ΡΡ
Π΄Π»Ρ ΡΡΠΈΡ
ΠΌΠ°ΡΠΈΠ½ ΡΠ°ΠΊΡΠΎΡΠΎΠ². ΠΠΈΠΊΠΎΠ³Π΄Π° Π½Π΅ Π΄ΠΎΠΏΡΡΠΊΠ°ΠΉΡΠ΅, ΡΡΠΎ ΠΌΠ΅ΡΠΊΠΈ Π²ΡΠ΅ΠΌΠ΅Π½ΠΈ Π±ΡΠ΄ΡΡ ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²ΡΠΌΠΈ Π½Π° Π²ΡΠ΅Ρ
ΠΌΠ°ΡΠΈΠ½Π°Ρ
.
Π’Π΅ΠΏΠ΅ΡΡ Π½Π°ΠΌ ΠΏΠΎΠ½ΡΡΠ½ΠΎ, ΡΡΠΎ Π΄Π΅Π»Π°Π΅Ρ currentServerTime()
: ΠΎΠ½ Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅Ρ ΠΌΠ΅ΡΠΊΡ Π²ΡΠ΅ΠΌΠ΅Π½ΠΈ ΡΠ΅ΡΠ²Π΅ΡΠ° ΡΠ΅ΠΊΡΡΠ΅Π³ΠΎ Π²ΡΠ΅ΠΌΠ΅Π½ΠΈ ΡΠ΅Π½Π΄Π΅ΡΠΈΠ½Π³Π°. ΠΡΡΠ³ΠΈΠΌΠΈ ΡΠ»ΠΎΠ²Π°ΠΌΠΈ, ΡΡΠΎ ΡΠ΅ΠΊΡΡΠ΅Π΅ Π²ΡΠ΅ΠΌΡ ΡΠ΅ΡΠ²Π΅ΡΠ° (firstServerTimestamp <+ (Date.now() - gameStart)
) ΠΌΠΈΠ½ΡΡ Π·Π°Π΄Π΅ΡΠΆΠΊΠ° ΡΠ΅Π½Π΄Π΅ΡΠΈΠ½Π³Π° (RENDER_DELAY
).
Π’Π΅ΠΏΠ΅ΡΡ Π΄Π°Π²Π°ΠΉΡΠ΅ ΡΠ°Π·Π±Π΅ΡΡΠΌΡΡ, ΠΊΠ°ΠΊ ΠΌΡ ΠΎΠ±ΡΠ°Π±Π°ΡΡΠ²Π°Π΅ΠΌ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΡ ΠΈΠ³ΡΡ. ΠΡΠΈ ΠΏΠΎΠ»ΡΡΠ΅Π½ΠΈΠΈ Ρ ΡΠ΅ΡΠ²Π΅ΡΠ° ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΡ Π²ΡΠ·ΡΠ²Π°Π΅ΡΡΡ processGameUpdate()
, ΠΈ ΠΌΡ ΡΠΎΡ
ΡΠ°Π½ΡΠ΅ΠΌ Π½ΠΎΠ²ΠΎΠ΅ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ Π² ΠΌΠ°ΡΡΠΈΠ² gameUpdates
. ΠΠ°ΡΠ΅ΠΌ, ΡΡΠΎΠ±Ρ ΠΏΡΠΎΠ²Π΅ΡΡΡΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ ΠΏΠ°ΠΌΡΡΠΈ ΠΌΡ ΡΠ΄Π°Π»ΡΠ΅ΠΌ Π²ΡΠ΅ ΡΡΠ°ΡΡΠ΅ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΡ Π΄ΠΎ Π±Π°Π·ΠΎΠ²ΠΎΠ³ΠΎ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΡ, ΠΏΠΎΡΠΎΠΌΡ ΡΡΠΎ ΠΎΠ½ΠΈ Π½Π°ΠΌ Π±ΠΎΠ»ΡΡΠ΅ Π½Π΅ Π½ΡΠΆΠ½Ρ.
Π§ΡΠΎ ΠΆΠ΅ ΡΠ°ΠΊΠΎΠ΅ Β«Π±Π°Π·ΠΎΠ²ΠΎΠ΅ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅Β»? ΠΡΠΎ ΠΏΠ΅ΡΠ²ΠΎΠ΅ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅, ΠΊΠΎΡΠΎΡΠΎΠ΅ ΠΌΡ Π½Π°Ρ ΠΎΠ΄ΠΈΠΌ, Π΄Π²ΠΈΠ³Π°ΡΡΡ Π½Π°Π·Π°Π΄ ΠΎΡ ΡΠ΅ΠΊΡΡΠ΅Π³ΠΎ Π²ΡΠ΅ΠΌΠ΅Π½ΠΈ ΡΠ΅ΡΠ²Π΅ΡΠ°. ΠΠΎΠΌΠ½ΠΈΡΠ΅ ΡΡΡ ΡΡ Π΅ΠΌΡ?
ΠΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ ΠΈΠ³ΡΡ Π½Π΅ΠΏΠΎΡΡΠ΅Π΄ΡΡΠ²Π΅Π½Π½ΠΎ ΡΠ»Π΅Π²Π° ΠΎΡ Β«Client Render TimeΒ» ΠΈ ΡΠ²Π»ΡΠ΅ΡΡΡ Π±Π°Π·ΠΎΠ²ΡΠΌ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ΠΌ.
ΠΠ»Ρ ΡΠ΅Π³ΠΎ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΡΡΡ Π±Π°Π·ΠΎΠ²ΠΎΠ΅ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅? ΠΠΎΡΠ΅ΠΌΡ ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ ΠΎΡΠ±ΡΠ°ΡΡΠ²Π°ΡΡ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΡ Π΄ΠΎ Π±Π°Π·ΠΎΠ²ΠΎΠ³ΠΎ? Π§ΡΠΎΠ±Ρ ΡΠ°Π·ΠΎΠ±ΡΠ°ΡΡΡΡ Π² ΡΡΠΎΠΌ, Π΄Π°Π²Π°ΠΉΡΠ΅ Π½Π°ΠΊΠΎΠ½Π΅Ρ-ΡΠΎ ΡΠ°ΡΡΠΌΠΎΡΡΠΈΠΌ ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΡ getCurrentState()
:
state.js, ΡΠ°ΡΡΡ 2
export function getCurrentState() {
if (!firstServerTimestamp) {
return {};
}
const base = getBaseUpdate();
const serverTime = currentServerTime();
// If base is the most recent update we have, use its state.
// Else, interpolate between its state and the state of (base + 1).
if (base < 0) {
return gameUpdates[gameUpdates.length - 1];
} else if (base === gameUpdates.length - 1) {
return gameUpdates[base];
} else {
const baseUpdate = gameUpdates[base];
const next = gameUpdates[base + 1];
const r = (serverTime - baseUpdate.t) / (next.t - baseUpdate.t);
return {
me: interpolateObject(baseUpdate.me, next.me, r),
others: interpolateObjectArray(baseUpdate.others, next.others, r),
bullets: interpolateObjectArray(baseUpdate.bullets, next.bullets, r),
};
}
}
ΠΡ ΠΎΠ±ΡΠ°Π±Π°ΡΡΠ²Π°Π΅ΠΌ ΡΡΠΈ ΡΠ»ΡΡΠ°Ρ:
base < 0
ΠΎΠ·Π½Π°ΡΠ°Π΅Ρ, ΡΡΠΎ Π΄ΠΎ ΡΠ΅ΠΊΡΡΠ΅Π³ΠΎ Π²ΡΠ΅ΠΌΠ΅Π½ΠΈ ΡΠ΅Π½Π΄Π΅ΡΠΈΠ½Π³Π° ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠΉ Π½Π΅Ρ (ΡΠΌ. Π²ΡΡΠ΅ ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΡgetBaseUpdate()
). ΠΡΠΎ ΠΌΠΎΠΆΠ΅Ρ ΡΠ»ΡΡΠΈΡΡΡΡ ΡΡΠ°Π·Ρ Π² Π½Π°ΡΠ°Π»Π΅ ΠΈΠ³ΡΡ ΠΈΠ·-Π·Π° Π·Π°Π΄Π΅ΡΠΆΠΊΠΈ ΡΠ΅Π½Π΄Π΅ΡΠΈΠ½Π³Π°. Π ΡΠ°ΠΊΠΎΠΌ ΡΠ»ΡΡΠ°Π΅ ΠΌΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌ ΡΠ°ΠΌΠΎΠ΅ ΠΏΠΎΡΠ»Π΅Π΄Π½Π΅Π΅ ΠΏΠΎΠ»ΡΡΠ΅Π½Π½ΠΎΠ΅ ΠΎΠ±Π½ΠΎΠ»Π²Π΅Π½ΠΈΠ΅.base
β ΡΡΠΎ ΡΠ°ΠΌΠΎΠ΅ ΠΏΠΎΡΠ»Π΅Π΄Π½Π΅Π΅ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅, ΠΊΠΎΡΠΎΡΠΎΠ΅ Ρ Π½Π°Ρ Π΅ΡΡΡ. ΠΡΠΎ ΠΌΠΎΠΆΠ΅Ρ ΠΏΡΠΎΠΈΠ·ΠΎΠΉΡΠΈ ΠΈΠ·-Π·Π° ΡΠ΅ΡΠ΅Π²ΠΎΠΉ Π·Π°Π΄Π΅ΡΠΆΠΊΠΈ ΠΈΠ»ΠΈ ΠΏΠ»ΠΎΡ ΠΎΠΉ ΡΠ²ΡΠ·ΠΈ Ρ ΠΠ½ΡΠ΅ΡΠ½Π΅ΡΠΎΠΌ. Π ΡΡΠΎΠΌ ΡΠ»ΡΡΠ°Π΅ ΠΌΡ ΡΠΎΠΆΠ΅ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌ ΡΠ°ΠΌΠΎΠ΅ ΠΏΠΎΡΠ»Π΅Π΄Π½Π΅Π΅ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅, ΠΊΠΎΡΠΎΡΠΎΠ΅ Ρ Π½Π°Ρ Π΅ΡΡΡ.- Π£ Π½Π°Ρ Π΅ΡΡΡ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ ΠΈ Π΄ΠΎ, ΠΈ ΠΏΠΎΡΠ»Π΅ ΡΠ΅ΠΊΡΡΠ΅Π³ΠΎ Π²ΡΠ΅ΠΌΠ΅Π½ΠΈ ΡΠ΅Π½Π΄Π΅ΡΠΈΠ½Π³Π°, ΠΏΠΎΡΡΠΎΠΌΡ ΠΌΠΎΠΆΠ½ΠΎ ΠΈΠ½ΡΠ΅ΡΠΏΠΎΠ»ΠΈΡΠΎΠ²Π°ΡΡ!
ΠΡΡ, ΡΡΠΎ ΠΎΡΡΠ°Π»ΠΎΡΡ Π² state.js
β ΡΡΠΎ ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΡ Π»ΠΈΠ½Π΅ΠΉΠ½ΠΎΠΉ ΠΈΠ½ΡΠ΅ΡΠΏΠΎΠ»ΡΡΠΈΠΈ, ΠΏΡΠ΅Π΄ΡΡΠ°Π²Π»ΡΡΡΠ°Ρ ΡΠΎΠ±ΠΎΠΉ ΠΏΡΠΎΡΡΡΡ (Π½ΠΎ ΡΠΊΡΡΠ½ΡΡ) ΠΌΠ°ΡΠ΅ΠΌΠ°ΡΠΈΠΊΡ. ΠΡΠ»ΠΈ Π²Ρ Ρ
ΠΎΡΠΈΡΠ΅ ΠΈΠ·ΡΡΠΈΡΡ Π΅Ρ ΡΠ°ΠΌΠΎΡΡΠΎΡΡΠ΅Π»ΡΠ½ΠΎ, ΡΠΎ ΠΎΡΠΊΡΠΎΠΉΡΠ΅ state.js
Π½Π°
Π§Π°ΡΡΡ 2. ΠΡΠΊΠ΅Π½Π΄-ΡΠ΅ΡΠ²Π΅Ρ
Π ΡΡΠΎΠΉ ΡΠ°ΡΡΠΈ ΠΌΡ ΡΠ°ΡΡΠΌΠΎΡΡΠΈΠΌ Π±ΡΠΊΠ΅Π½Π΄ Node.js, ΡΠΏΡΠ°Π²Π»ΡΡΡΠΈΠΉ Π½Π°ΡΠΈΠΌ
1. ΠΡ ΠΎΠ΄Π½Π°Ρ ΡΠΎΡΠΊΠ° ΡΠ΅ΡΠ²Π΅ΡΠ°
ΠΠ»Ρ ΡΠΏΡΠ°Π²Π»Π΅Π½ΠΈΡ Π²Π΅Π±-ΡΠ΅ΡΠ²Π΅ΡΠΎΠΌ ΠΌΡ Π±ΡΠ΄Π΅ΠΌ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ ΠΏΠΎΠΏΡΠ»ΡΡΠ½ΡΠΉ Π²Π΅Π±-ΡΡΠ΅ΠΉΠΌΠ²ΠΎΡΠΊ Π΄Π»Ρ Node.js ΠΏΠΎΠ΄ Π½Π°Π·Π²Π°Π½ΠΈΠ΅ΠΌ src/server/server.js
:
server.js, ΡΠ°ΡΡΡ 1
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackConfig = require('../../webpack.dev.js');
// Setup an Express server
const app = express();
app.use(express.static('public'));
if (process.env.NODE_ENV === 'development') {
// Setup Webpack for development
const compiler = webpack(webpackConfig);
app.use(webpackDevMiddleware(compiler));
} else {
// Static serve the dist/ folder in production
app.use(express.static('dist'));
}
// Listen on port
const port = process.env.PORT || 3000;
const server = app.listen(port);
console.log(`Server listening on port ${port}`);
ΠΠΎΠΌΠ½ΠΈΡΠ΅, ΡΡΠΎ Π² ΠΏΠ΅ΡΠ²ΠΎΠΉ ΡΠ°ΡΡΠΈ ΠΌΡ ΠΎΠ±ΡΡΠΆΠ΄Π°Π»ΠΈ Webpack? ΠΠΌΠ΅Π½Π½ΠΎ Π·Π΄Π΅ΡΡ ΠΌΡ Π±ΡΠ΄Π΅ΠΌ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ Π½Π°ΡΠΈ ΠΊΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΠΈ Webpack. ΠΡ Π±ΡΠ΄Π΅ΠΌ ΠΏΡΠΈΠΌΠ΅Π½ΡΡΡ ΠΈΡ Π΄Π²ΡΠΌΡ ΡΠΏΠΎΡΠΎΠ±Π°ΠΌΠΈ:
- ΠΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ
webpack-dev-middleware Π΄Π»Ρ Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΎΠΉ ΠΏΠ΅ΡΠ΅ΡΠ±ΠΎΡΠΊΠΈ Π½Π°ΡΠΈΡ ΠΏΠ°ΠΊΠ΅ΡΠΎΠ² ΡΠ°Π·ΡΠ°Π±ΠΎΡΠΊΠΈ, ΠΈΠ»ΠΈ - Π‘ΡΠ°ΡΠΈΡΠ΅ΡΠΊΠΈ ΠΏΠ΅ΡΠ΅Π΄Π°Π²Π°ΡΡ ΠΏΠ°ΠΏΠΊΡ
dist/
, Π² ΠΊΠΎΡΠΎΡΡΡ Webpack Π±ΡΠ΄Π΅Ρ Π·Π°ΠΏΠΈΡΡΠ²Π°ΡΡ Π½Π°ΡΠΈ ΡΠ°ΠΉΠ»Ρ ΠΏΠΎΡΠ»Π΅ ΡΠ±ΠΎΡΠΊΠΈ ΠΏΡΠΎΠ΄Π°ΠΊΡΠ΅Π½Π°.
ΠΡΡ ΠΎΠ΄Π½Π° Π²Π°ΠΆΠ½Π°Ρ Π·Π°Π΄Π°ΡΠ° server.js
Π·Π°ΠΊΠ»ΡΡΠ°Π΅ΡΡΡ Π² Π½Π°ΡΡΡΠΎΠΉΠΊΠ΅ ΡΠ΅ΡΠ²Π΅ΡΠ°
server.js, ΡΠ°ΡΡΡ 2
const socketio = require('socket.io');
const Constants = require('../shared/constants');
// Setup Express
// ...
const server = app.listen(port);
console.log(`Server listening on port ${port}`);
// Setup socket.io
const io = socketio(server);
// Listen for socket.io connections
io.on('connection', socket => {
console.log('Player connected!', socket.id);
socket.on(Constants.MSG_TYPES.JOIN_GAME, joinGame);
socket.on(Constants.MSG_TYPES.INPUT, handleInput);
socket.on('disconnect', onDisconnect);
});
ΠΠΎΡΠ»Π΅ ΡΡΠΏΠ΅ΡΠ½ΠΎΠΉ ΡΡΡΠ°Π½ΠΎΠ²ΠΊΠΈ ΡΠΎΠ΅Π΄ΠΈΠ½Π΅Π½ΠΈΡ socket.io Ρ ΡΠ΅ΡΠ²Π΅ΡΠΎΠΌ ΠΌΡ Π½Π°ΡΡΡΠ°ΠΈΠ²Π°Π΅ΠΌ ΠΎΠ±ΡΠ°Π±ΠΎΡΡΠΈΠΊΠΈ ΡΠΎΠ±ΡΡΠΈΠΉ Π΄Π»Ρ Π½ΠΎΠ²ΠΎΠ³ΠΎ ΡΠΎΠΊΠ΅ΡΠ°. ΠΠ±ΡΠ°Π±ΠΎΡΡΠΈΠΊΠΈ ΡΠΎΠ±ΡΡΠΈΠΉ ΠΎΠ±ΡΠ°Π±Π°ΡΡΠ²Π°ΡΡ ΠΏΠΎΠ»ΡΡΠ°Π΅ΠΌΡΠ΅ ΠΎΡ ΠΊΠ»ΠΈΠ΅Π½ΡΠΎΠ² ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΡ Π΄Π΅Π»Π΅Π³ΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ΠΌ ΠΎΠ±ΡΠ΅ΠΊΡΡ-ΡΠΈΠ½Π³Π»ΡΠΎΠ½Ρ game
:
server.js, ΡΠ°ΡΡΡ 3
const Game = require('./game');
// ...
// Setup the Game
const game = new Game();
function joinGame(username) {
game.addPlayer(this, username);
}
function handleInput(dir) {
game.handleInput(this, dir);
}
function onDisconnect() {
game.removePlayer(this);
}
ΠΡ ΡΠΎΠ·Π΄Π°ΡΠΌ ΠΈΠ³ΡΡ ΠΆΠ°Π½ΡΠ° .io, ΠΏΠΎΡΡΠΎΠΌΡ Π½Π°ΠΌ ΠΏΠΎΠ½Π°Π΄ΠΎΠ±ΠΈΡΡΡ ΡΠΎΠ»ΡΠΊΠΎ ΠΎΠ΄ΠΈΠ½ ΡΠΊΠ·Π΅ΠΌΠΏΠ»ΡΡ Game
(Β«GameΒ») β Π²ΡΠ΅ ΠΈΠ³ΡΠΎΠΊΠΈ ΠΈΠ³ΡΠ°ΡΡ Π½Π° ΠΎΠ΄Π½ΠΎΠΉ Π°ΡΠ΅Π½Π΅! Π ΡΠ»Π΅Π΄ΡΡΡΠ΅ΠΌ ΡΠ°Π·Π΄Π΅Π»Π΅ ΠΌΡ ΠΏΠΎΡΠΌΠΎΡΡΠΈΠΌ, ΠΊΠ°ΠΊ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ ΡΡΠΎΡ ΠΊΠ»Π°ΡΡ Game
.
2. Game ΡΠ΅ΡΠ²Π΅ΡΠ°
ΠΠ»Π°ΡΡ Game
ΡΠΎΠ΄Π΅ΡΠΆΠΈΡ ΡΠ°ΠΌΡΡ Π²Π°ΠΆΠ½ΡΡ Π»ΠΎΠ³ΠΈΠΊΡ Π½Π° ΡΡΠΎΡΠΎΠ½Π΅ ΡΠ΅ΡΠ²Π΅ΡΠ°. ΠΠ½ ΠΈΠΌΠ΅Π΅Ρ Π΄Π²Π΅ ΠΎΡΠ½ΠΎΠ²Π½ΡΠ΅ Π·Π°Π΄Π°ΡΠΈ: ΡΠΏΡΠ°Π²Π»Π΅Π½ΠΈΠ΅ ΠΈΠ³ΡΠΎΠΊΠ°ΠΌΠΈ ΠΈ ΡΠΈΠΌΡΠ»ΡΡΠΈΡ ΠΈΠ³ΡΡ.
ΠΠ°Π²Π°ΠΉΡΠ΅ Π½Π°ΡΠ½ΡΠΌ Ρ ΠΏΠ΅ΡΠ²ΠΎΠΉ Π·Π°Π΄Π°ΡΠΈ β Ρ ΡΠΏΡΠ°Π²Π»Π΅Π½ΠΈΡ ΠΈΠ³ΡΠΎΠΊΠ°ΠΌΠΈ.
game.js, ΡΠ°ΡΡΡ 1
const Constants = require('../shared/constants');
const Player = require('./player');
class Game {
constructor() {
this.sockets = {};
this.players = {};
this.bullets = [];
this.lastUpdateTime = Date.now();
this.shouldSendUpdate = false;
setInterval(this.update.bind(this), 1000 / 60);
}
addPlayer(socket, username) {
this.sockets[socket.id] = socket;
// Generate a position to start this player at.
const x = Constants.MAP_SIZE * (0.25 + Math.random() * 0.5);
const y = Constants.MAP_SIZE * (0.25 + Math.random() * 0.5);
this.players[socket.id] = new Player(socket.id, username, x, y);
}
removePlayer(socket) {
delete this.sockets[socket.id];
delete this.players[socket.id];
}
handleInput(socket, dir) {
if (this.players[socket.id]) {
this.players[socket.id].setDirection(dir);
}
}
// ...
}
Π ΡΡΠΎΠΉ ΠΈΠ³ΡΠ΅ ΠΌΡ Π±ΡΠ΄Π΅ΠΌ ΠΈΠ΄Π΅Π½ΡΠΈΡΠΈΡΠΈΡΠΎΠ²Π°ΡΡ ΠΈΠ³ΡΠΎΠΊΠΎΠ² ΠΏΠΎ ΠΏΠΎΠ»Ρ id
ΠΈΡ
ΡΠΎΠΊΠ΅ΡΠ° socket.io (Π΅ΡΠ»ΠΈ Π²Ρ Π·Π°ΠΏΡΡΠ°Π»ΠΈΡΡ, ΡΠΎ ΡΠ½ΠΎΠ²Π° Π²Π΅ΡΠ½ΠΈΡΠ΅ΡΡ ΠΊ server.js
). Socket.io ΡΠ°ΠΌ Π½Π°Π·Π½Π°ΡΠ°Π΅Ρ ΠΊΠ°ΠΆΠ΄ΠΎΠΌΡ ΡΠΎΠΊΠ΅ΡΡ ΡΠ½ΠΈΠΊΠ°Π»ΡΠ½ΡΠΉ id
, ΠΏΠΎΡΡΠΎΠΌΡ Π½Π°ΠΌ ΠΎΠ± ΡΡΠΎΠΌ Π±Π΅ΡΠΏΠΎΠΊΠΎΠΈΡΡΡΡ Π½Π΅ Π½ΡΠΆΠ½ΠΎ. Π― Π±ΡΠ΄Ρ Π½Π°Π·ΡΠ²Π°ΡΡ Π΅Π³ΠΎ ID ΠΈΠ³ΡΠΎΠΊΠ°.
ΠΠ°ΠΏΠΎΠΌΠ½ΠΈΠ² ΡΡΠΎ, Π΄Π°Π²Π°ΠΉΡΠ΅ ΠΈΠ·ΡΡΠΈΠΌ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΠ΅ ΡΠΊΠ·Π΅ΠΌΠΏΠ»ΡΡΠ° Π² ΠΊΠ»Π°ΡΡΠ΅ Game
:
sockets
β ΡΡΠΎ ΠΎΠ±ΡΠ΅ΠΊΡ, ΠΊΠΎΡΠΎΡΡΠΉ ΠΏΡΠΈΠ²ΡΠ·ΡΠ²Π°Π΅Ρ ID ΠΈΠ³ΡΠΎΠΊΠ° ΠΊ ΡΠΎΠΊΠ΅ΡΡ, ΠΊΠΎΡΠΎΡΡΠΉ ΡΠ²ΡΠ·Π°Π½ Ρ ΠΈΠ³ΡΠΎΠΊΠΎΠΌ. ΠΠ½ ΠΏΠΎΠ·Π²ΠΎΠ»ΡΠ΅Ρ Π½Π°ΠΌ Π·Π° ΠΏΠΎΡΡΠΎΡΠ½Π½ΠΎΠ΅ Π²ΡΠ΅ΠΌΡ ΠΏΠΎΠ»ΡΡΠ°ΡΡ Π΄ΠΎΡΡΡΠΏ ΠΊ ΡΠΎΠΊΠ΅ΡΠ°ΠΌ ΠΏΠΎ ΠΈΡ ID ΠΈΠ³ΡΠΎΠΊΠΎΠ².players
β ΡΡΠΎ ΠΎΠ±ΡΠ΅ΠΊΡ, ΠΏΡΠΈΠ²ΡΠ·ΡΠ²Π°ΡΡΠΈΠΉ ID ΠΈΠ³ΡΠΎΠΊΠ° ΠΊ ΠΎΠ±ΡΠ΅ΠΊΡΡ code>Player
bullets
β ΡΡΠΎ ΠΌΠ°ΡΡΠΈΠ² ΠΎΠ±ΡΠ΅ΠΊΡΠΎΠ² Bullet
, Π½Π΅ ΠΈΠΌΠ΅ΡΡΠΈΠΉ ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΠ½Π½ΠΎΠ³ΠΎ ΠΏΠΎΡΡΠ΄ΠΊΠ°.
lastUpdateTime
β ΡΡΠΎ ΠΌΠ΅ΡΠΊΠ° Π²ΡΠ΅ΠΌΠ΅Π½ΠΈ ΠΌΠΎΠΌΠ΅Π½ΡΠ° ΠΏΠΎΡΠ»Π΅Π΄Π½Π΅Π³ΠΎ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΡ ΠΈΠ³ΡΡ. ΠΡΠΊΠΎΡΠ΅ ΠΌΡ ΡΠ²ΠΈΠ΄ΠΈΠΌ, ΠΊΠ°ΠΊ ΠΎΠ½Π° ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΡΡΡ.
shouldSendUpdate
β ΡΡΠΎ Π²ΡΠΏΠΎΠΌΠΎΠ³Π°ΡΠ΅Π»ΡΠ½Π°Ρ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½Π°Ρ. ΠΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ ΠΌΡ ΡΠΎΠΆΠ΅ ΡΠ²ΠΈΠ΄ΠΈΠΌ Π²ΡΠΊΠΎΡΠ΅.
ΠΠ΅ΡΠΎΠ΄Ρ addPlayer()
, removePlayer()
ΠΈ handleInput()
ΠΎΠ±ΡΡΡΠ½ΡΡΡ Π½Π΅ Π½ΡΠΆΠ½ΠΎ, ΠΎΠ½ΠΈ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΡΡΡΡ Π² server.js
. ΠΡΠ»ΠΈ Π²Π°ΠΌ Π½ΡΠΆΠ½ΠΎ ΠΎΡΠ²Π΅ΠΆΠΈΡΡ ΠΏΠ°ΠΌΡΡΡ, Π²Π΅ΡΠ½ΠΈΡΠ΅ΡΡ Π½Π΅ΠΌΠ½ΠΎΠ³ΠΎ Π²ΡΡΠ΅.
ΠΠΎΡΠ»Π΅Π΄Π½ΡΡ ΡΡΡΠΎΠΊΠ° constructor()
Π·Π°ΠΏΡΡΠΊΠ°Π΅Ρ ΡΠΈΠΊΠ» ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΡ ΠΈΠ³ΡΡ (Ρ ΡΠ°ΡΡΠΎΡΠΎΠΉ 60 ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠΉ/Ρ):
game.js, ΡΠ°ΡΡΡ 2
const Constants = require('../shared/constants');
const applyCollisions = require('./collisions');
class Game {
// ...
update() {
// Calculate time elapsed
const now = Date.now();
const dt = (now - this.lastUpdateTime) / 1000;
this.lastUpdateTime = now;
// Update each bullet
const bulletsToRemove = [];
this.bullets.forEach(bullet => {
if (bullet.update(dt)) {
// Destroy this bullet
bulletsToRemove.push(bullet);
}
});
this.bullets = this.bullets.filter(
bullet => !bulletsToRemove.includes(bullet),
);
// Update each player
Object.keys(this.sockets).forEach(playerID => {
const player = this.players[playerID];
const newBullet = player.update(dt);
if (newBullet) {
this.bullets.push(newBullet);
}
});
// Apply collisions, give players score for hitting bullets
const destroyedBullets = applyCollisions(
Object.values(this.players),
this.bullets,
);
destroyedBullets.forEach(b => {
if (this.players[b.parentID]) {
this.players[b.parentID].onDealtDamage();
}
});
this.bullets = this.bullets.filter(
bullet => !destroyedBullets.includes(bullet),
);
// Check if any players are dead
Object.keys(this.sockets).forEach(playerID => {
const socket = this.sockets[playerID];
const player = this.players[playerID];
if (player.hp <= 0) {
socket.emit(Constants.MSG_TYPES.GAME_OVER);
this.removePlayer(socket);
}
});
// Send a game update to each player every other time
if (this.shouldSendUpdate) {
const leaderboard = this.getLeaderboard();
Object.keys(this.sockets).forEach(playerID => {
const socket = this.sockets[playerID];
const player = this.players[playerID];
socket.emit(
Constants.MSG_TYPES.GAME_UPDATE,
this.createUpdate(player, leaderboard),
);
});
this.shouldSendUpdate = false;
} else {
this.shouldSendUpdate = true;
}
}
// ...
}
ΠΠ΅ΡΠΎΠ΄ update()
ΡΠΎΠ΄Π΅ΡΠΆΠΈΡ, Π½Π°Π²Π΅ΡΠ½ΠΎ, ΡΠ°ΠΌΡΡ Π²Π°ΠΆΠ½ΡΡ ΡΠ°ΡΡΡ Π»ΠΎΠ³ΠΈΠΊΠΈ Π½Π° ΡΡΠΎΡΠΎΠ½Π΅ ΡΠ΅ΡΠ²Π΅ΡΠ°. ΠΠΎ ΠΏΠΎΡΡΠ΄ΠΊΡ ΠΏΠ΅ΡΠ΅ΡΠΈΡΠ»ΠΈΠΌ Π²ΡΡ, ΡΡΠΎ ΠΎΠ½ Π΄Π΅Π»Π°Π΅Ρ:
- ΠΡΡΠΈΡΠ»ΡΠ΅Ρ, ΡΠΊΠΎΠ»ΡΠΊΠΎ Π²ΡΠ΅ΠΌΠ΅Π½ΠΈ
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()
:
game.js, ΡΠ°ΡΡΡ 3
class Game {
// ...
getLeaderboard() {
return Object.values(this.players)
.sort((p1, p2) => p2.score - p1.score)
.slice(0, 5)
.map(p => ({ username: p.username, score: Math.round(p.score) }));
}
createUpdate(player, leaderboard) {
const nearbyPlayers = Object.values(this.players).filter(
p => p !== player && p.distanceTo(player) <= Constants.MAP_SIZE / 2,
);
const nearbyBullets = this.bullets.filter(
b => b.distanceTo(player) <= Constants.MAP_SIZE / 2,
);
return {
t: Date.now(),
me: player.serializeForUpdate(),
others: nearbyPlayers.map(p => p.serializeForUpdate()),
bullets: nearbyBullets.map(b => b.serializeForUpdate()),
leaderboard,
};
}
}
getLeaderboard()
Π΄ΠΎΠ²ΠΎΠ»ΡΠ½ΠΎ ΠΏΡΠΎΡΡ β ΠΎΠ½ ΡΠΎΡΡΠΈΡΡΠ΅Ρ ΠΈΠ³ΡΠΎΠΊΠΎΠ² ΠΏΠΎ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²Ρ ΠΎΡΠΊΠΎΠ², Π±Π΅ΡΡΡ ΠΏΡΡΡ Π»ΡΡΡΠΈΡ
ΠΈ Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅Ρ Π΄Π»Ρ ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ ΠΈΠΌΡ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ ΠΈ ΡΡΡΡ.
createUpdate()
ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΡΡΡ Π² update()
Π΄Π»Ρ ΡΠΎΠ·Π΄Π°Π½ΠΈΡ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠΉ ΠΈΠ³ΡΡ, ΠΊΠΎΡΠΎΡΡΠ΅ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΡΡΡ ΠΈΠ³ΡΠΎΠΊΠ°ΠΌ. ΠΠ³ΠΎ ΠΎΡΠ½ΠΎΠ²Π½Π°Ρ Π·Π°Π΄Π°ΡΠ° Π·Π°ΠΊΠ»ΡΡΠ°Π΅ΡΡΡ Π² Π²ΡΠ·ΠΎΠ²Π΅ ΠΌΠ΅ΡΠΎΠ΄ΠΎΠ² serializeForUpdate()
, ΡΠ΅Π°Π»ΠΈΠ·ΠΎΠ²Π°Π½Π½ΡΡ
Π΄Π»Ρ ΠΊΠ»Π°ΡΡΠΎΠ² Player
ΠΈ Bullet
. ΠΠ°ΠΌΠ΅ΡΡΡΠ΅, ΡΡΠΎ ΠΎΠ½ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΡ ΠΊΠ°ΠΆΠ΄ΠΎΠΌΡ ΠΈΠ³ΡΠΎΠΊΡ Π΄Π°Π½Π½ΡΠ΅ ΡΠΎΠ»ΡΠΊΠΎ ΠΎ Π±Π»ΠΈΠΆΠ°ΠΉΡΠΈΡ
ΠΈΠ³ΡΠΎΠΊΠ°Ρ
ΠΈ ΡΠ½Π°ΡΡΠ΄Π°Ρ
β Π½Π΅Ρ Π½Π΅ΠΎΠ±Ρ
ΠΎΠ΄ΠΈΠΌΠΎΡΡΠΈ ΠΏΠ΅ΡΠ΅Π΄Π°Π²Π°ΡΡ ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΡ ΠΎΠ± ΠΈΠ³ΡΠΎΠ²ΡΡ
ΠΎΠ±ΡΠ΅ΠΊΡΠ°Ρ
, Π½Π°Ρ
ΠΎΠ΄ΡΡΠΈΡ
ΡΡ Π΄Π°Π»Π΅ΠΊΠΎ ΠΎΡ ΠΈΠ³ΡΠΎΠΊΠ°!
3. ΠΠ³ΡΠΎΠ²ΡΠ΅ ΠΎΠ±ΡΠ΅ΠΊΡΡ Π½Π° ΡΠ΅ΡΠ²Π΅ΡΠ΅
Π Π½Π°ΡΠ΅ΠΉ ΠΈΠ³ΡΠ΅ ΡΠ½Π°ΡΡΠ΄Ρ ΠΈ ΠΈΠ³ΡΠΎΠΊΠΈ Π½Π° ΡΠ°ΠΌΠΎΠΌ Π΄Π΅Π»Π΅ ΠΎΡΠ΅Π½Ρ ΠΏΠΎΡ
ΠΎΠΆΠΈ: ΡΡΠΎ Π°Π±ΡΡΡΠ°ΠΊΡΠ½ΡΠ΅ ΠΊΡΡΠ³Π»ΡΠ΅ ΠΏΠΎΠ΄Π²ΠΈΠΆΠ½ΡΠ΅ ΠΈΠ³ΡΠΎΠ²ΡΠ΅ ΠΎΠ±ΡΠ΅ΠΊΡΡ. Π§ΡΠΎΠ±Ρ Π²ΠΎΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡΡΡ ΡΡΠΎΠΉ ΡΡ
ΠΎΠΆΠ΅ΡΡΡΡ ΠΈΠ³ΡΠΎΠΊΠΎΠ² ΠΈ ΡΠ½Π°ΡΡΠ΄ΠΎΠ², Π΄Π°Π²Π°ΠΉΡΠ΅ Π½Π°ΡΠ½ΡΠΌ Ρ ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΠΈ Π±Π°Π·ΠΎΠ²ΠΎΠ³ΠΎ ΠΊΠ»Π°ΡΡΠ° Object
:
object.js
class Object {
constructor(id, x, y, dir, speed) {
this.id = id;
this.x = x;
this.y = y;
this.direction = dir;
this.speed = speed;
}
update(dt) {
this.x += dt * this.speed * Math.sin(this.direction);
this.y -= dt * this.speed * Math.cos(this.direction);
}
distanceTo(object) {
const dx = this.x - object.x;
const dy = this.y - object.y;
return Math.sqrt(dx * dx + dy * dy);
}
setDirection(dir) {
this.direction = dir;
}
serializeForUpdate() {
return {
id: this.id,
x: this.x,
y: this.y,
};
}
}
ΠΠ΄Π΅ΡΡ Π½Π΅ ΠΏΡΠΎΠΈΡΡ
ΠΎΠ΄ΠΈΡ Π½ΠΈΡΠ΅Π³ΠΎ ΡΠ»ΠΎΠΆΠ½ΠΎΠ³ΠΎ. ΠΡΠΎΡ ΠΊΠ»Π°ΡΡ ΡΡΠ°Π½Π΅Ρ Ρ
ΠΎΡΠΎΡΠ΅ΠΉ ΠΎΠΏΠΎΡΠ½ΠΎΠΉ ΡΠΎΡΠΊΠΎΠΉ Π΄Π»Ρ ΡΠ°ΡΡΠΈΡΠ΅Π½ΠΈΡ. ΠΠ°Π²Π°ΠΉΡΠ΅ ΠΏΠΎΡΠΌΠΎΡΡΠΈΠΌ, ΠΊΠ°ΠΊ ΠΊΠ»Π°ΡΡ Bullet
ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅Ρ Object
:
bullet.js
const shortid = require('shortid');
const ObjectClass = require('./object');
const Constants = require('../shared/constants');
class Bullet extends ObjectClass {
constructor(parentID, x, y, dir) {
super(shortid(), x, y, dir, Constants.BULLET_SPEED);
this.parentID = parentID;
}
// Returns true if the bullet should be destroyed
update(dt) {
super.update(dt);
return this.x < 0 || this.x > Constants.MAP_SIZE || this.y < 0 || this.y > Constants.MAP_SIZE;
}
}
Π Π΅Π°Π»ΠΈΠ·Π°ΡΠΈΡ Bullet
ΠΎΡΠ΅Π½Ρ ΠΊΠΎΡΠΎΡΠΊΠ°! ΠΡ Π΄ΠΎΠ±Π°Π²ΠΈΠ»ΠΈ ΠΊ Object
ΡΠΎΠ»ΡΠΊΠΎ ΡΠ»Π΅Π΄ΡΡΡΠΈΠ΅ ΡΠ°ΡΡΠΈΡΠ΅Π½ΠΈΡ:
- ΠΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ ΠΏΠ°ΠΊΠ΅ΡΠ°
shortid Π΄Π»Ρ ΡΠ»ΡΡΠ°ΠΉΠ½ΠΎΠΉ Π³Π΅Π½Π΅ΡΠ°ΡΠΈΠΈ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!
ΠΠ΅ΡΡ ΠΊΠΎΠ΄ ΠΏΡΠΈΠΌΠ΅ΡΠ° ΠΈΠΌΠ΅Π΅Ρ ΠΎΡΠΊΡΡΡΡΠ΅ ΠΈΡΡ
ΠΎΠ΄Π½ΠΈΠΊΠΈ ΠΈ Π²ΡΠ»ΠΎΠΆΠ΅Π½ Π½Π°
ΠΡΡΠΎΡΠ½ΠΈΠΊ: habr.com