Explore o novo chatbot do Developer Center! O MongoDB AI chatbot pode ser acessado na parte superior da sua navegação para responder a todas as suas perguntas sobre o MongoDB .

Learn why MongoDB was selected as a leader in the 2024 Gartner® Magic Quadrant™
Desenvolvedor do MongoDB
Central de desenvolvedor do MongoDBchevron-right
Idiomaschevron-right
JavaScriptchevron-right

Manutenção de uma tabela de classificação de jogos específica de geolocalização com Phaser e MongoDB

Nic Raboy18 min read • Published Jan 18, 2022 • Updated Apr 02, 2024
AtlasJavaScript
Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Quando se trata de desenvolvimento de jogos, um componente frequentemente esquecido vem na forma de um banco de dados para armazenar informações de jogabilidade. O banco de dados pode contribuir para várias funções, como armazenar informações de perfil do usuário, estado do jogo e muito mais.
Na verdade, criei um tutorial anterior intitulado Criando um jogo de desenho multijogador com Phaser e MongoDB. Neste exemplo de desenho, cada pincelada feita foi armazenada no MongoDB.
Neste tutorial, veremos um componente de dados diferente para um jogo. Exploraremos as tabelas de classificação e algumas das coisas interessantes que você pode fazer com elas. Assim como meu outro tutorial, usaremos Phaser e JavaScript.
Para ter uma ideia do que queremos realizar, dê uma olhada na imagem animada a seguir:
Exemplo de Phaser com Tabela de Classificação do MongoDB
Exemplo de Phaser com Tabela de Classificação do MongoDB
O jogo acima tem três telas diferentes, que são chamadas de cenas no Phaser. A primeira tela (primeiros segundos do gif) aceita a entrada do usuário como nome de usuário e também reúne informações de geolocalização sobre o player. A segunda tela é onde você realmente joga o jogo e tenta acumular pontos (coletando folhas) enquanto evita bombas! Por fim, a terceira tela é onde suas informações de pontuação e localização são enviadas. Você também verá as melhores pontuações de todos os tempos, bem como as melhores pontuações perto da sua localização, todas elas podem ser consultadas facilmente usando o MongoDB!

Os requisitos

Não há muitos requisitos que devem ser atendidos para ter sucesso com este tutorial. Aqui está o que você precisa:
  • Um clusterMongoDB Atlas com função de usuário e configuração de rede adequadas
  • Node.js 12+
Para este exemplo, o MongoDB Atlas armazenará todas as informações da nossa tabela de classificação e o Node.js alimentará nossa API de back-end. O jogo Phaser não tem nenhuma dependência real além de ter algo disponível para atender ao projeto. Eu uso serve para fazer isso, mas você pode usar o que quiser.

Construindo o backend para a tabela de classificação com Node.js e MongoDB

Antes de entrarmos na parte de desenvolvimento de jogos (com o Phaser), devemos cuidar da nossa API de back-end. Esta API de backend será responsável pela interação direta com nosso banco de dados. Ele aceitará solicitações do nosso jogo para armazenar dados, bem como solicitações para buscá-los.
Crie um novo diretório de projeto em seu computador e, de dentro desse diretório, execute os seguintes comandos:
1npm init -y
2npm install express body-parser cors mongodb --save
Os comandos acima criarão um novo arquivopackage.json e instalarão as dependências do projeto para nosso backend. Iremos utilizar Express para criar nossa API e o driver MongoDB Node.js para comunicação com o banco de dados.
Para o backend, adicionaremos todo o nosso código a um arquivomain.js. Crie-o no diretório do seu projeto e adicione o seguinte JavaScript:
1const { MongoClient, ObjectID } = require("mongodb");
2const Express = require("express");
3const Cors = require("cors");
4const BodyParser = require("body-parser");
5const { request } = require("express");
6
7const client = new MongoClient(process.env["ATLAS_URI"]);
8const server = Express();
9
10server.use(BodyParser.json());
11server.use(BodyParser.urlencoded({ extended: true }));
12server.use(Cors());
13
14var collection;
15
16server.post("/create", async (request, response) => {});
17server.get("/get", async (request, response) => {});
18server.get("/getNearLocation", async (request, response) => {});
19
20server.listen("3000", async () => {
21 try {
22 await client.connect();
23 collection = client.db("gamedev").collection("scores");
24 collection.createIndex({ "location": "2dsphere" });
25 } catch (e) {
26 console.error(e);
27 }
28});
O JavaScript acima tem muitos códigos padrão que não entrará em detalhes. Se você quiser aprender como se conectar ao MongoDB com o Node.js, confira o tutorialde introdução deLauron Scheefer sobre o assunto.
Há algumas linhas para as quais gostaria de chamar a atenção, começando com a criação do objetoMongoClient :
1const client = new MongoClient(process.env["ATLAS_URI"]);
Neste exemplo, ATLAS_URI é uma variável de ambiente no meu computador e o Node.js está lendo a partir dessa variável. Para contexto, a variável se parece com algo assim:
1mongodb+srv://<username>:<password>@cluster0-yyarb.mongodb.net/<database>?retryWrites=true&w=majority
Independentemente de como você deseja criar o MongoClient, verifique se o URL de conexão do MongoDB Atlas contém as informações corretas de nome de usuário e senha que você definiu no painel do MongoDB Atlas.
O próximo ponto para o qual quero chamar a atenção está nas duas linhas a seguir:
1collection = client.db("gamedev").collection("scores");
2collection.createIndex({ "location": "2dsphere" });
Neste exemplo, meu banco de dados é gamedev e minha collection é scores. Se você quiser usar seu próprio nome, basta trocar o que temos com seus próprios nomes de banco de dados e coleção. Além disso, como planejamos usar queries geoespaciais, precisaremos de um índice geoespacial. É aqui que entra ocomandocreateIndex. Quando lançamos nosso backend, o índice será criado para nós no campo location de nossos documentos, assim como especificamos em nosso comando.
Não precisamos de nenhum documento criado neste ponto, mas quando os documentos forem inseridos, eles terão a seguinte aparência:
1{
2 "_id": "23abcd87ef",
3 "username": "nraboy",
4 "score": 35,
5 "location": {
6 "type": "Point",
7 "coordinates": [ -121, 37 ]
8 }
9}
O campolocationé um objeto especial e formatado em conformidade com o GeoJSON. A formatação é importante quando se trata de consultas geoespaciais e do próprio índice.
Com a base do nosso backend criada, vamos começar a criar cada uma das funções do endpoint.
Como não temos dados para trabalhar, vamos começar com a função de endpointcreate :
1server.post("/create", async (request, response) => {
2 try {
3 let result = await collection.insertOne(
4 {
5 "username": request.body.username,
6 "score": request.body.score,
7 "location": request.body.location
8 }
9 );
10 response.send({ "_id": result.insertedId });
11 } catch (e) {
12 response.status(500).send({ message: e.message });
13 }
14});
Quando o cliente faz uma solicitação de POST, pegamos o username, scoree location da carga útil e a inserimos no MongoDB como um novo documento.O_idresultante será devolvido ao usuário quando for bem-sucedido. A validação de dados está fora do escopo deste tutorial, mas é importante observar que não estamos validando nenhum dos dados recebidos do usuário.
Agora que podemos criar pontuações, precisaremos de uma maneira de consultá-las. Analisando o endpointget, podemos fazer o seguinte:
1server.get("/get", async (request, response) => {
2 try {
3 let result = await collection.find({}).sort({ score: -1 }).limit(3).toArray();
4 response.send(result);
5 } catch (e) {
6 response.status(500).send({ message: e.message });
7 }
8});
No código acima, estamos usando o métodofind em nossa coleção sem filtro. Isso significa que tentaremos recuperar todos os documentos da collection. No entanto, também estamos usando sort e limit, que diz que queremos apenas três documentos em ordem decrescente.
Com esse endpoint configurado dessa maneira, podemos tratá-lo como uma função global que obtém as três principais pontuações de qualquer local. Perfeito para uma tabela de classificação!
Agora podemos restringir nossos resultados. Vamos dar uma olhada na funçãogetNearLocation:
1server.get("/getNearLocation", async (request, response) => {
2 try {
3 let result = await collection.find({
4 "location": {
5 "$near": {
6 "$geometry": {
7 "type": "Point",
8 "coordinates": [
9 parseFloat(request.query.longitude),
10 parseFloat(request.query.latitude)
11 ]
12 },
13 "$maxDistance": 25000
14 }
15 }
16 }).sort({ score: -1 }).limit(3).toArray();
17 response.send(result);
18 } catch (e) {
19 response.status(500).send({ message: e.message });
20 }
21});
Aqui, também usamos o métodofind, mas utilizamos uma consulta geoespacial como nosso filtro usando o operador$near. Quando passamos uma posição de latitude e longitude como parâmetros de consulta na solicitação, criamos uma consulta geoespacial que retorna qualquer documento com um local que esteja dentro de 25,000 metros da posição fornecida. Você pode alterar os números para obter os resultados de que precisa.
O backend deve estar Go. Certifique-se de que está executando o aplicativo Node.js antes de tentar jogar o jogo que criamos na próxima etapa.

Criando e configurando um novo projeto Phaser com geolocalização

Quando criarmos nosso jogo, vamos querer criar um novo diretório de projeto. No seu computador, crie um novo diretório de projeto e, nele, crie um arquivoindex.html com a seguinte marcação HTML:
1<!DOCTYPE html>
2<html>
3 <head>
4 <script src="//cdn.jsdelivr.net/npm/phaser@3.24.1/dist/phaser.min.js"></script>
5 <script src="information-scene.js"></script>
6 <script src="main-scene.js"></script>
7 <script src="gameover-scene.js"></script>
8 </head>
9 <body>
10 <div id="game"></div>
11 <script>
12
13 const phaserConfig = {
14 type: Phaser.AUTO,
15 parent: "game",
16 width: 1280,
17 height: 720,
18 dom: {
19 createContainer: true
20 },
21 physics: {
22 default: "arcade",
23 arcade: {
24 debug: false
25 }
26 },
27 scene: []
28 };
29
30 const game = new Phaser.Game(phaserConfig);
31
32 </script>
33 </body>
34</html>
O código acima não deve ser executado, mas é o ponto de partida do nosso jogo Phaser. Vamos detalhá-lo.
Dentro de <head>, você verá as seguintes tags<script>:
1<head>
2 <script src="//cdn.jsdelivr.net/npm/phaser@3.24.1/dist/phaser.min.js"></script>
3 <script src="information-scene.js"></script>
4 <script src="main-scene.js"></script>
5 <script src="gameover-scene.js"></script>
6</head>
O primeiro arquivo JavaScript é a estrutura do Faser. Os outros três são arquivos que criaremos. Você pode criar cada um dos três arquivos restantes agora ou esperar até chegarmos a essa etapa do tutorial.
Neste ponto do tutorial, a informação mais importante está aqui:
1const phaserConfig = {
2 type: Phaser.AUTO,
3 parent: "game",
4 width: 1280,
5 height: 720,
6 dom: {
7 createContainer: true
8 },
9 physics: {
10 default: "arcade",
11 arcade: {
12 debug: false
13 }
14 },
15 scene: []
16};
Na configuração acima, estamos definindo a tela do jogo, mas também estamos ativando o mecanismo de física e a incorporação de elementos DOM.
A incorporação do elemento DOM nos permite incorporar um campo de entrada de usuário ao nosso jogo para que os usuários possam digitar seu nome. O mecanismo de física nos permite lidar com as colisões entre o jogador e a recompensa, bem como entre o jogador e o obstáculo. Há vários mecanismos de física disponíveis no Phaser, mas usaremos a opção de físicaarcadepor ser a mais fácil de usar.
Como planejamos manter as pontuações dos jogadores com base em seu nome e localização, precisaremos ativar o rastreamento de localização. Na marcação <script>que contémphaserConfig, modifique-a para o seguinte:
1<script>
2
3 // phaserConfig ...
4
5 if (navigator.geolocation) {
6 navigator.geolocation.getCurrentPosition(position => {
7 if(!position.coords || !position.coords.longitude) {
8 position.coords.latitude = 0;
9 position.coords.longitude = 0;
10 }
11 const game = new Phaser.Game(phaserConfig);
12 });
13 } else {
14 console.error("Geolocation is not supported by this browser!");
15 }
16
17</script>
O código acima aproveitará o rastreamento de localização do navegador da web. Quando o jogo começar, o navegador solicitará que o usuário ative o rastreamento de localização. Se a localização não puder ser determinada, valores zero serão usados. Se o rastreamento de localização não estiver disponível no navegador, um erro será exibido nos registros e o jogo falhará na configuração.
No momento, nada foi feito com a localização, mas depois que o usuário aceitar, o jogo começará. No entanto, como ainda não temos nenhuma cena criada, nada acontecerá.
É importante observar que entre obter a localização e iniciar o jogo, pode levar de alguns segundos a alguns minutos. Isso depende da rapidez com que o navegador pode detectar sua localização.
Com a configuração concluída, vamos criar a primeira cena que o jogador verá.

Criar um cenário de jogo para a entrada do usuário com HTML e JavaScript

A primeira cena que o jogador vê solicitará que ele insira um nome de usuário. Este nome de usuário será enviado ao nosso backend e armazenado no MongoDB com uma pontuação.
Exemplo de Phaser com MongoDB Leaderboard, cena de entrada do usuário
Exemplo de Phaser com MongoDB Leaderboard, cena de entrada do usuário
Se ainda não o fez, crie um arquivoinformation-scene.js e inclua o seguinte código:
1var InformationScene = new Phaser.Class({
2 Extends: Phaser.Scene,
3 initialize: function () {
4 Phaser.Scene.call(this, { key: "InformationScene" });
5 },
6 init: function (data) { },
7 preload: function () { },
8 create: async function () { },
9 update: function () { }
10});
A classe acima representa nossa cenário inicial. Nela, você verá os quatro eventos do ciclo de vida que o Stager usa para compor uma cenário. Como essa imagem requer a entrada do usuário, precisamos criar outro arquivo HTML que contenha nosso formulário.
Crie um arquivoformulário.html dentro do projeto do jogo e adicione a seguinte marcação HTML:
1<!DOCTYPE html>
2<html>
3 <head>
4 <style>
5 #input-form {
6 padding: 15px;
7 background-color: #CCCCCC;
8 }
9 #input-form input {
10 padding: 10px;
11 font-size: 20px;
12 width: 400px;
13 }
14 </style>
15 </head>
16 <body>
17 <div id="input-form">
18 <input type="text" name="username" placeholder="Enter a Name" />
19 </div>
20 </body>
21</html>
Não há nada particularmente elegante acontecer no HTML acima. No entanto, observe o atributo namena marcação <input> . Faremos referência a ele mais tarde em nosso arquivoinformation-scene.js.
Retornando ao arquivoinformation-scene.js, precisamos carregar o arquivo HTML que acabamos de criar na função preload:
1preload: function () {
2 this.load.html("form", "form.html");
3},
Com o formulário HTML carregado em nossa cenário, agora podemos trabalhar para exibi-lo e capturar quaisquer dados inseridos nele. Isso pode ser feito a partir da funçãocreateda seguinte forma:
1create: async function () {
2
3 this.usernameInput = this.add.dom(640, 360).createFromCache("form");
4
5 this.returnKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.ENTER);
6
7 this.returnKey.on("down", event => {
8 let username = this.usernameInput.getChildByName("username");
9 if(username.value != "") {
10 // Switch scene ...
11 }
12 })
13
14},
Na funçãopreload, referenciamos o arquivo HTML como form, que será adicionado como um elemento DOM na tela do Phaser. Queremos ouvir eventos em uma tecla específica, neste caso a tecla Enter, e quando a tecla Enter é pressionada, queremos obter os dados do elementousername. Lembra do atributoname na tag<input>? Esse é o valor que estamos usando na funçãogetChildByName.
Neste ponto, temos tecnicamente uma cena funcional, embora nada aconteça com a entrada do usuário. Vamos editar o arquivoindex.html para podermos alternar para ele:
1<script>
2
3 const phaserConfig = {
4 type: Phaser.AUTO,
5 parent: "game",
6 width: 1280,
7 height: 720,
8 dom: {
9 createContainer: true
10 },
11 physics: {
12 default: "arcade",
13 arcade: {
14 debug: false
15 }
16 },
17 scene: [InformationScene]
18 };
19
20 if (navigator.geolocation) {
21 navigator.geolocation.getCurrentPosition(position => {
22 if(!position.coords || !position.coords.longitude) {
23 position.coords.latitude = 0;
24 position.coords.longitude = 0;
25 }
26 const game = new Phaser.Game(phaserConfig);
27 game.scene.start("InformationScene", {
28 location: {
29 type: "Point",
30 coordinates: [
31 parseFloat(position.coords.longitude.toFixed(1)),
32 parseFloat(position.coords.latitude.toFixed(1))
33 ]
34 }
35 })
36 });
37 } else {
38 console.error("Geolocation is not supported by this browser!");
39 }
40
41</script>
Se você está se perguntando o que mudou, primeiro dê uma olhada no campo scenedo objeto phaserConfig . Observe que incluímos a classeInformationScenena array. Em seguida, dê uma olhada no que acontece depois que obtemos as informações de geolocalização do navegador:
1game.scene.start("InformationScene", {
2 location: {
3 type: "Point",
4 coordinates: [
5 parseFloat(position.coords.longitude.toFixed(1)),
6 parseFloat(position.coords.latitude.toFixed(1))
7 ]
8 }
9})
Essas alterações nos permitem iniciar a cenárioInformationScene e passar a ela as informações de localização que recebem do navegador. Para manter minha privacidade no exemplo, estou definindo a precisão decimal da latitude e longitude para apenas um único decimal. Dessa forma, ela mostra minha localização geral, mas não exatamente onde vivo.
As informações de localização são formatadas como GeoJSON apropriado, pois é disso que o MongoDB dependerá mais tarde.
Então, se estamos passando informações para a nossa cena, como podemos usá-las?
Abra o arquivoinformation-scene.js e altere a funçãoinit para ficar assim:
1init: function (data) {
2 this.location = data.location;
3},
Estamos pegando os dados que foram passados e armazenando-os em uma variável local para a classe. Quando mudarmos dessa cena para outra cena no futuro, passaremos a variável novamente.
Antes de começarmos a trabalhar na próxima cenário, vamos exibir as informações de localização abaixo da entrada de texto. Na função create do arquivoinformation-scene.js, adicione o seguinte:
1this.locationText = this.add.text(
2 640,
3 425,
4 `[${this.location.coordinates[1]}, ${this.location.coordinates[0]}]`,
5 {
6 fontSize: 20
7 }
8).setOrigin(0.5);
O código acima renderiza tudo o que está na variável location. Ele deve estar centralizado abaixo do campo de entrada nessa cena específica do jogo.
Mesmo que a próxima etapa ainda não exista, vamos colocar a lógica de mudança em funcionamento. Vamos mudar a lógica que acontece quando a tecla Enter é pressionada depois de digitar um nome de usuário:
1this.returnKey.on("down", event => {
2 let username = this.usernameInput.getChildByName("username");
3 if(username.value != "") {
4 this.scene.start("MainScene", { username: username.value, score: 0, location: this.location });
5 }
6})
Chamaremos a próxima cenário MainScene e passaremos a ela o nome de usuário que o usuário forneceu, a localização do navegador e uma pontuação inicial.

Desenvolvendo a lógica do jogo para uma experiência de jogo interativa

Temos um nome de usuário e alguns dados de localização para trabalhar. Agora é hora de criar o jogo que o usuário pode realmente jogar.
Exemplo de tabela de classificação Phaser com MongoDB, cena de jogo
Exemplo de tabela de classificação Phaser com MongoDB, cena de jogo
Crie um arquivoprincipal-scene.js em seu projeto se você ainda não tiver feito isso e inclua o seguinte código:
1var MainScene = new Phaser.Class({
2 Extends: Phaser.Scene,
3 initialize: function() {
4 Phaser.Scene.call(this, { key: "MainScene" });
5 },
6 init: function(data) {
7 this.username = data.username;
8 this.score = data.score;
9 this.location = data.location;
10 },
11 preload: function() { },
12 create: function() { },
13 update: function() { }
14});
Você notará que estamos aceitando os dados passados da cena anterior na init função . Antes de começarmos a modificar as outras funções do ciclo de vida, vamos adicionar esta cena ao phaserConfig encontrado no arquivoindex.html :
1scene: [InformationScene, MainScene]
Como MainScene representa nossa cena de jogo, precisamos pré-carregar nossos recursos de jogo.
Você pode baixar meus ativos abaixo ou usar suas próprias imagens.
Gráfico de folha do MongoDB Gráfico do jogo Bomb Gráfico do jogo de caixa
As imagens reais não são muito importantes desde que algo exista. Dependendo da resolução de suas imagens, talvez seja necessário alterar o dimensionamento que faremos mais adiante no tutorial.
Com os arquivos de imagem em seu projeto, altere a funçãopreload do arquivoprincipal-scene.js para ficar assim:
1preload: function() {
2 this.load.image("leaf", "leaf.png");
3 this.load.image("bomb", "bomb.png");
4 this.load.image("box", "box.png");
5},
Só vamos ter uma única caixa para representar nosso jogador, mas teremos muitos dos objetos de jogoleaf e bomb . Isso significa que precisaremos criar dois pools de objetos e um único ator para o jogador.
Se você é novo no conceito de pools de objetos, eles são comuns quando se trata de desenvolvimento de jogos. A ideia por trás deles é que, em vez de criar e destruir objetos do jogo conforme necessário, o que é ruim para o desempenho, um número específico de objetos é criado antecipadamente e esses objetos permanecem inativos e invisíveis até que sejam necessários. Quando o objeto não é mais necessário, ele é desativado e tornado invisível, voltando para a piscina para ser usado novamente no futuro.
Na funçãocreate do arquivomain-scene.js, adicione o seguinte código JavaScript:
1create: function() {
2 this.player = this.physics.add.sprite(640, 650, "box");
3 this.player.setScale(0.25);
4 this.player.setDepth(1);
5 this.player.setData("score", this.score);
6 this.player.setData("username", this.username);
7
8 this.leafGroup = this.physics.add.group({
9 defaultKey: "leaf",
10 maxSize: 30,
11 visible: false,
12 active: false
13 });
14
15 this.bombGroup = this.physics.add.group({
16 defaultKey: "bomb",
17 maxSize: 30,
18 visible: false,
19 active: false
20 });
21},
Lembra das informações de pontuação e nome de usuário que foram passadas da cena anterior? Estamos anexando esses dados ao player (linhas 5-6).
Para leafGroup e bombGroup, estamos criando pools de objetos. Esses pools de objetos têm trinta objetos cada e são inativos e invisíveis por padrão. Isso é conveniente, pois não queremos exibi-los ou ativá-los até que estejamos prontos para usá-los.
A pontuação está sendo rastreada no jogador, mas provavelmente é uma boa ideia mostrá-la também. Dentro da funçãocreate da função principal-scene.js, adicione o seguinte:
1create: function() {
2
3 // Sprite and object pool creation logic ...
4
5 this.scoreText = this.add.text(10, 10, "SCORE: 0", { fontSize: 40, color: '#000000', fontStyle: "bold", backgroundColor: "#FFFFFF", padding: 10 });
6 this.scoreText.setDepth(1);
7
8}
No código acima, estamos inicializando nosso texto a ser renderizado com formatação específica. Vamos alterá-la mais tarde à medida que a pontuação aumentar.
Embora não estejamos usando nossos pools de objetos no momento, vamos definir alguma lógica de colisão para quando eles colidirem com o nosso jogador.
Na função create do arquivoprincipal-scene.js, adicione o seguinte:
1create: function() {
2
3 // Sprite and object pool creation logic ...
4 // Render text ...
5
6 this.physics.add.collider(this.player, this.leafGroup, (player, leaf) => {
7 if (leaf.active) {
8 this.score = player.getData("score");
9 this.score++;
10 player.setData("score", this.score);
11 this.scoreText.setText("SCORE: " + this.score);
12 this.leafGroup.killAndHide(leaf);
13 }
14 });
15
16 this.physics.add.collider(this.player, this.bombGroup, (player, bomb) => {
17 if (bomb.active) {
18 this.bombGroup.killAndHide(bomb);
19 // Change scenes ...
20 }
21 });
22
23}
Temos dois colliders no código acima. Um com lógica para determinar o que acontece quando uma folha toca o jogador e outro para quando uma bomba toca o jogador. Quando a folha toca o jogador, precisamos primeiro nos certificar de que a folha estava ativa. Quando um objeto está ativo, determinadas lógicas de jogo e de cena podem ser aplicadas. Se a folha estava ativa, precisamos obter a pontuação atual do objeto player, aumentá-la, definir a nova pontuação no objetoplayer e atualizar o texto renderizado. Também precisamos adicionar a folha de volta ao pool para que ela possa ser usada novamente.
Quando a bomba toca o jogador, precisamos ter certeza de que a bomba está ativa e, se estiver, adicioná-la de volta à piscina e mudar o cenário. Trabalharemos na lógica para mudar a cenário em breve.
Agora é um bom momento para extrair objetos de ambos os nossos pools de objetos. Dentro da funçãocreate do arquivoprincipal-scene.js, adicione o seguinte código JavaScript:
1create: function() {
2
3 // Sprite and object pool creation logic ...
4 // Render text ...
5 // Collider logic ...
6
7 this.time.addEvent({
8 delay: 250,
9 loop: true,
10 callback: () => {
11 let leafPositionX = Math.floor(Math.random() * 1280);
12 let bombPositionX = Math.floor(Math.random() * 1280);
13 this.leafGroup.get(leafPositionX, 0)
14 .setScale(0.1)
15 .setActive(true)
16 .setVisible(true);
17 this.bombGroup.get(bombPositionX, 0)
18 .setScale(0.1)
19 .setActive(true)
20 .setVisible(true);
21 }
22 });
23
24}
No código acima, estamos criando um temporizador de repetição. Toda vez que o cronômetro é acionado, puxamos uma folha e uma bomba das piscinas de objetos e as colocamos em uma posição aleatória no eixo x. Também ativamos esse objeto específico e o tornamos visível.
Não tente extrair mais objetos do que os existentes no pool, caso contrário, você receberá erros se o pool estiver vazio.
O cronômetro está puxando objetos, mas esses objetos ainda não estão se movendo. Precisamos movê-los na funçãoupdate do arquivomain-scene.js. Altere a função para ficar assim:
1update: function() {
2 this.leafGroup.incY(6);
3 this.leafGroup.getChildren().forEach(leaf => {
4 if (leaf.y > 800) {
5 this.leafGroup.killAndHide(leaf);
6 }
7 });
8 this.bombGroup.incY(6);
9 this.bombGroup.getChildren().forEach(bomb => {
10 if (bomb.y > 800) {
11 this.bombGroup.killAndHide(bomb);
12 }
13 });
14}
Tanto para leafGroup quanto parabombGroup, estamos aumentando a posição no eixo y. Estamos fazendo isso para cada objeto nesses pools de objetos. Embora estejamos mudando a posição de toda a pool, você só verá e interagirá com os objetos ativos e visíveis.
Para evitar que fiquemos sem objetos no pool, podemos percorrer cada pool e ver se algum objeto se moveu além da tela. Se tiverem, adicione-os de volta ao pool.
Então, a partir de agora, temos objetos cair da tela em nossa cenário. Se o jogador tocar em qualquer um dos objetos de folha, nossa pontuação aumentará; caso contrário, as bombas encerrarão a cena. O problema é que ainda não podemos controlar nosso jogador. No entanto, esta é uma solução fácil.
Na função update do arquivoprincipal-scene.js, adicione o seguinte:
1update: function() {
2
3 // Object pool movement logic ...
4
5 if (this.input.activePointer.isDown) {
6 this.player.x = this.input.activePointer.position.x;
7 }
8
9}
Agora, quando o ponteiro estiver para baixo, seja no celular ou no computador, a posição do eixo x do player será atualizada para onde quer que o ponteiro esteja. Para este jogo em particular, não nos daremos ao trabalho de atualizar a posição do eixo y.
Com exceção de mudar desta cena atual para a próxima, temos um jogo jogável.
Vamos voltar à lógica do colisor da bomba:
1this.physics.add.collider(this.player, this.bombGroup, (player, bomb) => {
2 if (bomb.active) {
3 this.bombGroup.killAndHide(bomb);
4 this.scene.start("GameOverScene", {
5 "username": this.player.getData("username"),
6 "score": this.player.getData("score"),
7 "location": this.location
8 });
9 }
10});
Embora não tenhamos criado uma cenárioGameOverScene, presumimos que criamos. Então, se colidirmos com uma arma, o GameOverScene começará e passaremos as informações de nome de usuário, pontuação e localização para a próxima etapa.

Criação, consulta e exibição de informações de classificação armazenadas no MongoDB

A cena final é onde realmente incluímos o MongoDB em nosso jogo. Definimos nossas informações, jogamos nosso jogo e agora precisamos enviá-las para o MongoDB para armazenamento.
Exemplo de tabela de classificação do Phaser com MongoDB, cena de Game Over
Exemplo de tabela de classificação do Phaser com MongoDB, cena de Game Over
Se ainda não o fez, crie um arquivogameover-scene.js no diretório do seu projeto com o seguinte código:
1var GameOverScene = new Phaser.Class({
2 Extends: Phaser.Scene,
3 initialize: function() {
4 Phaser.Scene.call(this, { key: "GameOverScene" });
5 },
6 init: function(data) {
7 this.player = data;
8 },
9 preload: function() {},
10 create: async function() {},
11 update: function() {}
12});
A classe acima deve parecer familiar, pois foi usada como base para as duas classes anteriores. Na funçãoinit aceitamos o nome de usuário, pontuação e dados de localização da cena anterior. Antes de começarmos a definir nossa funcionalidade de cena, vamos adicionar a cena ao arquivoindex.html no objetophaserConfig:
1scene: [InformationScene, MainScene, GameOverScene]
Como não temos nenhum ativo de jogo, não precisamos usar a funçãopreload dentro do arquivojogoover-scene.js. Em vez disso, vamos dar uma olhada na função create:
1create: async function() {
2 try {
3 if (this.player.username && this.player.score) {
4 await fetch("http://localhost:3000/create", {
5 "method": "POST",
6 "headers": {
7 "content-type": "application/json"
8 },
9 "body": JSON.stringify(this.player)
10 });
11 }
12
13 this.globalScores = await fetch("http://localhost:3000/get")
14 .then(response => response.json());
15
16 this.nearbyScores = await fetch(`http://localhost:3000/getNearLocation?latitude=${this.player.location.coordinates[1]}&longitude=${this.player.location.coordinates[0]}`)
17 .then(response => response.json());
18 } catch (e) {
19 console.error(e);
20 }
21
22},
Na funçãocreateacima , estamos fazendo três solicitações. Primeiro, pegamos as informações do jogador fornecidas na cena anterior e as enviamos para nosso back-end por meio do endpointcreate. Depois de enviarmos nossos dados de pontuação, fazemos duas solicitações, a primeira para as três melhores pontuações globais e a segunda para as três melhores pontuações próximas à minha latitude e longitude.
A próxima etapa é renderizar os resultados dessas solicitações na tela como texto.
1create: async function() {
2 try {
3
4 // REST API logic ...
5
6 this.add.text(10, 100, "GLOBAL HIGH SCORES", { fontSize: 40, color: '#000000', fontStyle: "bold", backgroundColor: "#FFFFFF", padding: 10 });
7 this.add.text(600, 100, "NEARBY HIGH SCORES", { fontSize: 40, color: '#000000', fontStyle: "bold", backgroundColor: "#FFFFFF", padding: 10 });
8
9 this.add.text(10, 10, "YOUR SCORE: " + this.player.score, { fontSize: 40, color: '#000000', fontStyle: "bold", backgroundColor: "#FFFFFF", padding: 10 });
10
11 for(let i = 0; i < this.globalScores.length; i++) {
12 this.add.text(10, 100 * (i + 2), `${this.globalScores[i].username}: ${this.globalScores[i].score}`, { fontSize: 40, color: '#000000', fontStyle: "bold", backgroundColor: "#FFFFFF", padding: 10 });
13 }
14
15 for(let i = 0; i < this.nearbyScores.length; i++) {
16 this.add.text(600, 100 * (i + 2), `${this.nearbyScores[i].username}: ${this.nearbyScores[i].score}`, { fontSize: 40, color: '#000000', fontStyle: "bold", backgroundColor: "#FFFFFF", padding: 10 });
17 }
18
19 this.retryButton = this.add.text(1125, 640, "RETRY", { fontSize: 40, color: '#000000', fontStyle: "bold", backgroundColor: "#FFFFFF", padding: 10 });
20 this.retryButton.setInteractive();
21
22 this.retryButton.on("pointerdown", () => {
23 this.scene.start("MainScene", { username: this.player.username, score: 0, location: this.player.location });
24 }, this);
25
26 } catch (e) {
27 console.error(e);
28 }
29},
O código acima pode parecer confuso, mas a realidade é que estamos apenas renderizando texto na tela. Estamos analisando os resultados de ambas as pontuações e rasgando os resultados e também estamos renderizando a pontuação atual.
Para repetir o jogo, criamos um texto clicável:
1this.retryButton = this.add.text(1125, 640, "RETRY", { fontSize: 40, color: '#000000', fontStyle: "bold", backgroundColor: "#FFFFFF", padding: 10 });
2this.retryButton.setInteractive();
3
4this.retryButton.on("pointerdown", () => {
5 this.scene.start("MainScene", { username: this.player.username, score: 0, location: this.player.location });
6}, this);
Ao clicar no botão, o MainScene é iniciado e as informações do jogador atual são enviadas.

Conclusão

Você acabou de ver como trabalhar com informações de placar de líderes usando o MongoDB em um jogoPhaser. Nesse exemplo específico, vimos dois tipos diferentes de tabelas de classificação, uma global e outra geoespacial. Outra possibilidade que não exploramos pode ser no âmbito da plataforma, como móvel ou desktop.
Se você quiser ver outro exemplo de jogo com o MongoDB, confira meu tutorial anterior intitulado Creating a Multiplayer Drawing Game with Phaser and MongoDB (Criando um jogo de desenho para vários jogadores com Phaser e MongoDB).

Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Tutorial

Construir um elemento de formulário de preenchimento automático com Atlas Search e JavaScript


Sep 09, 2024 | 8 min read
Tutorial

Anúncio UDF do MongoDB para modelos do BigQuery Dataflow


Apr 02, 2024 | 4 min read
Tutorial

Introdução ao MongoDB e ao AWS Codewhisperer


Sep 26, 2024 | 3 min read
Tutorial

Combinando seu banco de dados com o Azure Blob Storage usando o Data Federation


Oct 08, 2024 | 7 min read
Sumário