Gerencie perfis de usuário do jogo com MongoDB, Changer e JavaScript
Avaliar este tutorial
Quando se trata de desenvolvimento de jogos, você quase sempre precisará
armazene informações sobre seu player. Essas informações podem estar por aí
quantos pontos de saúde você tem atualmente no jogo ou eles podem se estender
além da experiência de jogo e em detalhes como o faturamento
informações para a pessoa que está jogando o jogo. Quando falamos sobre isso
tipo de dados, estamos falando de uma loja de perfis de usuário.
O perfil do usuário contém tudo sobre o usuário ou jogador e não termina
nos pontos de saúde ou nas informações de faturamento.
Neste tutorial, veremos como criar perfis de usuário em um jogo que utiliza a estrutura de desenvolvimento de jogosPhaser , JavaScript e MongoDB.
Para ter uma ideia melhor do que vamos realizar, dê uma olhada em
a seguinte imagem animada:

Tudo bem, então não estamos fazendo um jogo muito emocionante. No entanto, muitos modernos
Os jogos permitem que você crie seu personagem antes do início do jogo ou em
algum ponto à medida que você avança no jogo. Essas personalizações que
que você adiciona ao seu personagem são armazenados no perfil do usuário. Em nosso
por exemplo, vamos personalizar nosso player e depois enviar o
informações ao nosso back-end para armazenamento no MongoDB.
Existem alguns componentes neste jogo, mas usaremos uma pilha JavaScript por toda parte. Para ter sucesso com este tutorial, você precisará do seguinte:
- Node.js 12+
- Um cluster MongoDB configurado corretamente com um banco de banco dedados jogos e uma coleção de perfis
Usaremos o Node.js para criar uma API de back-end que se comunicará com nosso cluster MongoDB. O jogo, que será nosso front-end,
comunicar-se com nosso back-end Node.js. Não entraremos em detalhes sobre como configurar e implantar uma MongoDB instance, mas você precisará de uma com banco de dados e collection disponíveis para uso.
Se você quiser entrar em operação rapidamente com o MongoDB, considere implantar um clusterdo MongoDB Atlas M0 , que é GRÁTIS para sempre!
Para criar um jogo Phaser, você não precisa de nada além de HTML básico, CSS,
e JavaScript, então não há requisitos prévios para o jogo
componente.
Antes de entrarmos no código, provavelmente é uma boa ideia explorar o que
nosso perfil de usuário deve conter. Essas não são regras gerais para todos os usuários
porque muitas vezes, os jogos não são os mesmos.
Com base na imagem criada no início deste tutorial, sabemos que temos o seguinte JSON:
1 { 2 "_id": "nraboy", 3 "cosmetics": { 4 "eyes": "player-eyes-1", 5 "mouth": "player-mouth-1" 6 }, 7 "hp": 100, 8 "mp": 30 9 }
A profundidade do perfil acima é quase nada, mas sabemos disso
jogador em particular que eles decidiram dar ao seu personagem um certo
aparência e esse personagem tem um conjunto particular de atributos, como
pontos de saúde.
Se você quiser ver um perfil de usuário mais agressivo, confira meu tutorial anterior, que é focado em um jogo Unity que estou desenvolvendo com Adriene Tacke.
Você pode considerar adicionar uma senha com hash, a posição do jogador dentro do
jogo, itens armazenados ou qualquer outro campo relevante para seu perfil de usuário.
O que torna o MongoDB particularmente poderoso quando se trata de jogos e perfis de usuário é a flexibilidade do modelo de dados pode ser. Se lançarmos um
atualização do nosso jogo que inclui personalizações na cor do
diamante, poderíamos facilmente adicionar esse campo aos nossos documentos NoSQL. Adicionar
e remoção de campos não requer migrações ou codificação especiais, o que
se traduz em um desenvolvimento mais rápido e em um código melhor.
Temos uma ideia aproximada de como deve ser nosso perfil de usuário para isso
jogo particular. Agora precisamos ser capazes de adicioná-lo ao MongoDB e consultá-lo. Para fazer isso, criaremos um back-end com duas API simples
endpoints: um endpoint para criar um documento com o perfil e um
ponto final para recuperá-lo.
Para este tutorial específico, não vamos nos preocupar com
autenticação. Em outras palavras, vamos criar apenas um usuário
perfis e não se importa a quem eles realmente pertencem.
Em um novo diretório em seu computador, execute os seguintes comandos:
1 npm init -y 2 npm install cors express body-parser mongodb --save
Os comandos acima criarão um novo pacote.json do pacote e baixarão as dependências que planejamos usar. Estamos usando o pacote cors porque planejamos executar nosso jogo localmente em um navegador da web. Se não gerenciarmos o compartilhamento de recursos entre origens (CORS), receberemos erros. Os pacotes Express e body-parser serão para nossa estrutura de desenvolvimento, e o pacote mongodb será para interação com o MongoDB.
Dentro do mesmo projeto, crie um arquivoprincipal.js com o seguinte código:
1 const { MongoClient, ObjectID } = require("mongodb"); 2 const Express = require("express"); 3 const BodyParser = require("body-parser"); 4 const Cors = require("cors"); 5 6 const server = Express(); 7 8 server.use(BodyParser.json()); 9 server.use(BodyParser.urlencoded({ extended: true })); 10 server.use(Cors()); 11 12 const client = new MongoClient(process.env["ATLAS_URI"]); 13 14 var collection; 15 16 server.post("/profile", async (request, response, next) => {}); 17 server.get("/profile/:username", async (request, response, next) => {}); 18 19 server.listen("3000", async () => { 20 try { 21 await client.connect(); 22 collection = client.db("gamedev").collection("profiles"); 23 console.log("Listening at :3000..."); 24 } catch (e) { 25 console.error(e); 26 } 27 });
O núcleo do que está acontecer no código acima é que estamos lançando a base para nosso aplicativo Express Framework importando as dependências e inicializando a estrutura. Quando começam a escutar conexões, nós nos conectamos ao MongoDB e definimos qual banco de dados e collection queremos usar. Neste exemplo, estamos utilizando um banco de dados
gamedev
e uma collectionprofiles
. Você pode usar o que fizer sentido para você.As informações reais de conexão do MongoDB são definidas com uma variável de ambiente
ATLAS_URI
. Você poderia facilmente usar um arquivo de configuração ou codificar esse valor, mas as variáveis de ambiente tendem a funcionar melhor por motivos de segurança e implantação.A string de conexão a ser usada seria mais ou menos assim:
1 mongodb+srv://<username>:<password>@plummeting-us-east-1.hrrxc.mongodb.net/<dbname>
Você pode coletar as informações do painel do MongoDB Atlas depois de criar as credenciais de usuário apropriadas para seu banco de dados e coleção.
Quando adiciono uma variável de ambiente, geralmente faço algo como o seguinte
seguinte:
1 export ATLAS_URI="mongodb+srv://<username>:<password>@plummeting-us-east-1.hrrxc.mongodb.net/<dbname>"
Existem várias maneiras de fazer isso e ela pode variar dependendo do seu sistema operacional.
Então, vamos nos concentrar nesses endpoints.
Quando se trata de criar um perfil de usuário no MongoDB, podemos ter um endpoint parecido com o seguinte:
1 server.post("/profile", async (request, response, next) => { 2 try { 3 let result = await collection.insertOne(request.body); 4 response.send(result); 5 } catch (e) { 6 response.status(500).send({ message: e.message }); 7 } 8 });
O endpoint acima é provavelmente o endpoint mais básico que você pode criar.
Estamos aceitando uma carga útil do front-end e inserindo-a
no banco de dados. Em seguida, estamos retornando a resposta que o banco de dados nos dá.
Em um jogo de produção, você provavelmente vai querer validar seus dados para evitar cola ou outras atividades maliciosas. Você pode pesquisar a validação de esquema com o MongoDB ou usar um pacote voltado para o cliente como o joi.
Com os dados entrarem no MongoDB, agora podemos criar nosso endpoint para recuperá-los:
1 server.get("/profile/:username", async (request, response, next) => { 2 try { 3 let result = await collection.findOne({ "_id": request.params.username }); 4 response.send(result); 5 } catch (e) { 6 response.status(500).send({ message: e.message }); 7 } 8 });
Depois de fornecer a esse endpoint um nome de usuário, que em nosso exemplo é o campo
_id
, o usamos para encontrar um documento específico. Esse documento é devolvido ao jogo ou ao que quer que tenha tentado recuperá-lo.Novamente, não estamos usando nenhuma validação ou autenticação de dados para este
tutorial, pois isso está fora do escopo do que queremos realizar.
Agora temos uma API para trabalhar.
Com a API em vigor, podemos nos concentrar no jogo em si. Lembre-se de que a
A profundidade do nosso jogo é bem pequena, mas ele oferece um recurso que muitos
muitos jogos oferecem. Esse recurso é personalizar o personagem.
Não vou compartilhar meus recursos gráficos porque quero que você use um pouco de
imaginação para criar os seus próprios. Você vai criar algo melhor?
Talvez, mas é por sua conta!
Crie um arquivoindex.html no seu computador com a seguinte marcação HTML:
1 2 <html> 3 <body> 4 <div id="game"></div> 5 <script src="//cdn.jsdelivr.net/npm/phaser@3.51.0/dist/phaser.min.js"></script> 6 <script> 7 8 const phaserConfig = { 9 type: Phaser.AUTO, 10 parent: "game", 11 width: 1280, 12 height: 720, 13 scene: { 14 init: initScene, 15 preload: preloadScene, 16 create: createScene 17 } 18 }; 19 20 const game = new Phaser.Game(phaserConfig); 21 22 function initScene() {} 23 function preloadScene() {} 24 function createScene() {} 25 26 </script> 27 </body> 28 </html>
A marcação acima é um boilerplate padrão do Phaser. Definimos o container onde o jogo deve ser carregado, importamos a dependência JavaScript e inicializamos o jogo enquanto dissemos para ele usar o container.
A verdadeira mágica vai acontecer nas funções do ciclo de vida
scene
. A
funçãoinitScene
é onde inicializaremos nossas variáveis, a
funçãopreloadScene
é onde carregaremos nossos ativos de mídia e a funçãocreateScene
é onde faremos nossa primeira renderização.
Começando com a
initScene
função, adicione o seguinte:1 function initScene() { 2 3 this.player = { 4 "_id": "nraboy", 5 "cosmetics": { 6 "eyes": "", 7 "mouth": "" 8 }, 9 "hp": 100, 10 "mp": 30 11 } 12 this.eyes = []; 13 this.mouth = []; 14 15 }
Para nosso exemplo, o objeto
player
representará nosso objeto de perfil de usuário armazenado localmente. Ele será exatamente igual ao que você encontraria armazenado no banco de dados. Esse objeto pode ser tão simples ou complexo quanto você quiser, dependendo das necessidades do jogo. O campo_id
deve ser exclusivo, portanto, se você preferir deixar o MongoDB gerá-lo, remova o campo e crie um username
ou um campo semelhante. Só depende de você.As arrays
eyes
e mouth
representarão todas as opções possíveis para customização de caracteres. A personalização escolhida será armazenada no perfil do usuário.O próximo passo é examinar a função
preloadScene
:1 function preloadScene() { 2 3 this.load.image("background", "game-background.png"); 4 this.load.image("player-base", "player-base.png"); 5 this.load.image("player-eyes-1", "player-eyes-1.png"); 6 this.load.image("player-eyes-2", "player-eyes-2.png"); 7 this.load.image("player-mouth-1", "player-mouth-1.png"); 8 this.load.image("player-mouth-2", "player-mouth-2.png"); 9 10 }
Na função acima, estamos carregando nossos arquivos de imagem e dando a eles um
nome de referência a ser usado durante todo o jogo. No exemplo acima, o
nome de referência é um nome muito próximo ao próprio nome do arquivo, mas
não precisa ser.
Não forneci os ativos da imagem, então fique à vontade para ser ousado.
A mágica acontece na função
createScene
:1 function createScene() { 2 3 this.add.image(640, 360, "background"); 4 this.add.image(800, 250, "player-base"); 5 this.eyesActive = this.add.image(800, 250, "player-eyes-1"); 6 this.mouthActive = this.add.image(800, 250, "player-mouth-1"); 7 8 for (let i = 1; i <= 2; i++) { 9 this.eyes.push( 10 this.add.image(75, 90 * i, "player-eyes-" + i) 11 .setCrop(150, 115, 100, 50) 12 .setScale(0.5) 13 .setInteractive() 14 .on("pointerdown", () => { 15 this.eyesActive.setTexture("player-eyes-" + i); 16 }) 17 ); 18 this.eyes[i - 1].input.hitArea = new Phaser.Geom.Rectangle(150, 115, 100, 50); 19 } 20 21 for (let i = 1; i <= 2; i++) { 22 this.mouth.push( 23 this.add.image(215, 90 * i - 40, "player-mouth-" + i) 24 .setCrop(150, 195, 100, 50) 25 .setScale(0.5) 26 .setInteractive() 27 .on("pointerdown", () => { 28 this.mouthActive.setTexture("player-mouth-" + i); 29 }) 30 ); 31 this.mouth[i - 1].input.hitArea = new Phaser.Geom.Rectangle(150, 195, 100, 50); 32 } 33 34 this.add.text(289, 525, "PROFILE:", { fontSize: 36, color: "#FFFFFF", fontStyle: "bold" }) 35 this.profileData = this.add.text(289, 575, "", { fontSize: 16, color: "#FFFFFF" }) 36 37 this.startButton = this.add.text(1125, 640, "START", { fontSize: 40, color: '#000000', fontStyle: "bold", backgroundColor: "#FFFFFF", padding: 10 }); 38 this.startButton.setInteractive(); 39 this.startButton.on("pointerdown", () => { 40 this.player.cosmetics.mouth = this.mouthActive.texture.key; 41 this.player.cosmetics.eyes = this.eyesActive.texture.key; 42 fetch("http://localhost:3000/profile", { 43 "method": "POST", 44 "headers": { 45 "content-type": "application/json" 46 }, 47 "body": JSON.stringify(this.player) 48 }) 49 .then(response => response.json()) 50 .then(response => { 51 this.profileData.setText(JSON.stringify(this.player)); 52 }, error => { 53 console.error(error); 54 }); 55 }, this); 56 57 }
Permitam-me começar por dizer algumas coisas primeiro.
- O jogo no meu exemplo é 1280x720 e os recursos da imagem refletem isso.
- A imagem base do personagem e qualquer imagem de anexo cosmético têm a mesma resolução.
Em vez de criar imagens de anexos cosméticos de tamanho perfeito, na minha
exemplo, acabei de remover a imagem base, mas deixei as imagens de anexo
do mesmo tamanho, mas com transparência. Tentando descobrir como
posicionar imagens pequenas podem se transformar em mais trabalho do que eu queria. Camadas
as imagens funcionaram melhor.
Com isso dito, temos o seguinte:
1 this.add.image(640, 360, "background"); 2 this.add.image(800, 250, "player-base"); 3 this.eyesActive = this.add.image(800, 250, "player-eyes-1"); 4 this.mouthActive = this.add.image(800, 250, "player-mouth-1");
As imagens de inicialização são colocadas na tela em posições variáveis. O
eyesActive
e mouthActive
representam os anexos iniciais do caractere. Estamos definindo isso apenas como uma variável porque planejamos alterá-las mais tarde.Com base na convenção de nomenclatura das imagens e nos tamanhos que escolhemos usar, podemos exibir as opções de anexo para
eyes
na tela:1 for (let i = 1; i <= 2; i++) { 2 this.eyes.push( 3 this.add.image(75, 90 * i, "player-eyes-" + i) 4 .setCrop(150, 115, 100, 50) 5 .setScale(0.5) 6 .setInteractive() 7 .on("pointerdown", () => { 8 this.eyesActive.setTexture("player-eyes-" + i); 9 }) 10 ); 11 this.eyes[i - 1].input.hitArea = new Phaser.Geom.Rectangle(150, 115, 100, 50); 12 }
Neste exemplo, temos duas possibilidades diferentes para os olhos. Quando
empurrando-os para nosso array, estamos especificando a posição, mas estamos
também mudando algumas coisas em relação à imagem. Por exemplo, sabemos que as imagens dos anexos são em sua maioria transparentes. Podemos cortá-las e escalá-las para mostrar apenas o que queremos. Isso é para fins de seleção
somente, não aparecendo no personagem real.
Também queremos responder a eventos de clique. Quando um evento de clique acontece, queremos que a imagem apropriada seja usada como a imagem ativa. No entanto, como dimensionamos e recortamos nossa imagem, precisamos redefinir o
hitArea
, que é a área que deve ser clicável na imagem.A execução da maioria dessas etapas só é necessária se você estiver usando imagens
do mesmo tamanho e estiver planejando colocá-las em camadas, como eu.
Podemos reproduzir nossas etapas para as opções de boca:
1 for (let i = 1; i <= 2; i++) { 2 this.mouth.push( 3 this.add.image(215, 90 * i - 40, "player-mouth-" + i) 4 .setCrop(150, 195, 100, 50) 5 .setScale(0.5) 6 .setInteractive() 7 .on("pointerdown", () => { 8 this.mouthActive.setTexture("player-mouth-" + i); 9 }) 10 ); 11 this.mouth[i - 1].input.hitArea = new Phaser.Geom.Rectangle(150, 195, 100, 50); 12 }
As etapas são as mesmas, mas estamos usando posições diferentes.
Lembre-se de que essas duas etapas são para definir as miniaturas de nossas imagens
e especificando uma região de clique para eles. Enquanto a imagem ativa é o que
mostramos no personagem, a liderança está relacionada às miniaturas.
Com as miniaturas fora do caminho, podemos adicionar algum texto de espaço reservado de
Qual perfil queremos enviar:
1 this.add.text(289, 525, "PROFILE:", { fontSize: 36, color: "#FFFFFF", fontStyle: "bold" }) 2 this.profileData = this.add.text(289, 575, "", { fontSize: 16, color: "#FFFFFF" })
O objetivo com este texto é apenas mostrar como é o nosso objeto
player
na tela. Isso nos dará uma boa ideia do que foi enviado para nosso MongoDB database.Por fim, temos o botão que enviará os dados para nossa API:
1 this.startButton = this.add.text(1125, 640, "START", { fontSize: 40, color: '#000000', fontStyle: "bold", backgroundColor: "#FFFFFF", padding: 10 }); 2 this.startButton.setInteractive(); 3 this.startButton.on("pointerdown", () => { 4 this.player.cosmetics.mouth = this.mouthActive.texture.key; 5 this.player.cosmetics.eyes = this.eyesActive.texture.key; 6 fetch("http://localhost:3000/profile", { 7 "method": "POST", 8 "headers": { 9 "content-type": "application/json" 10 }, 11 "body": JSON.stringify(this.player) 12 }) 13 .then(response => response.json()) 14 .then(response => { 15 this.profileData.setText(JSON.stringify(this.player)); 16 }, error => { 17 console.error(error); 18 }); 19 }, this);
Quando o botão é clicado, a referência da imagem é adicionada ao objeto
player
. Usando uma operaçãofetch
, podemos enviar o objetoplayer
para nosso backend, que o armazenará no MongoDB. Como não estamos fazendo nenhuma validação de dados, isso será bem-sucedido desde que o campo_id
seja exclusivo.
Se você estiver usando o MongoDB Atlas e examinar o gerenciador de dados, os documentos devem ser semelhantes à imagem acima.
Você acabou de ver como armazenar perfis de usuário para um jogo Phaser como NoSQL
documentos no MongoDB. Dependendo do jogo que você está criando, o usuário
documentos de perfil podem ser significativamente mais complexos em comparação com
o que foi visto neste exemplo. No final do dia, você está armazenando
informações sobre sua experiência de jogo e essas informações podem
incluir informações sobre o usuário, como senha, cobrança
informações, ou similares, e você também está armazenando informações sobre o
jogo, que pode incluir personalizações de roupas, inventário de itens, jogadores
localização, ou qualquer outra coisa relacionada ao jogo.
Se você estiver interessado em ver mais exemplos do Stager com o MongoDB, confira alguns dos meus tutoriais anteriores sobre o assunto. Se você é um desenvolvedor Unity, também tenho algum conteúdo sobre esse assunto.
Tem perguntas sobre este tutorial ou MongoDB? Visite os Fóruns daMongoDB Community e conecte-se conosco!