Dirigir balas al cursor al disparar (Unity)

totespare

Newbie here.

Estoy tratando de hacer un sistem de disparo en 2D con vista lateral ortográfica, en el que la bala vaya en la dirección del ratón. Mi primera idea fue crear balas con físicas, aunque con intención de que la gravedad no les afectara, pero no soy capaz de aplicar una fuerza constante sin que la gravedad afecte, y sin que el objeto sea kinemático ni tener que recurrir a corrutinas.

Esto me lleva a pensar en una corrutina y hacer el prefab de la bala, o incluso sin RG siendo únicamente un trigger (que no se si será buena práctica o no, son solo cosas de newbie que se le pasan a uno por la cabeza). El caso es, podría hacerlo con una corrutina, moviendo la bala en la dirección del puntero? Lo único que no se hacer es sacar una dirección con 2 vectores y aplicarsela a un objeto. No se si es restando el vector que obtengo del ScreenToWorldPoint del vector del SpawnPoint del arma consigo un V3 de dirección o qué cojones... xD.

Si alguien puede echarme un cable con esto, se lo agradezco :D.

PD: otra pequeña duda, si tengo un método que voltea al player en un objeto padre, cómo hago para llamarlo desde un hijo y que se ejecute con relación a ese padre, y no al hijo? Si lo llamo desde el hijo, me toma como pivote el hijo, y no el padre. Si fuera java o similar, haría lo normal de llamar al método sobre un objeto del tipo del padre, pero aquí el método es estático y no me deja llamarlo desde un objeto padre xD.

1
crazyguy

Para saber a dónde apunta el cursor: ScreenToWorldPoint de Camera

Si los tiros van por un esquema de control similar a Nuclear Throne, mi planteamiento sería lograr la posición del cursor, la posición del punto de partida (el cañón del arma), y con eso tienes un vector que, normalizado, te da la dirección de disparo, como planteas. Acuérdate de usar las posiciones en coordenadas del mundo, para evitar problemas. En función de la potencia del arma, le das más o menos velocidad y lo mueves en el Update, o le aplicas una fuerza al rigidbody.

Sobre usar RG o no: si tienes muchos tiros (donde muchos, en un juego de tiros, es un número más bien bajo), mejor pasa de rigidbodies y colliders/triggers. Piensa que si tienes colliders por ahí moviéndose, son más objetos que interfieren con colisiones con otros objetos. ¿Descartarlos? Para proyectiles básicos, te ayudará con el rendimiento. En su lugar, lo que haces es, en cada Update, un Raycast2D/CircleCast desde la posición anterior a la actual, y lo que toque, notificas el impacto en otro método, que ya sobreescribes si quieres proyectiles que hagan cosas diferentes al impactar (como explotar). Normalmente tendrás un campo público tipo LayerMask para indicar contra qué cosas puede impactar una bala. Yo tengo dos: uno cosas a dañar, y otro cosas que la detienen completamente.
También puedes usar las versiones Circle/RaycastALL si tu proyectil puede impactar más de un objeto antes de destruirse (¡usa pools para reutilizarlos!).

El proyectil puedes moverlo usando rigidbodies también, incluso sin colliders/triggers, porque la comprobación del impacto la haces en el Update. También podrías llegar a necesitar proyectiles con físicas, y no te quedaría otra más que meterles collider pa que reboten por ahí. En ese caso, no sabría decirte si sobreescribiría el Update para no hacer nada y recibir las notificaciones de colisión en el OnTrigger/CollisionEnter, o dejarlo como está. Ahí tira de Profiler.

Acabé montando algunas clases para los tipos de proyectiles que necesitaba (balas normales con el planteamiento que te comentaba, granadas, proyectiles de escopeta que rebotan a lo flask cannon del UT...).

En mi caso, con la cantidad de proyectiles que muevo, preferí no usar corutinas. Es más habitual que te generen basura para el recolector, y los beneficios en un caso tan simple, no me parecen suficientes, pero es sólo opinión.

¿Puedes dar algún ejemplo de la otra pequeña duda? En mi juego (Trespassers) roto los personajes rotando el padre. La parte gráfica es hija de la raíz, así que se rota con ella. Tambíen recuerda que puedes girar Transforms especificando el espacio de la rotación (parámetro opcional): referencia.

Saludos!

2 1 respuesta
totespare

#2 muchas gracias por contestar! Efectivamente, lo que planteo es algo similar al Nuclear Throne, salvando que el movimiento será completamente lateral, sin ser top-down (pero eso no debería influir). Haciéndolo con RG, cómo tendría que hacer para normalizar los 2 vectores, y el resultado (que sería una dirección, no?) a qué se lo aplico? He probado toqueteando addforce y velocity y consigo resultados penosos, aunque quizá tenga que ver con que no estoy calculando bien la dirección.

También había pensado en un Raycast, pero lo descarté por no saber cómo hacer para que le "acompañara" el típico rastro de bala, para que se vea que existe una bala y, aunque muy rápido, poder ver su trayectoria.

Con proyectiles con físicas (estilo granadas etc), ya veré cómo hago... A ver si esto me sale primero :P.

Sobre el ejemplo de la rotación del sprite, yo tengo un método que invierto la X para invertir el sprite:

spoiler

Y lo ejecuto cuando miro hacia un lateral o hacia el otro. Hasta ahí todo bien. El problema me viene cuando llamo a ese método desde otro script en el que tengo la funcionalidad de rotar los brazos según apunte con el ratón, y según el grado de rotación de los brazos, desde ese script llamo al método para voltear el sprite (vamos, que si apuntas hacia atrás, en vez de que se retuerzan los brazos, el tipo se da la vuelta xD). La cosa es que ahí rota en función del pivote del objeto en el que tengo ese script, y no del objeto en el que está el script original. Es decir, roto con el pivote en el brazo, y no con el pivote en el centro del cuerpo.

Muchas gracias de nuevo!

1 respuesta
crazyguy

#3 Una dirección siempre la consigues normalizando un vector, salvo uno cuyas componentes estén a 0 (su módulo sería 0). Así que restas posición destino - posición origen, normalizas el resultado, y tienes la dirección para disparar, que multiplicarás por la velocidad/fuerza para disparar el proyectil.

Lo de que el Raycast acompañe no lo pillo. En cada frame guardas la posición actual y luego la actualizas (mueves el proyectil, vaya). Con hacer el Raycast desde la posición previa a la actual, suficiente. Si luego quieres que gráficamente haya cambios, eso va por otro lado.

Rotar es algo más complicado, porque, si es un humanoide digamos, vas a tener el posible problema de que las piernas tengan la animación de avanzar en una dirección, mientras el tronco y los brazos apuntan hacia otra. Suponiendo que tu personaje sea estilo Nuclear Throne (tan pequeño que esto no tenga importancia), sólo tendrías que rotar el arma, girando desde el hombro posiblemente, hacia la dirección de disparo. Además, toma como referencia el eje vertical del personaje para saber si tienes que darle la vuelta o no al personaje. Así, nunca te deberían quedar los brazos apuntando hacia la espalda.

Por cierto, supongo que fue un error al copiar y pegar, pero:
transform.localScale = posicion;
imagino que será localPosition ;)

En mi experiencia es mejor usar posición global en vez de local. Ahorra problemas luego.

Saludos!

1 respuesta
totespare

#4 lo del localScale lo tengo así aposta xD, la intención es que el sprite se de la vuelta a sí mismo en el eje X (vamos, rollo espejo). Si le cambio el localPosition el sprite seguiría mirando hacia la derecha, y se me movería por el juego sin que yo lo quisiera. Con respecto a que sea local o global, sólo he visto la propiedad lossyScale, y es de lectura, así que tengo que usar la local.

Lo del Raycast creo que ya te he entendido, dices de lanzar uno por cada frame desde el origen hasta la situación de la bala, y cuando el raycast choque contra lo que me interese, que la bala deje de viajar? Había entendido otra cosa, si es así como dices, entonces le veo más sentido xDD. Pero si no uso rigidbody para la bala, cómo la muevo, con un Translate?

Ahora mismo he conseguido moverla parecido a como me interesa, dejándo la bala en kinematic, metiéndole la dirección * velocidad en el velocity, y de momento va más o menos guay, pero tengo un problema, la bala va más rápido cuanto más se aleja del arma. Estoy calculando mal la normalización? Hago un Vector3 v2 = Camera.main.ScreenToWorldPoint(Input.mousePosition) - transform.position; y luego hago v2.normalized, y se lo paso al velocity. También es que imagino que el velocity no recibe un vector de dirección, pero si no es así, no se cómo pasarle velocidad y dirección sin rigidbody (otra vez vuelvo a la pregunta del translate xD).

Espero que no te aburran mucho mis noobadas :santo: , gracias por la ayuda!

1 respuesta
crazyguy

#5 Te respondo por puntos para organizarlo mejor:

1) Modifica el localScale del objeto raíz, que es lo mismo. Lo que te recomiendo es que los personajes/props/enemigos o cualquier otro objeto que tenga cierta interacción, tenga un objeto raíz del que "cuelguen" otros, como el sprite/modelo. En ese raíz puedes meter el componente que mueve el objeto. Así, con cambiar el localScale de ese componente (que está en la raíz), le podrías dar la vuelta. Pero esto según te sientas más cómodo, claro.

2) Correcto.

3) La propiedad .normalized de un Vector te devuelve ese vector normalizado. Pero no lo "deja" normalizado, que para eso está el método Normalize(). Puede que sea el problema. En todo caso, si vas a hacer lo de lanzar el Raycast en cada Update, usa ese mismo Update para, con la velocidad inicial, mover el proyectil.

Pseudocódigo:
Desde el código del arma:
Detecta dirección de disparo, y la guarda en un vector normalizado "direction"
Crea (de un pool mejor) un proyectil "p"
Lo "dispara" llamando a "p.Shoot(direction, speed)", donde speed es un parámetro del arma que indica la velocidad a la que avanza el proyectil. Esos dos parámetros los guarda el proyectil para actualizar los datos. A partir de aquí, todo es responsabilidad del proyectil.

Desde el proyectil (en cada Update):
Guarda en "previousPosition" el transform.position actual
Actualiza la posición actual "transform.Translate(direction * speed)"
Haz todo el rollo del Raycast, lanzándolo desde "previousPosition" a "transform.position"
Recorrer todos los impactos detectados, y reaccionar a ellos

Si todo esto te funciona, luego lo vas puliendo si quieres, pero empieza por ahí para ir teniendo resultados que puedas probar.

Ya nos cuentas qué tal!

1 respuesta
totespare

#6 creo que entiendo todo más o menos. Si esta tarde me sobra algo de tiempo, intento hacerlo. Cuando lo haga lo cuento por aquí a ver si qué tal. Muchas gracias!

Usuarios habituales

  • totespare
  • crazyguy