├── .gitignore
├── client
├── assets
│ └── sprites
│ │ ├── car
│ │ └── car.png
│ │ └── asphalt
│ │ └── asphalt.png
├── state
│ ├── player
│ │ ├── createPlayer.js
│ │ └── index.js
│ ├── utils
│ │ └── index.js
│ ├── sockets
│ │ ├── newPlayer.js
│ │ └── updatePlayers.js
│ ├── world
│ │ └── createWorld.js
│ ├── predictions
│ │ └── playerMovementInterpolation.js
│ └── Game.js
├── config
│ ├── index.js
│ └── fileloader.js
├── index.js
└── index.html
├── .editorconfig
├── README.md
├── server
├── config
│ └── index.js
└── index.js
├── dist
└── client
│ ├── index.html
│ └── index.js
├── webpack.config.js
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 |
--------------------------------------------------------------------------------
/client/assets/sprites/car/car.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdomaradzki/simple-car-game/HEAD/client/assets/sprites/car/car.png
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*]
2 | charset = utf-8
3 | end_of_line = lf
4 | insert_final_newline = true
5 | indent_style = space
6 | indent_size = 2
7 |
--------------------------------------------------------------------------------
/client/assets/sprites/asphalt/asphalt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdomaradzki/simple-car-game/HEAD/client/assets/sprites/asphalt/asphalt.png
--------------------------------------------------------------------------------
/client/state/player/createPlayer.js:
--------------------------------------------------------------------------------
1 | const createPlayer = (x, y, game) => {
2 | const sprite = game.add.sprite(x, y, 'car')
3 | game.physics.p2.enable(sprite, false)
4 | return sprite
5 | }
6 |
7 | export default createPlayer
8 |
--------------------------------------------------------------------------------
/client/state/utils/index.js:
--------------------------------------------------------------------------------
1 | export const isDown = (game, key) => game.input.keyboard.isDown(key)
2 | export const createText = (game, target) =>
3 | game.add.text(target.x, target.y, '', {
4 | fontSize: '12px',
5 | fill: '#FFF',
6 | align: 'center'
7 | })
8 |
--------------------------------------------------------------------------------
/client/config/index.js:
--------------------------------------------------------------------------------
1 | export const WINDOW_WIDTH = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth
2 | export const WINDOW_HEIGHT = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
3 | export const WORLD_SIZE = { width: 1600, height: 1200 }
4 | export const ASSETS_URL = '../assets'
5 |
--------------------------------------------------------------------------------
/client/index.js:
--------------------------------------------------------------------------------
1 | import { WINDOW_WIDTH, WINDOW_HEIGHT } from './config'
2 | import Game from './state/Game'
3 |
4 | class App extends Phaser.Game {
5 | constructor () {
6 | super(WINDOW_WIDTH, WINDOW_HEIGHT, Phaser.AUTO)
7 | this.state.add('Game', Game)
8 | this.state.start('Game')
9 | }
10 | }
11 |
12 | const SimpleGame = new App()
13 |
--------------------------------------------------------------------------------
/client/config/fileloader.js:
--------------------------------------------------------------------------------
1 | import { ASSETS_URL } from '.'
2 |
3 | const fileLoader = game => {
4 | game.load.crossOrigin = 'Anonymous'
5 | game.stage.backgroundColor = '#1E1E1E'
6 | game.load.image('asphalt', `${ASSETS_URL}/sprites/asphalt/asphalt.png`)
7 | game.load.image('car', `${ASSETS_URL}/sprites/car/car.png`)
8 | }
9 |
10 | export default fileLoader
11 |
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple Online Car Game
6 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Simple Car Game
2 |
3 | This game is being developed as a tutorial.
4 |
5 | You can access it [here](https://medium.com/@gdomaradzki/how-to-make-a-simple-multiplayer-online-car-game-with-javascript-89d47908f995)
6 |
7 | # Build Setup
8 | ```bash
9 | # To install, just go to the root folder and run
10 | npm i
11 |
12 | # To build the files run
13 | npm run build
14 |
15 | # To run as dev
16 | npm run dev
17 |
18 | # To run as prod
19 | npm run start
20 | ```
21 |
--------------------------------------------------------------------------------
/server/config/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const express = require('express')
3 | const app = express()
4 | const bodyParser = require('body-parser')
5 | const cors = require('cors')
6 | const path = require('path')
7 |
8 | app.use(express.static(path.join(__dirname, './../../dist/client')))
9 | app.use('/assets', express.static(path.join(__dirname, './../../client/assets')))
10 | app.use('/vendor', express.static(path.join(__dirname, './../../vendor')))
11 | app.use(cors())
12 |
13 | module.exports = app
14 |
--------------------------------------------------------------------------------
/dist/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple Online Car Game
6 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/client/state/sockets/newPlayer.js:
--------------------------------------------------------------------------------
1 | const newPlayer = (socket, player) => {
2 | socket.on('connect', () => {
3 | socket.emit('new-player', {
4 | x: player.sprite.body.x,
5 | y: player.sprite.body.y,
6 | angle: player.sprite.rotation,
7 | playerName: {
8 | name: String(socket.id),
9 | x: player.playerName.x,
10 | y: player.playerName.y
11 | },
12 | speed: {
13 | value: player.speed,
14 | x: player.speed.x,
15 | y: player.speed.y
16 | }
17 | })
18 | })
19 | }
20 |
21 | export default newPlayer
22 |
--------------------------------------------------------------------------------
/client/state/world/createWorld.js:
--------------------------------------------------------------------------------
1 | import { WORLD_SIZE } from './../../config'
2 |
3 | const { width, height } = WORLD_SIZE
4 |
5 | const worldCreator = game => {
6 | // Start P2 physics engine
7 | game.physics.startSystem(Phaser.Physics.P2JS)
8 | // We set this to true so our game won't pause if we focus
9 | // something else other than the browser
10 | game.stage.disableVisibilityChange = true
11 | // Here we set the bounds of our game world
12 | game.world.setBounds(0, 0, width, height)
13 | createMap(game)
14 | }
15 |
16 | const createMap = game => {
17 | let groundTiles = []
18 | for (let i = 0; i <= width / 64 + 1; i++) {
19 | for (let j = 0; j <= height / 64 + 1; j++) {
20 | const groundSprite = game.add.sprite(i * 64, j * 64, 'asphalt')
21 | groundTiles.push(groundSprite)
22 | }
23 | }
24 | }
25 |
26 | export default worldCreator
27 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const HtmlWebpackPlugin = require('html-webpack-plugin')
2 | const webpack = require('webpack')
3 | const path = require('path')
4 |
5 | const config = {
6 | target: 'web',
7 | entry: './client/index.js',
8 | watch: true,
9 | output: {
10 | path: path.resolve(__dirname, 'dist/client'),
11 | filename: 'index.js'
12 | },
13 | module: {
14 | loaders: [
15 | {
16 | test: /\.js?$/,
17 | exclude: /node_modules/,
18 | loader: 'babel-loader',
19 | options: {
20 | presets: ['env']
21 | }
22 | }
23 | ]
24 | },
25 | plugins: [
26 | new webpack.optimize.UglifyJsPlugin({
27 | compress: {
28 | warnings: false
29 | },
30 | output: {
31 | comments: false
32 | }
33 | }),
34 | new HtmlWebpackPlugin({ template: './client/index.html' })
35 | ]
36 | }
37 |
38 | module.exports = config
39 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "basic-car-game",
3 | "version": "1.0.0",
4 | "description": "Simple multiplayer online car game made as a tutorial",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "webpack --progress --colors",
8 | "dev": "nodemon server/index.js --exec babel-node",
9 | "start": "nodemon server/index.js"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/gdomaradzki/simple-car-game.git"
14 | },
15 | "keywords": [
16 | "Node.js",
17 | "Socket.io",
18 | "Phaser.js"
19 | ],
20 | "author": "Gustavo Domaradzki",
21 | "license": "ISC",
22 | "bugs": {
23 | "url": "https://github.com/gdomaradzki/simple-car-game/issues"
24 | },
25 | "homepage": "https://github.com/gdomaradzki/simple-car-game#readme",
26 | "devDependencies": {
27 | "babel-cli": "^6.26.0",
28 | "babel-core": "^6.26.0",
29 | "babel-loader": "^7.1.2",
30 | "babel-preset-env": "^1.6.1",
31 | "babel-register": "^6.26.0",
32 | "html-webpack-plugin": "^2.30.1",
33 | "uglifyjs-webpack-plugin": "^1.1.6",
34 | "webpack": "^3.10.0"
35 | },
36 | "dependencies": {
37 | "body-parser": "^1.18.2",
38 | "cors": "^2.8.4",
39 | "express": "^4.16.2",
40 | "socket.io": "^2.0.4",
41 | "nodemon": "^1.14.10"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/client/state/predictions/playerMovementInterpolation.js:
--------------------------------------------------------------------------------
1 | const playerMovementInterpolation = otherPlayers => {
2 | for (let id in otherPlayers) {
3 | let player = otherPlayers[id]
4 | if (player.target_x !== undefined) {
5 | // Interpolate the player's position
6 | player.sprite.body.x += (player.target_x - player.sprite.body.x) * 0.30
7 | player.sprite.body.y += (player.target_y - player.sprite.body.y) * 0.30
8 |
9 | let angle = player.target_rotation
10 | let direction = (angle - player.sprite.body.rotation) / (Math.PI * 2)
11 | direction -= Math.round(direction)
12 | direction *= Math.PI * 2
13 | player.sprite.body.rotation += direction * 0.30
14 |
15 | // Interpolate the player's name position
16 | player.playerName.x += (player.playerName.target_x - player.playerName.x) * 0.30
17 | player.playerName.y += (player.playerName.target_y - player.playerName.y) * 0.30
18 |
19 | // Interpolate the player's speed text position
20 | player.speedText.x += (player.speedText.target_x - player.speedText.x) * 0.30
21 | player.speedText.y += (player.speedText.target_y - player.speedText.y) * 0.30
22 |
23 | player.updatePlayerStatusText('speed', player.speedText.x, player.speedText.y, player.speedText)
24 | }
25 | }
26 | }
27 |
28 | export default playerMovementInterpolation
29 |
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const http = require('http')
3 | const app = require('./config')
4 | const Server = http.Server(app)
5 | const PORT = process.env.PORT || 8000
6 | const io = require('socket.io')(Server)
7 |
8 | Server.listen(PORT, () => console.log('Game server running on:', PORT))
9 |
10 | const players = {}
11 |
12 | io.on('connection', socket => {
13 | // When a player connects
14 | socket.on('new-player', state => {
15 | console.log('New player joined with state:', state)
16 | players[socket.id] = state
17 | // Emit the update-players method in the client side
18 | io.emit('update-players', players)
19 | })
20 |
21 | socket.on('disconnect', state => {
22 | delete players[socket.id]
23 | io.emit('update-players', players)
24 | })
25 |
26 | // When a player moves
27 | socket.on('move-player', data => {
28 | const { x, y, angle, playerName, speed } = data
29 |
30 | // If the player is invalid, return
31 | if (players[socket.id] === undefined) {
32 | return
33 | }
34 |
35 | // Update the player's data if he moved
36 | players[socket.id].x = x
37 | players[socket.id].y = y
38 | players[socket.id].angle = angle
39 | players[socket.id].playerName = {
40 | name: playerName.name,
41 | x: playerName.x,
42 | y: playerName.y
43 | }
44 | players[socket.id].speed = {
45 | value: speed.value,
46 | x: speed.x,
47 | y: speed.y
48 | }
49 |
50 | // Send the data back to the client
51 | io.emit('update-players', players)
52 | })
53 | })
54 |
--------------------------------------------------------------------------------
/client/state/sockets/updatePlayers.js:
--------------------------------------------------------------------------------
1 | import player from './../player'
2 | import { createText } from '../utils'
3 |
4 | const updatePlayers = (socket, otherPlayers, game) => {
5 | socket.on('update-players', playersData => {
6 | let playersFound = {}
7 | // Iterate over all players
8 | for (let index in playersData) {
9 | const data = playersData[index]
10 | // In case a player hasn't been created yet
11 | // We make sure that we won't create a second instance of it
12 | if (otherPlayers[index] === undefined && index !== socket.id) {
13 | const newPlayer = player(data.x, data.y, game)
14 | newPlayer.playerName = createText(game, newPlayer)
15 | newPlayer.speedText = createText(game, newPlayer)
16 | newPlayer.updatePlayerName(data.playerName.name, data.playerName.x, data.playerName.y)
17 | otherPlayers[index] = newPlayer
18 | }
19 |
20 | playersFound[index] = true
21 |
22 | // Update players data
23 | if (index !== socket.id) {
24 | // Update players target but not their real position
25 | otherPlayers[index].target_x = data.x
26 | otherPlayers[index].target_y = data.y
27 | otherPlayers[index].target_rotation = data.angle
28 |
29 | otherPlayers[index].playerName.target_x = data.playerName.x
30 | otherPlayers[index].playerName.target_y = data.playerName.y
31 |
32 | otherPlayers[index].speedText.target_x = data.speed.x
33 | otherPlayers[index].speedText.target_y = data.speed.y
34 |
35 | otherPlayers[index].speed = data.speed.value
36 | }
37 | }
38 |
39 | // Check if there's no missing players, if there is, delete them
40 | for (let id in otherPlayers) {
41 | if (!playersFound[id]) {
42 | otherPlayers[id].sprite.destroy()
43 | otherPlayers[id].playerName.destroy()
44 | otherPlayers[id].speedText.destroy()
45 | delete otherPlayers[id]
46 | }
47 | }
48 | })
49 | }
50 |
51 | export default updatePlayers
52 |
--------------------------------------------------------------------------------
/client/state/Game.js:
--------------------------------------------------------------------------------
1 | import { WORLD_SIZE } from '../config'
2 | import { createText } from './utils'
3 | import fileLoader from '../config/fileloader'
4 | import createWorld from './world/createWorld'
5 | import player from './player'
6 | import newPlayer from './sockets/newPlayer'
7 | import updatePlayers from './sockets/updatePlayers'
8 | import playerMovementInterpolation from './predictions/playerMovementInterpolation'
9 |
10 | const SERVER_IP = 'https://simple-car-game.herokuapp.com/'
11 | let socket = null
12 | let otherPlayers = {}
13 |
14 | class Game extends Phaser.State {
15 | constructor () {
16 | super()
17 | this.player = {}
18 | }
19 |
20 | preload () {
21 | // Loads files
22 | fileLoader(this.game)
23 | }
24 |
25 | create () {
26 | const { width, height } = WORLD_SIZE
27 | // Creates the world
28 | createWorld(this.game)
29 | // Connects the player to the server
30 | socket = io(SERVER_IP)
31 | // Creates the player passing the X, Y, game and socket as arguments
32 | this.player = player(Math.random() * width, Math.random() * height / 2, this.game, socket)
33 | // Creates the player name text
34 | this.player.playerName = createText(this.game, this.player.sprite.body)
35 | // Creates the player speed text
36 | this.player.speedText = createText(this.game, this.player.sprite.body)
37 |
38 | // Sends a new-player event to the server
39 | newPlayer(socket, this.player)
40 | // update all players
41 | updatePlayers(socket, otherPlayers, this.game)
42 |
43 | // Configures the game camera
44 | this.game.camera.x = this.player.sprite.x - 800 / 2
45 | this.game.camera.y = this.player.sprite.y - 600 / 2
46 |
47 | // Scale game to fit the entire window
48 | this.game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL
49 | }
50 |
51 | update () {
52 | this.player.drive(this.game)
53 |
54 | // Move the camera to follow the player
55 | let cameraX = this.player.sprite.x - 800 / 2
56 | let cameraY = this.player.sprite.y - 600 / 2
57 | this.game.camera.x += (cameraX - this.game.camera.x) * 0.08
58 | this.game.camera.y += (cameraY - this.game.camera.y) * 0.08
59 |
60 | // Interpolates the players movement
61 | playerMovementInterpolation(otherPlayers)
62 | }
63 | }
64 |
65 | export default Game
66 |
--------------------------------------------------------------------------------
/client/state/player/index.js:
--------------------------------------------------------------------------------
1 | import createPlayer from './createPlayer'
2 | import { isDown } from '../utils'
3 |
4 | export default function (x, y, game, socket) {
5 | const player = {
6 | socket,
7 | sprite: createPlayer(x, y, game),
8 | playerName: null,
9 | speed: 0,
10 | speedText: null,
11 | drive (game) {
12 | /*
13 | Most of the driving logic was written by Daniel Wuggenig
14 | https://www.anexia-it.com/blog/en/introduction-to-the-phaser-framework/
15 | I decided to use it since this is supposed to be an introduction to multiplayer
16 | online car game, his driving solution is simple and clean and fits perfectly
17 | */
18 |
19 | const KEYS = {
20 | W: Phaser.Keyboard.W,
21 | S: Phaser.Keyboard.S,
22 | A: Phaser.Keyboard.A,
23 | D: Phaser.Keyboard.D
24 | }
25 |
26 | // Only emit if the player is moving
27 | if (this.speed !== 0) {
28 | this.emitPlayerData()
29 | }
30 |
31 | // Drive forward if W is pressed down
32 | if (isDown(game, KEYS.W) && this.speed <= 400) {
33 | this.speed += 10
34 | } else {
35 | if (this.speed >= 10) {
36 | this.speed -= 10
37 | }
38 | }
39 |
40 | // Drive backwards if S is pressed down
41 | if (isDown(game, KEYS.S) && this.speed >= -200) {
42 | this.speed -= 5
43 | } else {
44 | if (this.speed <= -5) {
45 | this.speed += 5
46 | }
47 | }
48 |
49 | // Steers the car
50 | if (isDown(game, KEYS.A)) {
51 | this.sprite.body.angularVelocity = -5 * (this.speed / 1000)
52 | } else if (isDown(game, KEYS.D)) {
53 | this.sprite.body.angularVelocity = 5 * (this.speed / 1000)
54 | } else {
55 | this.sprite.body.angularVelocity = 0
56 | }
57 |
58 | this.sprite.body.velocity.x = this.speed * Math.cos((this.sprite.body.angle - 360) * 0.01745)
59 | this.sprite.body.velocity.y = this.speed * Math.sin((this.sprite.body.angle - 360) * 0.01745)
60 |
61 | // Brings the player's sprite to top
62 | game.world.bringToTop(this.sprite)
63 |
64 | this.updatePlayerName()
65 | this.updatePlayerStatusText('speed', this.sprite.body.x - 57, this.sprite.body.y - 39, this.speedText)
66 | },
67 | emitPlayerData () {
68 | // Emit the 'move-player' event, updating the player's data on the server
69 | socket.emit('move-player', {
70 | x: this.sprite.body.x,
71 | y: this.sprite.body.y,
72 | angle: this.sprite.body.rotation,
73 | playerName: {
74 | name: this.playerName.text,
75 | x: this.playerName.x,
76 | y: this.playerName.y
77 | },
78 | speed: {
79 | value: this.speed,
80 | x: this.speedText.x,
81 | y: this.speedText.y
82 | }
83 | })
84 | },
85 | updatePlayerName (name = this.socket.id, x = this.sprite.body.x - 57, y = this.sprite.body.y - 59) {
86 | // Updates the player's name text and position
87 | this.playerName.text = String(name)
88 | this.playerName.x = x
89 | this.playerName.y = y
90 | // Bring the player's name to top
91 | game.world.bringToTop(this.playerName)
92 | },
93 | updatePlayerStatusText (status, x, y, text) {
94 | // Capitalize the status text
95 | const capitalizedStatus = status[0].toUpperCase() + status.substring(1)
96 | let newText = ''
97 | // Set the speed text to either 0 or the current speed
98 | this[status] < 0 ? this.newText = 0 : this.newText = this[status]
99 | // Updates the text position and string
100 | text.x = x
101 | text.y = y
102 | text.text = `${capitalizedStatus}: ${parseInt(this.newText)}`
103 | game.world.bringToTop(text)
104 | }
105 | }
106 | return player
107 | }
108 |
--------------------------------------------------------------------------------
/dist/client/index.js:
--------------------------------------------------------------------------------
1 | !function(e){function t(a){if(r[a])return r[a].exports;var i=r[a]={i:a,l:!1,exports:{}};return e[a].call(i.exports,i,i.exports,t),i.l=!0,i.exports}var r={};t.m=e,t.c=r,t.d=function(e,r,a){t.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:a})},t.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(r,"a",r),r},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=3)}([function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});t.WINDOW_WIDTH=window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth,t.WINDOW_HEIGHT=window.innerHeight||document.documentElement.clientHeight||document.body.clientHeight,t.WORLD_SIZE={width:1600,height:1200},t.ASSETS_URL="../assets"},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});t.isDown=function(e,t){return e.input.keyboard.isDown(t)},t.createText=function(e,t){return e.add.text(t.x,t.y,"",{fontSize:"12px",fill:"#FFF",align:"center"})}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var a=function(e,t,r){var a=r.add.sprite(e,t,"car");return r.physics.p2.enable(a,!1),a};t.default=a},function(e,t,r){"use strict";function a(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function s(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var n=r(0),o=r(4),p=function(e){return e&&e.__esModule?e:{default:e}}(o),u=function(e){function t(){a(this,t);var e=i(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,n.WINDOW_WIDTH,n.WINDOW_HEIGHT,Phaser.AUTO));return e.state.add("Game",p.default),e.state.start("Game"),e}return s(t,e),t}(Phaser.Game);new u},function(e,t,r){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function s(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function n(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var o=function(){function e(e,t){for(var r=0;r=10&&(this.speed-=10),(0,s.isDown)(e,t.S)&&this.speed>=-200?this.speed-=5:this.speed<=-5&&(this.speed+=5),(0,s.isDown)(e,t.A)?this.sprite.body.angularVelocity=this.speed/1e3*-5:(0,s.isDown)(e,t.D)?this.sprite.body.angularVelocity=this.speed/1e3*5:this.sprite.body.angularVelocity=0,this.sprite.body.velocity.x=this.speed*Math.cos(.01745*(this.sprite.body.angle-360)),this.sprite.body.velocity.y=this.speed*Math.sin(.01745*(this.sprite.body.angle-360)),e.world.bringToTop(this.sprite),this.updatePlayerName(),this.updatePlayerStatusText("speed",this.sprite.body.x-57,this.sprite.body.y-39,this.speedText)},emitPlayerData:function(){a.emit("move-player",{x:this.sprite.body.x,y:this.sprite.body.y,angle:this.sprite.body.rotation,playerName:{name:this.playerName.text,x:this.playerName.x,y:this.playerName.y},speed:{value:this.speed,x:this.speedText.x,y:this.speedText.y}})},updatePlayerName:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.socket.id,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.sprite.body.x-57,a=arguments.length>2&&void 0!==arguments[2]?arguments[2]:this.sprite.body.y-59;this.playerName.text=String(e),this.playerName.x=t,this.playerName.y=a,r.world.bringToTop(this.playerName)},updatePlayerStatusText:function(e,t,a,i){var s=e[0].toUpperCase()+e.substring(1);this[e]<0?this.newText=0:this.newText=this[e],i.x=t,i.y=a,i.text=s+": "+parseInt(this.newText),r.world.bringToTop(i)}}};var a=r(2),i=function(e){return e&&e.__esModule?e:{default:e}}(a),s=r(1)},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var a=function(e,t){e.on("connect",function(){e.emit("new-player",{x:t.sprite.body.x,y:t.sprite.body.y,angle:t.sprite.rotation,playerName:{name:String(e.id),x:t.playerName.x,y:t.playerName.y},speed:{value:t.speed,x:t.speed.x,y:t.speed.y}})})};t.default=a},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var a=r(7),i=function(e){return e&&e.__esModule?e:{default:e}}(a),s=r(1),n=function(e,t,r){e.on("update-players",function(a){var n={};for(var o in a){var p=a[o];if(void 0===t[o]&&o!==e.id){var u=(0,i.default)(p.x,p.y,r);u.playerName=(0,s.createText)(r,u),u.speedText=(0,s.createText)(r,u),u.updatePlayerName(p.playerName.name,p.playerName.x,p.playerName.y),t[o]=u}n[o]=!0,o!==e.id&&(t[o].target_x=p.x,t[o].target_y=p.y,t[o].target_rotation=p.angle,t[o].playerName.target_x=p.playerName.x,t[o].playerName.target_y=p.playerName.y,t[o].speedText.target_x=p.speed.x,t[o].speedText.target_y=p.speed.y,t[o].speed=p.speed.value)}for(var l in t)n[l]||(t[l].sprite.destroy(),t[l].playerName.destroy(),t[l].speedText.destroy(),delete t[l])})};t.default=n},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var a=function(e){for(var t in e){var r=e[t];if(void 0!==r.target_x){r.sprite.body.x+=.3*(r.target_x-r.sprite.body.x),r.sprite.body.y+=.3*(r.target_y-r.sprite.body.y);var a=r.target_rotation,i=(a-r.sprite.body.rotation)/(2*Math.PI);i-=Math.round(i),i*=2*Math.PI,r.sprite.body.rotation+=.3*i,r.playerName.x+=.3*(r.playerName.target_x-r.playerName.x),r.playerName.y+=.3*(r.playerName.target_y-r.playerName.y),r.speedText.x+=.3*(r.speedText.target_x-r.speedText.x),r.speedText.y+=.3*(r.speedText.target_y-r.speedText.y),r.updatePlayerStatusText("speed",r.speedText.x,r.speedText.y,r.speedText)}}};t.default=a}]);
--------------------------------------------------------------------------------