2015幎ã«ãªãªãŒã¹ããã
Agar.ioã¯ã
ã²ãŒã .ioã®æ°ãããžã£ã³ã«ã®å
é§è
ãšãªãããã®äººæ°ã¯ãã®åŸå€§ããæé·ããŸããã ç§ãçµéšãã.ioã²ãŒã ã®äººæ°ã®é«ãŸãïŒéå»3幎é
ã§ããã®ãžã£ã³ã«ã®2ã€ã®ã²ãŒã ãäœæããŠè²©å£²ããŸããã ã
ãã®ãããªã²ãŒã ãèããããšããªãå ŽåïŒãããã¯ãåå ããããç¡æã®ãã«ããã¬ã€ã€ãŒWebã²ãŒã ã§ãïŒã¢ã«ãŠã³ãã¯äžèŠã§ãïŒã éåžžã圌ãã¯åãã¢ãªãŒãã§å€ãã®æµãã¬ã€ã€ãŒã«ç«ã¡åãããŸãã .ioãžã£ã³ã«ã®ä»ã®æåãªã²ãŒã ã¯ã
Slither.ioãš
Diep.ioã§ãããã®æçš¿ã§ã¯
ã.ioã²ãŒã ããŒãããäœæããæ¹æ³
ãç解
ããŸã ã ãã®ããã«ã¯ãJavascriptã®ç¥èã ãã§ååã§ã
ãES6æ§æã
this
Promisesãªã©ãç解ããå¿
èŠããããŸãã Javascriptãå®å
šã«ç¥ã£ãŠããªããŠããã»ãšãã©ã®æçš¿ãç解ã§ããŸãã
ã²ãŒã äŸ.io
åŠç¿ãæ¯æŽããããã«
ããµã³ãã«ã®.ioã²ãŒã ãåç
§ããŸãã ãã¬ã€ããŠã¿ãŠãã ããïŒ
ã²ãŒã ã¯éåžžã«ç°¡åã§ããä»ã®ãã¬ã€ã€ãŒãããã¢ãªãŒãã§è¹ãæäœããŸãã ããªãã®è¹ã¯èªåçã«ç ²åŒŸãçºå°ããä»ã®ãã¬ã€ã€ãŒãæ»æããããšããªããã圌ãã®ç ²åŒŸãé¿ããŸãã
1.æŠèŠ/ãããžã§ã¯ãæ§é
ãµã³ãã«ã²ãŒã ã®ãœãŒã¹ã³ãŒããããŠã³ããŒãããŠããã©ããŒããŠãã ããã
ãã®äŸã§ã¯æ¬¡ã䜿çšããŸãã
- Expressã¯ãã²ãŒã ã®WebãµãŒããŒã管çããNode.jsçšã®æã人æ°ã®ããWebãã¬ãŒã ã¯ãŒã¯ã§ãã
- socket.ioã¯ããã©ãŠã¶ãšãµãŒããŒéã§ããŒã¿ã亀æããããã®websocketã©ã€ãã©ãªã§ãã
- Webpackã¯ã¢ãžã¥ãŒã«ãããŒãžã£ãŒã§ãã ãã㧠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ã¢ãžã¥ãŒã«ãããŒãžã£ãŒã䜿çšããŠãããžã§ã¯ãããã«ãããŸãã Webpackã®æ§æãèŠãŠã¿ãŸãããã
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ãèµ·åããã€ã³ããŒããããä»ã®ãã¡ã€ã«ãååž°çã«æ€çŽ¢ããŸãã- Webpackã¢ã»ã³ããªã®åºåJSã¯
dist/
ãã£ã¬ã¯ããªã«ãããŸãã ãã®ãã¡ã€ã«ãJSããã±ãŒãžãšåŒã³ãŸã ã - Babel ãç¹ã«@ babel / preset -envæ§æã䜿çšããŠãå€ããã©ãŠã¶ãŒçšã«JSã³ãŒããå€æããŸãã
- ãã©ã°ã€ã³ã䜿çšããŠãJSãã¡ã€ã«ã«ãã£ãŠåç
§ããããã¹ãŠã®CSSãååŸãããããã1ã€ã®å Žæã«çµåããŸãã CSSããã±ãŒãžãšåŒã³ãŸã ã
å¥åŠãªããã±ãŒãžãã¡ã€ã«å
'[name].[contenthash].ext'
æ°ä»ãããããããŸããã ãããã«ã¯ãWebpack
ãã¡ã€ã«ã®ååã®
眮æãå«ãŸã
ãŸã ã
[name]
ã¯å
¥åãã€ã³ãã®ååã«çœ®ãæãããïŒãã®å Žåãããã¯
game
ïŒã
[contenthash]
ã¯ãã¡ã€ã«ã³ã³ãã³ãã®ããã·ã¥ã«çœ®ãæããããŸãã
ãããžã§ã¯ããããã·ã¥çšã«
æé©åããããã«ãããè¡ããŸã-
ããã±ãŒãžãå€æŽããããšãã¡ã€ã«åãå€æŽãããïŒ
contenthash
ãå€æŽãããïŒãããJSããã±ãŒãžãç¡æéã«ãã£ãã·ã¥ãããããã©ãŠã¶ã«æ瀺ã§ããŸãã æçµçãªçµæã¯ã
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.dev.js
ã䜿çšãã
webpack.dev.js
ã«åãæ¿ããŠã
webpack.prod.js
å±éãããšãã«ããã±ãŒãžãµã€ãºãæé©åããŸãã
ããŒã«ã«èšå®
ãã®æçš¿ã«èšèŒãããŠããæé ãå®è¡ã§ããããã«ãããŒã«ã«ãã·ã³ã«ãããžã§ã¯ããã€ã³ã¹ããŒã«ããããšããå§ãããŸãã ã»ããã¢ããã¯ç°¡åã§ãããŸãã
ããŒããš
NPMãã·ã¹ãã ã«ã€ã³ã¹ããŒã«ããå¿
èŠããããŸãã 次ã«ããå¿
èŠããããŸã
$ git clone https:
ããã§æºåå®äºã§ãïŒ éçºãµãŒããŒãèµ·åããã«ã¯ãåã«å®è¡ããŸã
$ npm run develop
Webãã©ãŠã¶ã§
localhostïŒ3000ã«ã¢ã¯ã»ã¹ããŸãã éçºãµãŒããŒã¯ãã³ãŒããå€æŽããããã»ã¹ã§JSããã³CSSããã±ãŒãžãèªåçã«åæ§ç¯ããŸãããã¹ãŠã®å€æŽã確èªããã«ã¯ãããŒãžãæŽæ°ããã ãã§ãïŒ
3.顧客ã®ãšã³ããªãã€ã³ã
ã²ãŒã ã®ã³ãŒãèªäœã«åãããããŸãããã ãŸãã
index.html
ããŒãžãå¿
èŠã§ãããµã€ãã«ã¢ã¯ã»ã¹ãããšããã©ãŠã¶ãŒãæåã«ããŒãžãèªã¿èŸŒã¿ãŸãã ç§ãã¡ã®ããŒãžã¯éåžžã«ã·ã³ãã«ã§ãïŒ
index.html
<ïŒDOCTYPE html>
<html>
<head>
<title> .ioã²ãŒã ã®äŸ</ title>
<link type = "text / css" rel = "stylesheet" href = "/ game.bundle.css">
</ head>
<æ¬äœ>
<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">ãã¬ã€</ button>
</ div>
</ body>
</ html>
ãã®ã³ãŒãäŸã¯ãããããããããããã«è¥å¹²ç°¡ç¥åãããŠããŸããä»ã®å€ãã®æçš¿äŸã§ãåãããšãããŸãã å®å
šãªã³ãŒãã¯ãåžžã«Githubã§è¡šç€ºã§ããŸããç§ãã¡ãæã£ãŠããŸãïŒ
- HTML5 CanvasèŠçŽ ïŒ
<canvas>
ïŒãããã䜿çšããŠã²ãŒã ãã¬ã³ããªã³ã°ããŸãã <link>
ã䜿çšããŠãCSSããã±ãŒãžãè¿œå ããŸãã<script>
ã䜿çšããŠãJavascriptããã±ãŒãžãè¿œå ããŸãã- ãŠãŒã¶ãŒå
<input>
ããã³ "PLAY"ïŒ <button>
ïŒ <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 = () => {
ããã¯è€éã«æãããããããŸããããå®éã«ã¯ããã§ã¯å€ãã®ã¢ã¯ã·ã§ã³ãçºçããŠããŸããã
- ä»ã®ããã€ãã®JSãã¡ã€ã«ãã€ã³ããŒãããŸãã
- CSSãã€ã³ããŒãããŸãïŒWebpackãCSSããã±ãŒãžã«ããããå«ããããšãèªèããŸãïŒã
connect()
ãå®è¡ããŠãµãŒããŒãžã®æ¥ç¶ã確ç«ãã downloadAssets()
ãå®è¡ããŠã²ãŒã ã®ã¬ã³ããªã³ã°ã«å¿
èŠãªç»åãããŠã³ããŒãããŸãã- æé 3ãå®äºãããšãã¡ã€ã³ã¡ãã¥ãŒïŒ
playMenu
ïŒãplayMenu
ã - PLAYãã¿ã³ãæŒãããã®ãã³ãã©ãŒãæ§æããŸãã ãã¿ã³ãæŒããããšãã³ãŒãã¯ã²ãŒã ãåæåãããã¬ã€ããæºåãã§ããããšããµãŒããŒã«äŒããŸãã
ã¯ã©ã€ã¢ã³ããµãŒããŒããžãã¯ã®äž»ãªãèãã¯ã
index.js
ã«ãã£ãŠã€ã³ããŒãããããã¡ã€ã«ã«ãããŸãã 次ã«ããã¹ãŠãé çªã«æ€èšããŸãã
4.顧客ããŒã¿ã®äº€æ
ãã®ã²ãŒã ã§ã¯ãããç¥ãããŠãã
socket.ioã©ã€ãã©ãªã䜿çšããŠãµãŒããŒãšéä¿¡ããŸãã Socket.ioã«ã¯ãåæ¹åéä¿¡ã«é©ãã
WebSocketã®çµã¿èŸŒã¿ãµããŒãããããŸãããµãŒããŒã«ã¡ãã»ãŒãžãéä¿¡ã§ãããµãŒããŒã¯åãæ¥ç¶ãä»ããŠã¡ãã»ãŒãžãéä¿¡ã§ããŸãã
ãµãŒããŒãšã®
ãã¹ãŠã®éä¿¡ãåŠç
ãã src/client/networking.js
ãã¡ã€ã«ã1ã€ãããŸãã
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(() => {
ãã®ã³ãŒãã¯ãããããããããããã«ãããã«åæžãããŠããŸãããã®ãã¡ã€ã«ã«ã¯3ã€ã®äž»èŠãªã¢ã¯ã·ã§ã³ããããŸãã
- ãµãŒããŒã«æ¥ç¶ããããšããŠããŸãã
connectedPromise
ã¯ãæ¥ç¶ã確ç«ãããšãã«ã®ã¿èš±å¯ãããŸãã - æ¥ç¶ãæ£åžžã«ç¢ºç«ãããå ŽåããµãŒããŒããåä¿¡ã§ããã¡ãã»ãŒãžã®ã³ãŒã«ããã¯é¢æ°ïŒ
processGameUpdate()
ããã³onGameOver()
ïŒãç»é²ããŸãã play()
ããã³updateDirection()
ããšã¯ã¹ããŒãããŠãä»ã®ãã¡ã€ã«ã§äœ¿çšã§ããããã«ããŸãã
5.ã¯ã©ã€ã¢ã³ãã¬ã³ããªã³ã°
ç»é¢ã«ç»åã衚瀺ãããšããæ¥ãŸããïŒ
...ãããããããè¡ãåã«ãããã«å¿
èŠãªãã¹ãŠã®ã€ã¡ãŒãžïŒãªãœãŒã¹ïŒãããŠã³ããŒãããå¿
èŠããããŸãã ãªãœãŒã¹ãããŒãžã£ãŒãäœæããŸãããã
asset.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
ãæå¹ã«ã
downloadPromise
ã
ãªãœãŒã¹ãããŠã³ããŒãããããã¬ã³ããªã³ã°ãéå§ã§ããŸãã åè¿°ããããã«ã
HTML5 Canvas ïŒ
<canvas>
ïŒã䜿çšããŠWebããŒãžã«æç»ããŸãã ç§ãã¡ã®ã²ãŒã ã¯éåžžã«ã·ã³ãã«ãªã®ã§ã以äžãæãã ãã§ãã
- èæ¯
- ãã¬ã€ã€ãŒã·ãã
- ã²ãŒã å
ã®ä»ã®ãã¬ã€ã€ãŒ
- è²
äžèšã®4ã€ã®ãã€ã³ããæ£ç¢ºã«ã¬ã³ããªã³ã°ããéèŠãª
src/client/render.js
ã
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;
ãã®ã³ãŒãã¯ãããããããããããã«ççž®ãããŠããŸããrender()
ã¯ãã®ãã¡ã€ã«ã®äž»ãªæ©èœã§ãã
startRendering()
ããã³
stopRendering()
ã¯ã60 FPSã§ã®ã¬ã³ããªã³ã°ãµã€ã¯ã«ã®ã¢ã¯ãã£ãåãå¶åŸ¡ããŸãã
åå¥ã®è£å©ã¬ã³ããªã³ã°é¢æ°ïŒ
renderBullet()
ïŒã®ç¹å®ã®å®è£
ã¯ããã»ã©éèŠã§ã¯ãããŸããããããã«1ã€ã®ç°¡åãªäŸã瀺ããŸãã
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, ); }
以åã«
asset.js
èŠããã
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()
åŒã³åºãã€ãã³ããªã¹ããŒã§ãã
updateDirection()
ã¯å
¥åã€ãã³ããåŠçããããã«å¿ããŠã²ãŒã ã®ç¶æ
ãæŽæ°ãããµãŒããŒãšã®ã¡ãã»ãŒãžã³ã°ã«é¢äžããŠããŸãã
7.顧客ã®ç¶æ
ãã®ã»ã¯ã·ã§ã³ã¯ãæçš¿ã®æåã®éšåã§æãé£ããã§ãã ããªããæåã®èªæžãããããç解ããŠããªããªããèœèããªãã§ãã ããïŒ ã¹ãããããŠãåŸã§æ»ãããšãã§ããŸãã
ã¯ã©ã€ã¢ã³ããšãµãŒããŒã®ã³ãŒããå®æãããããã«å¿
èŠãªããºã«ã®æåŸã®ããŒã¹ã¯
stateã§ãã ã¯ã©ã€ã¢ã³ãã¬ã³ããªã³ã°ã»ã¯ã·ã§ã³ã®ã³ãŒãã¹ãããããèŠããŠããŸããïŒ
render.js
import { getCurrentState } from './state'; function render() { const { me, others, bullets } = getCurrentState();
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 } ] }
åã²ãŒã ã®æŽæ°ã«ã¯ã5ã€ã®åäžã®ãã£ãŒã«ããå«ãŸããŸãã
- t ïŒãã®æŽæ°ãäœæãããæå»ã®ãµãŒããŒã®ã¿ã€ã ã¹ã¿ã³ãã
- me ïŒãã®ã¢ããããŒããåãåã£ããã¬ã€ã€ãŒã«é¢ããæ
å ±ã
- ãã®ä» ïŒåãã²ãŒã ã«åå ããŠããä»ã®ãã¬ã€ã€ãŒã«é¢ããæ
å ±ã®é
åã
- bullets ïŒã²ãŒã å
ã®ã·ã§ã«ã«é¢ããæ
å ±ã®é
åã
- ãªãŒããŒããŒã ïŒçŸåšã®ãªãŒããŒããŒãããŒã¿ã ãã®æçš¿ã§ã¯ãããããèæ
®ããŸããã
7.1ã¯ã©ã€ã¢ã³ãã®çŽ æŽãªç¶æ
getCurrentState()
ã®åçŽãªå®è£
ã¯ãåä¿¡ããææ°ã®ã²ãŒã ã¢ããããŒãããã®ããŒã¿ã®ã¿ãçŽæ¥è¿ãããšãã§ããŸãã
naive-state.js
let lastGameUpdate = null;
çŸããã¯ãªã¢ïŒ ãããããã¹ãŠããšãŠãç°¡åã ã£ãå Žåã ãã®å®è£
ã«åé¡ãããçç±ã®1ã€ã¯ã
ã¬ã³ããªã³ã°ã®ãã¬ãŒã ã¬ãŒãããµãŒããŒã¯ããã¯ã®åšæ³¢æ°ã«å¶éããããšã§ãã
ãã¬ãŒã ã¬ãŒã ïŒãã¬ãŒã æ°ïŒã€ãŸãã1ç§ãããã®render()
åŒã³åºãããŸãã¯FPSïŒã ã²ãŒã ã¯éåžžãå°ãªããšã60 FPSã«éããåŸåããããŸãã
ãã£ãã¯ã¬ãŒã ïŒãµãŒããŒãã¯ã©ã€ã¢ã³ãã«ã²ãŒã ã®æŽæ°ãéä¿¡ããé »åºŠã å€ãã®å Žåããã¬ãŒã ã¬ãŒããããäœããªããŸãã ãã®ã²ãŒã ã§ã¯ããµãŒããŒã¯1ç§ããã30ãµã€ã¯ã«ã®é »åºŠã§å®è¡ãããŸãã
ã²ãŒã ã®ææ°ã®æŽæ°ãã¬ã³ããªã³ã°ããã ãã®å ŽåãFPSã¯å®éã«ã¯30ãè¶
ããããšã¯ã§ããŸããã
ãµãŒããŒããæ¯ç§30ãè¶
ããæŽæ°ãåãåãããš
ã¯ãªãããã§ãã 1ç§éã«60å
render()
ãåŒã³åºããŠãããããã®åŒã³åºãã®ååã¯åçŽã«åããã®ãåæç»ããæ¬è³ªçã«ã¯äœãããŸããã å¥ã®çŽ æŽãªå®è£
åé¡ã¯ããã
é
ããåŸåããããšããããšã§ãã çæ³çãªã€ã³ã¿ãŒãããé床ã§ã¯ãã¯ã©ã€ã¢ã³ãã¯æ£ç¢ºã«33 msïŒ1ç§ããã30ïŒããšã«ã²ãŒã ã®æŽæ°ãåãåããŸãã
æ®å¿µãªãããå®ç§ãªãã®ã¯ãããŸããã ããçŸå®çãªç»åã¯æ¬¡ã®ãšããã§ãã
åçŽãªå®è£
ã¯ãé
延ã«é¢ããŠã¯ã»ãšãã©ææªã®ã±ãŒã¹ã§ãã ã²ãŒã ã®æŽæ°ã50ããªç§ã®é
延ã§åä¿¡ããããšã
ã¯ã©ã€ã¢ã³ãã¯ä»¥åã®æŽæ°ããã®ã²ãŒã ã®ç¶æ
ãã¬ã³ããªã³ã°ãããããããã«50ããªç§
é
ããªããŸã ã ãã¬ã€ã€ãŒã«ãšã£ãŠã©ãã»ã©äžäŸ¿ãªããšãæ³åã§ããŸããbrakingæçãªãã¬ãŒãã³ã°ã®ãããã²ãŒã ã¯äžå®å®ã§äžå®å®ã«èŠããŸãã
7.2顧客ã¹ããŒã¿ã¹ã®æ¹å
åçŽãªå®è£
ã«ããã€ãã®æ¹åãå ããŸãã ãŸãã100 msã®
ã¬ã³ããªã³ã°é
延ã䜿çš
ããŸã ã ããã¯ãã¯ã©ã€ã¢ã³ãã®ãçŸåšã®ãç¶æ
ãåžžã«ãµãŒããŒäžã®ã²ãŒã ã®ç¶æ
ãã100ããªç§é
ããããšãæå³ããŸãã ããšãã°ããµãŒããŒã®æå»ã
150ã®å ŽåããµãŒããŒãæå»
50ã«ãã£ãç¶æ
ãã¯ã©ã€ã¢ã³ãã«è¡šç€ºãããŸãã
ããã«ããã100ããªç§ã®ãããã¡ãŒãæäŸãããã²ãŒã ã®æŽæ°ãåä¿¡ããäºæž¬äžå¯èœãªæéãä¹ãåãããšãã§ããŸãã
ããã«å¯Ÿããæ¯æãã¯
ã 100ããªç§ã®äžå®ã®
å
¥åé
延ïŒå
¥åé
延ïŒã§ãã ããã¯ã¹ã ãŒãºãªã²ãŒã ãã¬ã€ã®ããã®å°ããªç ç²ã§ã-ã»ãšãã©ã®ãã¬ã€ã€ãŒïŒç¹ã«ã«ãžã¥ã¢ã«ãªãã¬ã€ã€ãŒïŒã¯ãã®é
延ã«æ°ä»ããªãã§ãããã äºæž¬ã§ããªãé
延ã§éã¶ãããã100ããªç§ã®äžå®ã®é
延ã«é©å¿ããæ¹ãã¯ããã«ç°¡åã§ãã
ãã¯ã©ã€ã¢ã³ãåŽã®äºæž¬ããšåŒã°ããå¥ã®ææ³ã䜿çšããããšãã§ããŸããããã¯ãç¥èŠãããé
延ãæžããã®ã«è¯ãä»äºãããŸããããã®èšäºã§ã¯èæ
®ããŸããã
ç§ãã¡ã䜿çšããå¥ã®æ¹åç¹ã¯ã
ç·åœ¢è£éã§ãã ã¬ã³ããªã³ã°ã®é
延ã«ãããéåžžãå°ãªããšã1åã®æŽæ°ã§ã¯ã©ã€ã¢ã³ãã®çŸåšã®æéãè¿œãè¶ããŸãã
getCurrentState()
ãåŒã³åºããããšãã¯ã©ã€ã¢ã³ãã®çŸåšã®æå»ã®çŽåãšçŽåŸã«ã²ãŒã æŽæ°éã®
ç·åœ¢è£éãå®è¡ã§ããŸãã
ããã«ããããã¬ãŒã ã¬ãŒãã®åé¡ã解決ããŸããå¿
èŠãªä»»æã®åšæ³¢æ°ã§äžæã®ãã¬ãŒã ãã¬ã³ããªã³ã°ã§ããããã«ãªããŸããã
7.3æ¡åŒµã¯ã©ã€ã¢ã³ãã¹ããŒã¿ã¹ã®å®è£
src/client/state.js
å®è£
äŸã§ã¯ãã¬ã³ããªã³ã°é
延ãšç·åœ¢è£éã®äž¡æ¹ã䜿çšã
src/client/state.js
ããããã¯é·ãã¯
src/client/state.js
ãŸããã ã³ãŒãã2ã€ã®éšåã«åããŸãããã ãããæåã®ãã®ã§ãïŒ
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);
æåã®ã¹ãããã¯ã
currentServerTime()
ãäœãããããç解ããããšã§ãã åã«èŠãããã«ããµãŒããŒã®ã¿ã€ã ã¹ã¿ã³ãã¯ãã¹ãŠã®ã²ãŒã æŽæ°ã«å«ãŸããŸãã ã¬ã³ããªã³ã°é
延ã䜿çšããŠããµãŒããŒã®100ããªç§é
ããŠç»åãã¬ã³ããªã³ã°ããŸãã
ããµãŒããŒäžã®çŸåšã®æå»ãç¥ãããšã¯ã§ããŸãããããã¯ãæŽæ°ãã©ããããã®
æéã§è¡ããããããããªãããã§ãã ã€ã³ã¿ãŒãããã¯äºæž¬äžå¯èœã§ããããã®é床ã¯å€§ããç°ãªãå¯èœæ§ããããŸãïŒ
ãã®åé¡ãåé¿ããããã«ãåççãªè¿äŒŒã䜿çšã§ããŸãã
æåã®æŽæ°ãå³åº§ã«å°çãããšä»®å®ããŸãã ãããåœãŠã¯ãŸãå Žåããã®ç¹å®ã®ç¬éã®ãµãŒããŒæéãç¥ãããšãã§ããŸãïŒ ãµãŒããŒã®ã¿ã€ã ã¹ã¿ã³ãã
firstServerTimestamp
ã«ä¿åããåæã«
ããŒã«ã« ïŒã¯ã©ã€ã¢ã³ãïŒã¿ã€ã ã¹ã¿ã³ãã
firstServerTimestamp
ã«ä¿åããŸãã
ã¡ãã£ãšåŸ
ã£ãŠ
ãµãŒããŒã®æé=ã¯ã©ã€ã¢ã³ãã®æéãããã¹ãã§ã¯ãªãã§ããããïŒ ããµãŒããŒã¿ã€ã ã¹ã¿ã³ãããšãã¯ã©ã€ã¢ã³ãã¿ã€ã ã¹ã¿ã³ãããåºå¥ããçç± ããã¯çŽ æŽããã質åã§ãïŒ ããã¯åããã®ã§ã¯ãªãããšãããããŸããã
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();
3ã€ã®ã±ãŒã¹ãåŠçããŸããbase < 0
ã¯ãçŸåšã®ã¬ã³ããªã³ã°æéã®æŽæ°ããªãããšãæå³ããŸãïŒäžèšã®å®è£
ãåç
§getBaseUpdate()
ïŒãããã¯ãã¬ã³ããªã³ã°ã®é
延ã«ãããã²ãŒã ã®éå§æã«çºçããå¯èœæ§ããããŸãããã®å Žåãææ°ã®æŽæ°ã䜿çšããŸããbase
ããã¯ææ°ã®ã¢ããããŒãã§ããããã¯ããããã¯ãŒã¯é
延ãŸãã¯ã€ã³ã¿ãŒãããæ¥ç¶ã®äœäžãåå ã§ããå¯èœæ§ããããŸãããã®å Žåãææ°ã®æŽæ°ã䜿çšããŸãã- çŸåšã®ã¬ã³ããªã³ã°æéã®ååŸã«æŽæ°ããããããè£éã§ããŸãïŒ
æ®ã£ãŠstate.js
ããã®ã¯ãåçŽãªïŒãããéå±ãªïŒæ°åŠã§ããç·åœ¢è£éã®å®è£
ã§ããèªåã§åŠç¿ãããå Žåã¯state.js
ãGithubã§éããŠãã ãããããŒã2.ããã¯ãšã³ããµãŒããŒ
ãã®ããŒãã§ã¯ã.ioã²ãŒã ã®äŸãå®è¡ããNode.jsããã¯ãšã³ããèŠãŠãããŸãã1.ãµãŒããŒãšã³ããªãã€ã³ã
WebãµãŒããŒãå¶åŸ¡ããããã«ãExpressãšåŒã°ããNode.jsçšã®äžè¬çãªWebãã¬ãŒã ã¯ãŒã¯ã䜿çšããŸãããµãŒããŒã®ãšã³ããªãã€ã³ããã¡ã€ã«ã«ãã£ãŠæ§æãããŸã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');
æåã«Webpackã«ã€ããŠèª¬æããããšãèŠããŠããŸããïŒããã§Webpackæ§æã䜿çšããŸãããããã2ã€ã®æ¹æ³ã§é©çšããŸãã- webpack-dev-middlewareã䜿çšããŠãéçºããã±ãŒãžãèªåçã«åæ§ç¯ãããããŸãã¯
dist/
æ¬çªã®ã¢ã»ã³ããªåŸã«Webpackããã¡ã€ã«ãæžã蟌ããã©ã«ããŒãéçã«è»¢éããŸãã
å¥ã®éèŠãªã¿ã¹ã¯server.js
ã¯ãåã«ExpressãµãŒããŒã«æ¥ç¶ããsocket.ioãµãŒããŒãæ§æããããšã§ããserver.jsãããŒã2
const socketio = require('socket.io'); const Constants = require('../shared/constants');
ãµãŒããŒãšã®socket.ioæ¥ç¶ãæ£åžžã«ç¢ºç«ããåŸãæ°ãããœã±ããã®ã€ãã³ããã³ãã©ãŒãæ§æããŸããã€ãã³ããã³ãã©ã¯ãã·ã³ã°ã«ãã³ãªããžã§ã¯ããžã®å§ä»»ã«ãã£ãŠã¯ã©ã€ã¢ã³ãããåä¿¡ããã¡ãã»ãŒãžãåŠçããŸãgame
ãserver.jsãããŒã3
const Game = require('./game');
.ioãžã£ã³ã«ã®ã²ãŒã ãäœæããããã1ã€ã®ã€ã³ã¹ã¿ã³ã¹Game
ïŒãã²ãŒã ãïŒã®ã¿ãå¿
èŠã§ãããã¹ãŠã®ãã¬ã€ã€ãŒãåãã¢ãªãŒãã§ãã¬ã€ããŸãïŒæ¬¡ã®ã»ã¯ã·ã§ã³ã§ã¯ããã®ã¯ã©ã¹ãã©ã®ããã«æ©èœããããèŠãŠãããŸãGame
ã2.ã²ãŒã ãµãŒããŒ
ãã®ã¯ã©ã¹ã«Game
ã¯ãæãéèŠãªãµãŒããŒåŽã®ããžãã¯ãå«ãŸããŠããŸãããã¬ã€ã€ãŒç®¡çãšã²ãŒã ã·ãã¥ã¬ãŒã·ã§ã³ã® 2ã€ã®äž»ãªã¿ã¹ã¯ããããŸããæåã®ã¿ã¹ã¯-ãã¬ãŒã€ãŒç®¡çããå§ããŸãããã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;
ãã®ã²ãŒã ã§ã¯id
ããœã±ããsocket.ioã®ãã£ãŒã«ãã§ãã¬ãŒã€ãŒãèå¥ããŸãïŒæ··ä¹±ããŠããå Žåã¯ãã«æ»ããŸãserver.js
ïŒãSocket.ioèªäœãåãœã±ããã«äžæã®ãœã±ãããå²ãåœãŠãid
ãããããã«ã€ããŠå¿é
ããå¿
èŠã¯ãããŸããã圌ã®ãã¬ã€ã€ãŒIDã«é»è©±ããŸããããã念é ã«çœ®ããŠãã¯ã©ã¹ã®ã€ã³ã¹ã¿ã³ã¹å€æ°ã調ã¹ãŠã¿ãŸãããGame
ãsockets
ãã¬ãŒã€ãŒã«é¢é£ä»ããããŠãããœã±ããã«ãã¬ãŒã€ãŒIDããã€ã³ããããªããžã§ã¯ãã§ããããã«ãããäžå®æéããã¬ãŒã€ãŒIDã䜿çšããŠãœã±ããã«ã¢ã¯ã»ã¹ã§ããŸããplayers
ãã¬ãŒã€ãŒIDãã³ãŒãã«ãã€ã³ããããªããžã§ã¯ã>ãã¬ãŒã€ãŒãªããžã§ã¯ã
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()
ã¯ããããããµãŒããŒåŽã®ããžãã¯ã®æãéèŠãªéšåãå«ãŸããŠããŸããé çªã«ã圌ãè¡ããã¹ãŠããªã¹ãããŸãã- ,
dt
update()
. - . . ,
bullet.update()
true
, ( ). - . â
player.update()
Bullet
. applyCollisions()
, , . , ( player.onDealtDamage()
), bullets
.- .
update()
. shouldSendUpdate
. update()
60 /, 30 /. , 30 / ( ).
? . 30 â !
ãªããupdate()
æ¯ç§30åé»è©±ããªãã®ã§ããïŒã²ãŒã ã®ã·ãã¥ã¬ãŒã·ã§ã³ãæ¹åãããããããé »ç¹ã«åŒã³åºãããupdate()
ã»ã©ãã²ãŒã ã®ã·ãã¥ã¬ãŒã·ã§ã³ã¯ããæ£ç¢ºã«ãªããŸãããã ããåŒã³åºãåæ°ã«å€¢äžupdate()
ã«ãªããããªãã§ãã ãããããã¯èšç®ã³ã¹ãã®é«ãã¿ã¹ã¯ã§ããããã1ç§ããã60ã§ååã§ãã
ã¯ã©ã¹ã®æ®ãã¯ãGame
以äžã§äœ¿çšããããã«ããŒã¡ãœããã§æ§æããupdate()
ãŸããgame.jsãããŒã3
class Game {
getLeaderboard()
éåžžã«ç°¡åã§ã-ãã€ã³ãæ°ã§ãã¬ãŒã€ãŒããœãŒããããã¹ã5ãååŸããŠãåãŠãŒã¶ãŒåãšã¹ã³ã¢ãè¿ããŸãããã¬ãŒã€ãŒã«æž¡ãããã²ãŒã ã®æŽæ°ãäœæããããã«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; }
å®è£
ã¯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; }
ãã¬ãŒã€ãŒã¯ã·ã§ã«ãããè€éãªã®ã§ããã®ã¯ã©ã¹ã«ã¯ããã«ããã€ãã®ãã£ãŒã«ããæ ŒçŽããå¿
èŠããããŸãã圌ã®ã¡ãœããupdate()
ã¯å€ãã®äœæ¥ãè¡ããç¹ã«ãæ°ããäœæãããã·ã§ã«ãæ®ã£ãŠããªãå Žåã¯ãããè¿ããŸãfireCooldown
ïŒããã«ã€ããŠã¯åã®ã»ã¯ã·ã§ã³ã§èª¬æããŸãããïŒïŒããŸãserializeForUpdate()
ãã²ãŒã ã®æŽæ°ã«ãã¬ãŒã€ãŒã®è¿œå ãã£ãŒã«ããå«ããå¿
èŠããããããã¡ãœãããæ¡åŒµããŸããåºæ¬ã¯ã©ã¹ãæã€ããšObject
ã¯ãã³ãŒãã®åçŸæ§ãé¿ããããã®éèŠãªã¹ãããã§ããããšãã°ãã¯ã©ã¹ããªãå ŽåãObject
ãã¹ãŠã®ã²ãŒã ãªããžã§ã¯ãã®å®è£
ã¯åãã§ããå¿
èŠããããdistanceTo()
ãããã®ãã¹ãŠã®å®è£
ãè€æ°ã®ãã¡ã€ã«ã§ã³ããŒã¢ã³ãããŒã¹ãããã®ã¯æªå€¢ã§ããããã¯ãæ¡åŒµObject
ã¯ã©ã¹ã®æ°ãå¢ãããšã倧èŠæš¡ãããžã§ã¯ãã§ç¹ã«éèŠã«ãªããŸãã4.çŽäºã®èªè
ç§ãã¡ã«æ®ãããå¯äžã®ããšã¯ãç ²åŒŸããã¬ã€ã€ãŒã«åœäžããããšãèªèããããšã§ãïŒupdate()
ã¯ã©ã¹ã®ã¡ãœããã®æ¬¡ã®ã³ãŒããæãåºããŠãã ããGame
ïŒgame.js
const applyCollisions = require('./collisions'); class Game {
applyCollisions()
ãã¬ãŒã€ãŒã«ããããããã¹ãŠã®ã·ã§ã«ãè¿ãã¡ãœãããå®è£
ããå¿
èŠããããŸãã幞ããªããšã«ãããã¯ããã»ã©é£ãããããŸããããªããªãã- è¡çªãããªããžã§ã¯ãã¯ãã¹ãŠåã§ãããããã¯è¡çªèªèãå®è£
ããæãåçŽãªå³ã§ãã
distanceTo()
åã®ã»ã¯ã·ã§ã³ã®ã¯ã©ã¹ã§å®è£
ããã¡ãœããããã§ã«ããObject
ãŸãã
è¡çªèªèã®å®è£
ã¯æ¬¡ã®ããã«ãªããŸããcollisions.js
const Constants = require('../shared/constants');
ãã®åçŽãªè¡çªèªèã¯ãäžå¿éã®è·é¢ãååŸã®åèšãããå°ããå Žåã«2ã€ã®åãè¡çªãããšããäºå®ã«åºã¥ããŠããŸãã2ã€ã®åã®äžå¿éã®è·é¢ããããã®ååŸã®åèšã«æ£ç¢ºã«çããå Žåã¯æ¬¡ã®ãšããã§ããããã§ã¯ãããã€ãã®åŽé¢ãæ
éã«æ€èšããå¿
èŠããããŸãã- çºå°ç©ã¯ããããäœæãããã¬ã€ã€ãŒã«èœã¡ãŠã¯ãããŸãããããã¯ãæ¯èŒããããšã«ãã£ãŠéæããããšãã§ãã
bullet.parentID
ãšplayer.id
ã - çºå°äœã¯ãè€æ°ã®ãã¬ã€ã€ãŒãåæã«è¡çªãã極端ãªå Žåã«äžåºŠã ããããããå¿
èŠããããŸãããªãã¬ãŒã¿ãŒã®å©ããåããŠãã®åé¡ã解決
break
ããŸããçºå°äœãšè¡çªãããã¬ã€ã€ãŒãèŠã€ãããšããã«ãæ€çŽ¢ãåæ¢ããŠæ¬¡ã®çºå°äœã«é²ã¿ãŸãã
çµãã
以äžã§ãïŒ
.io Webã²ãŒã ãäœæããããã«ç¥ã£ãŠããå¿
èŠã®ãããã¹ãŠã網çŸ
ããŸããã 次ã¯ïŒ
ç¬èªã®.ioã²ãŒã ããã«ãããŠãã ããïŒãµã³ãã«ã³ãŒãã¯ãã¹ãŠãªãŒãã³ãœãŒã¹ã§ãããGithubã«æçš¿ãããŠããŸãã