Configurando um Jogo Multiplayer com Socket.io | hyperPad Documentation

Loading...

Logo

z-f2VkDQ.jpg

Parte 1: Introdução

Pré-requisitos Preferenciais

Neste tutorial, vamos nos concentrar em como configurar um servidor e conectar seu jogo a ele, para que você possa habilitar funções multiplayer para seu projeto por meio de comportamentos Socket.io. Vamos abordar recursos e comportamentos avançados no hyperPad e é fortemente recomendado revisar alguns outros tutoriais primeiro e ter uma boa noção do software antes de continuar.

Este tutorial assume que você tem um bom entendimento da funcionalidade básica do hyperPad, bem como uma compreensão mínima de programação escrita, já que estaremos nos concentrando na programação Javascript para criar um servidor de jogo. Este tutorial também assume algum conhecimento básico sobre redes e as diferenças entre um servidor e seus clientes.

O objetivo principal deste tutorial é ensinar o que você precisa fazer para adicionar funcionalidade multiplayer ao seu jogo; não para criar o jogo em si. Vamos explorar a relação entre os clientes Socket.io e o servidor, e como eles se comunicam entre si com os comportamentos Socket.io.

Requisitos

Para criar e hospedar o servidor, você precisará de um computador capaz de rodar Node.JS (Mac, Windows, etc). A instalação do Node.JS e a configuração para o nosso propósito serão abordadas neste tutorial. Se você estiver hospedando um servidor na sua rede doméstica, provavelmente precisará alterar as configurações de encaminhamento de porta do seu gateway se quiser ter conexões de entrada de fora da sua rede local.

Para este tutorial, já criamos um jogo demo simples. Nele, temos dois jogadores jogando uma partida de pega-pega em um pequeno labirinto. Vamos explorar como os comportamentos Socket.io são utilizados no projeto de exemplo, então é recomendável que você baixe e abra o projeto no hyperPad.

O projeto de exemplo do hyperPad completo pode ser baixado aqui: Tutorial de pega-pega multiplayer.tap

Visão Geral

Neste tutorial, vamos passar pelos fundamentos da criação de um servidor para seu jogo. O servidor lidará com a maioria dos detalhes do jogo, como pontuação, lobbies de jogos, etc. Em seguida, vamos criar comportamentos que enviam informações do nosso jogo para o servidor e vice-versa.

Aqui está o fluxo geral do nosso jogo:

1. A partir do menu principal, vamos nos conectar ao nosso servidor e deixar o usuário decidir criar ou entrar em um jogo.

a. Se criar um jogo, carregue a sala de espera.

b. Se entrar em um jogo, carregue uma lista de salas de jogos disponíveis.

c. Quando dois jogadores estiverem na sala de espera, o jogo será iniciado.

2. Em um jogo, o servidor colocará aleatoriamente os jogadores em uma das quatro áreas e atribuirá a um deles o status de "É".

a. Marcar o outro jogador vai randomizar suas localizações, trocar o status de É e adicionar um ponto ao jogador que marcou o outro.

b. Se nenhuma marcação acontecer dentro de um determinado período de tempo, o servidor removerá 1 ponto do jogador que está atualmente É e trocará o status de É antes de randomizar novamente os jogadores nas zonas de spawn.

3. Uma vez que um jogador tenha alcançado um determinado número de pontos, será carregado um sobreposicionamento declarando um vencedor. Então, os jogadores se desconectarão da sala e voltarão ao menu principal.

Parte 2: Configurando o Servidor

Criar um servidor Socket.io exige que executemos um aplicativo Javascript usando a biblioteca Socket.io, escutando as conexões dos jogadores. Socket.io é uma biblioteca de rede Javascript que simplifica muito o funcionamento interno da construção de um aplicativo em rede para nós. Mais detalhes a seguir.

Baixando e Instalando o Node.JS

Para começar, acesse Node.JS e baixe-o para o computador (Mac, Windows, etc.) onde deseja hospedar o servidor. Quando terminar de baixar, execute o instalador e siga suas instruções. Todas as opções durante a instalação podem ser deixadas como padrão. O Node.JS nos dá a capacidade de executar aplicativos Javascript por conta própria, sem a necessidade de um navegador da web.

Baixando e Executando o Servidor de Exemplo

Em seguida, baixe o Exemplo de Servidor Multiplayer deste tutorial no GitHub:

https://github.com/hyperPad/multiplayerServerExample

Clique no botão "Clone or download" e selecione "Download ZIP". Isso irá baixar uma cópia do código para o exemplo do servidor.

chrome_2019-08-05_14-22-14.png

Extraia o ZIP baixado. Dentro você encontrará alguns pequenos arquivos, mas o arquivo mais notável aqui é o arquivo "index.js", que é o código para nosso servidor. Em seguida, abra seu terminal dentro da pasta do exemplo do servidor.

Digite "npm install" e pressione enter, depois deixe rodar. npm é um gerenciador de pacotes que lê o arquivo package.json e baixa os pacotes necessários para nosso servidor. (Incluindo Socket.io!)

cmd_2019-08-05_14-55-19.png

Quando o comando terminar, teremos tudo o que precisamos para executar o servidor. No terminal, digite "node ." e o servidor será iniciado.

cmd_2019-08-05_15-02-38.png

É isso! Nosso servidor agora está ouvindo na porta 3000 por conexões Socket.io de entrada.

"node ." lança o Node.JS para o diretório atual, onde procurará o arquivo de índice do diretório e o executará. Por padrão, este é o arquivo Javascript "index.js", que contém a maior parte do código do nosso servidor e será analisado ao longo deste tutorial.

Deixe o terminal aberto, pois fechá-lo também fechará o servidor. Você verá mensagens impressas quando jogadores se conectarem ou desconectarem e quando salas forem criadas ou destruídas, entre outros eventos.

Nota: Para permitir conexões de entrada de fora de uma rede doméstica, você provavelmente precisará abrir a porta 3000 em seu gateway doméstico. Este processo varia de rede para rede, mas um guia pode ser geralmente encontrado fazendo uma pesquisa na internet por um guia de encaminhamento de portos para o modem/router da sua casa. Você pode precisar fechar e reiniciar o servidor Node.JS quando alterar as configurações de encaminhamento de porta.

Parte 3: Conectando-se ao Servidor

Uma vez que você tenha os conceitos básicos do servidor iniciados, agora podemos nos conectar a ele em um jogo hyperPad. Isso deve ser configurado para ser a primeira coisa que acontece em seu projeto, independentemente de qual cena você esteja (já que tudo precisa se comunicar com o servidor). É melhor vincular esse comportamento a um objeto na Camada Global, para que ele se aplique a todas as cenas.

Untitled.jpg

No projeto de exemplo, o rótulo da Camada Global "Servidor" contém o acima. (O download para o projeto pode ser encontrado na Parte 1 deste tutorial na seção Requisitos.)

Esses dois comportamentos são realmente tudo o que você precisa para se conectar ao servidor. Primeiro, na guia personalizada, pegue o comportamento Cliente Socket.io e arraste-o para baixo. Aqui, você inserirá a URL do seu servidor na guia URL em sua janela de propriedades. Na imagem acima, a URL do nosso servidor era "http://192.168.0.191:3000", incluindo protocolo e porta. Você vai querer mudar isso para corresponder à URL do seu servidor ou provavelmente não funcionará quando você iniciar o jogo.

Agora temos as informações do servidor que queremos, mas ainda precisamos nos conectar a ele. Então, tudo o que precisamos é do comportamento Conectar ao Socket. Arraste um para baixo, abra suas propriedades e selecione o cliente na caixa vazia e defina a propriedade Função como "Conectar".

Agora, quando nosso projeto for carregado, nos conectaremos automaticamente ao nosso servidor.

mceclip1.png

Parte 4: Criando e Entrando em Salas

A próxima etapa é criar nossos lobbies de jogo onde os jogadores podem entrar e jogar juntos.

mceclip2.png

Aqui está nossa tela simples de Menu Principal. Tudo o que precisa ser feito é tocar em um dos botões para iniciar uma sala ou procurar salas disponíveis.

Criando Salas

Vamos conferir como nos comunicamos com o servidor para criar nossa sala.

mceclip3.png

Bem simples, não é? Então, para revisar rapidamente, uma vez que tocamos em nosso botão, seremos solicitados a digitar um nome de sala. Depois que isso for feito, esse nome é enviado ao servidor, onde a sala é criada e carregamos a cena da sala de espera.

A partir daqui, começaremos a usar o Emit para Socket frequentemente. Isso porque é nossa principal forma de enviar dados ao servidor. Pense nisso simplesmente como a versão "online" da Mensagem de Transmissão e Mensagem de Recepção (Emit para Socket realiza ambas as ações ao mesmo tempo).

Agora, precisamos adicionar algumas informações ao nosso servidor para que ele crie a sala assim que receber a mensagem do nosso comportamento Emit.

mceclip4.png

socket.on('createRoom', (roomName, callback) => {

Essa linha cria um evento socket (como uma expressão lambda) no servidor, escutando Emissões com o evento 'createRoom'.

O primeiro parâmetro é o valor que passamos pelo comportamento Emit para Socket, neste caso, o nome da sala que o usuário digitou. Aqui, nomeamos esse parâmetro de 'roomName'.

O segundo parâmetro é uma função que chamamos mais tarde no evento socket para sinalizar o cliente. O comportamento Emit para Socket prosseguirá apenas se a função de callback for chamada no servidor. Aqui, nomeamos esse parâmetro de 'callback'.

const room = {
id: uuid(),
name: roomName,
sockets: []
};

Isso criará uma instância de uma estrutura que conterá informações essenciais sobre uma sala.

'id' será um identificador único gerado pela função utilitária 'uuid()'. Isso nos ajudará a identificar especificamente esta sala em relação a uma lista de muitas outras salas criadas posteriormente.

'name' será definido como o nome que o usuário digitou anteriormente.

'sockets' será inicializado como uma matriz vazia. Mais tarde, isso acompanhará os sockets dos jogadores conectados a essa sala.

rooms[room.id] = room;

'rooms' é uma lista global de salas que estão ativas atualmente. Como estamos criando uma nova sala, iremos armazená-la na lista pelo seu ID.

joinRoom(socket, room);

Isso chamará a função global 'joinRoom' (linha 29), que adicionará o socket do jogador à matriz 'sockets' da sala. Como o jogador criou a sala, isso também fará com que ele entre nela, também.

callback();
});

Por fim, chamamos a função de callback para que o cliente seja notificado de que a sala foi criada. Isso permitirá que o Emit para Socket continue a execução, o que carregará a cena da Sala de Espera a seguir. Isso também marca o fim do evento socket 'createRoom'.

Entrando em Salas

Entrar em salas é um pouco diferente, pois precisaremos acessar as informações de uma cena diferente a partir do botão.

mceclip5.png

Para entrar em salas, colocamos o início de nossos comportamentos na nossa camada global, juntamente com onde nos conectamos ao servidor. Assim, podemos acessar as informações de qualquer uma de nossas cenas, neste caso, nossa lista de salas. Para o botão, tocar nele simplesmente carregará o jogador na cena da lista de salas.

Aqui temos nosso segundo comportamento para se comunicar com o servidor. O Evento Socket pode ser pensado como Mensagem de Recepção, já que só será ativado uma vez que a mensagem definida tenha sido transmitida do servidor.

A melhor maneira de pensar ao usar Evento Socket e Emit para Socket é que o Evento Socket reage apenas a informações vindas do servidor, enquanto Emit para Socket é chamado em resposta a coisas feitas localmente.

Quanto à nossa lógica, uma vez que nos conectemos ao servidor, emitiremos a solicitação 'getRoomNames' para obter quaisquer nomes de sala disponíveis.

mceclip19.png

Então, definiríamos um rótulo que o jogador pode tocar para entrar.

mceclip6.png

Esta é a nossa cena da Lista de Salas. Aqui é onde quaisquer salas de jogos disponíveis serão carregadas e exibidas. Ao simplesmente tocar no nome da sala, o jogador será carregado. Graças aos nossos comportamentos anteriores, a lista carregará automaticamente qualquer sala aberta e gerará os rótulos dos nomes das salas na tela. Se nada aparecer, temos nosso botão Atualizar Lista que simplesmente repete os comportamentos para carregá-las mais uma vez.

mceclip8.png

Aqui temos a lógica para configurar a lista. Não vamos entrar em muitos detalhes aqui, pois só queremos saber como isso se conecta ao nosso servidor.

Para começar, temos nosso comportamento Emit para Socket. Isso chama o servidor para enviar informações sobre as salas disponíveis. A partir daí, temos um comportamento Obter Valor de Array. Todos os dados que vêm do servidor serão enviados como um Array e as informações necessárias estarão nos primeiros valores. Assim, definimos nosso Obter Valor de Array para obter o valor no índice 0. A partir daí, nossos comportamentos extrairão esses dados e criarão um rótulo para cada sala, gerando-os na nossa tela.

Em seguida, vamos verificar o objeto ao qual precisamos tocar, em nossa cena é o rótulo chamado Nome da Sala.

mceclip9.png

Esse texto atua como nosso botão quando gerado, mas ainda precisamos anexar o ID da sala à qual queremos nos conectar. Para fazer isso, primeiro precisamos obter o ID da sala, e fazemos isso com o comportamento Obter Atributo e configurá-lo como dinâmico. Em seguida, emitimos ao servidor que queremos entrar nesta sala e carregar a sala de espera.

mceclip10.png

socket.on('joinRoom', (roomId, callback) => {

Este é o ponto de entrada para o evento socket 'joinRoom'. O valor do primeiro parâmetro seria o ID da sala que queremos entrar, que foi emitido ao servidor. Aqui, nomeamos o parâmetro de 'roomId'.

const room = rooms[roomId];
joinRoom(socket, room);

Usando o roomId fornecido pelo cliente, podemos encontrar a instância correta da sala no servidor. Com isso, invocamos a função global 'joinRoom' (linha 29) com o socket do jogador querendo se conectar e a instância da sala. Vamos dar uma olhada na função 'joinRoom' em breve.

callback();
});

Por fim, chamamos a função de callback para notificar o cliente para continuar carregando a cena da Sala de Espera, marcando o fim do evento socket 'joinRoom'.

Então, o que exatamente está acontecendo na função 'joinRoom'? Vamos dar uma olhada.

mceclip11.png

room.socket.push(socket);

Como mencionado anteriormente, o membro do array 'room.socket' mantém o controle dos sockets conectados em uma sala. Essa linha faz exatamente isso, empurrando o socket para o array.

socket.join(room.id, () => {
socket.roomId = room.id;
console.log(socket.id, "Joined", room.id);
});

Esta é a chamada oficial para conectar o cliente a uma sala. Primeiro, dizemos ao socket para entrar em uma sala pelo seu ID. Quando isso for concluído, a seguinte função de callback é invocada, onde anexamos o ID da sala ao socket. Por fim, registramos no console que um jogador entrou em uma sala!

mceclip12.png

A Sala de Espera

A sala de espera é simplesmente uma cena que carregamos os jogadores enquanto esperam por outros jogadores se juntarem, ou por um jogo começar.

mceclip13.png

Ao entrar na sala, emitiremos uma mensagem de 'pronto' ao servidor. Uma vez que o servidor recebeu duas dessas, ele enviará uma mensagem que está iniciando o jogo ('initGame'). Capturamos essa mensagem com nosso Evento Socket e assim, carregamos nosso nível de jogo. Para nossos comportamentos, isso é tudo. Você pode adicionar um botão que desconectará você da sala e enviará de volta ao menu principal, se desejar.

No lado do servidor, vamos analisar o código para ver o que está acontecendo lá.

mceclip14.png

Este é o evento 'pronto' que é chamado quando um cliente entrou em uma sala e está pronto para se conectar.

const room = rooms[socket.roomId];

Uma vez que anexamos o ID da sala ao socket, podemos obter a sala para verificar se o jogo pode ser iniciado.

if (room.sockets.length == 2) {

Aqui checamos se agora existem dois jogadores aguardando na sala. Digamos que agora seja verdade, e continuamos para iniciar o jogo.

for (const client of room.sockets) {
client.emit('initGame');
}

Agora que existem dois jogadores, iteramos por cada socket e emitimos o evento 'initGame' para que cada cliente carregue a cena de Nível, como mostrado anteriormente.

Parte 5: Jogabilidade

Agora, vamos ao cerne das coisas. Aqui está o nível do jogo que projetamos para este tutorial.

mceclip15.png

Antes de entrarmos nisso, temos um rótulo intitulado "Lógica do Jogo", vamos abrir isso e dar uma olhada.

mceclip16.png

Uau, há muitos comportamentos! Não se preocupe, isso é simplesmente como geramos nossos jogadores no jogo. Vamos dar uma olhada mais de perto;

mceclip17.png

Começamos com um comportamento Emit para Socket que informa ao servidor que nosso jogo começou, com o evento 'startGame'. Em seguida, pegamos o array que o servidor retorna, pegamos seu primeiro valor e, com o Obter Valor de Dicionário, obtemos os vários atributos que nosso objeto precisará. Uma árvore separada faz o mesmo, exceto para o jogador oponente. Por fim, transmitimos a mensagem 'init' para nosso objeto jogador para dar início às coisas.

Vamos ver o que está acontecendo no lado do servidor quando emitimos o evento 'startGame'.

mceclip20.png

A primeira metade deste evento socket que você vê aqui está definindo alguns valores iniciais em cada cliente e, em seguida, adicionando qualquer cliente que não é o cliente emissor ao array local 'others'.

mceclip21.png

Na segunda metade, uma lista de dicionário local chamada 'ack' é criada. Nela, temos informações sobre nós mesmos e os outros clientes. Em seguida, enviamos essas informações de volta ao cliente passando nosso dicionário 'ack' para a função de callback, que se torna o valor resultante do comportamento Emit para Socket chamado.

Depois, uma chamada de timeout para 5 segundos é feita para finalmente iniciar a rodada, agora que todos têm todas as informações que precisam para jogar o jogo. A função 'beginRound' (linha 99) controla alguma lógica específica do jogo para este projeto. Não vamos entrar em muitos detalhes sobre isso, mas, essencialmente, lida com onde gerar os jogadores, checar pontuações, bem como informar os clientes quem está "É".

Como mencionado antes, a mensagem 'init' é chamada no nosso rótulo "Lógica do Jogo" quando tudo está pronto para começar. No objeto jogador, agora iremos olhar para os comportamentos onde ele recebe a mensagem 'init'.

mceclip22.png

Aqui você pode ver que temos várias árvores de comportamento em nosso jogador. Primeiro, começaremos com a árvore superior esquerda.

mceclip23.png

Esse é o comportamento que praticamente inicia tudo o que temos em nosso jogo.

Primeiro, recebemos a mensagem 'init' enviada do rótulo "Lógica do Jogo". A partir daí, pegamos o ID do servidor do nosso objeto e ativamos um dos Eventos Socket, além de configurar nossa tela do jogo para que possamos acompanhar nosso personagem com precisão.

Sincronização de Movimento

mceclip24.png

Aqui está um de nossos comportamentos mais importantes. Esta pequena árvore é projetada para atualizar a posição do nosso jogador no servidor toda vez que movemos o joystick. Você provavelmente notou que também está referenciando alguns valores de dicionário. Obtivemos esses valores de um comportamento de dicionário independente que contém as posições X e Y do nosso jogador.

Vamos dar uma olhada no evento 'moved' no servidor.

mceclip25.png

data = JSON.parse(data);

Dicionários, quando enviados de um cliente para um servidor, precisam ser analisados para que possamos ler os dados facilmente. Isso porque dicionários no hyperPad são codificados em uma estrutura JSON quando enviados para um servidor. Esta linha analisa a estrutura de string JSON e armazena a lista de dicionário de volta na mesma variável local 'data'.

socket.x = data.x;
socket.y = data.y;

Aqui, atualizamos as posições X e Y armazenadas no socket com os novos valores dados pelo cliente.

for (const client of room.sockets) {
if (client == socket) {
continue;
}
client.emit(socket.id, {
x: socket.x,
y: socket.y,
score: socket.score,
isIt: socket.isIt
});
}

Em seguida, iteramos através de todos os clientes para atualizá-los sobre nossa nova posição e outros detalhes, excluindo a nós mesmos (ou seja, o cliente emissor não precisa saber sua própria posição).

mceclip26.png

Esta árvore aqui controla a maior parte do nosso jogo. Usamos um Evento Socket quando o servidor está verificando qual jogador está marcado como É, que é emitido por 'beginRound' (linha 99) no servidor.

Em seguida, pegamos o ID do servidor do nosso objeto a partir do array e usamos o Valor de Dicionário para quebrá-lo nas várias partes de dados que contém. A partir daí, pegamos nossa Pontuação, se estamos marcados como É, assim como as posições x e y de nosso objeto e aplicamos a atributos do nosso objeto. O restante dos comportamentos é para definir e controlar a interface de usuário em nosso jogo.

Conclusão

Houve muito para absorver aqui, mas espero que se você chegou até aqui, deve ter uma compreensão de como utilizar os comportamentos Socket.io para criar experiências multiplayer para seus jogadores.

Tente você mesmo! Pegue um jogo existente que você criou e tente dar a ele alguma funcionalidade online, como uma cena de recordes que se conecta a um servidor e solicita uma lista das 10 melhores pontuações com nomes de jogadores para exibir.

É difícil ensinar uma linguagem de script, como Javascript, em um único artigo. Felizmente, se você estiver tendo problemas, há muitos outros recursos para ajudá-lo a escrever aplicativos Javascript para Node.JS e Socket.io;

Aprenda Javascript - https://developer.mozilla.org/bm/docs/Web/JavaScript

Aprenda Socket.io - https://socket.io/docs/

Aprenda Node.JS - https://nodejs.org/en/docs/