Al desarrollar juegos que involucran un elemento de acción, a menudo necesitamos encontrar una manera de chocar con un objetivo en movimiento. Dichos escenarios pueden llamarse típicamente un problema de 'golpear un objetivo en movimiento'. Esto es particularmente prominente en los juegos de defensa de la torre o en los comandos de misiles como los juegos. Es posible que tengamos que crear una IA o un algoritmo que pueda averiguar el movimiento del enemigo y dispararle.
Veamos cómo podemos resolver este problema en particular, esta vez en Unity..
Para este tutorial en particular, consideraremos un juego de comando de misiles. En el juego tenemos una torreta en el suelo que dispara misiles a un asteroide entrante. No debemos permitir que el asteroide toque el suelo..
El juego se basa en el toque, donde debemos tocar para apuntar la torreta. Con la ayuda humana, la mecánica del juego es bastante sencilla, ya que la torreta solo necesita apuntar y disparar. Pero imagina si la torreta necesita disparar automáticamente a los asteroides entrantes.
La torreta debe averiguar cuántos asteroides se están acercando al suelo. Una vez que tenga un conjunto de todos los asteroides que se aproximan, deberá realizar un análisis de amenazas para determinar a cuál atacar. Un asteroide de movimiento lento es una amenaza menor que uno de movimiento rápido. Además, un asteroide que está más cerca del suelo es también una amenaza inminente.
Estos problemas se pueden resolver comparando la velocidad y la posición de los asteroides entrantes. Una vez que hemos determinado cuál es el objetivo, llegamos al problema más complicado. ¿Cuándo debe disparar la torreta? ¿En qué ángulo debe disparar? ¿Cuándo debería el misil ponerse a explotar después de disparar? La tercera pregunta se vuelve relevante porque la explosión del misil también puede destruir el asteroide y también tiene un mayor radio de efecto..
Para simplificar el problema, la torreta puede decidir disparar de inmediato. Entonces solo tenemos que averiguar el ángulo de disparo y la distancia de detonación. Además, puede darse el caso de que el asteroide ya haya pasado el área donde podría ser alcanzado, lo que significa que no hay solución!
Debe descargar la fuente de la unidad proporcionada junto con este tutorial para ver la solución en acción. Veremos cómo derivamos esa solución..
Vamos a hacer un pequeño repaso de las matemáticas de nuestra escuela secundaria para encontrar la solución. Es muy sencillo e implica resolver una ecuación cuadrática. Una ecuación cuadrática se parece a axˆ2 + bx + c = 0
, dónde X
es la variable que se encuentra y se produce con la potencia más alta de 2.
Tratemos de representar nuestro problema esquemáticamente.
La línea verde muestra el camino predicho que seguirá el asteroide. Cuando tratamos con un movimiento uniforme, el asteroide se mueve con velocidad constante. Nuestra torreta deberá girar y disparar el misil a lo largo del camino azul para que choque con el asteroide en el futuro..
Para un movimiento uniforme, la distancia recorrida por un objeto es el producto del tiempo y la velocidad del objeto, es decir,. D = T x S
, dónde re
representa la distancia, T
es el tiempo que lleva viajar re
, y S
Es la velocidad de viaje. Suponiendo que nuestro asteroide y misiles definitivamente colisionen, podemos encontrar la distancia de la línea azul seguida por el misil en términos de tiempo. t
. Al mismo tiempo t
, Nuestro asteroide también alcanzará la misma posición..
Esencialmente, al mismo tiempo. t
, el asteroide alcanzará la posición de colisión desde su posición actual, y el misil también alcanzará la misma posición de colisión al mismo tiempo t
. Asi que a la hora t
, tanto el asteroide como el misil estarían a la misma distancia de la torreta, ya que chocarían entre sí.
Podemos igualar la distancia de la torreta al asteroide y al misil en este tiempo futuro t
Para derivar nuestra ecuación cuadrática con la variable. t
. Considere dos puntos en un plano bidimensional con coordenadas (x1, y1)
y (x2, y2)
. La distancia re
entre ellos se puede calcular utilizando la siguiente ecuación.
Dˆ2 = (x2-x1) ˆ2 + (y2-y1) ˆ2
Si denotamos la posición de la torreta como (Tx, Ty)
, la velocidad del misil como s
y la posición de colisión desconocida como (X, Y)
, entonces la ecuación anterior se puede reescribir como:
Dˆ2 = (X-Tx) ˆ2 + (Y-Ty) ˆ2; D = s * t;
dónde t
Es el tiempo que tarda el misil en recorrer la distancia. re
. Al igualar ambos, obtenemos nuestra primera ecuación para incógnitas. X
y Y
con otro desconocido t
.
sˆ2 * tˆ2 = (X-Tx) ˆ2 + (Y-Ty) ˆ2
Sabemos que el asteroide también alcanza el mismo punto de colisión. (X, Y)
al mismo tiempo t
, y tenemos las siguientes ecuaciones usando los componentes horizontales y verticales del vector de velocidad del asteroide. Si la velocidad del asteroide puede ser denotada por (Vx, Vy)
y la posición actual como (Axe, Ay)
, entonces lo desconocido X
y Y
se puede encontrar como a continuación.
X = t * Vx + Ax; Y = t * Vy + Ay;
Sustituir estos en la ecuación anterior nos da una ecuación cuadrática con un solo desconocido t
.
sˆ2 * tˆ2 = ((t * Vx + Ax) -Tx) ˆ2 + ((t * Vy + Ay) -Ty) ˆ2;
Expandiendo y combinando términos similares:
sˆ2 * tˆ2 = (t * Vx + Ax) ˆ2 + Txˆ2 - 2 * Tx * (t * Vx + Ax) + (t * Vy + Ay) ˆ2 + Tyˆ2 - 2 * Ty * (t * Vy + Ay); sˆ2 * tˆ2 = tˆ2 * Vxˆ2 + Axˆ2 + 2 * t * Vx * Ax + Txˆ2 - 2 * Tx * (t * Vx + Ax) + tˆ2 * Vyˆ2 + Ayˆ2 + 2 * t * Vy * Ay + Tyˆ2 - 2 * Ty * (t * Vy + Ay); sˆ2 * tˆ2 = tˆ2 * Vxˆ2 + Axˆ2 + 2 * t * Vx * Ax + Txˆ2 - 2 * Tx * t * Vx - 2 * Tx * Ax + tˆ2 * Vyˆ2 + Ayˆ2 + 2 * t * Vy * Ay + Tyˆ2 - 2 * Ty * t * Vy - 2 * Ty * Ay; 0 = (Vxˆ2 + Vyˆ2 - sˆ2) * tˆ2 + 2 * (Vx * Hacha - Tx * Vx + Vy * Ay - Ty * Vy) * t + Ayˆ2 + Tyˆ2 - 2 * Ty * Ay + Axˆ2 + Txˆ2 - 2 * Tx * Ax; (Vxˆ2 + Vyˆ2 - sˆ2) * tˆ2 + 2 * (Vx * (Ax - Tx) + Vy * (Ay - Ty)) * t + (Ay - Ty) ˆ2 + (Ax - Tx) ˆ2 = 0;
Representando el poder de dos como ˆ2
y el símbolo de multiplicación como *
puede haber hecho que lo anterior se vea como jeroglíficos, pero esencialmente se reduce a la ecuación cuadrática final axˆ2 + bx + c = 0
, dónde X
es la variable t
, una
es Vxˆ2 + Vyˆ2 - sˆ2
, segundo
es 2 * (Vx * (Ax - Tx) + Vy * (Ay - Ty))
, y do
es (Ay - Ty) ˆ2 + (Ax - Tx) ˆ2
. Utilizamos las siguientes ecuaciones en la derivación..
(a + b) ˆ2 = aˆ2 + 2 * a * b + bˆ2; (a-b) ˆ2 = aˆ2 - 2 * a * b + bˆ2;
Para resolver una ecuación cuadrática, necesitamos calcular el discriminante re
utilizando la fórmula:
D = bˆ2 - 4 * a * c;
Si el discriminante es menor que 0
entonces no hay solución, si es 0
entonces hay una solución única, y si es un número positivo, hay dos soluciones. Las soluciones se calculan utilizando las fórmulas dadas a continuación..
t1 = (-b + sqrt (D)) / 2 * a; t2 = (-b - sqrt (D)) / 2 * a;
Usando estas fórmulas, podemos encontrar valores para el tiempo futuro. t
Cuando ocurrirá la colisión. Un valor negativo para t
Significa que hemos perdido la oportunidad de disparar. Las incógnitas X
y Y
se puede encontrar sustituyendo el valor de t
en sus respectivas ecuaciones.
X = t * Vx + Ax; Y = t * Vy + Ay;
Una vez que sepamos el punto de colisión, podemos girar nuestra torreta para disparar el misil, que definitivamente golpearía el asteroide en movimiento después de t
segundos.
Para el proyecto de Unity de muestra, he usado la función de creación de sprites de la última versión de Unity para crear los activos de marcador de posición necesarios. Esto se puede acceder con Crear> Sprites> Como se muestra abajo.
Tenemos un script de juego llamado MissileCmdAI
que se adjunta a la cámara de escena. Contiene la referencia al sprite de torreta, la prefabricación de misiles y la prefabricación de asteroides. estoy usando SimplePool
por quill18 para mantener los grupos de objetos para misiles y asteroides. Se puede encontrar en GitHub. Hay scripts de componentes para misiles y asteroides que se adjuntan a sus prefabs y manejan su movimiento una vez liberados.
Los asteroides se generan aleatoriamente a una altura fija pero en una posición horizontal aleatoria y se lanzan en una posición horizontal aleatoria en el suelo con una velocidad aleatoria. La frecuencia de desove de asteroides se controla mediante un AnimaciónCurva
. los SpawnAsteroid
método en el MissileCmdAI
guión se ve como a continuación:
void SpawnAsteroid () GameObject asteroid = SimplePool.Spawn (asteroidPrefab, Vector2.one, Quaternion.identity); Asteroid AsteroidScript = asteroid.GetComponent(); asteroidScript.Launch (); SetNextSpawn ();
los Lanzamiento
método en el Asteroide
la clase se muestra a continuación.
Public void Launch () // coloca el asteroide en la parte superior con x aleatorio y lo lanza a la parte inferior con x bl = Camera.main.ScreenToWorldPoint (new Vector2 (10,0)); br = Camera.main.ScreenToWorldPoint (nuevo Vector2 (Screen.width-20,0)); tl = Camera.main.ScreenToWorldPoint (nuevo Vector2 (0, Screen.height)); tr = Camera.main.ScreenToWorldPoint (nuevo Vector2 (Screen.width, Screen.height)); transform.localScale = Vector2.one * (0.2f + Random.Range (0.2f, 0.8f)); asteroidSpeed = Random.Range (asteroidMinSpeed, asteroidMaxSpeed); asteroidPos.x = Random.Range (tl.x, tr.x); asteroidePos.y = tr.y + 1; destination.y = bl.y; destination.x = Random.Range (bl.x, br.x); Vector2 velocidad = velocidad de asteroide * ((destination-asteroidPos) .normalized); transform.position = asteroidPos; asteroidRb.velocity = velocidad; // establezca una velocidad al cuerpo rígido para ponerlo en movimiento deployDistance = Vector3.Distance (asteroidPos, destino); // después de recorrer esta distancia, vuelva al pool nula Update () if (Vector2. Distance (transform.position, asteroidPos)> deployDistance) // una vez que hayamos recorrido la distancia establecida, vuelva a la agrupación ReturnToPool (); void OnTriggerEnter2D (Collider2D proyectile) if (projectile.gameObject.CompareTag ("missile")) // chequee la colisión con el misil, vuelva al grupo ReturnToPool ();
Como se ve en el Actualizar
Método, una vez que el asteroide ha recorrido la distancia predeterminada a tierra, desplegar distancia
, volvería a su grupo de objetos. Esencialmente, esto significa que ha chocado con el suelo. Haría lo mismo en caso de colisión con el misil..
Para que la orientación automática funcione, debemos llamar al método correspondiente con frecuencia para encontrar y apuntar al asteroide entrante. Esto se hace en el MissileCmdAI
guión en su comienzo
método.
InvokeRepeating ("FindTarget", 1, aiPollTime); // establece el sondeo de códigos ai
los Buscar objetivo
El método recorre todos los asteroides presentes en la escena para encontrar los asteroides más cercanos y rápidos. Una vez encontrado, llama al AcquireTargetLock
Método para aplicar nuestros cálculos..
void FindTarget () // encuentra el asteroide más rápido y más cercano GameObject [] aArr = GameObject.FindGameObjectsWithTag ("asteroid"); GameObject closestAsteroid = null; Asteroide más rápidoAsteroide = nulo; Asteroide asteroide; foreach (GameObject go in aArr) if (go.transform.position.y(); if (más rápidoAsteroide == nulo) // encuentra más rápido más rápidoAsteroide = asteroide; else if (asteroid.asteroidSpeed> fastAsteroid.asteroidSpeed) mosteroAsteroid = asteroide; // si tenemos un objetivo más cercano que, si no, el más rápido if (closestAsteroid! = null) AcquireTargetLock (closestAsteroid); else if (el más rápido! asteroide! = nulo) AcquireTargetLock (el más rápidoAsteroid.gameObject);
AcquireTargetLock
es donde ocurre la magia cuando aplicamos nuestras habilidades de resolución de ecuaciones cuadráticas para encontrar el momento de colisión t
.
void AcquireTargetLock (GameObject targetAsteroid) Asteroid AsteroidScript = targetAsteroid.GetComponent(); Vector2 targetVelocity = asteroidScript.asteroidRb.velocity; float a = (targetVelocity.x * targetVelocity.x) + (targetVelocity.y * targetVelocity.y) - (missileSpeed * missileSpeed); float b = 2 * (targetVelocity.x * (targetAsteroid.gameObject.transform.position.x-turret.transform.position.x) + targetVelocity.y * (targetAsteroid.gameObject.transform.position.y-turret.transform.position .y)); float c = ((targetAsteroid.gameObject.transform.position.x-turret.transform.position.x) * (targetAsteroid.gameObject.transform.position.x-turret.transform.position.x)) + ((targetAsteroid.gameObject .transform.position.y-turret.transform.position.y) * (targetAsteroid.gameObject.transform.position.y-turret.transform.position.y)); disco flotante = b * b - (4 * a * c); si (disco<0) Debug.LogError("No possible hit!"); else float t1=(-1*b+Mathf.Sqrt(disc))/(2*a); float t2=(-1*b-Mathf.Sqrt(disc))/(2*a); float t= Mathf.Max(t1,t2);// let us take the larger time value float aimX=(targetVelocity.x*t)+targetAsteroid.gameObject.transform.position.x; float aimY=targetAsteroid.gameObject.transform.position.y+(targetVelocity.y*t); RotateAndFire(new Vector2(aimX,aimY));//now position the turret public void RotateAndFire(Vector2 deployPos)//AI based turn & fire float turretAngle=Mathf.Atan2(deployPos.y-turret.transform.position.y,deployPos.x-turret.transform.position.x)*Mathf.Rad2Deg; turretAngle-=90;//art correction turret.transform.localRotation=Quaternion.Euler(0,0,turretAngle); FireMissile(deployPos, turretAngle);//launch missile void FireMissile(Vector3 deployPos, float turretAngle) float deployDist= Vector3.Distance(deployPos,turret.transform.position);//how far is our target GameObject firedMissile=SimplePool.Spawn(missilePrefab,turret.transform.position,Quaternion.Euler(0,0,turretAngle)); Rigidbody2D missileRb=firedMissile.GetComponent (); Missile missileScript = firedMissile.GetComponent (); missileScript.LockOn (deployDist); missileRb.velocity = missileSpeed * firedMissile.transform.up; // el misil ya está girado en la dirección necesaria
Una vez que encontramos el punto de impacto, podemos calcular fácilmente la distancia que debe recorrer el misil para golpear el asteroide, que se transmite a través del deployDist
variable sobre el LockOn
Método del misil. El misil usa este valor para volver a su grupo de objetos una vez que ha recorrido esta distancia de la misma manera que el asteroide. Antes de que esto suceda, definitivamente habría golpeado el asteroide, y los eventos de colisión se habrían activado.
Una vez que lo implementamos, el resultado se ve casi mágico. Reduciendo el aiPollTime
valor, podemos convertirla en una invencible torreta AI que derribaría cualquier asteroide a menos que la velocidad del asteroide se acerque o supere a nuestra velocidad de misiles. La derivación que seguimos puede usarse para resolver una variedad de problemas similares que podrían representarse en forma de ecuación cuadrática..
Me gustaría que experimentaras más agregando el efecto de la gravedad al movimiento del asteroide y el misil. Esto cambiaría el movimiento al movimiento de proyectil, y las ecuaciones correspondientes cambiarían. Buena suerte.
Tenga en cuenta también que la unidad tiene una economía activa. Hay muchos otros productos que te ayudan a desarrollar tu proyecto. La naturaleza de la plataforma también lo convierte en una excelente opción desde la cual puede mejorar sus habilidades. En cualquier caso, puede ver lo que tenemos disponible en Envato Marketplace..