Snabbtips Skapa en jämn rörelse med sinusoidal rörelse

I denna Snabba Tips, visar jag dig hur du använder sinus funktion för att ge ditt spel objekt glatt fram och tillbaka rörelse - inte mer hårda zigzags där dina flytande fiender verkar studsa mot en osynlig vägg!


exempel

Låt mig först visa dig den typ av smidig fram och tillbaka rörelse som jag menar. (Grafik är från vårt helt gratis shoot-up-sprite-paket.)

Denna fiende rör sig upp och ner, skjutar kulor med jämna mellanrum när det går:

Denna fiende väver över skärmen:

Båda typerna av rörelser är praktiska för shoot-up-spel. Lägg märke till hur smidig och gradvis rörelsen känns - inga plötsliga rörelser, ingen "ryck" när fienden ändrar riktningen. Det står i stark kontrast till ...


Den naiva metoden

Ett vanligt första försök att skapa en fram och tillbaka rörelse är att göra något så här:

 var goingUp = false; // Funktionen körs några få millisekunder. // Se: http://gamedev.tutsplus.com/articles/glossary/quick-tip-what-is-the-ame-loop/ function gameLoop () om (ufo.y> = bottomOfRange) goingUp = true ;  annars om (ufo.y <= topOfRange)  goingUp = false;  if (goingUp)  ufo.y -= ufo.ySpeed;  else  ufo.y += ufo.ySpeed;  ufo.x += ufo.xSpeed; 

I grund och botten berättar detta att fienden ska flytta ner med konstant hastighet (dvs samma antal pixlar varje gång) tills den når den lägsta punkten i det tillåtna intervallet och sedan flytta upp med samma konstanta hastighet tills den når den högsta punkten i det tillåtna intervallet, om och om igen.

Fienden kan göras för att flytta horisontellt genom att sätta sin xSpeed till ett annat nummer än noll: ett negativt tal gör det rör sig till vänster och ett positivt tal gör att det rör sig rätt.

Dessa exempel visar hur den här typen av rörelse ser ut. Först utan horisontell rörelse:

Nu med horisontell rörelse:

Det uppnår målet att flytta fram och tillbaka, men det är verkligen inte så smidigt som vårt tidigare exempel.


Orsaken

Anledningen till den här humpiga rörelsen är att fiendens vertikala hastighet gör en plötslig stor förändring - även om värdet av ufo.ySpeed stannar densamma.

Anta ufo.ySpeed är 10. På vägen uppflyttar fienden uppåt på 10px / tick (pixlar per ficka, där en "tick" är längden på en spelslinga). När fienden når toppen, vänder den riktning och rör sig plötsligt vid 10px / tick nedåt. Skiftet från + 10px / tick till -10px / tick är en skillnad på 20px / tick, och det är så märkbart.

När orsaken stavas ut så här, verkar lösningen uppenbar: sakta ner fienden ner nära högsta och lägsta poängen! På så sätt blir förändringen i sin hastighet inte så stor när den vrider riktning.

Ett första försök på detta kan se ut så här:

 var goingUp = false; var movingSlowly = false; // Funktionen körs några få millisekunder. // Se: http://gamedev.tutsplus.com/articles/glossary/quick-tip-what-is-the-ame-loop/ function gameLoop () om (ufo.y> = bottomOfRange) goingUp = true ;  annars om (ufo.y <= topOfRange)  goingUp = false;  if (ufo.y <= bottomOfRange + 100)  movingSlowly = true;  else if (ufo.y >= topOfRange - 100) movingSlowly = true;  else movingSlowly = false;  om (movingSlowly) if (goingUp) ufo.y - = ufo.ySpeed ​​/ 2;  annars ufo.y + = ufo.ySpeed ​​/ 2;  annars om (går upp) ufo.y - = ufo.ySpeed;  annars ufo.y + = ufo.ySpeed;  ufo.x + = ufo.xSpeed; 

Denna kod är rörig, men du får tanken: om fienden är inom 100 px av sin högsta eller lägsta gräns, rör den sig vid hälften av sin normala hastighet.

Detta fungerar, även om det inte är perfekt. Fienden kommer fortfarande att ha ett "hopp" i hastighet när det ändrar riktning, men det kommer åtminstone inte att vara lika märkbart. Emellertid kommer fienden nu att ha ytterligare hopp i snabbhet när den flyttar från sin vanliga takt till den långsammare hastigheten! Dang.

Vi skulle kunna fixa detta genom att dela upp intervallet i mindre sektioner eller göra hastigheten lite flera av det exakta avståndet från fienden till dess gränser ... men det finns ett enklare sätt.


Sinusoidal rörelse

Tänk på ett modelltåg som går runt ett perfekt cirkulärt spår. Tåget ändras ständigt, men ändå går det i stadig takt, utan "hoppar".

Föreställ dig nu en vägg på ena sidan av det cirkulära spåret och ett stort starkt ljus på motsatt sida (så spåret och tåget är mellan de två). Tåget kommer att kasta en skugga på väggen. Men naturligtvis kommer skuggan inte att röra sig i en cirkel, eftersom väggen är platt: den kommer att röra sig fram och tillbaka, i en rak linje, men fortfarande med den jämna, jämna rörelsen av tåget!

Det är precis vad vi vill ha. Och lyckligtvis finns det en funktion som ger oss det: sinus fungera. Denna animerade GIF från Wikipedia visar:


Bild från Wikimedia Commons. Tack, Lucas!

Den röda linjen är kurvan för y = sin (x). Så, synd (0,5 * pi) är 1, sin (pi) är 0, och så vidare.

Det är lite obekvämt att pi (π) är den grundläggande enheten som används för denna funktion, men vi kan hantera. Vi kan använda det som så:

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

Se vad som händer här? Efter en kryssning, ufo.y kommer att ställas in på synd (1 * pi), vilket är 0. Efter två fästingar, ufo.y kommer att ställas in på synd (2 * pi), vilket är… 0, igen. Åh. Vänta.

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

Nu, efter en kryssning, ufo.y kommer att ställas in på synd (0,5 * pi), vilket är 1. Efter två fästingar, ufo.y kommer att ställas in på synd (1 * pi), vilket är 0. Efter tre fästingar, ufo.y kommer att ställas in på synd (1,5 * pi), vilket är -1, och så vidare. (Sinusfunktionen upprepas, så synd (a) == sin (a + (2 * pi)), alltid - du behöver inte oroa dig för att se till att en är under ett visst antal!)

Uppenbarligen kommer från 1 till 0 till -1 och så vidare är inte vad vi vill ha. För det första vill vi gränsvärdena vara något annat då 1 och -1. Det är enkelt - vi multiplicerar bara hela synd funktion med önskad maxgräns:

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

Nu kommer fienden att gå ifrån y = +250 till y = -250. Om vi ​​vill att den ska gå från 100 till 600, Vi kan bara lägga till en extra 350 vidare till detta värde (sedan 250 + 350 = 600 och -250 + 350 = 100):

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

Men värdet hoppar fortfarande från 100 till 350 till 600, eftersom det synd (numberOfTicks * 0.5 * pi) hoppar fortfarande från -1 till 0 till 1.

Men hej, vi vet varför det är händer: det är för att värdet av numberOfTicks * 0.5 * pi hoppar från 0,5 * pi till 1 * pi till 1,5 * pi. Titta på GIF igen om du inte ser varför det skulle orsaka det:

Så allt vi behöver göra är att välja ett annat gap mellan det nummer som vi matar in till synd() funktion, istället för numberOfTicks * 0.5 * pi. Om du vill att fram och tillbaka rörelse ska ta tio gånger så lång tid, använd numberOfTicks * 0.5 * pi / 10. Om du vill att den ska ta 25 gånger så lång tid, använd numberOfTicks * 0.5 * pi / 25, och så vidare.

Du kan använda denna regel för att göra rörelsen sist så länge du vill ha den. Om din spelslinga går en gång var 25: e millisekunder (40 gånger per sekund) kan du använda numberOfTicks * 0.5 * pi / 40 för att få fienden att flytta från centrum till toppen exakt en gång per sekund, eller numberOfTicks * 0,5 * pi / (40 * 2) för att få det att flytta från toppen till botten exakt en gång per sekund.

Naturligtvis kan du bara glömma allt detta och experimentera med olika siffror för att se vad som känns rätt. Denna demo använder synd (numberOfTicks / 50), och jag gillar resultatet:

Experimentera och ha kul!