¿Cómo golpear al aire?

Estoy intentando hacer un juego tipo plataformas donde se puede atacar a enemigos, pero solo puedo atacar SÍ hay uno, y si no, me salta un error: “Cannot read properties of undefined (reading ‘enviar_mensaje’)”. Adjunto la parte del código que parece ser el problema.

ataque_1_iniciar() {
    let logro_golpear = false;
    this.animacion = "ataque_1";

    if (this.espejado) {
      if (this.izquierda.cantidad_de_colisiones > 0) {
        logro_golpear = true;
      } else{
        logro_golpear = false;
      }
      if (this.derecha.cantidad_de_colisiones > 0) {
        logro_golpear = true;
      }
      else{
        logro_golpear = false;
      }
    }

    if (logro_golpear = true) {
      this.crear_efecto_de_golpe();
      let enemigo = this.pilas.obtener_actor_por_etiqueta("enemigo");
      enemigo.enviar_mensaje("ataque");
    
    }
  }


Por lo que entiendo, el problema está en que, como dentro de los sensores no esta el enemigo para enviarle el mensaje, sale el error, pero no sé como hacer que el mensaje solo se envíe si hay un enemigo en el sensor.

Hola @Varalan !!!, te comento que la función obtener_actor_por_etiqueta puede retornar el valor undefined si no encuentra ningún actor con esa etiqueta.

Por lo que veo en el mensaje de error, el problema podría venir por ahí.

Una forma rápida de resolverlo es reemplazar la linea que obtiene al enemigo con la función obtener_actor_por_etiqueta y la linea de abajo (enemigo.enviar_mensaje) por estas lineas de código:

let enemigo = this.pilas.obtener_actor_por_etiqueta("enemigo");

if (enemigo) {
    enemigo.enviar_mensaje("ataque");
}

Con ese if te vas a asegurar que no se produzca el error.

Otra opción es usar funciones como colisiona_con_etiqueta o cantidad_de_colisiones_con_la_etiqueta dentro del sensor, pero me parece que usar el if como te mencionaba antes es lo más fácil.

Avisanos si te sirve la sugerencia!!

¡Abrazo y gracias por escribir!

¡Muchísimas gracias! ahora funciona excelente :slight_smile:

Resulta que hay un error con este código, ahora si ataco cuando no hay un enemigo, no salta un error, sino que mata al enemigo mas cercano, sin importar que tan lejos esté.

Hola @Varalan !!!, es cierto, ahora que estoy leyendo tu código con más atención me doy cuenta que habría que modificar algunas cosas:

Cuando encuentras que el sensor colisiona con un enemigo, deberías preguntarle a ese sensor cuál es el enemigo que entró en colisión con el sensor.

Cambiaría la linea de código que te pasé, donde se obtiene el actor por etiqueta y podría algo como esto:

// ojo que aquí hay que poner dos == no uno solo:
if (logro_golpea == true) {
    let colisiones_con_el_sensor_izquierdo = self.izquierda.colisiones_con_la_etiqueta("enemigo");
    let colisiones_con_el_sensor_derecho = self.derecha.colisiones_con_la_etiqueta("enemigo");

   if (colisiones_con_el_sensor_izquierdo) {
       this.crear_efecto_de_golpe();
       let enemigo = colisiones_con_el_sensor_izquierdo[0]; // solo se toma al primero, si hay mas de uno
       enemigo.enviar_mensaje("ataque");
   }

   if (colisiones_con_el_sensor_derecho) {
       this.crear_efecto_de_golpe();
       let enemigo = colisiones_con_el_sensor_derecho[0]; // solo se toma al primero, si hay mas de uno
       enemigo.enviar_mensaje("ataque");
   }
}

Es decir, en el código anterior, cuando hacíamos obtener_actores_con_etiqueta, le estábamos pidiendo a pilas que nos traiga el primer actor que encontrara en la escena, sin importar dónde estuviera situado. Ahora, con este código, le estaríamos pidiendo a pilas que nos retorne el actor que está justo tocando el sensor.

Avisame si este cambio resuelve el problema, no llegué a probarlo en el editor así que puede que tenga algún error de sintaxis o alguna variable esté mal.

Algo está mal, porque ahora los ataques, en vez de matar al enemigo a distancia, no lo mata ni estando dentro del sensor. Como no se produce el efecto de golpe, el problema parece estar en:

if (logro_golpear == true) {

let colisiones_con_el_sensor_izquierdo = this.izquierda.colisiones_con_la_etiqueta("enemy");
 let colisiones_con_el_sensor_derecho = this.derecha.colisiones_con_la_etiqueta("enemy");`.

Porque si el problema fuese que el enemigo no recibe el mensaje, el efecto de golpe se produciría igual.

Oh, ¿la etiqueta es “enemigo” o “enemy”?, tal vez estaba mal el código que te envié… ¿será eso?

Por otro lado, si quieres puedes compartirme el juego por aquí en el foro, el editor tiene un botón “exportar” que nos va a ayudar a ver dónde puede estar el problema.

Hola @Varalan, entiendo que ¿estás tratando de golpear a los enemigos que estén “dentro” de un sensor? ¿También entiendo que utilizas estados de autómata?
Si la respuesta es sí a ambas preguntas, entonces lo que debes hacer es obtener ese actor enemigo que está colisionando con el sensor. En tu código original no estabas haciendo eso y además usabas en el último if el = en vez del ==. Este error suele pasar seguido y lo que hace es que el if siempre se ejecute.
La manera en que yo logré hacer que se golpeen enemigos dentro de un sensor fue:
1- obteniendo una lista de todos los actores con etiqueta enemy dentro del sensor.
2- usando un bucle for le avisé a cada actor de la lista que fue golpeado.
El código del estado de autómata pegar quedaría así:

 pegar_derecha_iniciar() {
    this.animacion = "pegar";
  }

  pegar_derecha_actualizar() {

  }

  pegar_derecha_cuando_finaliza_animacion(nombre: string) {
    
    //la variable enemigo es la lista con los actores dentro del sensor que tengan etiqueta enemy
    let enemigos: Actor[] = this.sensor_derecha.colisiones_con_la_etiqueta("enemy");
    for (let actor of enemigos) {
      actor.enviar_mensaje("pegar");
    }

    //lo siguiente devuelve al estado caminar
    this.estado = "caminar";
  }

Eso sería para pegar a la derecha donde el sensor se llama sensor_derecha. Para la izquierda habría que hacer lo mismo.
Para sincronizar el golpeo con la animación hice todo dentro de la función pegar_derecha_cuando_finaliza_animacion, pero podría hacerse todo dentro del iniciar. Te comparto el ejemplo que funciona con ese código:
Con las flechas mueves al espadachín, con la P pegas hacia la derecha y las cajas son las enemigas
Abrir este proyecto en el editor de pilas

Notar que este código soluciona todos los problemas: Puedes pegar al aire y solo pegas a los enemigos que estén cerca. En su momento me costó bastante hacerlo y por eso no se me ocurre una manera de pasar de tu código al mio. Espero haberme explicado bien cualquier cosa pregúntame.

1 Like

Aqui dejo mi proyecto, así será más fácil saber en que está mal mi codigo (que seguro son muchas cosas, porque este es casi el primer proyecto que hago). Ya que por mas que uso lo que tu me dices, no me funciona
Abrir este proyecto en el editor de pilas

Aquí los controles: Te mueves con A y D. Saltas con W y haces un doble salto con S. Atacas con Espacio. Si vuelves a tocar espacio(o lo tienes mantenido todo el tiempo) haces otro ataque con diferente animacion [como un combo muy simple]

Tiene muy buena pinta, es bastante complejo lo que estás haciendo. Compártelo cuando lo termines!
Lo primero que noto es que tienes una variable (técnicamente no es una variable, pero funciona casi igual) creada al principio del código del knight:

logro_golpear: boolean = false;

Supongamos que en una función, dentro del knight, le quieres cambiar el valor, entonces deberías hacer:

atacar_1() {
    this.logro_golpear = true;
}

Pero en vez de eso, estas haciendo:

atacar_2() {
    let logro_golpear = true;
}

Cuando utilizas el let, estás creando una variable local que sólo sirve en el bloque donde está. Es decir que fuera de la función atacar_2 la variable logro_golpear no existe y la variable antes creada logro_golpear no cambio su valor. Mientras que con la función atacar_1 cambiamos el valor de logro_golpear y puede usarse ese nuevo valor en otras partes del código… no se si me expliqué bien.
Por ejemplo, en la función iniciar de knight le asignaste el valor de false correctamente:

iniciar() {
    this.logro_golpear = false
    ...
}

Pero en el estado atack no le asignaste el valor, sino que creaste una variable local de igual nombre:

atack_2_iniciar() {
    this.crear_efecto_de_golpe();
    let logro_golpear = false;
   ....
}

En vez de let, debes usar:

atack_2_iniciar() {
    this.crear_efecto_de_golpe();
    this.logro_golpear = false;

El juego se comporta como esperabas, pero si le sigues agregando cosas quizás utilices esa variable y vas a renegar un buen rato hasta ver que allí estaba el problema.

1 Like

Respecto del uso del sensor para atacar y eliminar enemigos.
Hay algunas cosas a comentar:

  • Debido a que el personaje hace un dash al atacar, resulta que antes de que el sensor toque a los enemigos, el personaje se choca con ellos y muere. La solución es hacer los sensores der e izq más grandes y alejados del knight. Así al atacar primero el sensor los elimina y luego se desplaza el knight.

  • Aunque combinaste correctamente mi método con el de @hugoruscitti, en varias parte dices que vas a la izquierda, pero sigues utilizando el sensor derecha, en su lugar usa el sensor izquierda:

if (colisiones_con_el_sensor_izquierdo) {
        this.crear_efecto_de_golpe();
        let enemy: Actor[] = this.**derecha**.colisiones_con_la_etiqueta("enemy");
        ....
}
if (colisiones_con_el_sensor_izquierdo) {
        this.crear_efecto_de_golpe();
        let enemy: Actor[] = this.izquierda.colisiones_con_la_etiqueta("enemy");
        ....
}
  • Por último deberás “mudar” el código, que elimina a los enemigos en el sensor, a la función atack_1_iniciar(). Esto debido al dash, ya que antes que finalice la animación el knight cambia de posición chocando al enemigo y muriendo antes de que la función cuando_finaliza_animacion() se ejecute.

Aplicando todos estos cambios tu juego me funciona correctamente y, para darte una idea, el estado atack_1 me quedó:

atack_1_iniciar() {
    this.crear_efecto_de_golpe();//esto estaba varias veces, pero era rebundante, con una es suficiente
    this.logro_golpear = false;
    this.animacion = "atack_1";

    if (this.derecha.cantidad_de_colisiones_con_la_etiqueta("enemy")) {
      this.logro_golpear = true;
    }
    //aquí habia un else, pero no hace falta y además puede dar problemas
    if (this.izquierda.cantidad_de_colisiones_con_la_etiqueta("enemy")) {
        this.logro_golpear = true;
    }

    if (this.logro_golpear == true) {
      let colisiones_con_el_sensor_izquierdo = this.izquierda.colisiones_con_la_etiqueta("enemy");
      let colisiones_con_el_sensor_derecho = this.derecha.colisiones_con_la_etiqueta("enemy");

      if (colisiones_con_el_sensor_izquierdo) {
        let enemy: Actor[] = colisiones_con_el_sensor_izquierdo;//aca utilizabas el comando colisiones_con_la_etiqueta, pero la variable colisiones_con_el_sensor... de antes ya almacena el resultado de ese comando
        for (let actor of enemy) {
          actor.enviar_mensaje("atack");
        }
      }

      if (colisiones_con_el_sensor_derecho) {
        let enemy: Actor[] = colisiones_con_el_sensor_derecho;//aca utilizabas el comando colisiones_con_la_etiqueta, pero la variable colisiones_con_el_sensor... de antes ya almacena el resultado de ese comando
        for (let actor of enemy) {
          actor.enviar_mensaje("atack");
        }
      }
    }
  }
  atack_1_actualizar() {

  }

  atack_1_cuando_finaliza_animacion(nombre: string) {
    if (this.control.espacio) {
      this.estado = "atack_2";
    } 
    else {
      this.estado = "idle";
    }
  }

Espero haberte ayudado y de verdad que tiene buena pinta el juego!

1 Like

!Muchísimas gracias¡ No solo me corregiste el codigo, tambien me explicaste muy bien qué era lo que estaba mal, gracias por la ayuda y los animos :grin:

1 Like