├── .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}]); --------------------------------------------------------------------------------