Aliens

Aliens

  • Juego
  • Código
  • Tutorial

›Componentes

Instalación

  • Librerias que necesitamos

Context API

  • Creando el contexto (Context API)

Componentes

  • Creando nuestros componentes

Lógica del juego

  • Creando la lógica del juego

Creando nuestros componentes

5. Distribución de carpetas

Vamos a iniciar creando la carpeta components y crearemos los siguientes archivos:

  • Game.js
  • ActionKey.js
  • Intro.js
  • GameOver.js
  • Points.js
  • Life.js
  • Laser.js

6. Incluyendo assets

Este archivo será nuestro punto central del juego, desde el vamos a cargar los assets y los personajes, enemigos, y assets del entorno.

Primero crea la carpeta /img dentro de la carpeta /src, luego, ve al repositorio de github y copia los archivos graficos que vas a encontrar en la misma ruta del repositorio. (/src/img).

Luego, en el archivo context.js vas a incluir los assets en la variable de estado assets.

import s1 from '../img/s1.png';
import s2 from '../img/s2.png';
import s3 from '../img/s3.png';
import s4 from '../img/s4.png';
import enem1 from '../img/enem1.png';
import enem2 from '../img/enem2.png';
import enem3 from '../img/enem3.png';
import enem4 from '../img/enem4.png';
import logo from '../img/logo.png';

Estos archivos serán pasados por medio de props a los componentes hijos, como veremos mas adelante.

7. Estilos y animaciones

Para los estilos del juego, en la raiz del código en github, encontrarás dos archivos, normalize.css e index.css, copialos y pegalos en la raiz de tu proyecto. La explicación del código CSS se sale del objetivo de este tutorial, pero te dejo para que explores el código.

8. Creando nuestra escena Intro

Antes de que nuestro juego inicie, vamos a actualizar nuestro contexto, con una variable nueva para identificar que escena estamos visualizando en nuestro juego, para eso, vamos a nuestro archivo context.js y adicionamos las siguientes lineas:

const [currentScene, setCurrentScene] = useState('intro');

Y dentro de nuestro provider, exponemos las variables del estado que queremos consumir desde todas las otras escenas.

<AliensContext.Provider
    value={{
        isRunning,
        setIsRunning,
        currentScene,
        setCurrentScene
    }}>
        {props.children}
    </AliensContext.Provider>

Ahora, dentro de nuestro archivo Intro.js agregamos el siguiente código:

import React, { useContext } from 'react';
import { AliensContext } from '../context/context';

function Intro() {

    const { assets, currentScene, setCurrentScene } = useContext(AliensContext);

    const startGame = (e) => {
        setCurrentScene('game');
    };

    const isActive = currentScene === 'intro' ? 'active': '';

  return (
        <div className={isActive + ' scene intro'}>
            <img className="logo" src={assets.logo} alt="keyhero" />
            <div className="instructions">
            <p>Como jugar:</p>
            <p>Da click o Tap sobre el cañon para detener el ovni.</p>
            </div>
            <button className="button" onClick={startGame}>Iniciar</button>
        </div>
  );
}

export default Intro;

Como vemos, tenemos una constante definida llamada isActive la cual se alimenta de la variable de contexto que definimos en nuestro archivo de contexto. Con esto, podemos establecer que escena de nuestro juego está activa.

alt-text

9. Creando nuestro componente Life

Este componente solamente recibirá un valor de parte del contexto, ese valor se verá representado por las barras de vida las cuales se irán reduciendo a medida que el valor del contexto sea reducido.

En el archivo Life.js pega el siguiente código:

import React, { useContext } from 'react';
import { AliensContext } from '../context/context';

function Life() {

    const { currentLife } = useContext(AliensContext);

    const fillBars = () => {
        const bars = [1,2,3,4];
        const barsDom = bars.map((item, index) => 
            index < currentLife ? 
                <div className="block full" key={index}></div>:
                    <div className="block" key={index}></div>
        );

        return barsDom;
    }

  return (
        <div className="life">
            {fillBars()}
        </div>
  );
}

export default Life;

En este caso, nuestro componente es meramente visual y se alimenta del valor del contexto. La funcion fillBars retorna en este caso los items con el estado correspondiente, si el valor de currentLife cambia en nuestro contexto, este componente se actualizará automaticamente.

alt-text

10. Creando nuestro componente Points

Este componente es muy parecido al de life, básicamente recibe un parametro desde el contexto y lo renderiza. Abrimos nuestro archivo Points.js y pegamos el siguiente código:

import React, { useContext } from 'react';
import { AliensContext } from '../context/context';

function Points() {

    const { currentPoints } = useContext(AliensContext);

  return (
        <div className="points">
            <h2>Points: {currentPoints}</h2>
        </div>
  );
}

export default Points;

Recuerda también actualizar el contexto, en este momento, debería lucir de la siguiente manera:

import React, {createContext, useState} from 'react';

export const AliensContext = createContext();

const AliensProvider = (props) => {

    const [isRunning, setIsRunning] = useState(false);
    const [currentScene, setCurrentScene] = useState('intro');
    const [currentLife, setCurrentLife] = useState(4);
    const [currentPoints, setCurrentPoints] = useState(0);

    return (
        <AliensContext.Provider
            value={{
                isRunning,
                setIsRunning,
                currentScene,
                setCurrentScene,
                currentLife,
                setCurrentLife,
                currentPoints,
                setCurrentPoints
            }}
        >
            {props.children}
        </AliensContext.Provider>
    )
}

export default AliensProvider;

alt-text

11. Creando nuestro componente GameOver

Este componente solamente mostrará nuestra pantalla de finalización y un botón de reinicio, que al ser oprimido, reiniciará nuestro juego.

Pega este codigo en el archivo GameOver.js:

import React, { useContext, useRef } from 'react';
import { AliensContext } from '../context/context';
import anime from 'animejs/lib/anime.es.js';

function GameOver() {

    const ovniRef = useRef(null);

    const { 
        currentScene, assets
    } = useContext(AliensContext);

    const startGame = (e) => {
        window.location.reload();
        return false;
    }

    anime({
        targets: ovniRef.current,
        rotate: {
            value: '+=2turn',
            duration: 1800,
            easing: 'easeInOutSine'
        },
        autoplay: true,
        loop: true
    });

    const isActive = currentScene === 'gameover' ? 'active': '';

  return (
        <div className={isActive + ' scene gameover'}>
            <h2>Game Over</h2>
            <img ref={ovniRef} src={assets.enem2} alt="aliens" />
            <button className="button" onClick={startGame}>Reiniciar</button>
        </div>
  );
}

export default GameOver;

alt-text

12. Creando nuestro componente ActionKey

Este componente es donde estará la lógica principal del juego, tendremos 4 naves en la parte inferior de la pantalla y cada nave podrá disparar un laser al dar click (o tap en moviles). Por ahora, vamos a definir la parte visual.

En el archivo ActionKey.js pega el siguiente código:

import React from 'react';

function ActionKey({id, laserId, image, enemyId, enemyImage}) {
  return (
        <Fragment>
            <div id={enemyId} ref={enemyRef} className="enemy">
                <img src={enemyImage} alt='enemy'/>
            </div>
            <div id={id} ref={elementRef} className="action-key" 
                onTouchEnd={shootEnemy} onClick={shootEnemy}>
                <img src={image} alt='key'/>
                <Laser id={laserId} />
            </div>
        </Fragment>
  );
}

export default ActionKey;

La imagen de fondo y el ID serán enviados por medio de props desde el componente padre (game.js). Al final luciría de la siguiente manera nuestro código en game.js

import React, { useContext, useEffect } from 'react';
import { AliensContext } from '../context/context';
import Life from './Life';
import Points from './Points';
import ActionKey from './ActionKey';

function Game() {

    const {
        assets,
        currentScene,
        setCurrentScene,
    } = useContext(AliensContext);
    
    const isActive = currentScene === 'game' ? 'active': '';

  return (
        <div className={isActive + ' scene game'}>
            <div className="game-indicators">
                <Points />
                <Life />
            </div>
            <div className="game-container">
                <div className="side side1">
                    <ActionKey 
                        id="s1"
                        laserId="l1"
                        image={assets.s1} 
                        enemyImage={assets.enem1}
                        enemyId="enem1"
                    />
                </div>
                <div className="side side2">
                    <ActionKey 
                        id="s2"
                        laserId="l2"
                        image={assets.s2} 
                        enemyImage={assets.enem2}
                        enemyId="enem2"
                    />
                </div>
                <div className="side side3">
                    <ActionKey 
                        id="s3"
                        laserId="l3"
                        image={assets.s3} 
                        enemyImage={assets.enem3}
                        enemyId="enem3"
                    />
                </div>
                <div className="side side4">
                    <ActionKey 
                        id="s4"
                        laserId="l4"
                        image={assets.s4} 
                        enemyImage={assets.enem4}
                        enemyId="enem4"
                    />
                </div>
            </div>
        </div>
  );
}

export default Game;

Y por ahora, nuestro juego se verá así:

alt-text

13. Creando el componente Laser

Este componente es el proyectil que saldrá dirigido por cada uno de los ovnis para destruir a los enemigos, lo que haremos será rehusar el mismo componente.

En el archivo Laser.js pegamos el siguiente código:

import React, {useRef, useEffect, useContext} from 'react';
import { AliensContext } from '../context/context';
import anime from 'animejs/lib/anime.es.js';
import laser from '../img/laser.png';

function Laser({id}) {

    const laserRef = useRef(null);
    const { shoot, setShoot } = useContext(AliensContext);

    useEffect(() => {
        if (shoot.hit && shoot.id === id) {
            anime({
                targets: laserRef.current,
                translateY: '-50vh',
                opacity: 0,
                duration: 500,
                complete: function(anim) {
                    console.log(anim);
                    this.reset();
                    setShoot({id: id, hit: false});
                }
            });
        }
        
    });

  return (
        <div id={id} className="laser" ref={laserRef}>
            <img src={laser} alt='laser'/>
        </div>
  );
}

export default Laser;


Ya teniendo todos nuestros componentes creados, el siguiente paso es comenzar a crear nuestra lógica del juego, pero antes, revisa que tu archivo Game.js contenga todas las referencias y componentes.

import React, { useContext } from 'react';
import { AliensContext } from '../context/context';
import s1 from '../img/s1.png';
import s2 from '../img/s2.png';
import s3 from '../img/s3.png';
import s4 from '../img/s4.png';
import enem1 from '../img/enem1.png';
import enem2 from '../img/enem2.png';
import enem3 from '../img/enem3.png';
import enem4 from '../img/enem4.png';
import Life from './Life';
import Points from './Points';
import ActionKey from './ActionKey';

function Game() {

    const { currentScene } = useContext(AliensContext);

    const isActive = currentScene === 'game' ? 'active': '';

  return (
        <div className={isActive + ' scene game'}>
            <div className="game-indicators">
                <Points />
                <Life />
            </div>
            <div className="game-container">
                <div className="side side1">
                    <ActionKey 
                        id="s1"
                        laserId="l1"
                        image={s1} 
                        enemyImage={enem1}
                        enemyId="enem1"
                    />
                </div>
                <div className="side side2">
                    <ActionKey 
                        id="s2"
                        laserId="l2"
                        image={s2} 
                        enemyImage={enem2}
                        enemyId="enem2"
                    />
                </div>
                <div className="side side3">
                    <ActionKey 
                        id="s3"
                        laserId="l3"
                        image={s3} 
                        enemyImage={enem3}
                        enemyId="enem3"
                    />
                </div>
                <div className="side side4">
                    <ActionKey 
                        id="s4"
                        laserId="l4"
                        image={s4} 
                        enemyImage={enem4}
                        enemyId="enem4"
                    />
                </div>
            </div>
        </div>
  );
}

export default Game;

← Creando el contexto (Context API)Creando la lógica del juego →
  • 5. Distribución de carpetas
    • 6. Incluyendo assets
    • 7. Estilos y animaciones
  • 8. Creando nuestra escena Intro
  • 9. Creando nuestro componente Life
  • 10. Creando nuestro componente Points
  • 11. Creando nuestro componente GameOver
  • 12. Creando nuestro componente ActionKey
  • 13. Creando el componente Laser
Aliens
Tutorial
InstalaciónContext ApiComponentesLógica del juego
More
GitHubStar
Facebook Open Source
Copyright © 2020 Aramhack