ืืืคืจืืื ืืื 2015
ืืืื ืืืจ ืืึธื ืงืืื ืืึธื ืืขืืขืจื ืืืขืื ืื ืฉืคึผืืืขืจืืึท ืคืจืืขืจ, ืืื ืืขื ืขื ืคืจืื ืืืืืืคึผืืืึทืขืจ ืืืขื ืฉืคึผืืืขืจืืึท ืืืึธืก ืืขื ืขื ืืจืื ื ืฆื ืฉืคึผืืื (ืงืืื ืืฉืืื ืคืืจืืื ืื). ืืื ืืืืฉืึทืืืึทืื ืืจืื ืคืืืข ืึทืคึผืึธืืืื ื ืคึผืืืึทืขืจืก ืืื ืืืื ืืจืขื ืข. ืื ืืขืจืข ืืึทืจืืื .io ืฉืคึผืืืขืจืืึท:
ืืื ืืขื ืึทืจืืืงื ืืืจ ืืืขืื ืืขืคึฟืื ืขื ืืืืก ืืื ืฉืึทืคึฟื ืึทื .io ืฉืคึผืื ืคึฟืื ืงืจืึทืฆื. ืฆื ืืึธื ืืึธืก, ืืืืื ืืืืกื ืคืื ืืืฉืึทืืืึทืกืงืจืืคึผื ืืืขื ืืืื ืืขื ืื: ืืืจ ืืึทืจืคึฟื ืฆื ืคึฟืึทืจืฉืืืื ืืืื ืืื ืกืื ืืึทืงืก this
ะธ
ืืืึทืฉืคึผืื ืคืื ืึท .ืืึธ ืฉืคึผืื
ืคึฟืึทืจ ืืจืืื ืื ื ืืืืฃ ืืืจ ืืืขืื ืึธืคึผืฉืืงื ืฆื
ืืขืจ ืฉืคึผืื ืืื ืืึทื ืฅ ืคึผืฉืื: ืืืจ ืงืึธื ืืจืึธื ืึท ืฉืืฃ ืืื ืึทื ืืจืขื ืข ืืื ืื ืืขืจืข ืคึผืืืึทืขืจืก. ืืืื ืฉืืฃ ืคืืืขืจื ืืืืืึธืืึทืืืฉ ืคึผืจืึทืืืฉืขืงืืึทืื ืืื ืืืจ ืคึผืจืืืืจื ืฆื ืฉืืึธืื ืื ืืขืจืข ืคึผืืืึทืขืจืก ืืฉืขืช ืืืกืืืืื ืืืืขืจ ืคึผืจืึทืืืฉืขืงืืึทืื.
1. ืงืืจืฅ ืืืืขืจืืืืง / ืคึผืจืืืขืงื ืกืืจืืงืืืจ
ืจืขืงืึธืืขื ืืืจื
ืึธืคึผืืึธืืืจื ืืงืืจ ืงืึธื ืืืึทืฉืคึผืื ืฉืคึผืื ืึทืืื ืืืจ ืงืขื ืขื ื ืึธืืคืึธืืื ืืืจ.
ืืขืจ ืืืืฉืคึผืื ื ืืฆื ืื ืคืืืืขื ืืข:
ืขืงืกืคึผืจืขืกืก ืืื ืื ืืขืจืกื ืคืึธืืงืก ืืืขื ืคืจืืืืืืขืจืง ืคึฟืึทืจ Node.js ืืืึธืก ืืึทื ืืืืฉืื ืื ืืืขื ืกืขืจืืืขืจ ืคืื ืื ืฉืคึผืื.socket.io - ืืืขืืกืึธืงืงืขื ืืืืืืึธืืขืง ืคึฟืึทืจ ืืงืกืืฉืืื ืืืฉืื ื ืืึทืื ืฆืืืืฉื ืืขื ืืืขืืขืจืขืจ ืืื ืื ืกืขืจืืืขืจ.ืืืขืืคึผืึทืงืง - ืืึธืืืืข ืคืึทืจืืืึทืืืขืจ. ืืืจ ืงืขื ื ืืืืขื ืขื ืืืขืื ืืืึธืก ืฆื ื ืืฆื Webpackืืึธ .
ืืึธืก ืืื ืืื ืื ืคึผืจืืืขืงื ืืืขืืืืืึทืืขืจ ืกืืจืืงืืืจ ืงืืงื ืืื:
public/
assets/
...
src/
client/
css/
...
html/
index.html
index.js
...
server/
server.js
...
shared/
constants.js
ืขืคื ืืืขื/
ืึทืืฅ ืืื ืืื ืืขืจ ืืขืงืข public/
ืืืขื ืืืื ืกืืึทืืืงืื ืืจืึทื ืกืืืืืขื ืืืจื ืื ืกืขืจืืืขืจ. ืืื public/assets/
ืึผืืื ืืืืืขืจ ืืขื ืืฆื ืืืจื ืืื ืืืขืจ ืคึผืจืืืขืงื.
src /
ืื ืืงืืจ ืงืึธื ืืื ืืื ืืขืจ ืืขืงืข src/
. ืืืืืขื client/
ะธ server/
ืจืขืื ืคึฟืึทืจ ืืื ืืื shared/
ืึผืืื ืึท ืงืึทื ืกืืึทื ืฅ ืืขืงืข ืืืคึผืึธืจืืื ืืืจื ืื ืงืืืขื ื ืืื ืกืขืจืืืขืจ.
2. ืึทืกืขืืืื / ืคึผืจืืืขืงื ืคึผืึทืจืึทืืขืืขืจืก
ืืื ืกืืืืืื ืืืืื, ืืืจ ื ืืฆื ืึท ืืึธืืืืข ืคืึทืจืืืึทืืืขืจ ืฆื ืืืืขื ืืขื ืคึผืจืืืขืงื
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 ืงืึธื ืคึฟืึทืจ ืขืืืขืจืข ืืจืึทืืืขืจื. - ืืืจ ื ืืฆื ืึท ืคึผืืืืื ืฆื ืขืงืกืืจืึทืงื ืึทืืข ืื 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
, ืืืขื ืืืจ ืืึทืืืื ืืขื ืคึผืืึทืฅ, ืืขืจ ืืืขืืขืจืขืจ ืืืขื ืืึธืื ืขืก ืขืจืฉืืขืจ. ืืื ืืืขืจ ืืืึทื ืืืขื ืืืื ืืึทื ืฅ ืคึผืฉืื:
ืื ืืขืงืก.ืืืื
ื ืืืืฉืคึผืื .ืืึธ ืฉืคึผืื ืฉืคึผืื
ืืขื ืงืึธื ืืืึทืฉืคึผืื ืืื ืกืืืคึผืืึทืคืืื ืึท ืืืกื ืคึฟืึทืจ ืงืืขืจืืื, ืืื ืืื ืืืขื ืืึธื ืื ืืขืืืข ืืื ืคืืืข ืคืื โโืื ืื ืืขืจืข ืืืืฉืคืืื ืืื ืืขื ืคึผืึธืกืื. ืืืจ ืงืขื ื ืฉืืขื ืืืง ืงืืงื ืืื ืื ืคืื ืงืึธื ืืื
ืืืจ ืืืื:
HTML5 ืงืึทื ืืืึทืก ืขืืขืืขื ื (<canvas>
), ืืืึธืก ืืืจ ืืืขืื ื ืืฆื ืฆื ืืึทืื ืื ืฉืคึผืื.<link>
ืฆื ืืืืื ืืื ืืืขืจ CSS ืคึผืขืงื.<script>
ืฆื ืืืืื ืืื ืืืขืจ ืืืฉืึทืืืึทืกืงืจืืคึผื ืคึผืขืงื.- ืืืืคึผื ืืขื ืื ืืื ื ืืืขื
<input>
ืืื ืื "PLAY" ืงื ืขืคึผื (<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 (ืึทืืื ืืืขืืคึผืึทืงืง ืืืืืกื ืฆื ืึทืจืืึทื ื ืขืืขื ืืื ืืื ืืื ืืืขืจ 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()
ืงืขื ืขื ืืืืื ืืืืื ืฆืืจืืงืงืืืขื ืืึทืื ืคืื ืื ืืขืจืกื ืืขืฆืื ืก ืืืงืืืขื ืฉืคึผืื ืืขืจืืืึทื ืืืงื.
ื ืึทืืื-ืฉืืึทื.ืืืฉืก
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 ืืื (30 ืคึผืขืจ ืกืขืงืื ืืข):
ืฆืื ืืึทืืืืขืจื, ืืึธืจื ืืฉื ืืื ืืื ืฅ. ื ืืขืจ ืจืขืึทืืืกืืืฉ ืืืื ืืืึธืื ืืืื:
ื ื ืึทืืื ืืืคึผืืึทืืขื ืืืืฉืึทื ืืื ืฉืืื ืคืื ืื ืขืจืืกื ืคืึทื ืืืขื ืขืก ืงืืื ืฆื ืืืืืึทื ืกื. ืืืื ืึท ืฉืคึผืื ืืขืจืืืึทื ืืืงื ืืื ืืืงืืืขื ืืื ืึท ืคืึทืจืืึทืืื ืคืื 50ms, ืืขืืึธืื ืืขืจ ืงืืืขื ื ืืื ืกืืึธืื ืึทืจืึธืคึผ ืืื ืึทื ืขืงืกืืจืข 50ms ืืืืึทื ืขืก ืืื ื ืึธื ืจืขื ืืขืจืื ื ืื ืฉืคึผืื ืฉืืึทื ืคืื ืื ืคืจืืขืจืืืงืข ืืขืจืืืึทื ืืืงื. ืืืจ ืงืขื ืขื ืืืึทืืืฉืึทื ืืื ืืืืึทืงืืืขื ืืึธืก ืืื ืคึฟืึทืจ ืื ืฉืคึผืืืขืจ: ืจืขืื ืฆื ืึทืจืืืืจืึทืจืืฉ ืกืืึธืืืึทืื ื, ืื ืฉืคึผืื ืืืขื ืืืกืงืืืขื ืืืฉืขืจืงื ืืื ืึทื ืกืืืืืึทื.
7.2 ืืืคึผืจืืืื ืงืืืขื ื ืฉืืึทื
ืืืจ ืืืขืื ืืึทืื ืขืืืขืืข ืืืคึผืจืืืืืึทื ืฅ ืฆื ืื ื ืึทืืื ืืืคึผืืึทืืขื ืืืืฉืึทื. ืขืจืฉืืขืจ, ืืืจ ื ืืฆื ืจืขื ืืขืจืื ื ืคืึทืจืืึทืืื ืืืจื 100 ืืื. ืืขื ืืืื ืึทื ืื "ืงืจืึทื ื" ืฉืืึทื ืคืื ืืขื ืงืืืขื ื ืืืขื ืฉืืขื ืืืง ืืืื 100ms ืืื ืืขืจ ืื ืฉืคึผืื ืฉืืึทื ืืืืฃ ืื ืกืขืจืืืขืจ. ืคึฟืึทืจ ืืืึทืฉืคึผืื, ืืืื ืื ืกืขืจืืืขืจ ืฆืืื ืืื 150, ืืขืจ ืงืืืขื ื ืืืขื ืืึทืื ืื ืฉืืึทื ืืื ืืืึธืก ืื ืกืขืจืืืขืจ ืืื ืืขืืืขื ืืื ืืขืจ ืฆืืื 50:
ืืึธืก ืืื ืืื ืื ืึท 100ms ืืึทืคืขืจ ืฆื ืืืืึทืื ืืขืื ืื ืึทื ืคึผืจืืืืงืืึทืืึทื ืืืืืื ื ืคืื ืฉืคึผืื ืืขืจืืืึทื ืืืงืื ืืขื:
ืืขืจ ืคึผืจืืึทื ืคึฟืึทืจ ืืขื ืืืขื ืืืื ืฉืืขื ืืืง
ืืืจ ืงืขื ืขื ื ืืฆื ืื ืื ืืขืจ ืืขืื ืืง ืืขืจืืคื
"ืงืืืื ื-ืืืึทื ืคืึธืจืืืกืืึธืื" , ืืืึธืก ืืื ืึท ืืื ืึทืจืืขื ืคืื ืจืืืืกืื ื ืืืืขืจืงื ืืืืืึทื ืกื, ืึธืืขืจ ืืืขื ื ืืฉื ืืืื ืืืกืงืึทืกื ืืื ืืขื ืคึผืึธืกืื.
ืื ืื ืืขืจ ืคึฟืึทืจืืขืกืขืจืื ื ืืืึธืก ืืืจ ื ืืฆื ืืื ืืื ืขืึทืจ ืื ืืขืจืคึผืึธืืึทืืืึธื. ืจืขืื ืฆื ืจืขื ืืขืจืื ื ืึธืคึผืฉืืื, ืืืจ ืืขื ืขื ืืืืฉืึทืืืึทืื ืืคึผืืืช ืืืื ืืขืจืืืึทื ืืืงื ืคืึธืจืืืก ืคืื ืื ืงืจืึทื ื ืฆืืื ืืื ืืขื ืงืืืขื ื. ืืืขื ืืขืจืืคื 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()
. ืืื ืืืจ ืืขืืขื ืคืจืืขืจ, ืืขืืขืจ ืฉืคึผืื ืืขืจืืืึทื ืืืงื ืืืื ืึท ืกืขืจืืืขืจ ืืืืขืกืืึทืืคึผ. ืืืจ ืืืืื ืฆื ื ืืฆื ืจืขื ืืขืจ ืืืืืึทื ืกื ืฆื ืืึทืื ืื ืืืื 100ms ืืื ืืขืจ ืืขื ืกืขืจืืืขืจ, ืึธืืขืจ ืืืจ ืืืขืื ืงืืื ืืึธื ืืืืกื ืื ืงืจืึทื ื ืฆืืื ืืืืฃ ืื ืกืขืจืืืขืจ, ืืืืึทื ืืืจ ืงืขื ืขื ื ืืฉื ืืืืกื ืืื ืืึทื ื ืขืก ืืขื ืืืขื ืคึฟืึทืจ ืงืืื ืคืื ืื ืืขืจืืืึทื ืืืงืื ืืขื ืฆื ืืขืจืืจืืืื ืืื ืื. ืืขืจ ืืื ืืขืจื ืขืฅ ืืื ืึทื ืคึผืจืืืืงืืึทืืึทื ืืื ืื ืืืืงืืึทื ืงืขื ืืืื ืืืืขืจ ืึทื ืืขืจืฉ!
ืฆื ืืึทืงืืืขื ืึทืจืื ืืขื ืคึผืจืึธืืืขื, ืืืจ ืงืขื ืขื ื ืืฆื ืึท ืืืืึทื ืึทืคึผืจืึทืงืกืึทืืืืฉืึทื: ืืืจ ืืึธืืืจ ืคืึทืจืืืื ืึทื ืืขืจ ืขืจืฉืืขืจ ืืขืจืืืึทื ืืืงื ืืื ืื ืืขืงืืืขื ืืืืงืขืฃ. ืืืื ืืึธืก ืืื ืืืช, ืืืจ ืืืึธืื ืืืืกื ืื ืกืขืจืืืขืจ ืฆืืื ืืื ืืขื ืืึทืืื ืืขืจ ืืึธืืขื ื! ืืืจ ืงืจืึธื ืื ืกืขืจืืืขืจ ืฆืืื ืกืืึทืืคึผ ืืื firstServerTimestamp
ืืื ืจืึทืืขืืืขื ืืื ืืืขืจ ืืืืข (ืงืืืขื ื) ืฆืืื ืกืืึทืืคึผ ืืื ืืขืจ ืืขืืืืงืขืจ ืืึธืืขื ื ืืื gameStart
.
ืื, ืืืืจื ื ืืื ืื. ืืึธื ื ืืฉื ืืืื ืฆืืื ืืืืฃ ืื ืกืขืจืืืขืจ = ืฆืืื ืืืืฃ ืืขื ืงืืืขื ื? ืคืืจืืืืก ืืึธื ืืืจ ืืืคืขืจืขื ืฉืืืื ืฆืืืืฉื "ืกืขืจืืืขืจ ืืืืขืกืืึทืืคึผ" ืืื "ืงืืืขื ื ืืืืขืกืืึทืืคึผ"? ืืึธืก ืืื ืึท ืืจืืืก ืงืฉืื! ืขืก ืงืืื ืืืืก ืื ืืืก ืืขื ืขื ื ืืฉื ืื ืืขืืืข ืืื. Date.now()
ืืืขื ืฆืืจืืงืงืืืขื ืคืึทืจืฉืืืขื ืข ืืืืขืกืืึทืืคึผืก ืืื ืืขื ืงืืืขื ื ืืื ืกืขืจืืืขืจ ืืื ืืึธืก ืืขืคึผืขื ืืก ืืืืฃ ืื ืืืืข ืกืืืืช ืคึฟืึทืจ ืื ืืืฉืื ืขื. ืงืืื ืืึธื ืืืขืจื ืขืืขื ืึทื ืืืืขืกืืึทืืคึผืก ืืืขื ืืืื ืื ืืขืืืข ืืืืฃ ืึทืืข ืืืฉืื ืขื.
ืืืฆื ืืืจ ืคึฟืึทืจืฉืืืื ืืืึธืก ืขืก ืืื currentServerTime()
: ืขืก ืงืขืจื ืืื ืกืขืจืืืืจืขืจ ืืืืขืกืืึทืืคึผ ืคืื ืื ืงืจืึทื ื ืจืขื ืืขืจืื ื ืฆืืื. ืืื ืื ืืขืจืข ืืืขืจืืขืจ, ืืึธืก ืืื ืื ืงืจืึทื ื ืกืขืจืืืขืจ ืฆืืื (firstServerTimestamp <+ (Date.now() - gameStart)
) ืืื ืืก ืจืขื ืืขืจืื ื ืคืึทืจืืึทืืื (RENDER_DELAY
).
ืืืฆื ืืึธืื ืก ืงืืง ืืื ืืืจ ืฉืขืคึผื ืฉืคึผืื ืืขืจืืืึทื ืืืงืื ืืขื. ืืืขื ืึท ืืขืจืืืึทื ืืืงื ืืื ืืืงืืืขื ืคืื ืื ืกืขืจืืืขืจ, ืขืก ืืื ืืขืจืืคื processGameUpdate()
, ืืื ืืืจ ืจืึทืืขืืืขื ืื ื ืืึทืข ืืขืจืืืึทื ืืืงื ืฆื ืึท ืืขื ืืข gameUpdates
. ืืขืจื ืึธื, ืฆื ืงืึธื ืืจืึธืืืจื ืืืงืึธืจื ืืึทื ืืฅ, ืืืจ ืืึทืืืึทืืืงื ืึทืืข ืึทืื ืืขืจืืืึทื ืืืงืื ืืขื ืฆื ืืึทืืข ืืขืจืืืึทื ืืืงืืืืืื ืืืจ ืืืจืคื ืืื ืืขืจ ื ืืฉื.
ืืืึธืก ืืื ืึท "ืืึทืจืฅ ืืขืจืืืึทื ืืืงื"? ืืืก ืืขืจ ืขืจืฉืืขืจ ืืขืจืืืึทื ืืืงื ืืืจ ืืขืคึฟืื ืขื ืืืจื ืืึธืืืื ื ืงืึทืคึผืืืขืจ ืคืื ืื ืงืจืึทื ื ืกืขืจืืืขืจ ืฆืืื. ืืขืืขื ืงื ืืขื ืืืึทืืจืึทืืข?
ืืขืจ ืฉืคึผืื ืืขืจืืืึทื ืืืงื ืืืืึทื ืฆื ืื ืืื ืงืก ืคืื "ืงืืืขื ื ืจืขื ืืขืจ ืฆืืื" ืืื ืื ืืึทืืข ืืขืจืืืึทื ืืืงื.
ืืืึธืก ืืื ืื ืืึทืืข ืืขืจืืืึทื ืืืงื ืืขื ืืฆื ืคึฟืึทืจ? ืคืืจืืืืก ืงืขื ืขื ืืืจ ืคืึทืื ืืขืจืืืึทื ืืืงืื ืืขื ืฆื ืืึทืืข? ืฆื ืคึฟืึทืจืฉืืืื ืืขื, ืืึธืื ืืื ืื ืืขืกืึธืฃ ืืื ืก ืงืืง ืืื ืื ืืืคึผืืึทืืขื ืืืืฉืึทื getCurrentState()
:
state.js, ืืืื 2
export function getCurrentState() {
if (!firstServerTimestamp) {
return {};
}
const base = getBaseUpdate();
const serverTime = currentServerTime();
// If base is the most recent update we have, use its state.
// Else, interpolate between its state and the state of (base + 1).
if (base < 0) {
return gameUpdates[gameUpdates.length - 1];
} else if (base === gameUpdates.length - 1) {
return gameUpdates[base];
} else {
const baseUpdate = gameUpdates[base];
const next = gameUpdates[base + 1];
const r = (serverTime - baseUpdate.t) / (next.t - baseUpdate.t);
return {
me: interpolateObject(baseUpdate.me, next.me, r),
others: interpolateObjectArray(baseUpdate.others, next.others, r),
bullets: interpolateObjectArray(baseUpdate.bullets, next.bullets, r),
};
}
}
ืืืจ ืคืืจื ืืจืืึท ืงืึทืกืขืก:
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}`);
ืืขืืขื ืงื ืึทื ืืื ืืขืจ ืขืจืฉืืขืจ ืืืื ืืืจ ืืืกืงืึทืกื ืืืขืืคึผืึทืงืง? ืืึธืก ืืื ืืื ืืืจ ืืืขืื ื ืืฆื ืืื ืืืขืจ ืืืขืืคึผืึทืงืง ืงืึทื ืคืืืืขืจืืืฉืึทื ื. ืืืจ ืืืขืื ืฆืืืืืื ืืื ืืื ืฆืืืื ืืืขืื:
- ื ืืฅ
ืืืขืืคึผืึทืง-ืืขืื-ืืืืืืืึทืจืข ืฆื ืืืืืึธืืึทืืืฉ ืจืืืืื ืืื ืืืขืจ ืึทื ืืืืืงืืื ื ืคึผืึทืงืึทืืืฉืึทื, ืึธืืขืจ - ืกืืึทืืืงืื ืึทืจืืืขืจืคืืจื ืึท ืืขืงืข
dist/
, ืืื ืืืึธืก ืืืขืืคึผืึทืงืง ืืืขื ืฉืจืืึทืื ืืื ืืืขืจ ืืขืงืขืก ื ืึธื ืื ืคึผืจืึธืืืงืฆืืข ืืืืขื.
ืื ืื ืืขืจ ืืืืืืืง ืึทืจืืขื 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
.
2. ืฉืคึผืื ืกืขืจืืืขืจืก
ืกืึธืจื Game
ืึผืืื ืื ืืขืจืกื ืืืืืืืง ืกืขืจืืืขืจ ืืืึทื ืืึธืืืง. ืขืก ืืื ืฆืืืื ืืืืคึผื ืืึทืกืงืก: ืฉืคึผืืืขืจ ืคืึทืจืืืึทืืืื ื ะธ ืฉืคึผืื ืกืืืืึทืืืืฉืึทื.
ืืื ืก ืึธื ืืืืื ืืื ืืขืจ ืขืจืฉืืขืจ ืึทืจืืขื - ืึธื ืคืืจืื ื ืื ืคึผืืืึทืขืจืก.
game.js, ืืืื 1
const Constants = require('../shared/constants');
const Player = require('./player');
class Game {
constructor() {
this.sockets = {};
this.players = {};
this.bullets = [];
this.lastUpdateTime = Date.now();
this.shouldSendUpdate = false;
setInterval(this.update.bind(this), 1000 / 60);
}
addPlayer(socket, username) {
this.sockets[socket.id] = socket;
// Generate a position to start this player at.
const x = Constants.MAP_SIZE * (0.25 + Math.random() * 0.5);
const y = Constants.MAP_SIZE * (0.25 + Math.random() * 0.5);
this.players[socket.id] = new Player(socket.id, username, x, y);
}
removePlayer(socket) {
delete this.sockets[socket.id];
delete this.players[socket.id];
}
handleInput(socket, dir) {
if (this.players[socket.id]) {
this.players[socket.id].setDirection(dir);
}
}
// ...
}
ืืื ืืขื ืฉืคึผืื ืืืจ ืืืขืื ืืืขื ืืืคืืฆืืจื ืคึผืืืึทืขืจืก ืืืจื ืคืขืื id
ืืืืขืจ ืืึธืืขื socket.io (ืืืื ืืืจ ืืขื ื ืฆืขืืืฉื, ืืืื ืฆืืจืืง ืฆื server.js
). Socket.io ืืื ืึทืกืืื ื ืืขืืขืจ ืืึธืืขื ืึท ืืื ืฆืืง id
, ืึทืืื ืืืจ ืืึธื ื ืื ืืึทืจืคึฟื ืฆื ืืึธืจื ืืืขืื ืืื. ืืื ืืืขื ืืื ืจืืคื ืฉืคึผืืืขืจ ืฉืืึทื.
ืืื ืืขื ืืื ืืื ืขื, ืืึธืื ืืื ืื ืื ืืขืจืืืื ืื ืืืึทืฉืคึผืื ืืืขืจืืึทืืึทืื ืืื ืื ืงืืึทืก Game
:
sockets
ืืื ืึท ืืืืคืขืฅ ืืืึธืก ืืืื ืื ืื ืฉืคึผืืืขืจ ืฉืืึทื ืฆื ืื ืืึธืืขื ืืืึธืก ืืื ืคืืจืืื ืื ืืื ืื ืฉืคึผืืืขืจ. ืขืก ืึทืืึทืื ืืื ืื ืฆื ืึทืงืกืขืก ืกืึทืงืึทืฅ ืืืจื ืืืืขืจ ืฉืคึผืืืขืจ ืืืก ืืืืขืจ ืฆืืึทื.players
ืืื ืึท ืืืืคืขืฅ ืืืึธืก ืืืื ืื ืื ืฉืคึผืืืขืจ ืฉืืึทื ืฆื ืื ืงืึธื> ืฉืคึผืืืขืจ ืืืืคืขืฅ
bullets
ืืื ืึท ืงืืื ืคืื ืึทืืืืฉืขืงืฅ Bullet
, ื ืืฉื ืืึธืื ืึท ืกืคึผืขืฆืืคืืฉ ืกืืจ.
lastUpdateTime
- ืืึธืก ืืื ืืขืจ ืฆืืื ืกืืึทืืคึผ ืคืื ืื ืืขืฆืืข ืฉืคึผืื ืืขืจืืืึทื ืืืงื. ืืืจ ืืืขืื ืืึทืื ืืขื ืืื ืขืก ืืื ืืขื ืืฆื.
shouldSendUpdate
ืืื ืึท ืึทืืืืืืขืจื ืืืึทืืขืืืืืง. ืืืจ ืืืขืื ืืืื ืืึทืื ืืขื ืืืึทื ื ืืฆื.
ืืขืืืึธืืก addPlayer()
, removePlayer()
ะธ handleInput()
ื ืื ืืึทืจืคึฟื ืฆื ืืขืจืงืืขืจื, ืืื ืืขื ืขื ืืขื ืืฆื ืืื server.js
. ืืืื ืืืจ ืืึทืจืคึฟื ืึท ืจืืคืจืขืฉืขืจ, ืืืื ืฆืืจืืง ืึท ืืืกื ืืขืืขืจ.
ืืขืฆืืข ืฉืืจื constructor()
ืกืืึทืจืฅ ืึทืจืืืฃ ืืขืจืืืึทื ืืืงื ืฆืืงื ืฉืคึผืืืขืจืืึท (ืืื ืึท ืึธืคืืงืืึทื ืคืื 60 ืืขืจืืืึทื ืืืงืื ืืขื / s):
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 ืืื / s, ืืืจ ืฉืืงื ืฉืคึผืื ืืขืจืืืึทื ืืืงืื ืืขื 30 ืืื / s. ืืืื, ืืืืืขืจ ืึธืคืืงืืึทื ืกืขืจืืืขืจ ืืื 30 ืืืืืขืจ ืกืืืงืึทืื / s (ืืืจ ืืขืจืขืื ืืืขืื ืื ืืืืืขืจ ืึธืคืืงืืึทื ืืื ืืขืจ ืขืจืฉืืขืจ ืืืื).
ืคืืจืืืืก ืฉืืงื ืฉืคึผืื ืืขืจืืืึทื ืืืงืื ืืขื ืืืืื ืืืจื ืฆืืึทื ? ืฆื ืจืึทืืขืืืขื ืงืึทื ืึทื. 30 ืฉืคึผืื ืืขืจืืืึทื ืืืงืื ืืขื ืคึผืขืจ ืกืขืงืื ืืข ืืื ืึท ืคึผืืึทืฅ!
ืคืืจืืืืก ื ืืฉื ืกืชื ืจืืคื?
update()
30 ืืื ืคึผืขืจ ืกืขืงืื ืืข? ืฆื ืคึฟืึทืจืืขืกืขืจื ืื ืฉืคึผืื ืกืืืืึทืืืืฉืึทื. ืื ืืขืจ ืึธืคื ืขืก ืืื ืืขืจืืคืupdate()
, ืื ืืขืจ ืคึผืื ืืืขื ืื ืฉืคึผืื ืกืืืืึทืืืืฉืึทื ืืืขื ืืืื. ืึธืืขืจ ืืึธื ื ืื ืืึทืงืืืขื ืฆื ืืขืคืืจื ืึทืืืขืง ืืืจื ืื ื ืืืขืจ ืคืื ืืฉืึทืืึทื ืืืฉืืupdate()
, ืืืืึทื ืืึธืก ืืื ืึท ืงืึทืืคึผืืืืืฉืึทื ืึทืื ืืืึทืขืจ ืึทืจืืขื - 60 ืคึผืขืจ ืกืขืงืื ืืข ืืื ืืึทื ืฅ ืืขื ืื.
ืื ืจืขืฉื ืคืื ืื ืงืืึทืก Game
ืืืฉืืืื ืคืื ืืขืืคึผืขืจ ืืขืืืึธืืก ืืขื ืืฆื ืืื update()
:
game.js, ืืืื 3
class Game {
// ...
getLeaderboard() {
return Object.values(this.players)
.sort((p1, p2) => p2.score - p1.score)
.slice(0, 5)
.map(p => ({ username: p.username, score: Math.round(p.score) }));
}
createUpdate(player, leaderboard) {
const nearbyPlayers = Object.values(this.players).filter(
p => p !== player && p.distanceTo(player) <= Constants.MAP_SIZE / 2,
);
const nearbyBullets = this.bullets.filter(
b => b.distanceTo(player) <= Constants.MAP_SIZE / 2,
);
return {
t: Date.now(),
me: player.serializeForUpdate(),
others: nearbyPlayers.map(p => p.serializeForUpdate()),
bullets: nearbyBullets.map(b => b.serializeForUpdate()),
leaderboard,
};
}
}
getLeaderboard()
ืขืก ืืื ืืึทื ืฅ ืคึผืฉืื - ืขืก ืกืึธืจืฅ ืคึผืืืึทืขืจืก ืืืจื ืืขืืฉืื, ื ืขืื ืื ืฉืคึผืืฅ ืคืื ืฃ ืืื ืงืขืจื ืื ื ืืืขื ืืื ืืขืืฉืื ืคึฟืึทืจ ืืขืืขืจ.
createUpdate()
ืืขื ืืฆื ืืื update()
ืฆื ืฉืึทืคึฟื ืฉืคึผืื ืืขืจืืืึทื ืืืงืื ืืขื ืืืึธืก ืืขื ืขื ืคืื ืื ืืขืจืืขืืืืื ืฆื ืคึผืืืึทืขืจืก. ืืืึทื ืืืืคึผื ืึทืจืืขื ืืื ืฆื ืจืืคื ืืขืืืึธืืก serializeForUpdate()
, ืืืคึผืืึทืืขื ืึทื ืคึฟืึทืจ ืงืืืกื Player
ะธ Bullet
. ืืึทืืขืจืงืื ื ืึทื ืขืก ื ืึธืจ ืืจืึทื ืกืืืื ืืึทืื ืฆื ืืขืืขืจ ืฉืคึผืืืขืจ ืืืขืื ื ืื ืืกืืข ืคึผืืืึทืขืจืก ืืื ืคึผืจืึทืืืฉืขืงืืึทืื - ืขืก ืืื ื ืื ืืึทืจืคึฟื ืฆื ืึทืจืืืขืจืคืืจื ืืื ืคึฟืึธืจืืึทืฆืืข ืืืขืื ืฉืคึผืื ืึทืืืืฉืขืงืฅ ืืืึธืก ืืขื ืขื ืืืื ืืืืึทื ืคืื ืื ืฉืคึผืืืขืจ!
3. ืฉืคึผืื ืึทืืืืฉืขืงืฅ ืืืืฃ ืื ืกืขืจืืืขืจ
ืืื ืืื ืืืขืจ ืฉืคึผืื, ืคึผืจืึทืืืฉืขืงืก ืืื ืคึผืืืึทืขืจืก ืืขื ืขื ืึทืงืฉืื ืืืืขืจ ืขื ืืขื: ืืื ืืขื ืขื ืึทืืกืืจืึทืงื ืงืืึทืืขืืืง ืืึธืืืื ื ืฉืคึผืื ืึทืืืืฉืขืงืฅ. ืฆื ื ืืฆื ืืขื ืขื ืืขืืงืืื ืฆืืืืฉื ืคึผืืืึทืขืจืก ืืื ืคึผืจืึทืืืฉืขืงืืึทืื, ืืึธืื ืืื ืื ืึธื ืืืืื ืืื ืืืคึผืืึทืืขื ืื ื ืึท ืืึทืืข ืงืืึทืก Object
:
object.js
class Object {
constructor(id, x, y, dir, speed) {
this.id = id;
this.x = x;
this.y = y;
this.direction = dir;
this.speed = speed;
}
update(dt) {
this.x += dt * this.speed * Math.sin(this.direction);
this.y -= dt * this.speed * Math.cos(this.direction);
}
distanceTo(object) {
const dx = this.x - object.x;
const dy = this.y - object.y;
return Math.sqrt(dx * dx + dy * dy);
}
setDirection(dir) {
this.direction = dir;
}
serializeForUpdate() {
return {
id: this.id,
x: this.x,
y: this.y,
};
}
}
ืขืก ืืื ืืึธืจื ืืฉื ืงืึธืืคึผืืืฆืืจื ืืึธ. ืืขืจ ืงืืึทืก ืืืขื ืืืื ืึท ืืื ืกืืึทืจืืื ื ืคืื ื ืคึฟืึทืจ ืืงืกืคึผืึทื ืฉืึทื. ืืื ืก ืืขื ืืื ืื ืงืืึทืก Bullet
ื ืืฆื Object
:
bullet.js
const shortid = require('shortid');
const ObjectClass = require('./object');
const Constants = require('../shared/constants');
class Bullet extends ObjectClass {
constructor(parentID, x, y, dir) {
super(shortid(), x, y, dir, Constants.BULLET_SPEED);
this.parentID = parentID;
}
// Returns true if the bullet should be destroyed
update(dt) {
super.update(dt);
return this.x < 0 || this.x > Constants.MAP_SIZE || this.y < 0 || this.y > Constants.MAP_SIZE;
}
}
ืืืคึผืืขืืขื ืืึทืืืึธื Bullet
ืืืืขืจ ืงืืจืฅ! ืืืจ ืืึธืื ืฆืืืขืืืืื ืฆื Object
ืืืืื ืื ืคืืืืขื ืืข ืืงืกืืขื ืฉืึทื ื:
- ื ืืฆื ืืขื ืคึผืขืงื
ืงืืจืฅ ืคึฟืึทืจ ืืจืึทืค - ืืืจid
ืคึผืจืึทืืืฉืขืงืืึทื. - ืึทืืื ื ืึท ืคืขืื
parentID
, ืึทืืื ืืืจ ืงืขื ืขื ืฉืคึผืืจ ืื ืฉืคึผืืืขืจ ืืืึธืก ืืืฉืืคื ืืขื ืคึผืจืึทืืืฉืขืงืืึทื. - ืึทืืื ื ืื ืฆืืจืืงืงืืืขื ืืืขืจื ืฆื
update()
, ืืืึธืก ืืื ืืืืึทืtrue
, ืืืื ืื ืคึผืจืึทืืืฉืขืงืืึทื ืืื ืึทืจืืืก ืื ืืจืขื ืข (ืืขืืขื ืงืขื ืืืจ ืืขืจืขืื ืืืขืื ืืขื ืืื ืื ืืขืฆืืข ืึธืคึผืืืืืื ื?).
ืืื ืก ืืึทื ืืืืฃ ืฆื Player
:
player.js
const ObjectClass = require('./object');
const Bullet = require('./bullet');
const Constants = require('../shared/constants');
class Player extends ObjectClass {
constructor(id, username, x, y) {
super(id, x, y, Math.random() * 2 * Math.PI, Constants.PLAYER_SPEED);
this.username = username;
this.hp = Constants.PLAYER_MAX_HP;
this.fireCooldown = 0;
this.score = 0;
}
// Returns a newly created bullet, or null.
update(dt) {
super.update(dt);
// Update score
this.score += dt * Constants.SCORE_PER_SECOND;
// Make sure the player stays in bounds
this.x = Math.max(0, Math.min(Constants.MAP_SIZE, this.x));
this.y = Math.max(0, Math.min(Constants.MAP_SIZE, this.y));
// Fire a bullet, if needed
this.fireCooldown -= dt;
if (this.fireCooldown <= 0) {
this.fireCooldown += Constants.PLAYER_FIRE_COOLDOWN;
return new Bullet(this.id, this.x, this.y, this.direction);
}
return null;
}
takeBulletDamage() {
this.hp -= Constants.BULLET_DAMAGE;
}
onDealtDamage() {
this.score += Constants.SCORE_BULLET_HIT;
}
serializeForUpdate() {
return {
...(super.serializeForUpdate()),
direction: this.direction,
hp: this.hp,
};
}
}
ืคึผืืืึทืขืจืก ืืขื ืขื ืืขืจ ืงืึธืืคึผืืืฆืืจื ืืื ืคึผืจืึทืืืฉืขืงืืึทืื, ืึทืืื ืืขื ืงืืึทืก ืืึธื ืงืจืึธื ืึท ืืืกื ืืขืจ ืคืขืืืขืจ. ืืืื ืฉืืื update()
ืืื ืืขืจ ืึทืจืืขื, ืกืคึผืขืฆืืขื ืฆืืจืืงืงืืืขื ืื ื ืื ืืืฉืืคื ืคึผืจืึทืืืฉืขืงืืึทื ืืืื ืขืก ืืขื ืขื ืงืืื ืืื ืงืก fireCooldown
(ืืขืืขื ืงืขื ืืืจ ืืขืจืขืื ืืืขืื ืืขื ืืื ืื ืคืจืืขืจืืืงืข ืึธืคึผืืืืืื ื?). ืขืก ืืืื ืืงืกืืขื ืื ืืขื ืืืคึฟื serializeForUpdate()
, ืืืืึทื ืืืจ ืืึทืจืคึฟื ืฆื ืึทืจืืึทื ื ืขืืขื ื ืึธื ืคืขืืืขืจ ืคึฟืึทืจ ืื ืฉืคึผืืืขืจ ืืื ืื ืฉืคึผืื ืืขืจืืืึทื ืืืงื.
ืึทืืืึทืืืึทืืืืืื ืคืื ืึท ืืึทืืข ืงืืึทืก Object
- ืึท ืืืืืืืง ืฉืจืื ืฆื ืืืกืืืืื ืงืึธื ืืืขืจืืึทืืขืจืื ื. ืคึฟืึทืจ ืืืึทืฉืคึผืื, ืึธื ืงืืึทืก Object
ืืขืืขืจ ืฉืคึผืื ืืืืคืขืฅ ืืืื ืืึธืื ืื ืืขืืืข ืืืคึผืืึทืืขื ืืืืฉืึทื distanceTo()
, ืืื ืงืึธืคึผืืข-ืคึผืึทืกืืื ื ืึทืืข ืื ืืืคึผืืึทืืึทื ืฅ ืึทืจืืืขืจ ืงืืืคื ืืขืงืขืก ืืืึธืื ืืืื ืึท ื ืืืืืขืจ. ืืึธืก ืืื ืกืคึผืขืฆืืขื ืืืืืืืง ืคึฟืึทืจ ืืจืืืก ืคึผืจืึทืืืฉืขืงืก, ืืืขื ืื ื ืืืขืจ ืคืื ืืงืกืคึผืึทื ืืื ื Object
ืงืืืกื ืืืึทืงืกื.
4. ืฆืื ืืืคืฉืืืืก ืืืืขืงืฉืึทื
ืืขืจ ืืืืื ืืึทื ืืืึธืก ืืื ืืื ืงืก ืคึฟืึทืจ ืืื ืื ืฆื ืืึธื ืืื ืืขืจืงืขื ืขื ืืืขื ืื ืคึผืจืึทืืืฉืขืงืืึทืื ืฉืืึธืื ืื ืคึผืืืึทืขืจืก! ืืขืืขื ืงื ืืขื ืงืึธื ืกื ืืคึผืึทื ืคืื ืืขื ืืืคึฟื update()
ืืื ืงืืึทืก Game
:
game.js
const applyCollisions = require('./collisions');
class Game {
// ...
update() {
// ...
// Apply collisions, give players score for hitting bullets
const destroyedBullets = applyCollisions(
Object.values(this.players),
this.bullets,
);
destroyedBullets.forEach(b => {
if (this.players[b.parentID]) {
this.players[b.parentID].onDealtDamage();
}
});
this.bullets = this.bullets.filter(
bullet => !destroyedBullets.includes(bullet),
);
// ...
}
}
ืืืจ ืืึทืจืคึฟื ืฆื ืื ืกืืจืืืขื ื ืืขื ืืืคึฟื applyCollisions()
, ืืืึธืก ืงืขืจื ืึทืืข ืคึผืจืึทืืืฉืขืงืืืืื ืืืึธืก ืฉืืึธืื ืคึผืืืึทืขืจืก. ืฆืื ืืืืง, ืืึธืก ืืื ื ืืฉื ืึทืืื ืฉืืืขืจ ืฆื ืืึธื ืืืืึทื
- ืึทืืข ืงืึทืืืืืื ื ืึทืืืืฉืขืงืฅ ืืขื ืขื ืงืจืืืื, ืืื ืืึธืก ืืื ืื ืกืืืคึผืืึทืกื ืคืึธืจืขื ืฆื ืื ืกืืจืืืขื ื ืฆืื ืืืคืฉืืืืก ืืืืขืงืฉืึทื.
- ืืืจ ืืึธืื ืฉืืื ืึท ืฉืืื
distanceTo()
, ืืืึธืก ืืืจ ืืืคึผืืึทืืขื ืึทื ืืื ืื ืงืืึทืก ืืื ืื ืคืจืืขืจืืืงืข ืึธืคึผืืืืืื ืObject
.
ืืึธืก ืืื ืืื ืืื ืืืขืจ ืืืคึผืืึทืืขื ืืืืฉืึทื ืคืื ืฆืื ืืืคืฉืืืืก ืืืืขืงืฉืึทื ืงืืงื ืืื:
collisions.js
const Constants = require('../shared/constants');
// Returns an array of bullets to be destroyed.
function applyCollisions(players, bullets) {
const destroyedBullets = [];
for (let i = 0; i < bullets.length; i++) {
// Look for a player (who didn't create the bullet) to collide each bullet with.
// As soon as we find one, break out of the loop to prevent double counting a bullet.
for (let j = 0; j < players.length; j++) {
const bullet = bullets[i];
const player = players[j];
if (
bullet.parentID !== player.id &&
player.distanceTo(bullet) <= Constants.PLAYER_RADIUS + Constants.BULLET_RADIUS
) {
destroyedBullets.push(bullet);
player.takeBulletDamage();
break;
}
}
}
return destroyedBullets;
}
ืืขื ืคึผืฉืื ืฆืื ืืืคืฉืืืืก ืืืืขืงืฉืึทื ืืื ืืืืืจื ืืืืฃ ืืขื ืคืึทืงื ืึทื ืฆืืืื ืงืจืืืื ืฆืื ืืืคืฉืืืืก ืืืื ืื ืืืืึทืืงืืื ืฆืืืืฉื ืืืืขืจ ืฆืขื ืืขืจ ืืื ืืืืื ืืงืขืจ ืืื ืื ืกืึทืืึทืงื ืคืื ืืืืขืจ ืจืึทืืืขืก. ืืึธ ืืื ืึท ืคืึทื ืืื ืื ืืืืึทืืงืืื ืฆืืืืฉื ืื ืกืขื ืืขืจืก ืคืื ืฆืืืื ืงืจืืืื ืืื ืคึผืื ืงื ืืืืึทื ืฆื ืื ืกืึทืืึทืงื ืคืื ืืืืขืจ ืจืึทืืืืก:
ืืึธ ืืืจ ืืึทืจืคึฟื ืฆื ืืึทืฆืึธืื ืืคืืขืจืงืืึทืืงืืึทื ืฆื ืึท ืคึผืึธืจ ืืขืจ ืึทืกืคึผืขืงืฅ:
- ืื ืคึผืจืึทืืืฉืขืงืืึทื ืืึธื ื ืืฉื ืฉืืึธืื ืื ืฉืคึผืืืขืจ ืืืืก ืืืฉืืคื ืขืก. ืืขื ืงืขื ืขื ืืืื ืึทืืฉืืืื ืืืจื ืงืึทืืคึผืขืจืื ื
bullet.parentID
ัplayer.id
. - ืื ืคึผืจืึทืืืฉืขืงืืึทื ืืึธื ื ืึธืจ ืฉืืึธืื ืึทืืึธื ืืื ืื ืขืงืกืืจืขื ืคืึทื ืคืื ืืืืืื ื ืงืืืคื ืคึผืืืึทืขืจืก ืืื ืืขืจ ืืขืืืืงืขืจ ืฆืืื. ืืืจ ืืืขืื ืกืึธืืืืข ืืขื ืคึผืจืึธืืืขื ื ืืฆื ืื ืึธืคึผืขืจืึทืืึธืจ
break
: ืึทืืึธื ืึท ืฉืคึผืืืขืจ ืงืึทืืืืืื ื ืืื ืึท ืคึผืจืึทืืืฉืขืงืืึทื ืืื ืืขืคึฟืื ืขื, ืืืจ ืืึทืืื ืืืื ืืื ืืึทื ืืืืฃ ืฆื ืืขืจ ืืืืึทืืขืจ ืคึผืจืึทืืืฉืขืงืืึทื.
ืขืง
ืึทื ืก ืึทืืข! ืืืจ ืืึธืื ืืืืขืงื ืึทืืฅ ืืืจ ืืึทืจืคึฟื ืฆื ืืืืกื ืฆื ืฉืึทืคึฟื ืึท .io ืืืขื ืฉืคึผืื. ืืืืก ืืื ื ืขืงืกื? ืืืืขื ืืืื ืืืืืขื ืข .ืืึธ ืฉืคึผืื!
ืื ืืืึทืฉืคึผืื ืงืึธื ืืื ืึธืคึฟื ืืงืืจ ืืื ืึทืจืืึทื ืืขืฉืืงื ืืืืฃ
ืืงืืจ: www.habr.com