Está en la página 1de 37

UNIVERSIDAD PRIVADA

DOMINGO SAVIO

PROYECTO FINAL
“APLICACION ANDROID CON REACT
NATIVE”
(Arcade Classics)

ESTUDIANTE: JOSE MANUEL TAPIA


MARTINEZ
MATERIA: APLICACIONES MOVILES I
DOCENTE: ING. JAIME ZAMBRANA
CHACON
CARRERA: ING. DE SISTEMAS
pág. 1
Santa Cruz de la sierra – 25 de noviembre 2022
“ARCADE CLASSICS” – REACT NATIVE APP

a. INTRODUCCION
La siguiente aplicación ha sido desarrollada utilizando React Native para la
plataforma de Android, enfocada en el entretenimiento y apoyada de
diversas tecnologías de programación como JavaScript otorga una interfaz
intuitiva y fácil de manejar con un menú en la parte inferior donde
podremos seleccionar entre tres diferentes juegos del arcade clásico
intentando capturar el interés del usuario y brindarle una experiencia de
entretenimiento única en la palma de la mano.

b. MARCO TEORICO
Arcade Classics ha sido desarrollado utilizando la tecnología de REACT-
NATIVE, este un framework de programación de aplicaciones nativas
multiplataforma que está basado en JavaScript y ReactJS.
Creado en Facebook y publicado en Febrero de 2015 React Native es un
framework para la creación de aplicaciones nativas para iOS y Android.
Para programar aplicaciones usando React Native usamos como lenguaje
de programación JavaScript, en lugar de Swift, JAVA u otros lenguajes y
por el resto usamos React para la creación de la interfaz del usuario.

pág. 2
A diferencia de otras soluciones como Ionic, Cordova o Phonegap, React
Native no utiliza WebViews ni produce HTML o CSS, es decir, no usa
tecnologías web para la interfaz de tu aplicación. La pregunta es, si no usa
estas tecnologías cómo es que podemos usar React y JavaScript para la
creación de aplicaciones nativas.
Saber que las aplicaciones de React Native no son 100% nativas, es el
primer paso para entender qué está sucediendo internamente para que una
app escrita en JavaScript se ejecute de manera “nativa” entre comillas.
Las aplicaciones de React Native se componen de 3 elementos
principalmente:
1. Código nativo: Un proyecto de React Native puede contener código
nativo en combinación con el código de JavaScript, es decir, si estás
desarrollando una app para Android podrías tener código de JAVA
aparte del proyecto de JavaScript.
2. JavaScriptCore VM: Las aplicaciones de ReactNative utilizan
JavaScriptCore, el motor de ejecución de JavaScript de Safari para
ejecutar el código de JavaScript de nuestra app, esto significa que el
código que escribas de JavaScript no será compilado, será ejecutado
como JavaScript dentro de tu app, por eso decimos que no es 100%
nativo, porque internamente hay una máquina virtual que ejecuta el
código de JavaScript.
3. React Native Bridge: Como su nombre lo dice, el React Native
Bridge es un puente que se encarga de comunicar el código de la
máquina virtual de JavaScript con el código nativo y las APIs nativas
de la plataforma en la que se ejecuta nuestra aplicación.
Quizás hayas escuchado de un concepto muy importante de las
aplicaciones de React, el Virtual DOM. El Virtual DOM es una
representación virtual, como su nombre indica, de cómo se deben mostrar
los componentes en la interfaz, de cómo se hará el render.
En los navegadores web, el virtual DOM se representa a través del
Document Object Model con elementos de HTML, la ventaja de que el
Virtual DOM sea una capa intermedia entre el código y la representación
final de nuestra app permite que modifiquemos dicha representación
basado en la descripción del virtual DOM, piensa en el Virtual DOM como
una especificación textual de cómo debe verse la app, digamos, habrá un
text input aquí, un botón de color rojo allá, etc. Después esta especificación

pág. 3
debe trasladarse a los componentes de la plataforma donde se verá, de
nuevo, en el caso de una página web esto significa elementos de HTML.

En efecto para el desarrollo de Arcade Classics se opto por incluir algunos


frameworks de React Native para la elaboración de videojuegos entre los
cuales cabe destacar los siguientes.

React Native Game Engine


Este es un framework nativo exclusivo para elaboración de videojuegos en
react native, el cual nos proporciona una facilidad al momento de manejar
las físicas de juego y también las pantallas de carga del mismo, y para
poder integrarlo en nuestro proyecto solo debemos instanciar algunas líneas
de código dentro de nuestra terminal e importarlo dentro de nuestras clases.
Matter.js
Esta es una librería de Javascript el cual consiste en un motor de físicas de
2 dimensiones, nos felicita el manejo de diferentes figuras geométricas para
la elaboración de objetos simples y complejos que podemos manipular en
cuanto a colisiones, gravedad y otros aspectos, el cual a sido instanciado en
este proyecto para para la elaboración de uno de los videojuegos que
requiere acciones físicas, es capaz de manejar cuerpos rígidos, cuerpos
compuesto, cóncavos y convexos, además de detectar propiedades fisicas
tales como masa, área, densidad, y más.

c. DESARROLLO DE LA APP MOVIL


Para la elaborar nuestro proyecto en React Native necesitamos crear
nuestro proyecto y utilizar un editor de código como en mi caso, Visual
Studio Code.
Tendremos que instalar Node.js y npm además utilizaremos expo como
gestor de paquetes.
Comenzamos abriendo nuestra terminal y digitando los siguientes
comandos.

pág. 4
Con esto ya tendremos nuestro proyecto creado de React Native
Podemos ejecutarlo enseguida con el comando

Con el proyecto listo y el emulador activo procedemos a instalar las


dependencias necesarias para ejecutar juegos en la interfaz

React native game engine


Procedemos a instalar el paquete de game engine

Y luego lo importamos

Matter.js
pág. 5
Procedemos a instalar el paquete matter.js

Y luego lo importamos

ELABORACION DE LA INTERFAZ
Primero incorporamos un bottom navigator para que sea nuestro menú de la
aplicación. Para esto fue necesario elaborar un componente llamado
MainContainer, el cual llama a diferentes clases de acuerdo con su menú.

Código empleado en el Bottom Navigator :

import * as React from 'react';


import { StatusBar } from 'expo-status-bar';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import Ionicons from 'react-native-vector-icons/Ionicons';

// Screens
import GameOne from './screens/GameOne';
import GameTwo from './screens/GameTwo';
import GameThree from './screens/GameThree';
import GameFour from './screens/GameFour';

//Screen names
const gameOne = "Memory";
const gameTwo = "Snake";
const gameThree = "Flappy";
const gameFour = "About Us"

const Tab = createBottomTabNavigator();

function MainContainer() {
  return (
    <NavigationContainer>
      <Tab.Navigator
        initialRouteName={gameOne}
        screenOptions={({ route }) => ({

pág. 6
          tabBarIcon: ({ focused, color, size }) => {
            let iconName;
            let rn = route.name;

            if (rn === gameOne) {


              iconName = focused ? 'rocket' : 'rocket-outline';

            } else if (rn === gameTwo) {


              iconName = focused ? 'paw' : 'paw-outline';

            } else if (rn === gameThree) {


              iconName = focused ? 'paper-plane' : 'paper-plane-outline';
      
            } else if (rn === gameFour) {
              iconName = focused ? 'book' : 'book-outline';
      }
      

            // You can return any component that you like here!


            return <Ionicons name={iconName} size={size} color={color} />;
          },
        })}
        tabBarOptions={{
          activeTintColor: 'white',
          inactiveTintColor: 'white',        
          labelStyle: { paddingBottom: 10, fontSize: 14 },
          style: {
            padding: 10,
            height: 70,
            margin: 15,
            position: 'absolute',
            marginTop: 0,
            borderRadius: 100/2,
            backgroundColor: 'tomato',
            shadowColor: "gray",
            shadowOffset: {
              width: 0,
              height: 12,
            },
            shadowOpacity: 0.58,
            shadowRadius: 16.00,

            elevation: 24,
     }
        }}>

pág. 7
        <Tab.Screen name={gameOne} component={GameOne} />
        <Tab.Screen name={gameTwo} component={GameTwo} />
        <Tab.Screen name={gameThree} component={GameThree} />
        <Tab.Screen name={gameFour} component={GameFour} />

      </Tab.Navigator>
      <StatusBar style="auto" />
    </NavigationContainer>
  );
}

export default MainContainer;

de esta manera el código empleado en el componente principal App.js para


la ejecución de la aplicación se resume en lo siguiente:
import * as React from 'react';
import MainContainer from './navigation/MainContainer';
import { StatusBar } from 'expo-status-bar';
import { View } from 'react-native';

function App() {
  return (
      <MainContainer/>
  );
}

export default App;

con todo esto preparado ahora es cuestión de enfocar nuestras vistas en


cada una de las clases instanciadas dentro del MainContainer.

A continuación, la programación de las diferentes


Screens del Menú.

pág. 8
CRAZY CARDS

Comenzamos con la clase GameOne, es un divertido memorama de frutas


que tiene una lógica bastante simple, desarollada enteramente con
JavaScript.

import * as React from "react";


import { StatusBar } from 'expo-status-bar';
import { Button, StyleSheet, Text, View, TouchableOpacity, Image, ImageBackground }
from 'react-native';
import Card from '../components/Card';

const cards = [
  "🍇",
  "🍉",
  "🍏",
  "🍊",
  "🍍",
  "🍌",
];

export default function GameOne() {


  const [board, setBoard] = React.useState(() => shuffle([...cards, ...cards]));
  const [selectedCards, setSelectedCards] = React.useState([]);
  const [matchedCards, setMatchedCards] = React.useState([]);
  const [score, setScore] = React.useState(0);

  const localImagen = require('../img/game1.jpg')


  const title = require('../img/crazycard.png')
  const winner = require('../img/winner.png')
 
  React.useEffect(() => {
    if(selectedCards.length < 2) return;
    if(board[selectedCards[0]] === board[selectedCards[1]]) {
      setMatchedCards([...matchedCards, ...selectedCards])
      setSelectedCards([]);
    } else {

pág. 9
      const timeOutId = setTimeout(() => setSelectedCards([]), 500);
      return () => clearTimeout(timeOutId);
  }
  }, [selectedCards])

  const handleTapCard = index => {


    if(selectedCards.length >= 2 || selectedCards.includes(index)) return;
    setSelectedCards([...selectedCards, index]);
    setScore(score + 1);  
 }

  const didPlayerWin = () => matchedCards.length === board.length;


 
  const resetGame = () => {
    setMatchedCards([]);
    setSelectedCards([]);
    setScore(0);
 }

  return (
    <ImageBackground source={localImagen} style={styles.container}>
   
      <View style={styles.title2}>{didPlayerWin() ?
            <Image source={winner}
              style={{
                  resizeMode:'cover',
                  height: 150,
                  width: 350,
              }}
            />
      
       :
       
              <Image source={title}
                style={{
                    resizeMode:'cover',
                    height: 150,
                    width: 350,
                }}
              />
      
      }
      </View>
   
      <Text style={styles.title}>Score : {score}</Text>
pág. 10
      <View style={styles.board}>
        {board.map((card, index) => {
          const isTurnedOver = selectedCards.includes(index) || matchedCards.includes(index);
          return(
            <Card
              key={index}
              isTurnedOver={isTurnedOver}
              onPress={() => handleTapCard(index)}
            >{card}</Card>
     )

        })}
      </View>
      {didPlayerWin() &&
   
      <TouchableOpacity style={{ backgroundColor: 'tomato', paddingHorizontal: 30,
paddingVertical: 10, borderRadius: 100/2, position: 'relative', bottom: 70}}
            onPress={resetGame}>
            <Text style={{ fontWeight: 'bold', color: 'white', fontSize: 30 }}>
              Jugar De Nuevo
            </Text>
      </TouchableOpacity>
   }
      <StatusBar hidden={true} />
    </ImageBackground>
  );
}

const styles = StyleSheet.create({


  container: {
    flex: 1,
    backgroundColor: 'white',
    alignItems: 'center',
    justifyContent: 'center',
  },
  title: {
    fontSize: 32,
    color: 'white',
    fontWeight: 'bold',
    margin: 5,
    position: 'relative',
    bottom: 80,
    backgroundColor: 'tomato',
    borderRadius:50/2,
    paddingHorizontal: 20,
    paddingVertical: 5

pág. 11
  },
  title2: {
    position: 'relative',
    bottom: 40,
    justifyContent: 'center',
    alignItems: 'center',
 

  },
  titleBtn: {
    padding: 25,
    paddingBottom: 4,
    backgroundColor:'tomato',
    borderRadius:50/2,
    fontWeight: 'bold',
    margin: 15,
    position: 'relative',
    bottom: 60,
     
  },
  board: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    justifyContent: 'center',
    position: 'relative',
    bottom: 80,
 }
});

//Devuelve el ArrayCards mezclado en orden aleatorio

function shuffle(array) {
  for (let i = array. length - 1; i > 0; i--){
    const randomIndex = Math.floor(Math.random() * (i + 1));
  
    //Intercambia los elementos en i y randomIndex
    [array[i], array[randomIndex]] = [array[randomIndex], array[i]];
 }
  return array;
}

pág. 12
Para evitar la sobrecarga de código se utilizó un componente externo
llamado card.js que es únicamente una tarjeta props con parámetros
modificables.
import * as React from 'react';
import { Pressable, Text, StyleSheet } from 'react-native';

export default function Card({onPress, isTurnedOver, children}) {


    return(
        <Pressable
            style={isTurnedOver ? styles.cardUp : styles.cardDown}
            onPress={onPress}
    >
            {isTurnedOver ? (
                <Text style={styles.text}>{children}</Text>
            ):(
                <Text style={styles.text}>❓</Text>
            )}
      
        </Pressable>
    );
}

const styles = StyleSheet.create({


    cardUp: {
        width: 100,
        height: 100,
        margin: 10,
        borderWidth: 10,
        borderColor:'tomato',
        backgroundColor:'#FFF',
        alignItems: 'center',
        justifyContent: 'center',
        borderRadius:50/2,
        shadowColor: "gray",
        shadowOffset: {
            width: 0,
            height: 12,
        },
        shadowOpacity: 0.58,
        shadowRadius: 16.00,

        elevation: 24,
    },
    cardDown: {
        width: 100,

pág. 13
        height: 100,
        margin: 10,
        borderWidth: 10,
        borderColor:'tomato',
        backgroundColor:'#FFD2BB',
        alignItems: 'center',
        justifyContent: 'center',
        borderRadius:50/2,
        shadowColor: "gray",
        shadowOffset: {
            width: 0,
            height: 12,
        },
        shadowOpacity: 0.58,
        shadowRadius: 16.00,

        elevation: 24,
    },
    text: {
        fontSize: 46,
        color: 'black',
  }
})

THE SNAKE GAME

Ahora utilizamos todo el arsenal de código y las librerías


predispuestas para la elaboración de GameTwo.js, se
trata de un videojuego indie de la vieja arcade THE
SNAKE un control simple de una serpiente y el objetivo
de conseguir el alimento para poder crecer
indefinidamente con el único propósito de alcanzar el
máximo tamaño.

pág. 14
En su programación encontramos el código principal que encapsula las
diferentes clases que se mencionaran a continuación.
import React, { Component } from "react";
import { AppRegistry, StyleSheet,ImageBackground, StatusBar, SafeAreaView, View,
Alert, Text, Button, TouchableOpacity } from "react-native";
import { GameEngine, dispatch } from "react-native-game-engine";
import { Head } from "../components/head";
import { Food } from "../components/food";
import { Tail } from "../components/tail";
import { GameLoop } from "../components/systems";
import Constants from '../components/Constants';

export default class GameTwo extends Component {


    constructor(props) {
        super(props);
        this.boardSize = Constants.GRID_SIZE * Constants.CELL_SIZE;
        this.engine = null;
        this.state = {
            running: true
    }
  }

    randomBetween = (min, max) => {


        return Math.floor(Math.random() * (max - min + 1) + min);
  }

    onEvent = (e) => {


        if (e.type === "game-over"){
            this.setState({
                running: false
            });
            Alert.alert("Haz Perdido (💀) \n Presiona REINICIAR ✅🐍");
    }
  }

    reset = () => {
        this.engine.swap({
            head: { position: [0,  0], xspeed: 1, yspeed: 0, nextMove: 10, updateFrequency: 10,
size: 20, renderer: <Head />},
            food: { position: [this.randomBetween(0, Constants.GRID_SIZE - 1),
this.randomBetween(0, Constants.GRID_SIZE - 1)], size: 20, renderer: <Food />},
            tail: { size: 20, elements: [], renderer: <Tail /> }
        });
        this.setState({
            running: true

pág. 15
        });
  }

    render() {
        const localImagen = require('../img/game2.jpg')
        return (
            <ImageBackground source={localImagen} style={styles.container}>
        
                <View>
                <TouchableOpacity style={{ backgroundColor: 'tomato', paddingHorizontal: 30,
paddingVertical: 10, borderRadius: 100/2, position: 'relative', bottom: 10}}
                    onPress={this.reset
                    }>
                    <Text style={{ fontWeight: 'bold', color: 'white', fontSize: 30 }}>
                    Reiniciar 🐍
                    </Text>
                </TouchableOpacity>
                </View>

                <GameEngine
                    ref={(ref) => { this.engine = ref; }}
                    style={[{ width: this.boardSize, height: this.boardSize, backgroundColor:
'#ffffff', flex: null, borderColor: 'black', borderWidth: 2, marginBottom: 20}]}
                    systems={[ GameLoop ]}
                    entities={{
                        head: { position: [0,  0], xspeed: 1, yspeed: 0, nextMove: 10,
updateFrequency: 10, size: 20, renderer: <Head />},
                        food: { position: [this.randomBetween(0, Constants.GRID_SIZE - 1),
this.randomBetween(0, Constants.GRID_SIZE - 1)], size: 20, renderer: <Food />},
                        tail: { size: 20, elements: [], renderer: <Tail /> }
                    }}
                    running={this.state.running}
                    onEvent={this.onEvent}>

                    <StatusBar hidden={true} />

                </GameEngine>

                <View style={styles.controls}>
                    <View style={styles.controlRow}>
                        <TouchableOpacity onPress={() => { this.engine.dispatch({ type: "move-
up" })} }>
              
                            <View style={styles.control}>
                                <Text style={{fontWeight: 'bold', fontSize: 80}}>⬆️</Text>
                            </View>

pág. 16
              
                        </TouchableOpacity>
                    </View>
                    <View style={styles.controlRow}>
                        <TouchableOpacity onPress={() => { this.engine.dispatch({ type: "move-
left" })} }>
                            <View style={styles.control}>
                                <Text style={{fontWeight: 'bold', fontSize: 80}}>⬅️</Text>
                            </View>
                        </TouchableOpacity>
                        <View style={[styles.control, { backgroundColor: null}]} />
                        <TouchableOpacity onPress={() => { this.engine.dispatch({ type: "move-
right" })}}>
                            <View style={styles.control}>
                                <Text style={{fontWeight: 'bold', fontSize: 80}}>➡️</Text>
                            </View>
                        </TouchableOpacity>
                    </View>
                    <View style={styles.controlRow}>
                        <TouchableOpacity onPress={() => { this.engine.dispatch({ type: "move-
down" })} }>
                            <View style={styles.control}>
                                <Text style={{fontWeight: 'bold', fontSize: 80}}>⬇️</Text>
                            </View>
                        </TouchableOpacity>
                    </View>
                </View>

                <View>
                    <Text>jas</Text>
                </View>
            </ImageBackground>
      
        );
  }
}

const styles = StyleSheet.create({


    container: {
        flex: 1,
        backgroundColor: '#FFA580',
        alignItems: 'center',
        justifyContent: 'center'
    },
    controls: {
        width: 300,

pág. 17
        height: 300,
        flexDirection: 'column',
        marginBottom: 40,
        position: 'relative',
        bottom: 20,
    },
    controlRow: {
        height: 100,
        width: 300,
        alignItems: 'center',
        justifyContent: 'center',
        flexDirection: 'row'
    },
    control: {
        width: 100,
        height: 100,
        alignItems: 'center',
        justifyContent: 'center',
    
    },
    titleBtn: {
        paddingHorizontal: 25,
        marginBottom: 5,
        borderWidth: 5,
        backgroundColor:'tomato',
        borderColor:'white',
        borderRadius:50/2,
        position: 'relative',
        bottom: 10,
      },
});

AppRegistry.registerComponent("Snake", () => GameTwo);

Código de la clase Constants que establece las configuraciones de


dimensión para la pantalla de juego.
import { Dimensions } from 'react-native';

export default Constants = {


    MAX_WIDTH: Dimensions.get("screen").width,
    MAX_HEIGHT: Dimensions.get("screen").height,
    GRID_SIZE: 15,
    CELL_SIZE: 20
}

pág. 18
Código de Clase Systems donde podremos configurar las variables de
juego , velocidad, tamaño y demás.
import React, { Component } from "react";
import Constants from './Constants';

const randomBetween = (min, max) => {


    return Math.floor(Math.random() * (max - min + 1) + min);
}

const GameLoop = (entities, { touches, dispatch, events }) => {


    let head = entities.head;
    let food = entities.food;
    let tail = entities.tail;

    if (events.length){
        for(let i=0; i<events.length; i++){
            if (events[i].type === "move-down" && head.yspeed != -1){
                head.yspeed = 1;
                head.xspeed = 0;
            } else if (events[i].type === "move-up" && head.yspeed != 1){
                head.yspeed = -1;
                head.xspeed = 0;
            } else if (events[i].type === "move-left" && head.xspeed != 1){
                head.yspeed = 0;
                head.xspeed = -1;
            } else if (events[i].type === "move-right" && head.xspeed != -1){
                head.yspeed = 0;
                head.xspeed = 1;
      }
    }
  }

 
    head.nextMove -= 1;
    if (head.nextMove === 0){
        head.nextMove = head.updateFrequency;
        if (
            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
    ){
            // snake hits the wall
            dispatch({ type: "game-over" })
        } else {
pág. 19
            // move the tail
            let newTail = [[head.position[0], head.position[1]]];
            tail.elements = newTail.concat(tail.elements).slice(0, -1);

            // snake moves
            head.position[0] += head.xspeed;
            head.position[1] += head.yspeed;

            // check if it hits the tail


            for(let i=0; i<tail.elements.length; i++){
                if (tail.elements[i][0] === head.position[0] && tail.elements[i][1] ===
head.position[1]){
                    dispatch({ type: "game-over" })
        }
      }

            if (head.position[0] === food.position[0] && head.position[1] ===


food.position[1]){
                // eating Food
                tail.elements = [[food.position[0], food.position[1]]].concat(tail.elements);

                food.position[0] = randomBetween(0, Constants.GRID_SIZE - 1);


                food.position[1] = randomBetween(0, Constants.GRID_SIZE - 1);
      }
    }
  }

    return entities;
};

export { GameLoop };

ahora los componentes principales del juego SNAKE, entre ellos la comida
FOOD
import React, { Component } from "react";
import { StyleSheet, View } from "react-native";

class Food extends Component {


    constructor(props){
        super(props);
  }

    render() {
        const x = this.props.position[0];

pág. 20
        const y = this.props.position[1];
        return (
            <View style={[styles.finger, { width: this.props.size, height: this.props.size, left: x *
this.props.size, top: y * this.props.size }]} />
        );
  }
}

const styles = StyleSheet.create({


    finger: {
        backgroundColor: 'red',
        position: "absolute",
        borderRadius: 50/2
  }
});

export { Food };

a continuación debemos tener una clase especial para la cabeza de la


serpiente
import React, { Component } from "react";
import { StyleSheet, View } from "react-native";

class Head extends Component {


    constructor(props){
        super(props);
  }

    render() {
        const x = this.props.position[0];
        const y = this.props.position[1];
        return (
            <View style={[styles.finger, { width: this.props.size, height: this.props.size, left: x *
this.props.size, top: y * this.props.size }]} />
        );
  }
}

const styles = StyleSheet.create({


    finger: {
        backgroundColor: '#14BD00',
        position: "absolute"
  }
});

pág. 21
export { Head };

luego un componente para controlar el cuerpo de nuestra serpiente


import React, { Component } from "react";
import { StyleSheet, View } from "react-native";
import Constants from './Constants';

class Tail extends Component {


    constructor(props){
        super(props);
  }

    render() {

        let tailList = this.props.elements.map((el, idx) => {


            return <View key={idx} style={{ width: this.props.size, height: this.props.size,
position: 'absolute', left: el[0] * this.props.size, top: el[1] * this.props.size,
backgroundColor: '#1BFF00' }} />
        });

        return (
            <View style={{ width: Constants.GRID_SIZE * this.props.size, height:
Constants.GRID_SIZE * this.props.size }}>
                {tailList}
            </View>
        );
  }
}

const styles = StyleSheet.create({


    finger: {
        backgroundColor: '#888888',
        position: "absolute"
  }
});

export { Tail };

FLAPPY SQUARE

pág. 22
Este es un videojuego que utiliza fisicas mas avanzadas como el uso de
gravedad y componentes en 2D, con la finalidad de poder brindar una
experiencia de usuario de un pájaro que vuela a través de obstáculos
generados de manera automática.

en la pantalla de la clase GameFour tenemos nada mas que una pantalla de


carga con un simple botón, su código es el siguiente.
import { StatusBar } from 'expo-status-bar';
import React, { useState, useEffect } from 'react';
import { Text, TouchableOpacity, View, ImageBackground } from 'react-native';
import { GameEngine } from 'react-native-game-engine';
import entities from '../entities';
import Physics from '../../physics';

export default function GameThree({ navigation }) {


  const localImagen = require('../img/game3.jpg')
  const [running, setRunning] = useState(false)
  const [gameEngine, setGameEngine] = useState(null)
  const [currentPoints, setCurrentPoints] = useState(0)
  useEffect(() => {
    setRunning(false)
  }, [])
  return (
    <ImageBackground source={localImagen} style={{ flex: 1, backgroundColor:'cyan' }}>
      <Text style={{ textAlign: 'center', fontSize: 40, fontWeight: 'bold', margin:
20 }}>{currentPoints}</Text>
      <GameEngine
        ref={(ref) => { setGameEngine(ref) }}
        systems={[Physics]}
        entities={entities()}
        running={running}
        onEvent={(e) => {
          switch (e.type) {
            case 'game_over':
              setRunning(false)
              gameEngine.stop()
              break;
            case 'new_point':
              setCurrentPoints(currentPoints + 1)
              break;
     }
        }}

pág. 23
        style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }}
   >
        <StatusBar style="auto" hidden={true} />

      </GameEngine>

      {!running ?
        <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center'}}>
          <TouchableOpacity style={{ backgroundColor: 'tomato', paddingHorizontal: 30,
paddingVertical: 10, borderRadius: 100/2 }}
            onPress={() => {
              setCurrentPoints(0)
              setRunning(true)
              gameEngine.swap(entities())
            }}>
            <Text style={{ fontWeight: 'bold', color: 'white', fontSize: 30 }}>
              INICIAR 🐥
            </Text>
          </TouchableOpacity>

        </View> : null}
        <StatusBar hidden={true} />
    </ImageBackground>
  );
}

La pantalla de carga tiene la función de ejecutar el código del juego una


vez presionemos el botón de iniciar, lo que internamente hacemos es llamar
a la clase index.js que se encuentra en otra carpeta dentro del mismo
directorio del proyecto.
La clase index.js se encarga de montar los componentes en escena y de la
gestión del código, quien a su vez instancia los componentes principales
del juego como la clase bird.js, obstacle.js entre otros, a continuación, el
código de la clase index.js
import Matter from "matter-js"
import Bird from "../components/Bird";
import Floor from "../components/Floor";
import Obstacle from "../components/Obstacle";

import { Dimensions } from 'react-native'


import { getPipeSizePosPair } from "../utils/random";

const windowHeight = Dimensions.get('window').height

pág. 24
const windowWidth = Dimensions.get('window').width

export default restart => {


    let engine = Matter.Engine.create({ enableSleeping: false })

    let world = engine.world

    world.gravity.y = 0.4;

    const pipeSizePosA = getPipeSizePosPair()


    const pipeSizePosB = getPipeSizePosPair(windowWidth * 0.9)
    return {
        physics: { engine, world },

        Bird: Bird(world, 'green', { x: 50, y: 300 }, { height: 40, width: 40 }),

        ObstacleTop1: Obstacle(world, 'ObstacleTop1', 'red', pipeSizePosA.pipeTop.pos,


pipeSizePosA.pipeTop.size),
        ObstacleBottom1: Obstacle(world, 'ObstacleBottom1', 'blue',
pipeSizePosA.pipeBottom.pos, pipeSizePosA.pipeBottom.size),

        ObstacleTop2: Obstacle(world, 'ObstacleTop2', 'red', pipeSizePosB.pipeTop.pos,


pipeSizePosB.pipeTop.size),
        ObstacleBottom2: Obstacle(world, 'ObstacleBottom2', 'blue',
pipeSizePosB.pipeBottom.pos, pipeSizePosB.pipeBottom.size),

        Floor: Floor(world, '#7C5E31', { x: windowWidth / 2, y: windowHeight }, { height: 1,


width: windowWidth })
  }
}

Siendo la clase index.js la clase padre del juego Flappy Square, esta se
encarga de poner en escena los diferentes componentes, entre ellos la clase
physics.js esta clase utiliza la librería matter.js para la simulación de la
gravedad y la interacción de los objetos en escena dicho de otra forma las
colisiones, velocidad y movimientos en escena.
import Matter from "matter-js";
import { getPipeSizePosPair } from "./navigation/utils/random";

import { Dimensions } from 'react-native'

const windowHeight = Dimensions.get('window').height

pág. 25
const windowWidth = Dimensions.get('window').width

const Physics = (entities, { touches, time, dispatch }) => {


    let engine = entities.physics.engine

    touches.filter(t => t.type === 'press')


        .forEach(t => {
            Matter.Body.setVelocity(entities.Bird.body, {
                x: 0,
                y: -8
            })
        })

    Matter.Engine.update(engine, time.delta)

    for (let index = 1; index <= 2; index++) {

        if (entities[`ObstacleTop${index}`].body.bounds.max.x <= 50 && !


entities[`ObstacleTop${index}`].point) {
            entities[`ObstacleTop${index}`].point = true
            dispatch({ type: 'new_point' })

    }

        if (entities[`ObstacleTop${index}`].body.bounds.max.x <= 0) {
            const pipeSizePos = getPipeSizePosPair(windowWidth * 0.9);

            Matter.Body.setPosition(entities[`ObstacleTop${index}`].body,
pipeSizePos.pipeTop.pos)
            Matter.Body.setPosition(entities[`ObstacleBottom${index}`].body,
pipeSizePos.pipeBottom.pos)

            entities[`ObstacleTop${index}`].point = false
    }

        Matter.Body.translate(entities[`ObstacleTop${index}`].body, { x: -3, y: 0 })
        Matter.Body.translate(entities[`ObstacleBottom${index}`].body, { x: -3, y: 0 })
  }

    Matter.Events.on(engine, 'collisionStart', (event) => {


        dispatch({ type: 'game_over' })
    })
    return entities;
}
export default Physics
pág. 26
a continuación cabe destacar un resumen de los componentes principales de
la escena de juego Flappy Square, como primer lugar la clase bird.js quien
es el jugador del juego.
import Matter from 'matter-js'
import React from 'react'
import { View } from 'react-native'

const Bird = props => {


    const widthBody = props.body.bounds.max.x - props.body.bounds.min.x
    const heightBody = props.body.bounds.max.y - props.body.bounds.min.y

    const xBody = props.body.position.x - widthBody /2


    const yBody = props.body.position.y - heightBody /2

    const color = props.color;

    return(
        <View style={{
            borderWidth: 2,
            borderColor: color,
            borderStyle: 'solid',
            backgroundColor: 'yellow',
            position: 'absolute',
            left: xBody,
            top: yBody,
            width: widthBody,
            height: heightBody
        }}/>
  )
}

export default (world, color, pos, size) => {


   const initialBird = Matter.Bodies.rectangle(
       pos.x,
       pos.y,
       size.width,
       size.height,
       {label: 'Bird'}
   )
   Matter.World.add(world, initialBird)

   return {
       body: initialBird,

pág. 27
       color,
       pos,
       renderer: <Bird/>
   }
}

Luego tenemos los obstáculos del mundo los cuales instanciamos en el


index.js desde la clase obstacles.js
import Matter from 'matter-js'
import React from 'react'
import { View } from 'react-native'

const Obstacle = props => {


    const widthBody = props.body.bounds.max.x - props.body.bounds.min.x
    const heightBody = props.body.bounds.max.y - props.body.bounds.min.y

    const xBody = props.body.position.x - widthBody / 2


    const yBody = props.body.position.y - heightBody / 2

    const color = props.color;

    return (
        <View style={{
            borderWidth: 2,
            borderColor: color,
            borderStyle: 'solid',
            position: 'absolute',
            backgroundColor:'#3AFF00',
            left: xBody,
            top: yBody,
            width: widthBody,
            height: heightBody
        }} />
  )
}

export default (world, label, color, pos, size) => {


    const initialObstacle = Matter.Bodies.rectangle(
        pos.x,
        pos.y,
        size.width,
        size.height,
    {

pág. 28
            label,
            isStatic: true
    }
  )
    Matter.World.add(world, initialObstacle)

    return {
        body: initialObstacle,
        color,
        pos,
        renderer: <Obstacle />
  }
}

Y finalmente otro componente que nos ayudara a detectar la colisión del


objeto jugador una vez que este entre en contacto con el piso esta clase se
llama Floor.js

import Matter from 'matter-js'


import React from 'react'
import { View } from 'react-native'

const Floor = props => {


    const widthBody = props.body.bounds.max.x - props.body.bounds.min.x
    const heightBody = props.body.bounds.max.y - props.body.bounds.min.y

    const xBody = props.body.position.x - widthBody /2


    const yBody = props.body.position.y - heightBody /2

    const color = props.color;

    return(
        <View style={{
            backgroundColor: color,
            position: 'absolute',
            left: xBody,
            top: yBody,
            width: widthBody,
            height: heightBody
        }}/>
  )
}

pág. 29
export default (world, color, pos, size) => {
   const initialFloor = Matter.Bodies.rectangle(
       pos.x,
       pos.y,
       size.width,
       size.height,
       {
           label: 'Floor',
           isStatic: true

    }
   )
   Matter.World.add(world, initialFloor)

   return {
       body: initialFloor,
       color,
       pos,
       renderer: <Floor/>
   }
}

De esa manera tenemos la programación de nuestro juego Flappy Square,


cabe destacar que la generación de todos estos elementos dentro de la
pantalla de carga es un bucle infinito y el límite es la colisión del objeto
jugador con los obstáculos o el piso además incorpora un contador en la
parte superior que nos va indicando cada que logramos sobrepasar un
obstáculo.

PANTALLA FINAL DE PRESENTACION

Esta pantalla final de presentación también


denominad como GameFour.js dentro del directorio
del proyecto no es mas que, la descripción del
pág. 30
desarrollador, y consta de componentes y assets de diseño nativos de react
native.

A continuacion el código empleado para la clase de presentación.


import * as React from 'react';
import { View, Text, Image, ImageBackground, StatusBar } from 'react-native';

export default function GameFour({ navigation }) {


    const localImagen = require('../img/pngegg.png')
    const tapiaCode = require('../img/tapiaCode.jpg')
    const arcade = require('../img/arcade.png')
    const topText = require('../img/desarrolladoPor.png')
    const bottomText = require('../img/josemanuel.png')

    return (
        <ImageBackground source={localImagen} style={{ flex: 1, alignItems: 'center',
justifyContent: 'center' }}>
      
            <Image source={topText}
                style={{
                    resizeMode:'cover',
                    height: 100,
                    width: 350,
                }}
            />
      
            <Image source={tapiaCode}
                style={{
                    resizeMode:'cover',
                    height: 250,
                    width: 250,
                    borderRadius: 300/2,
                    marginBottom: 20
                }}
            />
      
            <Image source={bottomText}
                style={{
                    resizeMode:'cover',

pág. 31
                    height: 100,
                    width: 350,
                }}
            />
      
            <Image source={arcade}
                style={{
                    resizeMode:'cover',
                    height: 100,
                    width: 250,
                    marginTop: 10
                }}
            />
            <StatusBar hidden={true} />
        </ImageBackground>
    );
}

Comentarios Finales
Para el desarrollo de este proyecto se empleo la siguiente paqueteria de
react native , además de usar como gestor de paquetes predeterminados la
SDK 42 de expo y una version anterior de react native, por motivos de
estabilidad.
"dependencies": {
    "@react-native-community/masked-view": "0.1.10",
    "@react-navigation/bottom-tabs": "^5.11.11",
    "@react-navigation/native": "^5.9.4",
    "@react-navigation/stack": "^5.14.5",
    "expo": "~42.0.0",
    "expo-font": "~9.2.1",
    "expo-status-bar": "~1.0.4",
    "expo-updates": "~0.8.2",
    "matter-js": "^0.18.0",
    "react": "16.13.1",
    "react-dom": "16.13.1",
    "react-native": "https://github.com/expo/react-native/archive/sdk-42.0.0.tar.gz",
    "react-native-game-engine": "^1.2.0",
    "react-native-gesture-handler": "~1.10.2",
    "react-native-reanimated": "~2.2.0",
    "react-native-safe-area-context": "3.2.0",
    "react-native-screens": "~3.4.0",
    "react-native-web": "~0.13.12"

pág. 32
  },
  "devDependencies": {
    "@babel/core": "^7.9.0"
  },
  "private": true
}

De esta manera finaliza este proyecto no sin antes mencionar la


compilación del proyecto en su respetivo archivo en formato .apk para la
plataforma de ANDROID, para lo cual recurrimos a los servicios de EXPO
quien automáticamente genera las firmas correspondientes para el control
de versiones de nuestra aplicación dentro de las plataformas de posteo
correspondientes, para ello es necesario crear una cuenta gratuita en la
plataforma web de Expo y gestionar la compilación a través de comandos.

Primero que nada debemos adicionar un script para la construcción del


proyecto dentro del archivo package.json :

Además, debemos modificar el archivo app.json con la configuración


correspondiente tal como vemos a continuación
{
  "expo": {
    "name": "Arcade Classics",
    "slug": "arcadeClassic",
    "version": "1.0.0",
    "orientation": "portrait",

pág. 33
    "icon": "./assets/icon.png",
    "splash": {
      "image": "./assets/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#ffffff"
    },
    "updates": {
      "fallbackToCacheTimeout": 0
    },
    "assetBundlePatterns": [
      "**/*"
    ],
    "ios": {
      "supportsTablet": true
    },
    "android": {
      "package": "com.tapiacode.arcadeclassic",
      "versionCode": 1
    },
    "web": {
      "favicon": "./assets/favicon.png"
  }
 }
}

A continuación, procedemos desde consola:

Aquí debemos seleccionar .apk y el proceso comienza demorando un


aproximado de 10 minutos

pág. 34
Para finalizar podemos ingresar a la web de expo con el siguiente enlace
https://expo.dev/accounts/tapiacode/projects/arcadeClassic/builds/
832fb046-20ce-446d-a64d-522f9051ae52
donde tenemos alojado el apk-release de nuestro proyecto compilado como
se observa a continuacion.

CONCLUSION
Para finalizar destacar que el uso de react native para desarrollo de
aplicaciones móviles hibridas resulto muy practico y una experiencia
entretenida que vale la pena destacar, por la gran facilidad con la gestión de
sus paquetes y la posibilidad de ser multiplataforma y además de generar
las llaves y compilación de manera rápida y eficiente para ambas
plataformas, quizá el manejo de sus componentes es algo tedioso cuando
comenzamos pero podemos acostumbrarnos con la practica y con la gran
cantidad de documentación en la web.

d. BIBLIOGRAFIA
pág. 35
Documentación en la web:
 https://reactnative.dev/docs/environment-setup
 https://www.npmjs.com/package/react-native-game-
engine#introduction
 https://www.npmjs.com/package/matter-js#features
 https://blog.logrocket.com/react-native-game-development-tutorial/
 https://ethercreative.github.io/react-native-shadow-generator/
 https://codewithbeto.dev/projects
 https://codewithbeto.dev/projects/memory-game
Documentación en YouTube:
 https://www.youtube.com/@betomoedano
 https://www.youtube.com/@AniaKubow
 https://www.youtube.com/@SimpleCoder
 https://www.youtube.com/watch?v=zK2xYD4Nytw&t=393s
 https://www.youtube.com/watch?v=0gFt7W3bhTY
 https://www.youtube.com/watch?v=IfRd9OWQAxw&t=40s

e. ANEXOS
English words appended
# WORD PRONUNCIATION TRANSLATION
1 Modified madufaid modificado
2 Because bɪˈkəz porque
3 Building bil′ding construccion
4 Standalone stan·duh·lown autónomo
5 Complexity kəm plek′si tē complejidad
6 Becoming bikamin convirtiendose
7 Handler hand′lər manipulador
8 Highlight hī′līt Destacar
9 Expecting spɛktɪŋ Esperando
10 Turnover ˈtɝnˌoʊvɚ envuelto
11 wrapped ræpt envuelto
12 should shŏŏd debería
13 engine enyaine Motor
14 making meykin Construyendo
15 introduction introducshion introduccion
16 getting gerin consiguiendo
17 meaning minin sentid

pág. 36
18 dependency dapendenci Dependecy
19 package Pak-eish Paquete
20 command Coumand dominio
21 styling stayling estilismo
22 hook jok gancho
23 render wrender prestar
24 entities entitis entidades
25 board bord tablero
26 grow grou Crecer
27 randomness randones aletoriedad
28 proactively proactivly proactivamente
29 framework freimwork Estructura
30 happen japan morder

pág. 37

También podría gustarte