Publicado por

2d Pec03 – Un juego de artilleria

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:

  1. Iniciar el ejecutable del juego.
  2. En el menu puede escoger dos modos de juego un jugador o dos jugadores, seleccione uno.
  3. Una vez dentro del juego debera intentar acabar con el enemigo disparandole antes de que el acabe contigo.
  4. 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.

    // Update is called once per frame
    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.

    /// <summary>
    /// Detecta la colision de bala y quita vida
    /// </summary>
    /// <param name="collision"></param>
    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.

    /// <summary>
    /// Activa el escudo
    /// </summary>
    private void activarEscudo()
    {
        escudoObject.SetActive(true);
        Invoke(nameof(desactivarEscudo), 0.250f);
    }

    /// <summary>
    /// Desactiva el escudo
    /// </summary>
    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.

    // Start is called before the first frame update
    void Start()
    {
        ///lanzamos corrutina tiempo de partida
        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.

    // Update is called once per frame
    void Update()
    {
        if(tiempoPartidaRestante == 0)
        {
            //finalizamos el juego
            terminarJuego();
        }else if(tiempoTurnoRestante == 0)
        {
            //cambiamos de jugador
            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.

    /// <summary>
    /// Realizamos las peticiones necesarias para activar el siguiente jugador
    /// </summary>
    public void cambiarTurno()
    {
        //Al anterior jugador lo inhabilitamos
        jugadores[jugadorActual].GetComponent<Animator>().SetInteger("Estado", 0);
        jugadores[jugadorActual].GetComponent<Controlador>().habilitado = false;

        //cambiamos de jugador
        if (jugadores.Length > (jugadorActual+1))
        {
            jugadorActual++;
        }
        else
        {
            jugadorActual = 0;
        }

        //habilitamos al jugador y reiniciamos el tiempo
        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.

    /// <summary>
    /// Evaluamos si uno de los jugadores a perdido o quien tiene menos vida
    /// </summary>
    public void terminarJuego()
    {
        //Eliminamos las rutinas
        CancelInvoke();

        GameObject jugadorConMasVida = null;

        //Vamos a buscar el jugador con mas vida, este sera quien gane
        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];
            }
        }

        //Desactivamos UI y activamos pantalla final
        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.

    // Start is called before the first frame update
    void Start()
    {
        //iniciamos la rutina para crear objetos
        InvokeRepeating(nameof(generarObjeto), 0,intervaloGeneracion);
        //Instanciamos evento
        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).


    /// <summary>
    /// Genera un objeto de vida o escudo de forma aleatoria en una posicion aleatoria
    /// </summary>
    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.

    // Update is called once per frame
    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.

    /// <summary>
    /// Genera una explosion en el lugar de la bala
    /// </summary>
    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

Debate0en 2d Pec03 – Un juego de artilleria

Deja un comentario