Introdução

Hoje vamos aprender como fazer um jogo usando React Native. Como estamos usando React Native, este jogo será multiplataforma, o que significa que você pode jogar o mesmo jogo no Android, iOS e na web também. Hoje, no entanto, vamos nos concentrar apenas em dispositivos móveis. Então, vamos começar.

Primeiros passos

Para fazer qualquer jogo, precisamos de um loop que atualize nosso jogo enquanto jogamos. Este loop é otimizado para executar o jogo sem problemas e, para esse propósito, usaremos o React Native Game Engine .

Primeiro, vamos criar um novo aplicativo React Native com o seguinte comando:

 npx react-nativa init ReactNativeGame

Depois de criar o projeto, precisamos adicionar uma dependência para adicionar um motor de jogo:

 npm i-S react-native-game-engine

Este comando adicionará React Native Game Engine ao nosso projeto.

Então, que tipo de jogo vamos fazer? Para simplificar, vamos fazer um jogo com uma cobra que come pedaços de comida e cresce em comprimento.

Uma breve introdução ao React Native Game Engine

React Native Game Engine é um motor de jogo leve. Inclui um componente que nos permite adicionar matrizes de objetos como entidades para que possamos manipulá-los. Para escrever nossa lógica para o jogo, usamos uma série de adereços de sistema que nos permitem manipular entidades (objetos do jogo), detectar toques e muitos outros detalhes incríveis que nos ajudam a fazer um jogo simples e funcional.

Vamos construir um jogo de cobra no React Native

Para fazer um jogo, precisamos de uma tela ou contêiner onde adicionaremos objetos do jogo. Para fazer uma tela, simplesmente adicionamos um componente de visualização com estilo como:

//App.js


E podemos adicionar nosso estilo assim:

 const styles=StyleSheet.create ({ tela de pintura: { flex: 1, backgroundColor:"# 000000", alignItems:"center", justifyContent:"center", }
});

Na tela, usaremos o componente GameEngine com alguns estilos do React Native Game Engine:

 import {GameEngine} de"react-native-game-engine";
import React, {useRef} de"react";
importar constantes de"./Constantes"; função padrão de exportação App () { const BoardSize=Constants.GRID_SIZE * Constants.CELL_SIZE; const engine=useRef (null); Retorna (   
);

Também adicionamos um ref ao mecanismo de jogo usando o useRef () React Hook para uso posterior.

Também criamos um arquivo Constants.js na raiz do projeto para armazenar nossos valores constantes:

//Constants.js
import {Dimensions} from"react-native";
export default { MAX_WIDTH: Dimensions.get ("tela"). Largura, MAX_HEIGHT: Dimensions.get ("tela"). Height, GRID_SIZE: 15, CELL_SIZE: 20
};

Você notará que estamos fazendo uma grade de 15 por 15 por onde nossa cobra se moverá.

Neste momento, nosso motor de jogo está configurado para mostrar a cobra e sua comida. Precisamos adicionar entidades e adereços ao componente GameEngine , mas antes disso precisamos criar um componente cobra e comida que será renderizado no dispositivo.

Criação de entidades de jogo

Vamos primeiro fazer a cobra. A cobra é dividida em duas partes, a cabeça e o corpo (ou cauda). Por enquanto, faremos a cabeça da cobra e adicionaremos a cauda da cobra posteriormente neste tutorial.

Para fazer a cabeça da cobra, faremos um componente Cabeça na pasta de componentes:

Como você pode ver, temos três componentes: Head , Food e Tail . Veremos o que há dentro desses arquivos um por um neste tutorial.

No componente Head , criaremos uma visualização com alguns estilos:

 import React from"react";
import {View} de"react-native";
função padrão de exportação Head ({position, size}) { Retorna (   );
}

Vamos passar alguns adereços para definir o tamanho e a posição da cabeça.

Estamos usando a propriedade position:"absolute" para mover facilmente a cabeça.

Isso renderizará um quadrado e não usaremos nada mais complexo; um quadrado ou retângulo para o corpo da cobra e um círculo para a comida.

Agora vamos adicionar a cabeça desta cobra ao GameEngine .

Para adicionar qualquer entidade, precisamos passar um objeto nas propriedades entity do GameEngine :

//App.js
import Head from"./components/Head"; , } }}
/>

Passamos um objeto para as entidades prop com a chave head. Estas são as propriedades que ele define:

  • posição é um conjunto de coordenadas para colocar a cabeça da cobra
  • size é o valor para definir o tamanho da cabeça da cobra
  • xspeed e yspeed são os valores que determinam o movimento e a direção da cobra e podem ser 1, 0 ou-1. Observe que quando xspeed é definido como 1 ou-1, o valor de yspeed deve ser 0 e vice-versa
  • Finalmente, o renderer é responsável por renderizar o componente
  • updateFrequency e nextMove serão discutidos mais tarde.

Depois de adicionar o componente Head , vamos adicionar outros componentes também:

//commponets/Food/index.js
importar React de"react";
import {View} de"react-native";
função padrão de exportação Food ({position, size}) { Retorna (   );
}

O componente Food é semelhante ao componente Head , mas mudamos a cor de fundo e o raio da borda para torná-lo um círculo.

Agora crie um componente Tail . Este pode ser complicado:

//componentes/Tail/index.js importar React de"react";
import {View} de"react-native";
importar constantes de"../../Constantes";
função padrão de exportação Tail ({elements, position, size}) { const tailList=elements.map ((el, idx)=> (  )); Retorna (  {tailList}  );
}

Quando a cobra comer a comida, adicionaremos um elemento no corpo da cobra para que ela cresça. Esses elementos passarão para o componente Tail , o que indicará que ele deve ficar maior.

Faremos um loop por todos os elementos para criar todo o corpo da cobra, anexá-lo e, em seguida, renderizar.

Depois de fazer todos os componentes necessários, vamos adicionar esses dois componentes como entidades GameEngine :

//App.js importar alimentos de"./components/Food";
importar cauda de"./components/Tail"; //App.js
const randomPositions=(min, max)=> { retornar Math.floor (Math.random () * (max-min + 1) + min); }; //App.js , }, Comida: { posição: [ randomPositions (0, Constants.GRID_SIZE-1), randomPositions (0, Constants.GRID_SIZE-1), ], tamanho: Constants.CELL_SIZE, renderizador: , }, cauda: { tamanho: Constants.CELL_SIZE, elementos: [], renderizador: , }, }} />

Para garantir a aleatoriedade da posição dos alimentos, criamos uma função randomPositions com parâmetros mínimo e máximo.

No cauda , adicionamos uma matriz vazia no estado inicial, de modo que quando a cobra comer a comida, ela armazenará cada comprimento da cauda nos elementos : espaço.

Neste ponto, criamos com sucesso nossos componentes de jogo. Agora é hora de adicionar lógica de jogo ao loop de jogo.

Lógica do jogo

Para fazer um loop de jogo, o componente GameEngine tem um prop chamado sistemas que aceita uma série de funções.

Para manter tudo estruturado, estou criando uma pasta chamada sistemas e inserindo um arquivo chamado GameLoop.js :

Neste arquivo, estamos exportando uma função com certos parâmetros:

//GameLoop.js função padrão de exportação (entidades, {eventos, despacho}) { ... retornar entidades;
}

O primeiro parâmetro é entidades , que contém todas as entidades que passamos para o componente GameEngine para que possamos manipulá-las. Outro parâmetro é um objeto com propriedades, ou seja, events e dispatch .

Movendo a cabeça da cobra

Vamos escrever o código para mover a cabeça da cobra na direção certa.

Na função GameLoop.js , vamos atualizar a posição da cabeça, pois esta função será chamada em cada frame:

//GameLoop.js
função padrão de exportação (entidades, {eventos, despacho}) { const head=entity.head; head.position [0] +=head.xspeed; head.position [1] +=head.yspeed;
}

Estamos acessando a cabeça usando o parâmetro entidades e em cada quadro estamos atualizando a posição da cabeça da cobra.

Se você jogar agora, nada acontecerá porque definimos xspeed e yspeed como 0. Se você definir xspeed ou yspeed para 1, a cabeça da cobra se moverá muito rápido.

Para diminuir a velocidade da cobra, usaremos os valores nextMove e updateFrequency como este:

 const head=entity.head; head.nextMove-=1;
if (head.nextMove===0) { head.nextMove=head.updateFrequency; head.position [0] +=head.xspeed; head.position [1] +=head.yspeed;
}

Estamos atualizando o valor de nextMove para 0 subtraindo por 1 em cada quadro. Quando o valor é 0, a condição if é definida como true e o valor nextMove é atualizado de volta para o valor inicial, movendo assim o valor da cobra cabeça.

Agora, a velocidade da cobra deve ser mais lenta do que antes.

”Fim do jogo!” condições

Neste ponto, não adicionamos um “Fim de jogo!” doença. O primeiro “Fim de jogo!” condição é quando a cobra toca a parede, o jogo para de rodar e mostra ao usuário uma mensagem para indicar que o jogo acabou.

Para adicionar esta condição, estamos usando este código:

 if (head.nextMove===0) { head.nextMove=head.updateFrequency; E se ( head.position [0] + head.xspeed <0 || head.position [0] + head.xspeed>=Constants.GRID_SIZE || head.position [1] + head.yspeed <0 || head.position [1] + head.yspeed>=Constants.GRID_SIZE ) { despacho ("game-over"); } senão { head.position [0] +=head.xspeed; head.position [1] +=head.yspeed; }

A segunda condição if é verificar se a cabeça da cobra tocou as paredes ou não. Se essa condição for verdadeira, então estamos despachando um evento "game-over" usando a função dispatch .

Com o else , estamos atualizando a posição da cabeça da cobra.

Agora vamos adicionar o “Fim do jogo!” funcionalidade.

Sempre que enviarmos um evento "game-over", pararemos o jogo e mostraremos um alerta que dirá “Game over!” Vamos implementar isso.

Para ouvir o evento "game-over", precisamos passar o prop onEvent para o componente GameEngine . Para parar o jogo, precisamos adicionar um prop running e passar useState .

Nosso GameEngine deve ser semelhante a:

//App.js
import React, {useRef, useState} de"react";
importar GameLoop de"./systems/GameLoop"; ....
.... const [isGameRunning, setIsGameRunning]=useState (true); ....
.... , }, Comida: { posição: [ randomPositions (0, Constants.GRID_SIZE-1), randomPositions (0, Constants.GRID_SIZE-1), ], tamanho: Constants.CELL_SIZE, renderizador: , }, cauda: { tamanho: Constants.CELL_SIZE, elementos: [], renderizador: , }, }} sistemas={[GameLoop]} running={isGameRunning} onEvent={(e)=> { switch (e) { caso"game-over": alerta ("Fim do jogo!"); setIsGameRunning (false); Retorna; } }} />

No GameEngine , adicionamos o prop systems e passamos uma matriz com nossa função GameLoop , junto com um em execução prop com um estado isGameRunning . Finalmente, adicionamos o prop onEvent , que aceita uma função com um parâmetro de evento para que possamos ouvir nossos eventos.

Nesse caso, estamos ouvindo o evento "game-over" na instrução switch, portanto, quando recebemos o evento, mostramos a mensagem "Game over!" alert e definir o estado isGameRunning como false para parar o jogo.

Comendo a comida

Escrevemos o “Fim do jogo!” lógica, agora vamos trabalhar na lógica de fazer a cobra comer a comida.

Quando a cobra come a comida, a posição da comida deve mudar aleatoriamente.

Abra GameLoop.js e escreva o código abaixo:

//GameLoop.js const randomPositions=(min, max)=> { retornar Math.floor (Math.random () * (max-min + 1) + min);
}; função padrão de exportação (entidades, {eventos, despacho}) { const head=entity.head; const food=entity.food; .... .... .... E se ( head.position [0] + head.xspeed <0 || head.position [0] + head.xspeed>=Constants.GRID_SIZE || head.position [1] + head.yspeed <0 || head.position [1] + head.yspeed>=Constants.GRID_SIZE ) { despacho ("game-over"); } senão { head.position [0] +=head.xspeed; head.position [1] +=head.yspeed; E se ( head.position [0]==food.position [0] && head.position [1]==food.position [1] ) { food.position=[ randomPositions (0, Constants.GRID_SIZE-1), randomPositions (0, Constants.GRID_SIZE-1), ]; } }

Adicionamos uma condição if para verificar se a cabeça da cobra e a posição da comida são as mesmas (o que indicaria que a cobra “comeu” a comida). Então, estamos atualizando a posição da comida usando a função randomPositions como fizemos acima em App.js . Observe que estamos acessando a comida a partir do parâmetro entidades .

Controlando a cobra

Agora vamos adicionar os controles da cobra. Usaremos botões para controlar para onde a cobra se move.

Para fazer isso, precisamos adicionar botões na tela abaixo da tela:

//App.js import React, {useRef, useState} de"react";
import {StyleSheet, Text, View} de"react-native";
importar {GameEngine} de"react-native-game-engine";
importar {TouchableOpacity} de"react-native-gesture-handler";
importar alimentos de"./components/Food";
import Head from"./components/Head";
importar cauda de"./components/Tail";
importar constantes de"./Constantes";
importar GameLoop de"./systems/GameLoop";
função padrão de exportação App () { const BoardSize=Constants.GRID_SIZE * Constants.CELL_SIZE; const engine=useRef (null); const [isGameRunning, setIsGameRunning]=useState (true); const randomPositions=(min, max)=> { retornar Math.floor (Math.random () * (max-min + 1) + min); }; const resetGame=()=> { engine.current.swap ({ cabeça: { posição: [0, 0], tamanho: Constants.CELL_SIZE, updateFrequency: 10, nextMove: 10, velocidade x: 0, velocidade y: 0, renderizador: , }, Comida: { posição: [ randomPositions (0, Constants.GRID_SIZE-1), randomPositions (0, Constants.GRID_SIZE-1), ], tamanho: Constants.CELL_SIZE, updateFrequency: 10, nextMove: 10, velocidade x: 0, velocidade y: 0, renderizador: , }, cauda: { tamanho: Constants.CELL_SIZE, elementos: [], renderizador: , }, }); setIsGameRunning (true); }; Retorna (  , }, Comida: { posição: [ randomPositions (0, Constants.GRID_SIZE-1), randomPositions (0, Constants.GRID_SIZE-1), ], tamanho: Constants.CELL_SIZE, renderizador: , }, cauda: { tamanho: Constants.CELL_SIZE, elementos: [], renderizador: , }, }} sistemas={[GameLoop]} running={isGameRunning} onEvent={(e)=> { switch (e) { caso"game-over": alerta ("Fim do jogo!"); setIsGameRunning (false); Retorna; } }} />    engine.current.dispatch ("mover para cima")}>      engine.current.dispatch ("mover para a esquerda")} >     engine.current.dispatch ("move-right")} >      engine.current.dispatch ("mover para baixo")} >     {! isGameRunning && (   Começar novo jogo   )}  );
}
estilos const=StyleSheet.create ({ tela de pintura: { flex: 1, backgroundColor:"# 000000", alignItems:"center", justifyContent:"center", }, controlContainer: { marginTop: 10, }, controllerRow: { flexDirection:"row", justifyContent:"center", alignItems:"center", }, controlBtn: { backgroundColor:"amarelo", largura: 100, altura: 100, },
});

Além dos controles, também adicionamos um botão para iniciar um novo jogo quando o anterior terminar. Este botão só aparece quando o jogo não está rodando. Ao clicar nesse botão, estamos reiniciando o jogo usando a função swap do mecanismo de jogo, passando o objeto inicial da entidade e atualizando o estado de execução do jogo.

Agora, para os controles. Nós adicionamos tocáveis, que, quando pressionados, despacham os eventos que serão tratados no loop do jogo:

//GameLoop.js
....
.... função padrão de exportação (entidades, {eventos, despacho}) { const head=entity.head; const food=entity.food; if (events.length) { events.forEach ((e)=> { switch (e) { case"move-up": if (head.yspeed===1) return; head.yspeed=-1; head.xspeed=0; Retorna; caso"mover para a direita": if (head.xspeed===-1) return; head.xspeed=1; head.yspeed=0; Retorna; caso"mover para baixo": if (head.yspeed===-1) return; head.yspeed=1; head.xspeed=0; Retorna; caso"mover para a esquerda": if (head.xspeed===1) return; head.xspeed=-1; head.yspeed=0; Retorna; } }); } ....
....
});

No código acima, adicionamos uma instrução switch para identificar os eventos e atualizar a direção da cobra.

Ainda está comigo? Excelente! A única coisa que resta é a cauda.

Funcionalidade de cauda

Quando a cobra come a comida, queremos que a sua cauda cresça. Também queremos enviar uma mensagem de “Fim de jogo!” evento quando a cobra morde a própria cauda ou corpo.

Vamos adicionar lógica final:

//GameLoop.js const tail=entity.tail; ....
.... .... senão { tail.elements=[[head.position [0], head.position [1]],... tail.elements]; tail.elements.pop (); head.position [0] +=head.xspeed; head.position [1] +=head.yspeed; tail.elements.forEach ((el, idx)=> { E se ( head.position [0]===el [0] && head.position [1]===el [1] ) despacho ("game-over"); }); E se ( head.position [0]==food.position [0] && head.position [1]==food.position [1] ) { tail.elements=[ [head.position [0], head.position [1]], ... tail.elements, ]; food.position=[ randomPositions (0, Constants.GRID_SIZE-1), randomPositions (0, Constants.GRID_SIZE-1), ]; } }

Para fazer a cauda seguir a cabeça da cobra, estamos atualizando os elementos da cauda. Fazemos isso adicionando a posição da cabeça ao início da matriz de elementos e, em seguida, removendo o último elemento da matriz de elementos da cauda.

Agora, depois disso, escrevemos uma condição de que, se a cobra morder o próprio corpo, despachamos o evento "game-over".

E, finalmente, sempre que a cobra come a comida, estamos acrescentando os elementos da cauda pela posição atual da cabeça para aumentar o comprimento da cauda.

Aqui está o código completo de GameLoop.js :

//GameLoop.js importar constantes de"../Constantes";
const randomPositions=(min, max)=> { retornar Math.floor (Math.random () * (max-min + 1) + min);
}; função padrão de exportação (entidades, {eventos, despacho}) { const head=entity.head; const food=entity.food; const tail=entity.tail; if (events.length) { events.forEach ((e)=> { switch (e) { case"move-up": if (head.yspeed===1) return; head.yspeed=-1; head.xspeed=0; Retorna; caso"mover para a direita": if (head.xspeed===-1) return; head.xspeed=1; head.yspeed=0; //ToastAndroid.show ("mover para a direita", ToastAndroid.SHORT); Retorna; caso"mover para baixo": if (head.yspeed===-1) return; //ToastAndroid.show ("mover para baixo", ToastAndroid.SHORT); head.yspeed=1; head.xspeed=0; Retorna; caso"mover para a esquerda": if (head.xspeed===1) return; head.xspeed=-1; head.yspeed=0; //ToastAndroid.show ("mover para a esquerda", ToastAndroid.SHORT); Retorna; } }); } head.nextMove-=1; if (head.nextMove===0) { head.nextMove=head.updateFrequency; E se ( head.position [0] + head.xspeed <0 || head.position [0] + head.xspeed>=Constants.GRID_SIZE || head.position [1] + head.yspeed <0 || head.position [1] + head.yspeed>=Constants.GRID_SIZE ) { despacho ("game-over"); } senão { tail.elements=[[head.position [0], head.position [1]],... tail.elements]; tail.elements.pop (); head.position [0] +=head.xspeed; head.position [1] +=head.yspeed; tail.elements.forEach ((el, idx)=> { console.log ({el, idx}); E se ( head.position [0]===el [0] && head.position [1]===el [1] ) despacho ("game-over"); }); E se ( head.position [0]==food.position [0] && head.position [1]==food.position [1] ) { tail.elements=[ [head.position [0], head.position [1]], ... tail.elements, ]; food.position=[ randomPositions (0, Constants.GRID_SIZE-1), randomPositions (0, Constants.GRID_SIZE-1), ]; } } } retornar entidades;
}

Conclusão

Agora seu primeiro jogo no React Native está pronto! Você pode executar este jogo em seu próprio dispositivo para jogá-lo. Espero que você tenha aprendido algo novo e que possa compartilhar com seus amigos também.

Obrigado por ler e tenha um ótimo dia.

A postagem Como construir um jogo simples no React Native apareceu primeiro no LogRocket Blog .

Source link