Buenas, les adjunto el readme del proyecto realizado para la PEC03, es un juego basado en worms.
Enlace al git: https://gitlab.com/jantoncoy/2d-pec03.git
Como jugar
Controles:
- A / Flecha Izquierda -> Ir hacia la izquierda
- D / Flecha Derecha -> Ir hacia la derecha
- W / Flecha Hacia Arriba -> Saltar
- SPACE -> Disparar
Pasos para comenzar el juego:
- Iniciar el ejecutable del juego.
- En el menu puede escoger dos modos de juego un jugador o dos jugadores, seleccione uno.
- Una vez dentro del juego debera intentar acabar con el enemigo disparandole antes de que el acabe contigo.
- Cuando un jugador agote la vida del contrario, dicho jugador ganara. En el caso de que se acabe el tiempo y no se debilite ningun enemigo ganara el que mas vida + escudo tenga.
Video
Enlace al video gameplay
Instaladores
Partes importantes del codigo
Estructura del proyecto
- Assets
- Scenes: En esta carpeta se encuentran las diferentes escenas del juego (menu y juego).
- Scripts: Se encuentra los scripts de todo el juego.
- Juego: Los scripts del nivel. (jugador, enemigos, objetos, IA)
- Jugadores: contiene todos los scripts de los personajes del juego.
- IA: en esta carpeta se encuentran los diferentes estados de los arboles de comportamiento.
- Objetos: contiene todos los scripts de los objetos.
- Menu: Scripts para el menu del juego.
- Imagenes: Contiene todas las imagenes y tiles del juego.
- p1 y p2: contiene los sprites de los jugadores.
- Escenario: contiene los sprites del escenario.
- Prefabs: Contiene los prefabs que se utilizan en el juego.
- Sonidos: Contiene los sonidos utilizados en el juego.
Elementos del juego
En este apartado se muestran los diferentes elementos del juego.
Escenario

Jugador 1

Jugador 2

Elemento vida

Elemento escudo

Bala

Arboles de comportamiento
Para esta entrega se ha instalado el asset behavior bricks, con el que hemos implementado el siguiente arbol de comportamiento:

Los comportamientos son comportamientos personalizados, solo se ha utilizado las etiquetas de repeticion, secuencia y selector propios de los arboles de comportamiento.
Comportamientos
Los comportamiento los puede encontrar en la carpeta assets > Scripts > Juego > Jugadores > IA
Nombre comportamiento |
Descripcion |
AcercarseJugador |
Se acerca al jugador. |
AlejarseJugador |
Se aleja del jugador en direccion contraria, si colisiona con un limite del escenario cambia su direccion. |
EnemigoLejos |
Determina si esta lejos de algun enemigo. |
EnemigoCerca |
Determina si esta cerca de algun enemigo. |
EnemigoATiro |
Indica si un enemigo esta entre un rango del jugador para poder disparar. |
Disparar |
Dispara en la direccion del enemigo. |
Jugadores
Archivo: Assets/Scripts/Juego/Jugadores/Controlador
La clase controlador es instanciado por los personajes de los jugadores tanto controlado como por la IA.
En el update() lo primero que realizamos es actualizar los marcadores, luego revisamos si esta habilitado (si es su turno), si es asi procesamos los inputs.
Los arboles de comportamiento tienen los mismos parametros de entrada que el jugador por lo que su proceso de controlar al npc es muy similar.
void Update()
{
actualizarMarcadores();
if (habilitado)
{
if (isIA)
{
controlar();
resetearControles();
}
else
{
actualizarBotones();
controlar();
resetearControles();
}
}
}
En esta PEC se ha tomado a cambiado la forma de gestionar las colisiones, cada objeto realiza sus operaciones individuales cuando detecta la colision, de esta forma desacoplamos la logica entre entidades.
Para quitar vida hemos utilizado un numero aleatorio.
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.name.ToLower().Contains("bala"))
{
quitarVida(Random.Range(1, 40));
}else if (collision.gameObject.name.ToLower().Contains("itemvida"))
{
agregarVida(20);
}
else if (collision.gameObject.name.ToLower().Contains("itemescudo"))
{
agregarEscudo(50);
}
if (isIA && collision.gameObject.name.ToLower().Contains("limite"))
{
this.colisionaLimite = true;
}
}
Para darle mejor jugabilidad al juego se han implementado unos escudos, que se hacen visible cuando se quita vida y queda resistencia del escudo, para ello hemos realizado un subproceso con el invoke.
private void activarEscudo()
{
escudoObject.SetActive(true);
Invoke(nameof(desactivarEscudo), 0.250f);
}
private void desactivarEscudo()
{
escudoObject.SetActive(false);
}
Logica del juego
Archivo: Assets/Scripts/Juego/ControladorJuego
Lo primero que hacemos es inicializar las variables y revisar que modo de juego se ha escogido para instanciar los personajes, aparte inicializamos el tiempo de turno y el tiempo de la partida. En este punto indicamos si un personaje esta controlado por la IA, si no es controlado se elimina del prefab el componente de behavior bricks.
En la inicializacion tambien ejecutamos las tareas con invoke de restar el tiempo.
void Start()
{
tiempoPartidaRestante = 300;
InvokeRepeating(nameof(restarTiempoJuego), 0, 1.0f);
InvokeRepeating(nameof(restarTiempoTurno), 0, 1.0f);
if(OpcionesJuego.numeroJugadores == 1)
{
iniciar1P();
}
else
{
iniciar2P();
}
EventosJuego.perder = terminarJuego;
EventosJuego.restarTiempo = restarTiempo;
EventosIA.obtenerJugadores = obtenerJugadores;
}
En el update comprobamos si el tiempo de partida es igual a 0, ademas comprobamos si el tiempo de turno es cero para pasar el turno al siguiente jugador. Para desacoplar la camara y el jugador hacemos que se actualice la posicion por cada update segun el centro del personaje que tenga el turno.
void Update()
{
if(tiempoPartidaRestante == 0)
{
terminarJuego();
}else if(tiempoTurnoRestante == 0)
{
cambiarTurno();
}
actualizarMarcadores();
vincularCamara();
}
Cuando cambiamos de jugador primero deshabilitamos el anterior llamando al array que tiene interno de jugadores con el indice del jugador que tiene el turno.
Una vez que deshabilitamos el anterior, habilitamos el siguiente y reiniciamos el tiempo de turno.
public void cambiarTurno()
{
jugadores[jugadorActual].GetComponent<Animator>().SetInteger("Estado", 0);
jugadores[jugadorActual].GetComponent<Controlador>().habilitado = false;
if (jugadores.Length > (jugadorActual+1))
{
jugadorActual++;
}
else
{
jugadorActual = 0;
}
jugadores[jugadorActual].GetComponent<Controlador>().habilitado = true;
tiempoTurnoRestante = tiempoTurno;
}
Cuando terminamos el juego deshabilitamos todos los personajes, ademas quitamos la subrutinas de restar tiempo a los marcadores, finalmente revisamos quien tiene menos vida, cuando se tiene este valor se habilita el canvas con el menu de fin de juego.
public void terminarJuego()
{
CancelInvoke();
GameObject jugadorConMasVida = null;
for(int a=0; a < jugadores.Length; a++)
{
jugadores[a].GetComponent<Controlador>().habilitado = false;
int totalJugador = 0;
if (jugadorConMasVida != null)
totalJugador = jugadorConMasVida.GetComponent<Controlador>().vida+jugadorConMasVida.GetComponent<Controlador>().escudo;
int totalJugador2 = jugadores[a].GetComponent<Controlador>().vida + jugadores[a].GetComponent<Controlador>().escudo;
if (jugadorConMasVida == null || totalJugador < totalJugador2)
{
jugadorConMasVida = jugadores[a];
}
}
pantallaUI.SetActive(false);
pantallaFinal.SetActive(true);
textoFinal.text = "Ha ganado: " + jugadorConMasVida.GetComponent<Controlador>().nombre;
}
Generador de objetos
Archivo: Assets/Scripts/Juego/Objetos/GeneradorObjetos
Para el generador de objetos en este caso vida y escudo, aunque se podria poner municion o otro tipo de armas, cuando inicializamos el objeto creamos una rutina que se repite con el intervalo dado en el objeto que lo instancie, ademas definimos el evento para parar la generacion de los objetos o items.
void Start()
{
InvokeRepeating(nameof(generarObjeto), 0,intervaloGeneracion);
EventosGeneradorObjetos.pararGenerador = pararGeneradorObjetos;
}
Cuando generamos un nuevo item lo generamos a partir de su prefab, el tipo de objeto y la posicion es aleatoria, aunque la posicion tiene un rango fijado para no generar objetos fuera de la zona (esto se podria cambiar a que solo se genere dentro de la camara o un rango reducido).
private void generarObjeto()
{
int numeroRandom = Random.Range(1, 3);
float posicionRandom = Random.Range(longitudXMin, longitudXMax);
if (numeroRandom == 1) {
Instantiate(vida, new Vector3(posicionRandom, this.transform.position.y, 0),this.transform.rotation);
}
else
{
Instantiate(escudo, new Vector3(posicionRandom, this.transform.position.y, 0), this.transform.rotation);
}
}
Balas
Archivo: Assets/Scripts/Juego/Objetos/Bala.cs
Al incializar una bala lo primero que hacemos es quitar dos segundos del contador de turno, ademas instanciamos un sonido de disparo, este sonido tiene un script que autodestruye el objeto pasado unos segundos.
rigidBody = this.GetComponent<Rigidbody2D>();
EventosJuego.restarTiempo(2);
Instantiate(sonidoDisparo, new Vector3(this.transform.position.x, this.transform.position.y, this.transform.position.z), this.gameObject.transform.rotation);
Con cada update modificamos su velocidad a una velocidad constante hacia la direccion en la que se haya disparado.
void Update()
{
if (!explotada)
{
if (irHaciaDerecha)
{
rigidBody.velocity = new Vector2(8, rigidBody.velocity.y);
}
else
{
rigidBody.velocity = new Vector2(-8, rigidBody.velocity.y);
}
}
}
Cuando colisiona se llama a generarExplosion, esta funcion instancia tanto el sonido de la explosion como la animacion de la misma.
private void generarExplosion()
{
if (!traza)
{
Instantiate(explosionSonido, new Vector3(this.transform.position.x, this.transform.position.y, this.transform.position.z), this.gameObject.transform.rotation);
Instantiate(explosion, new Vector3(this.transform.position.x, this.transform.position.y, this.transform.position.z), this.gameObject.transform.rotation);
}
}
Mejoras
Mejoras para siguientes versiones:
- Arreglar acoplamiento entre dos cuerpos.
- Realizar trazas de los disparos.
- Objetos que no se pueden coger durante el juego.
- Arreglos en la IA del enemigo
Creditos
Creditos de las diferentes obras que se han utilizado:
Sonidos
- Loading screen loop
Autor: Brandon Morris
Enlace: OpenGameArts
- 5 Chiptunes (Action)
Autor: SubspaceAudio
Enlace: OpenGameArts
- Rumble/explosion
Autor: Michel Baradari
Enlace: OpenGameArts
- gunloop 8bit
Autor: Luke.RUSTLTD
Enlace: OpenGameArts
Tiles, Imagenes
- Industrial Parallax Background
Autor: ansimuz
Enlace: OpenGameArts
- 2D Hero Guy Character, 2D Soldier Guy Character
Autor: Segel
Enlace: OpenGameArts
- 2D Explosion Animations | Frame by frame
Autor: Sinestesia
Enlace: OpenGameArts
- Simple shooter icons
Autor: qubodup
Enlace: OpenGameArts
- Shield Aura Effect
Autor: sholev
Enlace: OpenGameArts
Debatecontribution 0en PEC FINAL Programacion de videojuegos 2D
No hay comentarios.
Lo siento, debes estar conectado para publicar un comentario.