
แ 2015 แฐแแแ แจแ แฒแฑ แแแ แ
แตแ แ แซแต แแ แจแแซแ แแ แแแฎ แ แณแแแแต แซแฐแแ. แ แแ แจ.io แจแแณแแฝ แฐแแณแ
แแต แแจแแญ แ แแฅแแแแก แฃแแแต แถแตแต แ แแณแต แแตแฅแฃ แ แแ .
แตแแฅแแแ แจแแณแแฝ แจแแ แ แแต แฐแแฐแ แจแแณแแแ แจแแแฃ แแแซแแต แแแ แจแแ แแป แฃแแฅแ แฐแซแแฝ แจแตแญ แจแแณแแฝ แแธแ (แแแ แแแซ แ แซแตแแแแ)แข แฅแแแ แแ แ แฐแแณแณแญ แแตแจแญ แฅแ แฐแแซแ แฐแซแแพแฝแ แญแแแฃแแข แแแฝ แณแแ แจ.io แจแแณแแฝแก- ะธ .
แ แแ
แฝแแ แแตแฅ, แฅแแดแต แฅแแฐแแ แฅแแแจแแซแแ แจแฃแถ แจ.io แจแแณ แญแแ แฉ. แแแ
, แจแแซแตแญแชแแต แฅแแแต แฅแป แ แ แญแแแ: แฅแแฐ แ แแฃแฅ แซแ แแแฎแฝแ แแจแณแต แซแตแแแแแณแ แฃ แแแ แแ this ะธ . แจแแซแตแญแชแแต แฅแแแตแ แแแ แฃแญแแแ แ แฅแแแแ แแฅแแ แแจแณแต แญแฝแแแข
.io แจแแณ แแณแ
แแตแแ แญแต แฅแญแณแณแฃ แฅแแ แ แณแแแข . แฅแฑแ แแแซแแต แญแแญแฉ!

แจแแณแ แ แฃแ แแแ แแแก แแแฝ แฐแซแแพแฝ แฃแแ แต แแตแจแญ แแญ แแญแจแฅแ แตแแฃแ แซแแฝแแข แจแฅแญแตแ แแญแจแฅ แ แซแต-แฐแญ แแฎแแญแฐแฎแฝแ แซแแฅแแ แฅแ แแแฝ แฐแซแแพแฝแ แแฎแแญแฐแฎแฝแ แ แแตแแแต แแญ แฅแซแ แแแแณแต แญแแญแซแแข
1. แจแแฎแแญแฑ แ แญแญ แแแแซ / แแแ แญ
แฅแ แ แแฐแแแแ แฅแแ แแจแฐแ แฅแแฒแฝแ แจแแณแ แจแแณแข
แแณแแ แจแแจแฐแแตแ แญแ แแแ:
- แจแจแแณแแ แจแตแญ แ แแแแญ แจแแซแตแฐแณแตแญ แ แฃแ แณแแแ แจ Node.js แจแตแญ แแแแ แแแข
- - แ แ แณแฝ แฅแ แ แ แแแแญ แแซแจแ แแแฅ แแแแแแฅ แจแแฅแถแฌแต แคแฐ-แแฝแแแตแข
- - แแแ แ แตแฐแณแณแช. แแแ Webpack แฅแแฐแแ แแ แแแ แฅ แญแฝแแ. .
แจแแฎแแญแต แแแซ แแแ แญ แแ แฅแแฐแแแตแ แฅแแแก-
แจแ แแฅ / แแฅแจแถแฝ / ... src / แฐแแ แ / css / ... html/ index.html index.js ... แ แแแแญ / แ แแแแญ.js ... แจแฐแแซ / แแแแฝ.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แจแแซแตแญแชแแต (JS) แฐแแ แ แแแขแซ แแฅแฅ แแแข แแฅแแญ แจแแ แญแแแซแ แฅแ แแแฝ แแฐ แแแญ แแตแฅ แจแแแก แแญแแฝแ แ แจแแแ แญแแแแแข- แจแฅแ แจแแฅแแญ แแแฃแณ แแคแต JS แ แแแซแ แแตแฅ แญแแแฃแ
dist/. แญแ แแ แแญแ แจแ แฅแฐแแแแแข js แฅแ แ. - แฅแ แฅแแ แแแแ , แฅแ แ แฐแแญแ แ แแแแฉ แแ แฎแ แ แณแพแฝ แจแแ JS แฎแต แแแแแข
- แ JS แแญแแฝ แจแฐแ แแฑ แแแแ CSS แแแแฃแต แฅแ แ แ แแต แฆแณ แแแฃแแญ แแแแ แฅแจแฐแ แแแ แแแข แจแ แฅแแแแแข css แฅแ แ.
แฅแแแณ แจแฅแ
แ แแญแ แตแแฝแ แ แตแฐแแแ
แญแแแแข '[name].[contenthash].ext'. แญแญแแ แจแตแญ แฆแญแณ [name] แ แแคแต แแฅแก แตแ แญแฐแซแแ (แ แฅแ แแแณ แญแ
game) แฃ แฅแ [contenthash] แ แแญแ แญแแต แแฝ แญแฐแซแแข แฅแแฐแญแแแแ - แ แณแพแฝ แจแฅแแ JS แฅแ
แแฝ แแแฐแแฐแ แแ แฅแแฒแธแแก แแแแญ แญแฝแแแฃ แแญแแซแฑแ แ แแต แฅแ
แ แจแฐแแแ แจแแญแ แตแแ แญแแแฃแ (แญแแแฃแ 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แฅแ แแฐ แตแญ แ แณแฝ แญแแฑ . แฎแฑ แฒแแจแญ แจแแแต แ แแแแฉ แจJS แฅแ CSS แแฌแแฝแ แ แซแต แฐแญ แแแถ แญแแแฃแ - แแแแ แแแฆแฝ แแแจแต แแนแ แฅแป แซแตแฑ!
3. แจแฐแแ แ แแแขแซ แแฅแฆแฝ
แแฐ แจแแณแ แฎแต แฅแซแฑ แฅแแแจแตแข แแแแชแซ แแฝ แฅแแแแแแ index.html, แฃแขแซแแ แฒแแ แ, แ แณแน แแแแชแซ แญแญแแแ. แแปแฝแ แ แฃแ แแแ แญแแแแก-
index.html
แแณแ .io แจแแณ แฐแซแแต
แญแ แจแฎแต แแณแ แแแฝ แแแตแจแ แ แตแแน แแแ แซแ แแแฃ แฅแ แจแฅแ แแแฝ แจแแฅแ แแณแแแฝ แแญ แฐแแณแณแญ แแแญ แ แฐแญแแแแข แแ แฎแต แแ แแ แ แแญ แแณแญ แญแฝแแแข .
แฅแ แ แแ:
- (
<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). - แจ"PLAY" แแแแ แแแซแ แฐแแฃแฃแชแแ แ แแแแแต แแญแข แแแ แฒแซแ แฎแฑ แจแแณแแ แญแแแซแ แฅแ แฅแ แแแซแแต แแแ แแแแฝแแ แแ แแแแฉ แญแแแจแแแข
แจแฅแ แจแฐแแ แ-แ แแแแญ แ แแญแแฎ แแแ "แตแ" แ แแญแ แแฐ แแแญ แแตแฅ แ แแกแต แแญแแฝ แแตแฅ แแ index.js. แ แแ แแแแ แ แ
แฐแ แฐแจแฐแ แฅแแแแจแณแแ.
4. แจแฐแแ แ แแแฅ แแแแแฅ
แ แแ แจแแณ แจแ แแแแฉ แแญ แแแแแแต แจแณแแ แคแฐ-แแฝแแแตแ แฅแแ แแแแแข . Socket.io แคแฐแ แตแแ แ แแแข , แแแแต แแแแต แแแแแต แ แฃแ แฐแตแแ แจแแแต: แแฐ แ แแแแฉ แแแฅแญแต แแแญ แฅแแฝแแแ ะธ แ แแแแฉ แ แฐแแณแณแญ แแแแแต แแญ แแแฅแญแต แแแญแแ แญแฝแแแข
แ แแต แแญแ แญแแจแแ src/client/networking.jsแแ แญแแจแฃแจแฃแ แแแ แฐแ แจแ แแแแฉ แแญ แแแแแต;
แ แแณแจ แแจแฅ.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. แจแฐแแ แ แ แแซแจแฅ
แแตแแ แ แตแญแชแ แแญ แแแณแจแต แแแ แ แแ แแ!
แแแญ แแ แซแแ แจแแตแจแแฝแ แ แแตแฃ แแแ โโแจแแซแตแแแแตแ แแแแ แแตแแฝ (แแฅแถแฝ) แแแจแต แ แแฅแแข แจแแฅแต แ แตแฐแณแณแชแ แฅแแแแก-
แแฅแจแถแฝ.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แจแแญ แจแฐแแจแแฉแตแ แ แซแต แแแฎแฝ แ แตแญแญแ แจแแซแแญแฅแก-
แแ แจแฅ.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() แจ render loop แ แฅแแ
แตแแด แ 60 FPS แญแแฃแ แฉแข
แจแแแฐแฃแ แจแณแต แฐแแฃแซแต แฐแจแฃแญ แตแแ แซแแฝ (แแแณแ แฃ renderBullet()) แซแ แซแ
แ แ แตแแแ แ แญแฐแแแฃ แแ แ แแต แแแ แแณแ แฅแแ
แ แแก-
แแ แจแฅ.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!
แตแ แแแฝ แ แแฅ แจแณแถแฝ แแแแญ แแแแต แซแแต แจแแจแแ แซแแฅแกแข .
6. แจแฐแแ แ แแคแต
แจแแณ แแแตแซแต แแแ แ แแ แแแข แแซแแต แจแแฝแ! แจแแฅแฅแญ แแญแแแฅแฉ แ แฃแ แแแ แญแแแ-แจแฅแแ
แตแแดแแ แ แ
แฃแซ แแแแแฅ, แ แญแคแแ (แ แฎแแแฐแญ แแญ) แแ แแ แแญแ แแซ แแนแ (แ แแฃแญแ แแณแชแซ แแญ) แแแซแต แญแฝแแ. แญแ
แแ แฐแแฃแซแ แแแตแจแ แฅแแแแแฃแแแข แ Mouse แฅแ Touch แญแตแฐแถแฝ.
แญแ
แแ แแ แญแแจแฃแจแฃแ src/client/input.js:
แแฅแแต.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. แจแฐแแ แ แแแณ
แญแ แญแแ แ แแตแณแ แจแแแแชแซ แญแแ แแตแฅ แ แฃแ แ แตแธแแชแ แแ. แแแแแชแซ แแ แตแณแแกแต แซแแฐแจแณแฝแแต แฐแตแ แ แตแแจแก! แฅแแฒแซแแ แแแแ แตแฝแแแ แฅแ แ แแ แแฐ แฅแฑ แแแแต แตแฝแแแ แข
แจแฐแแ แแ/แจแ แแแแญ แฎแต แแแ แแแ แจแแซแตแแแแ แจแแจแจแปแ แจแฅแแแ แแฝ แญแแ แแแข แแแต. แจแฐแแ แ แ แฐแจแแแ แญแแ แจแฐแแแแ แจแฎแต แ แแฃแข แ แตแณแแต?
แแ แจแฅ.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 Naive แฐแแ แ แแแณ
แจแแ
แ แฐแแฃแ แญ getCurrentState() แ แ
แญแก แจแฐแแ แแแ แจแจแแณ แแแ แแแฅ แฅแป แแ แแแแต แจแแฝแแแข
naive-state.js
let lastGameUpdate = null;
// Handle a newly received game update.
export function processGameUpdate(update) {
lastGameUpdate = update;
}
export function getCurrentState() {
return lastGameUpdate;
}แแแ แฅแ แแแฝ! แแ แซแ แซแ แ แแแ แขแแ แแฎแข แจแแ แ แฐแแฃแ แญ แ แแฑ แแญแแซแต แฝแแญ แซแแ แต แแแก- แจแแฌแ แแฅแแฑแ แ แ แแแแญ แฐแแต แแฅแแต แญแแตแฃแ.
แจแแฌแ แแ แแจแญแแแฝ แฅแแต (แแแตแ แฅแชแแฝ
render()) แ แฐแจแแต แแญแ FPS. แจแแณแแฝ แ แฅแแแแ แแ แขแซแแต 60 FPS แแแแแต แญแฅแซแ.
แจแแแญแต แแ แ: แ แแแแฉ แจแจแแณ แแแแแฝแ แแฐแแ แแฝ แจแแแญแ แต แตแแแแฝแข แฅแแแ แแ แจแญแแ แแฅแแต แซแแฐ แแ. แ แฅแ แจแแณ แ แแแแฉ แ แฐแจแแต แ 30 แแฐแถแฝ แตแแแแฝ แญแฐแซแแข
แจแจแแณแแ แจแ
แญแฅ แแ แแแ แฅแป แซแแจแฅแแฃ FPS แ แแ แจแฑ แจ 30 แ แแญ แ แญแ แแฅแ แฃ แแญแแซแฑแ แจแ แแแแฉ แ แฐแจแแต แจ30 แ แแญ แแแแแฝแ แ แแแแแข. แฅแแ แซแ render() แ แฐแจแแต 60 แแแฃ แจแแซ แจแฅแแแ
แฅแชแแฝ แแตแฅ แแแพแน แ แแต แ แญแแต แแแญ แฅแแฐแแ แญแแญแซแแฃ แ แแ แจแฑ แแแ แแแญ แ แซแฐแญแแแข แแแ แจแแ
แ แฐแแฃแ แญ แฝแแญ แแแข แแแแแจแต แจแฐแแแ . แ แฅแฉ แจแขแแฐแญแแต แแฅแแตแฃ แฐแแ แแ แ แจ33แแด (แ แดแฎแแต 30 แ แฐแจแแต) แจแจแแณ แแแ แญแแ แแแข

แ แแซแณแแ แแแณ, แแแ แแแญ แแนแ แ แญแฐแแ. แจแ แแ แตแญแญแแ แแตแ แจแแจแฐแแ แญแแแ-

แจแแ
แแต แ แฐแแฃแ แญ แแฐ แแแแจแต แฒแแฃ แ แฐแแฃแญ แฅแ
แ แจแจแ แแแข แจแจแแณ แแแ แ 50ms แแแแจแต แจแฐแจแฐ แฃ แจแแซ แจแฐแแ แ แแธแซแแฝ แฐแจแแช 50 แแด แแญแแซแฑแ แ แแแ แจแจแแณแแ แแแณ แจแแฐแแ แแแ แฅแซแแจแ แแแข แญแ
แแฐแซแแน แแ แซแ
แ แจแแญแแฝ แฅแแฐแแ แแแแต แตแฝแแแฝแแก แจแแแแฐ แฅแฌแชแแ แจแแณแแ แซแธแ แจแ แฅแ แซแแฐแจแแ แซแฐแญแแแแข
7.2 แจแฐแปแปแ แจแฐแแ แ แแแณ
แ แ แแแแ แแญ แ แแณแแต แแปแปแซแแฝแ แฅแแฐแญแแแแข แ แแแแชแซ, แฅแแ แแแแ แแแแจแต แแณแจแต แ 100 ms. แญแ แแแต แจแฐแแ แแ "แจแ แแ" แแแณ แแ แแ แ แ แแแแฉ แแญ แซแแ แจแจแแณ แแแณ แ 100 แ.แด. แแแณแ, แ แ แแแแฉ แแญ แซแแ แแ แจแแ 150, แจแแซแ แฐแแ แแ แ แแแแฉ แ แแ แฑ แจแแ แจแแ แแแณ แซแแญแฃแ 50:

แญแ
แจแแญแแแต แจแจแแณ แแแ แแแแฝแ แแแตแจแ แจ100ms แแต แญแฐแ แแแก

แแแ
แซแแ แญแแซ แแแ แญแแแ แ 100 ms. แญแ
แแตแแณ แ แจแแแต แแแ แแตแแฅแตแแต แแ - แ แฅแแแน แฐแซแแพแฝ (แ แฐแแญ แฐแซ แฐแซแแพแฝ) แญแ
แ แแแแจแต แฅแแณแ แ แซแตแฐแแแแข แฃแแฐแ แ แ แแแแจแต แจแแซแแต แญแแ
แฐแแฝ แแ แจแแ แจ100 แ.แ แแแแจแตแ แแตแฐแซแจแ แแแ แแแข
แแ แจแแฃแ แดแญแแญ แแ แแ แฅแแฝแแแ , แญแ แ แจแณแฐแ แแ แแแแจแตแ แ แแแแต แฅแฉ แตแซ แญแฐแซแ, แแแญ แแ แ แแ แฝแแ แแตแฅ แ แญแซแฐแตแ.
แแแ แฅแจแฐแ แแแแ แต แซแแ แแปแปแซ แแแข แแตแแซแ แฃแแแแฅแแต. แ แแณแจแต แแแแจแต แแญแแซแตแฃ แฅแ แฅแแแ แแ แ แฐแแ แแ แแตแฅ แซแแ แจแ แแ แแ แ แแต แขแซแแต แ แแต แแแ แฅแแแแแแข แฒแ แซ getCurrentState(), แแแธแ แฅแแฝแแแ แ แฐแแ แแ แแตแฅ แซแแ แจแ แแ แแ แ แแต แฅแ แ แแ แฃแแ แจแจแแณ แแแแแฝ แแซแจแแก-

แญแ แจแแฌแ แฐแแ แฝแแญแ แญแแณแแก แ แแ แแฉ แแฌแแฝแ แ แแแแแแ แจแแฌแ แแฅแแต แแตแซแต แฅแแฝแแแ!
7.3 แจแฐแปแปแ แจแฐแแ แ แแแณแ แ แแฐแแ แญ แแญ
แจแตแแ แซ แแณแ แ src/client/state.js แแแฑแแ render lag แฅแ linear interpolation แญแ แแแแฃ แแ แแจแ
แ แแ แ แญแฐแแแข แฎแฑแ แ แแแต แญแแแฝ แฅแแจแแแแข แจแแแแชแซแ แญแธแแแก-
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 แแแ
แจแฅ แจแแตแ แตแซ แแแแจแตแ แแ แแ แฅแแแแแแ แแแญ แแ แ แ แแแแฉ แแญ แซแแแ แแ แแฝแ แ แแแ
แแฃ แแญแแซแฑแ แแปแปแซแแน แแฐ แฅแ แแแตแจแต แแ แซแ
แ แแ แฅแแฐแแฐแฐ แแแ
แ แแฝแแแข แ แญแแแจแก แจแแญแณแแ
แฅแ แแฅแแฑ แ แฃแ แแแซแญ แญแฝแแ!
แ แแ
แฝแแญ แแชแซ แแแแแต, แฅแ แแญแแซแณแ approximation แแ แแ แญแฝแแ: แฅแ แจแแแแชแซแ แแแ แแฒแซแแ แฅแแฐแฐแจแฐ แ แตแแตแ. แญแ
แฅแแแต แจแแแฃ แ แแ
แแ แจแ แแแแฉแ แแ แฅแแแ
แแ แญ! แจแ แแแแฉแ แจแแ แแ
แฐแ แฅแแจแแปแแแข 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:
แ แแแแญ.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 แฅแแฐแฐแแแแญแ แ แตแณแแต? แจแแ แจแแฅแแญ แ แแแแฎแฝแ แจแแแ แแแ แต แญแ แแแข แ แแแต แแแแถแฝ แฅแแ แแแธแแแแก-
- แฐแ แแ แจแฅแแ แจแฅแตแแต แแฌแแฝแ แ แซแต แฐแญ แฅแแฐแแ แแแแแฃแต แแญแ
- แ แตแณแฒแตแฒแญแต แ แแ แซแตแฐแแแ
dist/แจแแญแฑ แแแฃแณ แ แแ แแญแแปแฝแแ แ แจแตแแ แจแแฅแแญ แฅแแฝแแแแข
แแแ แ แตแแแ แฐแแฃแญ server.js แ แแแแฉแ แแแแญ แแแข แจแคแญแตแแจแต แ แแแแญ แแญ แจแแแแแแก-
แ แแแแญ.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:
แ แแแแญ.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 their socket.io socket (แแซ แจแฐแแกแฃ แจแแซ แญแแแฑ server.js). Socket.io แซแฑ แแฅแซแแณแแฑ แถแฌแต แแฉ แญแแตแฃแ idแตแแแ
แตแแแ
แแณแญ แแจแแ
แ แซแตแแแแแ. แฅแฐแแแแณแแแข แจแฐแซแแฝ แแณแแแซ.
แซแแ แ แ แฅแแฏแฝแ แญแแแฃ แ แ แแต แญแแ แแตแฅ แซแ แจแ แฅแแต แฐแแแแฎแฝแ แฅแแแญแแญ Game:
socketsแจแฐแซแแฝ แแณแแแซแแ แจแฐแซแแน แแญ แจแฐแแแแ แถแฌแต แแญ แจแแซแแแ แแแญ แแแข แ แแ แแ แแตแฅ แถแฌแถแฝแ แ แฐแซแแฝ แแณแแแซแธแ แฅแแตแแฐแญแต แซแตแฝแแแแขplayersแจแฐแซแแฝ แแณแแแซแแ แจแฎแฑ>แฐแซแแฝ แแแญ แแญ แจแแซแแแ แแ แแแข
bullets แจแแแฎแฝ แตแฅแตแฅ แแแข Bullet, แฅแฑแ แจแฐแแฐแ แ
แฐแ แฐแจแฐแ แจแแแ.
lastUpdateTime แจแแณแ แแแจแจแป แแ แจแฐแแแแ แต แจแแ แแ
แฐแ แแแข แฅแแดแต แฅแ
แ แแญ แฅแแฐแแแ แ แ
แญแก แฅแแแแจแณแแแข
shouldSendUpdate แจแณแต แฐแแแแญ แแ. แ แ แแแแแ แ แ
แญแก แฅแแแแจแณแแแข
แแดแแฝ addPlayer(), removePlayer() ะธ handleInput() แแฅแซแซแต แ แซแตแแแแ, แ แแตแฅ แฅแ
แ แแญ แญแแแ server.js. แจแแตแณแแต แฝแแณแแ แแฐแต แจแแแ แตแแฝ แจแ แฅแแ แญแแแฑแข
แจแแจแจแปแ แแตแแญ constructor() แญแแแซแ แจแแแ แแฐแต แจแแณแแฝ (แจ60 แแแแแฝ แตแแแแฝ แแญ)
game.js แญแแ 2
const Constants = require('../shared/constants');
const applyCollisions = require('./collisions');
class Game {
// ...
update() {
// Calculate time elapsed
const now = Date.now();
const dt = (now - this.lastUpdateTime) / 1000;
this.lastUpdateTime = now;
// Update each bullet
const bulletsToRemove = [];
this.bullets.forEach(bullet => {
if (bullet.update(dt)) {
// Destroy this bullet
bulletsToRemove.push(bullet);
}
});
this.bullets = this.bullets.filter(
bullet => !bulletsToRemove.includes(bullet),
);
// Update each player
Object.keys(this.sockets).forEach(playerID => {
const player = this.players[playerID];
const newBullet = player.update(dt);
if (newBullet) {
this.bullets.push(newBullet);
}
});
// Apply collisions, give players score for hitting bullets
const destroyedBullets = applyCollisions(
Object.values(this.players),
this.bullets,
);
destroyedBullets.forEach(b => {
if (this.players[b.parentID]) {
this.players[b.parentID].onDealtDamage();
}
});
this.bullets = this.bullets.filter(
bullet => !destroyedBullets.includes(bullet),
);
// Check if any players are dead
Object.keys(this.sockets).forEach(playerID => {
const socket = this.sockets[playerID];
const player = this.players[playerID];
if (player.hp <= 0) {
socket.emit(Constants.MSG_TYPES.GAME_OVER);
this.removePlayer(socket);
}
});
// Send a game update to each player every other time
if (this.shouldSendUpdate) {
const leaderboard = this.getLeaderboard();
Object.keys(this.sockets).forEach(playerID => {
const socket = this.sockets[playerID];
const player = this.players[playerID];
socket.emit(
Constants.MSG_TYPES.GAME_UPDATE,
this.createUpdate(player, leaderboard),
);
});
this.shouldSendUpdate = false;
} else {
this.shouldSendUpdate = true;
}
}
// ...
} แแด update() แแแแฃแต แ แฃแ แ แตแแแ แจแแแแ แจแ แแแแญ-แแ แ แแญแแฎ แญแแแข แจแแ แซแ แญแธแแแฃ แ แ
แฐแ แฐแจแฐแแก-
- แแแ แซแ
แ แแ แซแฐแแ
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:
แแแญ.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:
แฅแญแต.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:
แฐแซแแฝ.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:
แจแแณ.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.
แจแฅแ แจแแญแต แแแแซ แ แฐแแฃแ แญ แแ แญแแตแแแข
แแญแถแฝ.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 แจแแณ แญแแแก!
แแแ แจแแแ แฎแต แญแแต แแแญ แฅแ แจแฐแแ แ แแแข .
แแแญ: hab.com
