Astuce créez un mouvement lisse et ennemi avec un mouvement sinusoïdal

Dans ce petit conseil, je vais vous montrer comment utiliser le sinus fonction pour donner aux objets de votre jeu un mouvement de va-et-vient fluide - plus besoin de zig-zag durs où vos ennemis flottants semblent rebondir contre un mur invisible!


Exemples

Tout d’abord, laissez-moi vous montrer le genre de mouvement en douceur que je veux dire. (Les graphiques proviennent de notre pack sprite totalement gratuit.)

Cet ennemi monte et descend, tirant des balles à intervalles réguliers:

Cet ennemi traverse l'écran:

Les deux types de mouvement sont pratiques pour les jeux shoot-up. Remarquez à quel point le mouvement est fluide et progressif - pas de mouvements brusques, pas de "jerk" lorsque l’ennemi change de direction. C'est à l'opposé de…


L'approche naïve

Une première tentative courante de créer un mouvement de va-et-vient consiste à faire quelque chose comme ceci:

 var goingUp = false; // Fonction exécutée toutes les quelques millisecondes. // Voir: 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; 

En gros, cela indique à l'ennemi de descendre à une vitesse constante (le même nombre de pixels à chaque fois) jusqu'à atteindre le point le plus bas de sa plage autorisée, puis de monter à la même vitesse constante jusqu'à ce qu'il atteigne le point le plus haut de sa plage. sa plage autorisée, encore et encore.

L’ennemi peut se déplacer horizontalement en définissant sa position. xSpeed à un nombre autre que zéro: un nombre négatif le fait bouger à gauche et un nombre positif le fait bouger à droite.

Ces exemples montrent à quoi ressemble ce type de mouvement. Tout d'abord, sans mouvement horizontal:

Maintenant, avec un mouvement horizontal:

Il permet d’avancer dans les deux sens, mais ce n’est certainement pas aussi fluide que notre exemple précédent..


La cause

La raison de ce mouvement cahoteux est que la vitesse verticale de l'ennemi fait un changement énorme et soudain - même si la valeur de ufo.ySpeed reste le même.

Supposer ufo.ySpeed est dix. En montant, l'ennemi monte à 10px / tick (pixels par tick, où un "tick" correspond à la longueur d'une boucle de jeu). Une fois que l'ennemi a atteint le sommet, il change de direction et se déplace soudainement à 10px / tick vers le bas. Le passage de + 10px / tick à -10px / tick est une différence de 20px / tick, et c'est ce qui est perceptible.

Lorsque la cause est énoncée de la sorte, la solution semble évidente: ralentissez l'ennemi près des points les plus élevés et les plus bas! De cette façon, le changement de vitesse ne sera pas si grand quand il changera de direction.

Une première tentative pourrait ressembler à ceci:

 var goingUp = false; var movingSlowly = false; // Fonction exécutée toutes les quelques millisecondes. // Voir: 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 if (goingUp) ufo.y - = ufo.ySpeed;  else ufo.y + = ufo.ySpeed;  ufo.x + = ufo.xSpeed; 

Ce code est en désordre, mais vous avez l’idée: si l’ennemi se trouve à moins de 100 pixels de ses limites maximale ou minimale, il se déplace à la moitié de sa vitesse normale..

Cela fonctionne, même si ce n'est pas parfait. L'ennemi aura toujours un "saut" de vitesse lorsqu'il changera de direction, mais au moins, cela ne sera pas aussi visible. Cependant, la vitesse de l’ennemi augmentera encore lorsqu’elle passera de son rythme normal à la vitesse la plus lente! Dang.

nous pourrait Résolvez ce problème en divisant la plage en sections plus petites ou en multipliant la vitesse par la distance exacte entre l'ennemi et ses limites… mais il existe un moyen plus simple..


Mouvement sinusoïdal

Pensez à un train miniature circulant sur une voie parfaitement circulaire. Le train change constamment de direction, et pourtant, il avance à un rythme soutenu, sans "sauts".

Maintenant, imaginez un mur d’un côté de la voie circulaire et une grande lumière brillante de l’autre côté (la voie et le train se trouvent entre les deux). Le train jettera une ombre sur le mur. Mais bien sûr, cette ombre ne bougera pas en cercle, car le mur est plat: elle va et vient, en ligne droite, mais toujours avec ce mouvement fluide et sans saut du train.!

C'est exactement ce que nous voulons. Et heureusement, il existe une fonction qui nous le donnera: le sinus une fonction. Ce GIF animé de Wikipedia montre:


Image de Wikimedia Commons. Merci Lucas!

La ligne rouge est la courbe de y = péché (x). Alors, péché (0.5 * pi) est 1, péché (pi) est 0, et ainsi de suite.

Il est un peu gênant que pi (π) soit l'unité de base utilisée pour cette fonction, mais nous pouvons le gérer. Nous pouvons l'utiliser comme ceci:

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

Voir ce qui se passe ici? Après une tique, ufo.y sera mis à péché (1 * pi), lequel est 0. Après deux ticks, ufo.y sera mis à péché (2 * pi), lequel est… 0, encore. Oh. Attendre.

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

Maintenant, après une tique, ufo.y sera mis à péché (0.5 * pi), lequel est 1. Après deux ticks, ufo.y sera mis à péché (1 * pi), lequel est 0. Après trois ticks, ufo.y sera mis à péché (1,5 * pi), lequel est -1, etc. (La fonction sinus se répète, donc péché (a) == péché (a + (2 * pi)), toujours - vous n'avez pas à vous soucier de vous assurer que une est en dessous d'un certain nombre!)

Évidemment allant de 1 à 0 à -1 et cetera n'est pas ce que nous voulons. Tout d'abord, nous voulons que les valeurs limites soient quelque chose d'autre, puis 1 et -1. C'est facile - on multiplie simplement le tout péché fonction de notre limite maximale souhaitée:

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

Maintenant, l'ennemi partira de y = +250 à y = -250. Si on veut que ça passe de 100 à 600, nous pouvons simplement ajouter un extra 350 sur cette valeur (depuis 250 + 350 = 600 et -250 + 350 = 100):

 var numberOfTicks = 0; fonction gameLoop () numberOfTicks ++; ufo.y = (250 * sin (numberOfTicks * 0,5 * pi)) + 350; ufo.x + = ufo.xSpeed; 

Mais la valeur saute encore de 100 à 350 à 600, parce que le sin (numberOfTicks * 0.5 * pi) est encore en train de sauter -1 à 0 à 1.

Mais bon, on sait pourquoi c'est passe: c’est parce que la valeur de numberOfTicks * 0.5 * pi est de sauter 0.5 * pi à 1 * pi à 1,5 * pi. Regardez à nouveau le GIF si vous ne voyez pas pourquoi cela le causerait:

Il suffit donc de choisir un espace différent entre le nombre que nous alimentons le péché() fonction, au lieu de numberOfTicks * 0.5 * pi. Si vous voulez que le mouvement de va-et-vient dure dix fois plus longtemps, utilisez numberOfTicks * 0.5 * pi / 10. Si vous voulez que cela prenne 25 fois plus longtemps, utilisez numberOfTicks * 0.5 * pi / 25, etc.

Vous pouvez utiliser cette règle pour que la motion dure aussi longtemps que vous le souhaitez. Si votre boucle de jeu s’exécute une fois toutes les 25 millisecondes (40 fois par seconde), vous pouvez utiliser numberOfTicks * 0.5 * pi / 40 pour que l'ennemi se déplace du centre vers le haut avec précision une fois par seconde, ou numberOfTicks * 0.5 * pi / (40 * 2) pour le faire passer du haut vers le bas précisément une fois par seconde.

Bien sûr, vous pouvez simplement oublier tout cela et expérimenter avec différents chiffres pour voir ce qui vous convient. Cette démo utilise sin (numberOfTicks / 50), et j'aime le résultat:

Expérimentez et amusez-vous!