Snelle tip creëer soepele vijandbewegingen met sinusvormige beweging

In deze Snelle tip zal ik u laten zien hoe u de sinus functie om de objecten van uw spel vloeiende heen-en-weergaande beweging te geven - geen harde zig-zags waarbij uw zwevende vijanden tegen een onzichtbare muur lijken te stuiteren!


Voorbeelden

Laat me je eerst de soort soepele heen en weer beweging laten zien die ik bedoel. (Afbeeldingen zijn afkomstig van ons volledig gratis shoot-'em-up sprite-pakket.)

Deze vijand beweegt op en neer en schiet met regelmatige tussenpozen op kogels:

Deze vijand weeft over het scherm:

Beide soorten bewegingen zijn handig voor shoot-'em-up-games. Let op hoe soepel en geleidelijk de beweging aanvoelt - geen plotselinge bewegingen, geen "ruk" als de vijand van richting verandert. Dat staat in schril contrast met ...


De naïeve benadering

Een veel voorkomende eerste poging om een ​​heen-en-weergaande beweging te maken, is door iets als dit te doen:

 var goingUp = false; // De functie wordt om de paar milliseconden uitgevoerd. // Zie: 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; 

Kort gezegd, dit geeft de vijand de opdracht om met een constante snelheid (dat wil zeggen telkens hetzelfde aantal pixels) omlaag te gaan tot het het laagste punt in het toegestane bereik bereikt, om vervolgens met dezelfde constante snelheid omhoog te gaan totdat het het hoogste punt bereikt in het toegestane bereik, steeds weer opnieuw.

De vijand kan horizontaal worden verplaatst door de vijand in te stellen xSpeed naar een ander nummer dan nul: een negatief getal laat het naar links gaan en een positief getal zorgt ervoor dat het naar rechts beweegt.

Deze voorbeelden laten zien hoe deze beweging eruit ziet. Ten eerste zonder horizontale beweging:

Nu met horizontale beweging:

Het bereikt het doel om heen en weer te gaan, maar het is zeker niet zo soepel als ons eerdere voorbeeld.


De oorzaak

De reden voor deze hobbelige beweging is dat de verticale snelheid van de vijand een plotselinge enorme verandering teweegbrengt, ook al is de waarde van ufo.ySpeed blijft hetzelfde.

Veronderstellen ufo.ySpeed is 10. Op weg naar boven beweegt de vijand omhoog met 10px / tick (pixels per tik, waarbij een "vinkje" de lengte is van één gamelus). Zodra de vijand de top heeft bereikt, keert deze om van richting en beweegt zich plotseling met 10 px / tik naar beneden. De verschuiving van + 10px / tick naar -10px / tick is een verschil van 20px / tick, en dat is wat zo opvalt.

Wanneer de oorzaak zo is uiteengezet, lijkt de oplossing voor de hand liggend: vertraag de vijand dichtbij de hoogste en laagste punten! Op die manier zal de verandering in snelheid niet zo groot zijn als het de richting omkeert.

Een eerste poging hiertoe kan er als volgt uitzien:

 var goingUp = false; var movingSlowly = false; // De functie wordt om de paar milliseconden uitgevoerd. // Zie: 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 (goUp) 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; 

Deze code is rommelig, maar je krijgt het idee: als de vijand zich binnen 100px van zijn hoogste of laagste begrenzing bevindt, beweegt hij met de helft van zijn normale snelheid.

Dit werkt, hoewel het niet perfect is. De vijand zal nog steeds een "sprong" in snelheid maken wanneer hij van richting verandert, maar hij zal in ieder geval niet zo opvallen. De vijand krijgt nu echter extra sprongen in snelheid wanneer deze van het normale tempo naar de langzamere snelheid beweegt! Dang.

Wij kon repareer dit door het bereik op te splitsen in kleinere secties, of door de snelheid wat meervoudig te maken van de exacte afstand van de vijand tot zijn grenzen ... maar er is een eenvoudigere manier.


Sinusoïdale beweging

Denk aan een modeltrein die rond een perfect cirkelvormige baan gaat. De trein verandert voortdurend van richting en toch beweegt hij in een gestaag tempo, zonder "sprongen".

Stel je nu een muur voor aan de ene kant van de cirkelvormige baan en een groot helder licht aan de andere kant (dus de baan en de trein zitten tussen de twee). De trein werpt een schaduw op de muur. Maar natuurlijk zal die schaduw niet in een cirkel bewegen, omdat de muur vlak is: hij zal heen en weer bewegen, in een rechte lijn, maar nog steeds met die soepele sprongvrije beweging van de trein!

Dat is precies wat we willen. En gelukkig is er een functie die het ons zal geven: de sinus functie. Deze geanimeerde GIF van Wikipedia toont:


Afbeelding van Wikimedia Commons. Bedankt, Lucas!

De rode lijn is de curve van y = sin (x). Zo, zonde (0.5 * pi) is 1, sin (pi) is 0, enzovoort.

Het is een beetje onhandig dat pi (π) de basiseenheid is die voor deze functie wordt gebruikt, maar we kunnen het wel beheren. We kunnen het als volgt gebruiken:

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

Zie je wat hier gebeurt? Na één vinkje, ufo.y zal worden ingesteld op sin (1 * pi), welke is 0. Na twee tikken, ufo.y zal worden ingesteld op zonde (2 * pi), wat is ... 0, nog een keer. Oh. Wacht even.

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

Nu, na één vinkje, ufo.y zal worden ingesteld op zonde (0.5 * pi), welke is 1. Na twee tikken, ufo.y zal worden ingesteld op sin (1 * pi), welke is 0. Na drie tikken, ufo.y zal worden ingesteld op sin (1,5 * pi), welke is -1, enzovoorts. (De sinusfunctie wordt herhaald, dus sin (a) == sin (a + (2 * pi)), altijd - u hoeft zich daar geen zorgen over te maken een is onder een bepaald aantal!)

Vanzelfsprekend vanuit 1 naar 0 naar -1 en zo verder is niet wat we willen. Ten eerste willen we dat de grenswaarden dan iets anders zijn 1 en -1. Dat is gemakkelijk - we vermenigvuldigen het geheel gewoon zonde functie door onze gewenste maximale grens:

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

Nu zal de vijand verdwijnen y = +250 naar y = -250. Als we willen dat het gaat 100 naar 600, we kunnen gewoon een extra toevoegen 350 op deze waarde (sinds 250 + 350 = 600 en -250 + 350 = 100):

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

Maar de waarde springt nog steeds van 100 naar 350 naar 600, omdat het sin (numberOfTicks * 0.5 * pi) springt nog steeds van -1 naar 0 naar 1.

Maar ach, we weten waarom dat is gebeurt: het is omdat de waarde van numberOfTicks * 0.5 * pi springt van 0,5 * pi naar 1 * pi naar 1,5 * pi. Kijk nogmaals naar de GIF als je niet begrijpt waarom dat het zou veroorzaken:

Dus alles wat we moeten doen is een andere kloof kiezen tussen het nummer dat we invoeren in de zonde() functie, in plaats van numberOfTicks * 0.5 * pi. Als u wilt dat de heen-en-weergaande beweging tien keer zo lang duurt, gebruik dan numberOfTicks * 0,5 * pi / 10. Als u wilt dat het 25 keer zo lang duurt, gebruik dan numberOfTicks * 0,5 * pi / 25, enzovoorts.

U kunt deze regel gebruiken om de beweging precies zo lang te laten duren als u wilt. Als je gamelus eens in de 25 milliseconden (40 keer per seconde) wordt uitgevoerd, kun je deze gebruiken numberOfTicks * 0,5 * pi / 40 om de vijand precies één keer per seconde van het centrum naar de top te laten bewegen, of numberOfTicks * 0.5 * pi / (40 * 2) om het van de top naar de te verplaatsen bodem precies één keer per seconde.

Natuurlijk kun je dat allemaal gewoon vergeten en met verschillende nummers experimenteren om te zien wat goed voelt. Deze demo gebruikt sin (numberOfTicks / 50), en ik hou van het resultaat:

Experimenteer en heb plezier!