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.
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.
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;
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;
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í:
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;