Consejo rápido Crea un suave movimiento enemigo con un movimiento sinusoidal

En este Consejo rápido, te mostraré cómo usar el seno Función para dar a los objetos de tu juego un movimiento suave hacia adelante y hacia atrás - no más zigzags ásperos donde tus enemigos flotantes parecen rebotar contra un muro invisible!


Ejemplos

Primero, permítame mostrarle el tipo de movimiento de ida y vuelta que quiero decir. (Los gráficos son de nuestro paquete de sprites de disparos totalmente gratis).

Este enemigo se mueve hacia arriba y hacia abajo, disparando balas a intervalos regulares a medida que avanza:

Este enemigo teje a través de la pantalla:

Ambos tipos de movimiento son útiles para los juegos de disparos. Observe lo suave y gradual que se siente el movimiento: sin movimientos bruscos, sin "sacudidas" a medida que el enemigo cambia de dirección. Eso está en marcado contraste con ...


El enfoque ingenuo

Un primer intento común de crear un movimiento de ida y vuelta es hacer algo como esto:

 var goingUp = falso; // Función ejecutar cada pocos milisegundos. // Ver: http://gamedev.tutsplus.com/articles/glossary/quick-tip-what-is-the-game-loop/ function gameLoop () if (ufo.y> = bottomOfRange) goingUp = true ;  else if (ufo.y <= topOfRange)  goingUp = false;  if (goingUp)  ufo.y -= ufo.ySpeed;  else  ufo.y += ufo.ySpeed;  ufo.x += ufo.xSpeed; 

Básicamente, esto le dice al enemigo que se mueva hacia abajo a una velocidad constante (es decir, el mismo número de píxeles cada vez) hasta que alcance el punto más bajo en su rango permitido, luego que se mueva hacia arriba a esa misma velocidad constante hasta que alcance el punto más alto en Su rango permitido, una y otra vez..

Se puede hacer que el enemigo se mueva horizontalmente estableciendo su xSpeed a cualquier número que no sea cero: un número negativo hace que se mueva a la izquierda y un número positivo lo hace a la derecha.

Estos ejemplos muestran cómo se ve este tipo de movimiento. Primero, sin movimiento horizontal:

Ahora, con movimiento horizontal:

Alcanza el objetivo de avanzar y retroceder, pero ciertamente no es tan suave como nuestro ejemplo anterior.


La causa

La razón de este movimiento irregular es que la velocidad vertical del enemigo produce un cambio repentino e inmenso, aunque el valor de ufo.y Velocidad Sigue igual.

Suponer ufo.y Velocidad es 10. En el camino hacia arriba, el enemigo se está moviendo hacia arriba a 10px / tick (píxeles por tick, donde "tick" es la longitud de un bucle de juego). Una vez que el enemigo llega a la cima, invierte la dirección y se mueve repentinamente a 10px / tick hacia abajo. El cambio de + 10px / tick a -10px / tick es una diferencia de 20px / tick, y eso es lo que se nota..

Cuando se explica la causa de esta manera, la solución parece obvia: ¡ralentizar al enemigo cerca de los puntos más altos y más bajos! De esa manera, el cambio en su velocidad no será tan grande cuando invierta la dirección.

Un primer intento de esto podría verse así:

 var goingUp = falso; var movingSlowly = false; // Función ejecutar cada pocos milisegundos. // Ver: http://gamedev.tutsplus.com/articles/glossary/quick-tip-what-is-the-game-loop/ function gameLoop () if (ufo.y> = bottomOfRange) goingUp = true ;  else if (ufo.y <= topOfRange)  goingUp = false;  if (ufo.y <= bottomOfRange + 100)  movingSlowly = true;  else if (ufo.y >= topOfRange - 100) movingSlowly = true;  else movingSlowly = false;  if (movingSlowly) if (goingUp) ufo.y - = ufo.ySpeed ​​/ 2;  else ufo.y + = ufo.ySpeed ​​/ 2;  else else if (goingUp) ufo.y - = ufo.ySpeed;  else ufo.y + = ufo.ySpeed;  ufo.x + = ufo.xSpeed; 

Este código es complicado, pero tienes una idea: si el enemigo está dentro de 100px de sus límites más altos o más bajos, se mueve a la mitad de su velocidad normal.

Esto funciona, aunque no es perfecto. El enemigo seguirá teniendo un "salto" en velocidad cuando cambie de dirección, pero al menos no será tan notable. ¡Sin embargo, el enemigo ahora tendrá saltos adicionales de velocidad cuando se mueva de su ritmo regular a una velocidad más lenta! Dang.

Nosotros podría arregla esto dividiendo el rango en secciones más pequeñas, o haciendo que la velocidad sea un poco mayor de la distancia exacta del enemigo a sus límites ... pero hay una manera más fácil.


Movimiento sinusoidal

Piense en un modelo de tren que recorre una vía perfectamente circular. El tren cambia constantemente de dirección y, sin embargo, se mueve a un ritmo constante, sin "saltos".

Ahora imagine una pared en un lado de la vía circular y una gran luz brillante en el lado opuesto (por lo tanto, la vía y el tren están entre los dos). El tren arrojará una sombra en la pared. Pero, por supuesto, esa sombra no se moverá en un círculo, porque la pared es plana: se moverá hacia adelante y hacia atrás, en línea recta, pero aún con ese movimiento suave y sin saltos del tren.!

Eso es exactamente lo que queremos. Y afortunadamente hay una función que nos la dará: la seno función. Este GIF animado de Wikipedia demuestra:


Imagen de Wikimedia Commons. Gracias lucas!

La línea roja es la curva de y = pecado (x). Asi que, pecado (0.5 * pi) es 1, pecado (pi) es 0, y así sucesivamente.

Es un pequeño inconveniente que pi (π) es la unidad básica utilizada para esta función, pero podemos administrar. Podemos usarlo así:

 var numberOfTicks = 0; function gameLoop () numberOfTicks ++; ufo.y = sin (numberOfTicks * pi); ufo.x + = ufo.xSpeed; 

¿Ves lo que está pasando aquí? Después de una garrapata, ufo.y se establecerá en pecado (1 * pi), cual es 0. Después de dos garrapatas, ufo.y se establecerá en pecado (2 * pi), cual es… 0, otra vez. Oh. Aférrate.

 var numberOfTicks = 0; function gameLoop () numberOfTicks ++; ufo.y = sin (numberOfTicks * 0.5 * pi); ufo.x + = ufo.xSpeed; 

Ahora, después de una garrapata, ufo.y se establecerá en pecado (0.5 * pi), cual es 1. Después de dos garrapatas, ufo.y se establecerá en pecado (1 * pi), cual es 0. Despues de tres garrapatas, ufo.y se establecerá en pecado (1.5 * pi), cual es -1, y así. (La función seno se repite, por lo que sin (a) == sin (a + (2 * pi)), siempre - no tiene que preocuparse por asegurarse de que una está por debajo de un cierto número!)

Obviamente va desde 1 a 0 a -1 Y así sucesivamente no es lo que queremos. Primero, queremos que los valores de los límites sean otra cosa 1 y -1. Eso es fácil, simplemente multiplicamos el conjunto. pecado Funcionar por nuestro límite máximo deseado:

 var numberOfTicks = 0; function gameLoop () numberOfTicks ++; ufo.y = 250 * sin (numberOfTicks * 0.5 * pi); ufo.x + = ufo.xSpeed; 

Ahora el enemigo pasará de y = +250 a y = -250. Si queremos que vaya desde 100 a 600, solo podemos añadir un extra 350 a este valor (ya que 250 + 350 = 600 y -250 + 350 = 100):

 var numberOfTicks = 0; function gameLoop () numberOfTicks ++; ufo.y = (250 * sen (numberOfTicks * 0.5 * pi)) + 350; ufo.x + = ufo.xSpeed; 

Pero el valor sigue saltando desde 100 a 350 a 600, porque el sin (numberOfTicks * 0.5 * pi) todavía está saltando de -1 a 0 a 1.

Pero, diablos, sabemos por qué. eso es sucediendo: es porque el valor de numberOfTicks * 0.5 * pi está saltando de 0.5 * pi a 1 * pi a 1.5 * pi. Mira el GIF de nuevo si no ves por qué eso lo causaría:

Entonces, todo lo que tenemos que hacer es elegir un espacio diferente entre el número que ingresamos en el pecado() función, en lugar de numberOfTicks * 0.5 * pi. Si desea que el movimiento de ida y vuelta dure diez veces más, utilice numberOfTicks * 0.5 * pi / 10. Si quieres que tome 25 veces más, usa numberOfTicks * 0.5 * pi / 25, y así.

Puede usar esta regla para hacer que el movimiento dure exactamente el tiempo que desee. Si su ciclo de juego se ejecuta una vez cada 25 milisegundos (40 veces por segundo), entonces puede usar numberOfTicks * 0.5 * pi / 40 para hacer que el enemigo se mueva desde el centro a la parte superior, precisamente una vez por segundo, o numberOfTicks * 0.5 * pi / (40 * 2) Para hacer que se mueva desde la parte superior a la fondo precisamente una vez por segundo.

Por supuesto, puedes olvidarte de todo eso y experimentar con diferentes números para ver qué se siente bien. Esta demo utiliza sin (numberOfTicks / 50), y me gusta el resultado:

Experimenta y diviértete.!