Lançado em 2015, o 
Agar.io se tornou o progenitor do novo gênero de 
games .io , cuja popularidade desde então cresceu significativamente. O crescimento da popularidade dos jogos .io que experimentei: nos últimos três anos, 
criei e vendi dois jogos desse gênero. .
Caso você nunca tenha ouvido falar desses jogos antes: estes são jogos on-line gratuitos para vários jogadores, fáceis de participar (sem necessidade de conta). Geralmente eles empurram muitos jogadores adversários na mesma arena. Outros jogos famosos do gênero .io são: 
Slither.io e 
Diep.io.Neste post, descobriremos como 
criar um jogo .io a partir do zero . Para isso, apenas o conhecimento de Javascript será suficiente: você precisa entender coisas como a sintaxe 
ES6 , a 
this e 
Promises . Mesmo se você não souber o Javascript perfeitamente, ainda poderá descobrir a maior parte do post.
Exemplo de jogo .io
Para ajudar você a aprender, vamos nos referir a 
um exemplo de jogo .io . Tente jogar!
O jogo é bastante simples: você controla um navio em uma arena onde existem outros jogadores. Seu navio dispara conchas automaticamente e você tenta acertar outros jogadores, evitando as conchas deles.
1. Visão geral / estrutura do projeto
Eu recomendo baixar o código fonte de um jogo de exemplo para que você possa me seguir.
O exemplo usa o seguinte:
- Express é a estrutura da web mais popular do Node.js que gerencia o servidor da web do jogo.
- socket.io - uma biblioteca de websocket para troca de dados entre um navegador e um servidor.
- Webpack é um gerenciador de módulos. Você pode ler sobre por que usar o Webpack aqui .
Aqui está a aparência da estrutura de diretórios do projeto:
 public/ assets/ ... src/ client/ css/ ... html/ index.html index.js ... server/ server.js ... shared/ constants.js 
público /
Tudo no 
public/ pasta será transmitido estaticamente pelo servidor. 
public/assets/ contém as imagens usadas pelo nosso projeto.
src /
Todo o código fonte está na pasta 
src/ . Os nomes 
client/ e 
server/ falam por si e 
shared/ contém um arquivo constante importado pelo cliente e pelo servidor.
2. Montagens / parâmetros do projeto
Como afirmado acima, usamos o 
gerenciador de módulo 
Webpack para construir o projeto. Vamos dar uma olhada na nossa configuração do 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', }), ], }; 
O mais importante aqui são as seguintes linhas:
- src/client/index.jsé o ponto de entrada para o cliente Javascript (JS). O Webpack começará a partir daqui e procurará recursivamente outros arquivos importados.
- O JS de saída do nosso assembly Webpack estará localizado no diretório dist/. Vou chamar esse arquivo de pacote JS .
- Usamos o Babel e, em particular, a configuração @ babel / preset-env para transpilar nosso código JS para navegadores mais antigos.
- Usamos o plug-in para extrair todo o CSS referenciado pelos arquivos JS e combiná-los em um só lugar. Vou chamá-lo de nosso pacote CSS .
Você deve ter notado nomes estranhos de arquivos de pacotes 
'[name].[contenthash].ext' . Eles contêm a 
substituição dos nomes dos arquivos Webpack: 
[name] será substituído pelo nome do ponto de entrada (no nosso caso, este é um 
game ) e 
[contenthash] será substituído por um hash do conteúdo do arquivo. Fazemos isso para 
otimizar o projeto para o hash - você pode dizer aos navegadores para armazenar em cache nossos pacotes JS indefinidamente, porque 
se o pacote for alterado, o nome do arquivo será alterado (o 
contenthash alterado). O resultado final será um nome de arquivo no formato 
game.dbeee76e91a97d0c7207.js .
O arquivo 
webpack.common.js é o arquivo de configuração base que importamos nas configurações de desenvolvimento e projeto finalizado. Por exemplo, uma configuração de desenvolvimento:
webpack.dev.js
 const merge = require('webpack-merge'); const common = require('./webpack.common.js'); module.exports = merge(common, { mode: 'development', }); 
Para eficiência, usamos o 
webpack.dev.js no 
webpack.dev.js desenvolvimento e 
webpack.prod.js para o 
webpack.prod.js para otimizar o tamanho dos pacotes quando implementados na produção.
Configuração local
Eu recomendo instalar o projeto em uma máquina local para que você possa seguir as etapas listadas nesta postagem. A configuração é simples: primeiro, o 
Node e o 
NPM devem estar instalados no sistema. Em seguida, você precisa executar
 $ git clone https: 
e você está pronto para ir! Para iniciar o servidor de desenvolvimento, basta executar
 $ npm run develop 
e acesse o 
localhost: 3000 em um navegador da web. O servidor de desenvolvimento reconstruirá automaticamente os pacotes JS e CSS conforme o código for alterado - basta atualizar a página para ver todas as alterações!
3. Pontos de entrada do cliente
Vamos ao código do jogo em si. Primeiro, precisamos da página 
index.html . Quando você visita o site, o navegador o carrega primeiro. Nossa página será bem simples:
index.html
  <! DOCTYPE html>
 <html>
 <head>
   <title> Um exemplo de jogo .io </title>
   <tipo de link = "text / css" rel = "folha de estilo" href = "/ game.bundle.css">
 </head>
 <body>
   <canvas id = "game-canvas"> </canvas>
   <script async src = "/ game.bundle.js"> </script>
   <div id = "play-menu" class = "oculto">
     <input type = "text" id = "nome do usuário-entrada" espaço reservado = "Nome do Usuário" />
     <button id = "play-button"> JOGAR </button>
   </div>
 </body>
 </html> Este exemplo de código é um pouco simplificado para maior clareza, farei o mesmo com muitos outros exemplos da postagem. O código completo sempre pode ser visualizado no Github .Nós temos:
- <canvas>Canvas HTML5 (- <canvas>) que usaremos para renderizar o jogo.
- <link>para adicionar nosso pacote CSS.
- <script>para adicionar nosso pacote Javascript.
- O menu principal com o nome de usuário <input>e o<button>"PLAY" (<button>).
Depois de carregar a página inicial, o código Javascript começará a ser executado no navegador, começando com o arquivo JS do ponto de entrada: 
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 = () => {  
Isso pode parecer complicado, mas na realidade não há muitas ações acontecendo aqui:
- Importe vários outros arquivos JS.
- Importe CSS (para que o Webpack saiba incluí-los em nosso pacote CSS).
- Execute connect()para estabelecer uma conexão com o servidor e executedownloadAssets()para baixar as imagens necessárias para renderizar o jogo.
- Após concluir a etapa 3 , o menu principal ( playMenu) éplayMenu.
- Configurando o manipulador para pressionar o botão PLAY. Quando o botão é pressionado, o código inicializa o jogo e informa ao servidor que estamos prontos para jogar.
A principal "carne" da nossa lógica cliente-servidor está nos arquivos que foram importados pelo arquivo 
index.js . Agora vamos considerá-los todos em ordem.
4. Troca de dados do cliente
Neste jogo, usamos a conhecida biblioteca 
socket.io para se comunicar com o servidor. O Socket.io possui suporte interno para 
WebSockets , que são adequados para comunicação bidirecional: podemos enviar mensagens para o servidor 
e o servidor pode nos enviar mensagens pela mesma conexão.
Teremos um arquivo 
src/client/networking.js que tratará de 
todas as comunicações com o servidor:
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(() => {  
Este código também é um pouco reduzido para maior clareza.Existem três ações principais neste arquivo:
- Estamos tentando conectar ao servidor. connectedPromiseé permitido apenas quando estabelecemos uma conexão.
- Se a conexão for estabelecida com sucesso, registramos funções de retorno de chamada ( processGameUpdate()eonGameOver()) para mensagens que podemos receber do servidor.
- Exportamos play()eupdateDirection()para que outros arquivos possam usá-los.
5. Renderização do cliente
É hora de exibir uma imagem na tela!
... mas antes que possamos fazer isso, precisamos fazer o download de todas as imagens (recursos) necessárias para isso. Vamos escrever um gerenciador de recursos:
assets.js
 const ASSET_NAMES = ['ship.svg', 'bullet.svg']; const assets = {}; const downloadPromise = Promise.all(ASSET_NAMES.map(downloadAsset)); function downloadAsset(assetName) { return new Promise(resolve => { const asset = new Image(); asset.onload = () => { console.log(`Downloaded ${assetName}`); assets[assetName] = asset; resolve(); }; asset.src = `/assets/${assetName}`; }); } export const downloadAssets = () => downloadPromise; export const getAsset = assetName => assets[assetName]; 
O gerenciamento de recursos não é tão difícil de implementar! O ponto principal é armazenar o objeto de 
assets , que vinculará a chave do nome do arquivo ao valor do objeto 
Image . Quando o recurso é carregado, nós o salvamos no objeto de 
assets para recuperação rápida no futuro. Quando o download for permitido para cada recurso individual (ou seja, 
todos os recursos serão baixados), ativamos o 
downloadPromise .
Depois de baixar os recursos, você pode começar a renderizar. Como afirmado anteriormente, usamos o 
HTML5 Canvas ( 
<canvas> ) para desenhar na página da web. Nosso jogo é bastante simples, então precisamos desenhar o seguinte:
- Antecedentes
- Navio do jogador
- Outros jogadores no jogo
- Conchas
Aqui estão os trechos importantes de 
src/client/render.js que renderizam exatamente os quatro pontos listados acima:
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;  
Este código também é reduzido para maior clareza.render() é a principal função deste arquivo. 
startRendering() e 
stopRendering() controlam a ativação do ciclo de renderização a 60 FPS.
As implementações específicas das funções individuais de renderização auxiliar (por exemplo, 
renderBullet() ) não são tão importantes, mas aqui está um exemplo simples:
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, ); } 
Observe que usamos o método 
getAsset() que foi visto anteriormente em 
asset.js !
Se você estiver interessado em explorar outras funções auxiliares de renderização, leia o restante do src / client / render.js .
6. Entrada do cliente
É hora de tornar o jogo 
jogável ! O esquema de controle será muito simples: você pode usar o mouse (no computador) ou tocar na tela (no dispositivo móvel) para alterar a direção do movimento. Para implementar isso, registraremos 
Listeners de Eventos para eventos Mouse e Touch.
src/client/input.js fará tudo isso:
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() e 
onTouchInput() são ouvintes de eventos que chamam 
updateDirection() (de 
networking.js ) quando ocorre um evento de entrada (por exemplo, ao mover o mouse). 
updateDirection() está envolvido em mensagens com um servidor que processa o evento de entrada e atualiza o estado do jogo de acordo.
7. Condição do cliente
Esta seção é a mais difícil na primeira parte da postagem. Não desanime se você não o entender desde a primeira leitura! Você pode até pular e voltar mais tarde.
A última peça do quebra-cabeça necessária para concluir o código cliente-servidor é 
state . Lembra do snippet de código da seção Renderização de cliente?
render.js
 import { getCurrentState } from './state'; function render() { const { me, others, bullets } = getCurrentState();  
getCurrentState() deve poder nos fornecer o estado atual do jogo no cliente 
a qualquer momento, com base nas atualizações recebidas do servidor. Aqui está um exemplo de uma atualização de jogo que um servidor pode enviar:
 { "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 } ] } 
Cada atualização do jogo contém cinco campos idênticos:
- t : carimbo de data e hora do servidor para o momento em que esta atualização foi criada.
- eu : informações sobre o jogador que está recebendo esta atualização.
- outros : um conjunto de informações sobre outros jogadores que participam do mesmo jogo.
- balas : um conjunto de informações sobre as conchas do jogo.
- tabela de classificação : dados atuais da tabela de classificação. Neste post, não os levaremos em consideração.
7.1 O estado ingênuo do cliente
A implementação ingênua de 
getCurrentState() só pode retornar dados diretamente da atualização de jogo mais recente recebida.
naive-state.js
 let lastGameUpdate = null;  
Bonito e claro! Mas se tudo fosse tão simples. Um dos motivos pelos quais essa implementação é problemática: 
limita a taxa de quadros da renderização à frequência do relógio do servidor .
Taxa de quadros: o número de quadros (ou seja, chamadas de render() ) por segundo ou FPS. Os jogos geralmente tendem a atingir pelo menos 60 FPS.
Tick Rate : A frequência com que o servidor envia atualizações de jogos para os clientes. Muitas vezes, é menor que a taxa de quadros . Em nosso jogo, o servidor roda a uma frequência de 30 ciclos por segundo.
Se apenas renderizarmos a atualização mais recente do jogo, o FPS nunca poderá exceder 30, porque 
nunca receberemos mais de 30 atualizações por segundo do servidor . Mesmo se chamarmos 
render() 60 vezes por segundo, metade dessas chamadas simplesmente redesenhará a mesma coisa, essencialmente sem fazer nada. Outro problema da implementação ingênua é que é 
propenso a atrasos . Com uma velocidade ideal da Internet, o cliente receberá uma atualização do jogo exatamente a cada 33 ms (30 por segundo):
Infelizmente, nada é perfeito. Uma imagem mais realista seria:
Uma implementação ingênua é praticamente o pior caso de atraso. Se a atualização do jogo for recebida com um atraso de 50 ms, o 
cliente ficará mais lento por mais 50 ms, porque ainda renderiza o estado do jogo da atualização anterior. Você pode imaginar o quão inconveniente é para um jogador: devido à frenagem arbitrária, o jogo parecerá instável e instável.
7.2 Status aprimorado do cliente
Faremos algumas melhorias na implementação ingênua. Primeiro, usamos 
um atraso de renderização de 100 ms. Isso significa que o estado "atual" do cliente sempre ficará atrás do estado do jogo no servidor em 100 ms. Por exemplo, se o horário no servidor for 
150 , o estado em que o servidor estava no horário 
50 será renderizado no cliente:
Isso nos fornece um buffer de 100 ms, permitindo sobreviver ao tempo imprevisível para receber atualizações de jogos:
Pagar por isso será um 
atraso de entrada constante 
(atraso de entrada) de 100 ms. Este é um pequeno sacrifício pela jogabilidade suave - a maioria dos jogadores (especialmente os casuais) nem notam esse atraso. É muito mais fácil para as pessoas se adaptarem a um atraso constante de 100 ms do que jogar com um atraso imprevisível.
Podemos usar outra técnica chamada “previsão do lado do cliente”, que faz um bom trabalho na redução de atrasos percebidos, mas isso não será considerado nesta postagem.
Outra melhoria que usamos é 
a interpolação linear . Devido a atrasos na renderização, geralmente ultrapassamos o tempo atual no cliente por pelo menos uma atualização. Quando 
getCurrentState() é chamado, podemos executar uma 
interpolação linear entre as atualizações do jogo imediatamente antes e depois da hora atual no cliente:
Isso resolve o problema com a taxa de quadros: agora podemos renderizar quadros únicos com a frequência necessária!
7.3 Implementando o status aprimorado do cliente
Um exemplo de implementação em 
src/client/state.js usa atraso de renderização e interpolação linear, mas isso não é por muito tempo. Vamos dividir o código em duas partes. Aqui está o primeiro:
state.js parte 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);  
O primeiro passo é descobrir o que o 
currentServerTime() faz. Como vimos anteriormente, um registro de data e hora do servidor é incluído em todas as atualizações do jogo. Queremos usar o atraso de renderização para renderizar a imagem 100 ms atrás do servidor, mas 
nunca saberemos a hora atual no servidor , porque não podemos saber quanto tempo uma das atualizações chegou até nós. A Internet é imprevisível e sua velocidade pode variar bastante!
Para contornar esse problema, você pode usar uma aproximação razoável: 
fingiremos que a primeira atualização chegou instantaneamente . Se isso fosse verdade, saberíamos a hora do servidor nesse momento específico! 
firstServerTimestamp o registro de data e hora do servidor no 
firstServerTimestamp e salvamos o registro de data e hora 
local (cliente) ao mesmo tempo no 
gameStart .
Oh espera. 
Não deveria haver tempo no servidor = tempo no cliente? Por que distinguimos entre "registro de data e hora do servidor" e "registro de data e hora do cliente"? Esta é uma ótima pergunta! Acontece que isso não é a mesma coisa. 
Date.now() retornará diferentes registros de data e hora no cliente e no servidor, e isso depende de fatores locais para essas máquinas. 
Nunca assuma que os carimbos de data e hora são iguais em todas as máquinas.Agora entendemos o que 
currentServerTime() faz: retorna o 
carimbo de data e hora do servidor para o tempo de renderização atual . , ( 
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();  
:
- base < 0, (.- getBaseUpdate()). - . .
- base— , . - . , .
- , , !
, 
state.js — , ( ) . , 
state.js Github .
2. -
Node.js, 
.io .
1.
- - Node.js 
Express . 
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. :
server.js socket.io , Express:
server.js, 2
 const socketio = require('socket.io'); const Constants = require('../shared/constants');  
socket.io . - 
game :
server.js, 3
 const Game = require('./game');  
Criamos um jogo do gênero .io, por isso precisamos de apenas uma instância Game("Jogo") - todos os jogadores jogam na mesma arena! Na próxima seção, veremos como essa classe funciona Game.2. servidor de jogo
A classe Gamecontém a lógica mais importante do lado do servidor. Ele tem duas tarefas principais: gerenciamento de jogadores e simulação de jogos .Vamos começar com a primeira tarefa - com o gerenciamento de jogadores.game.js, parte 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 code>Player
bulletsÉ uma matriz de objetos Bulletque não possui uma ordem específica.lastUpdateTime- Este é o registro de data e hora da última atualização do jogo. Em breve veremos como é usado.shouldSendUpdateÉ uma variável auxiliar. Também veremos seu uso em breve.Métodos addPlayer(), removePlayer()e handleInput()não há necessidade de explicar, eles são usados no server.js. Se você precisar atualizar sua memória, volte um pouco mais.A última linha constructor()inicia o ciclo de atualização do jogo (com uma frequência de 60 atualizações / s):game.js, parte 2
 const Constants = require('../shared/constants'); const applyCollisions = require('./collisions'); class Game {  
O método update()provavelmente contém a parte mais importante da lógica do lado do servidor. Em ordem, listamos tudo o que ele faz:- Calcula quanto tempo dtse passou desde o últimoupdate().
- . . , bullet.update()true, ( ).
- . — player.update()Bullet.
- applyCollisions(), , . , (- player.onDealtDamage()),- bullets.
- .
- update().- shouldSendUpdate.- update()60 /, 30 /. , 30 / ( ).
? . 30 – !
Por que simplesmente não ligar update()30 vezes por segundo? Para melhorar a simulação do jogo. Quanto mais frequentemente chamado update(), mais precisa será a simulação do jogo. Mas não se empolgue com o número de chamadas update(), porque é uma tarefa computacionalmente cara - 60 por segundo é suficiente.
O restante da classe Gameconsiste em métodos auxiliares usados em update():game.js, parte 3
 class Game {  
getLeaderboard() – , .
createUpdate() update() , . 
serializeForUpdate() , 
Player Bullet . , 
– , !
3.
: . , 
Object :
object.js
 class Object { constructor(id, x, y, dir, speed) { this.id = id; this.x = x; this.y = y; this.direction = dir; this.speed = speed; } update(dt) { this.x += dt * this.speed * Math.sin(this.direction); this.y -= dt * this.speed * Math.cos(this.direction); } distanceTo(object) { const dx = this.x - object.x; const dy = this.y - object.y; return Math.sqrt(dx * dx + dy * dy); } setDirection(dir) { this.direction = dir; } serializeForUpdate() { return { id: this.id, x: this.x, y: this.y, }; } } 
. . , 
Bullet Object :
bullet.js
 const shortid = require('shortid'); const ObjectClass = require('./object'); const Constants = require('../shared/constants'); class Bullet extends ObjectClass { constructor(parentID, x, y, dir) { super(shortid(), x, y, dir, Constants.BULLET_SPEED); this.parentID = parentID; }  
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');  
, 
, . , :
:
- . , bullet.parentIDplayer.id.
- . break: , , .
! , - .io. O que vem a seguir? 
.io!Github .