Artificiell jakt och flockbeteende inom datorspel
Abstrakt Chasing and Evading är ett begrepp som används för att beskriva hur agenter rör sig mot (jagar), eller undviker en spelare. Liksom i de flesta fall av AI finns det olika nivåer att implementera ett sådant system. Ju fler eventuella hinder som existerar eller om spelaren rör på sig, ju smartare måste agenten vara. Ett sådant system är grundläggande för de flesta spel då möjligheten för de datorstyrda fienderna att kunna nå spelaren kan tyckas vara ganska väsentlig. Evading är i princip motsatsen till Chasing och fungerar på ungefär samma sätt. Rapporten kommer att fokusera på Chasing, hur en datorstyrd agent tar sig fram och/eller jagar en spelare och hur den löser eventuella problem som kan dyka upp. Även vilka komplikationer som kan uppstå och lösningar som kan implementeras ifall man vill använda mer än en agent i en så kallad flock.
Innehållsförteckning Abstrakt... 2 Inledning... 4 Syfte... 4 Basic chasing... 6 Line-of-sight chasing... 6 Bresenhams linje algoritm... 7 Rörelse mönster... 8 Obstacle avoidance... 8 Flocking... 8 Synfält... 9 Cohension... 10 Alignment... 11 Seperation... 12 Diskussion... 13
Inledning Mycket framgång har gjorts i AI teknologin som används inom spelutveckling. Länge har spelföretagen tävlat om att göra de grafiskt snyggaste spelen och i takt med utvecklingen av hårdvara har resultaten varit häpnadsväckande. Även om strävan efter den vassaste grafiken fortfarande finns läggs allt mer fokus på den artificiella intelligensen hos de datorstyrda agenterna. Allt för att göra spelupplevelsen så verklighetstrogen som möjligt. En av de mest grundläggande egenskaperna för en datorstyrd agent är att kunna ta sig fram i den skapade världen. I de flesta datorspelen, av alla sorter, uppstår det dessutom någon form av jakt där den datorstyrda agenten har som mål att ta sig fram till spelaren. Hur får man en datorstyrd agent att aktivt jaga spelaren, och hur löser man det på ett intelligent sätt? Hur ser det ut om man använder sig av mer än en agent? Syfte Syftet med rapporten är att skapa en fördjupad kunskap inom datorspelens AI och fenomenet Chase and Evade. Vad begreppet innebär, hur det är uppbyggt och hur man generellt löser olika problem som kan dyka upp på vägen. Även hur man löser problem när det är mer än en agent involverad och skapar ett såkallat flockbeteende.
Miljön som en Chase sekvens kan utspela sig i kan antingen vara plattformad eller kontinuerlig miljö. Med plattformad menas att välden är uppbyggd av distinkta plattor tiles som representeras av koordinater beskrivna i heltal och tillåter bara rörelse från platta till platta i åtta olika riktningar (t ex N, W, E, NW, NE etc.) I en kontinuerlig värld är positionen representerad av floating-point koordinater, vilket kan representera vilken plats i världen som helst. Agenterna representeras av stela kroppar vilket i princip betyder att agenterna får en volym. Detta ger agenterna utrymme att gå i vilken riktning som helst. I en kontinuerlig värld använder man sig även av fysikens lagar och lägger till krafter för att röra eller styra agenten. Chasing and Evading problemet består av 3 delar: Valet att initiera en Chase eller Evade Själva initieringen av jakten Att undvika eventuella hinder Till att börja med kan man konstatera det enklaste och vanligaste AI-tricket är att datorn fuskar. Den datorstyrda agenten har redan all information som krävs för att ta sig till spelaren. Men för att göra spelet mer realistiskt och ge spelaren en chans kräver man vissa indikatorer för att datorn skall börja initiera en jakt. Detta kan t ex vara om spelaren hamnar inom agentens synfält, ett område runt agenten som känner av ifall spelaren är i närheten, när agenten väl har fått syn på spelaren initieras jakten. Sättet som agenten tar sig till spelaren kan se ut på olika sätt.
Basic chasing Basic chasing är en simpel lösning på jakt problemet i en plattbaserad miljö. Det går enkelt ut på att rätta agentens koordinater utefter spelarens koordinater tills agenten har samma koordinater som spelaren. Det sättet må vara effektivt och lätt att implementera men det får själva rörelsen att se väldigt hackig och mekanisk ut. Pseudo kod exempel: if ( predatorx > preyx ) predatorx - else if ( predatorx < preyx ) predatorx++ if ( predatory > preyy ) predatory - else if ( predatory < preyy ) predatory++ Line-of-sight chasing Line-of-sight chasing uppför sig mer naturligt och mer intelligent än Basic chasing. Här skapas illusionen av att agenten ser spelaren och tar den rakaste och mest direkta vägen. I en platt baserat miljö där spelaren bara kan röra sig i 8 olika riktningar är det väsentligt att agenten bara rör sig till närliggande plattor. Som lösning på det problemet använder man Bresenhams linje algoritm för att bestämma den kortaste vägen fram till målet. I en kontinuerlig miljö används samma princip men genom att använda sig av stela kroppar som representerar agenten och fysik motor kan man istället använda sig av krafter för att röra agenten i riktningen som Bresenhams algoritm ritar ut.
Bresenhams linje algoritm Bresenhams linje algoritm använder sig av räta linjens ekvation y = kx+m för att räkna ut den sanna linjen mellan p1 och p2. Men eftersom det är omöjligt att rita ut den sanna linjen i en pixlerad värld ritar Bresenhams algoritm istället ut en approximerade linjen. Det första den gör är att bestämma vilken av axlarna som är den större axeln och vilken som är den mindre. I bilden är x- axeln den större axeln. I varje upprepning av algoritmen utvecklas värdet av den större axeln med en pixel. Efter det räknar den ut vilken pixel på den mindre axeln som är lämplig för den nuvarande pixel i den stora axeln. Detta gör algoritmen genom att checka vilken pixels centerum som är närmst den sanna linjen. Den tar koordinaterna för den sanna linjen och jämför med koordinaterna för pixelns centrum. Om felmarginalen från pixelns centrum till den sanna linjen är mindre eller lika med 0.5 ritas pixeln ut på den positionen. Exempel: På bilden ser man den sanna linjen i svart och de gråmarkerade rutorna visar den approximerade vägen agenten kan ta som resultat av uträkningen. Det vita strecket från pixel-center ner till den sanna linjen visar felmarginalen. deltax = x2 - x1; deltay = y2 - y1; m = deltay / deltax; y = y1; for (x = x1; x <= x2; x++) { PutPixel(x, round(y)); y += m; } // Skillnaden mellan x // Skillnaden mellan y // Lutningen på linjen // Startar på första pixel värdet // Ritar ut pixel // Räknar ut nästa y värde Genom att hela tiden rita ut riktningen med hjälp av Bresenhams algoritm kan agenten bestämma vilken platta den skall gå till härnäst. Varje gång målet byter position blir uträkningen därför felaktig och en ny uträkning måste därför göras.
Rörelse mönster Med hjälp av Bresenhams linje algoritm kan man även få agenten att röra sig i ett speciellt mönster. Detta gör den genom att rita ut flera linjer i följd, och sedan låta agenten följa vägen från den först ritade linjen mellan p1 till p2 och sedan fortsätta mellan p2 till p3 osv. En enkel lösning på när agenten får flera möjliga vägar att ta är att enkelt slumpa valet av väg. Detta sätt är vanligt att använda för när agenten är i ett Roaming State och alltså inte ännu har initierat en jakt. Obstacle avoidance För att undvika kollision med objekt i en kontinuerlig miljö skapar man virtuella känselspröt, dessa representeras av vektorer som har samma riktning som agenten rör sig i. När dessa vektorer bryts tvingas agenten att byta riktning tills vektorn återigen är obruten. För att undvika att agenten skär igenom eller fastnar på skarpa kanter skapar man mer än en vektor som täcker hela agentens kropp och tvingar agenten att ta en aningen längre väg runt hindret för att alla vektorerna skall återigen vara obrutna. Flocking Om man lägger till mer än en agent som jagar samma spelare kan olika problem uppstå. Agenterna kan börja krocka eller stå i vägen för varandra, kanske se varandra som hinder och försöka går runt. För att undvika dessa problem implementerar man något som kallas Flocking. Begreppet Flocking innebär att agenterna agerar i ett slags flockbeteende istället för att jaga spelaren individuellt. Det innebär att agenterna skapar en grupp som hela tiden tar hänsyn till varandras positioner och riktningar. Craig Reynolds har skapat en effektiv algoritm för det här beteende som har en viss likhet med det hos ett stim fiskar. Algoritmen bygger på tre enkla regler: Cohension: Varje enhet styr mot den genomsnittliga positionen av sina grannar. Alignment: Varje individ styr så att den ställer upp sig själv i samma riktning som sina grannar Separation: Varje individ styr sig själv för att undvika kollision med sina grannar.
Utefter dessa tre regler är det tydligt att varje individ måste kunna använda sig att styrnings mekanismer för att kunna reglera sin riktning och position. Dessutom måste varje enhet vara medveten om sin omgivning, var grannarna befinner sig vart de är på väg och hur nära de är sitt mål. I en fysisk simulerad, kontinuerlig miljö kan man använda sig av styrningskrafter för att få agenterna att lyckas med detta. I en platt baserad miljö kan man använda sig av något liknande line-of-sight chasing, t ex för Cohension kan man använda sig av Bresenhams algoritm för grannarnas positioner och räkna ut den genomsnittliga positionen i plattornas koordinater. Synfält Genom att implementera en Visability arc ger man agenten ett begränsat synfält som berättar vad som finns i dess närhet och detta hjälper dem att hålla koll på sina grannar. Synfältet runt agenten tar två parametrar, vinkeln på bågen och radien, en större radie tillåter för bättre kontroll över agentens omgivning, medans en mindre radie riskerar att tappa bort sina grannar. En stor radie med 360 graders vinkel skulle vara en relativt enkel lösning för att få agenterna att rada upp sig snyggt med full koll på allt i sin närhet men risken finns att det skulle se orealistiskt ut. Istället är det vanligare att man ger agenten ett begränsat synfält som har samma riktning som agenten. T ex en vinkel på 270 grader (vänstra exemplet i bilden) med en hyfsat stor radie skulle resultera i en välformerad flock medans en vinkel på 45 grader (högra exemplet på i bilden) mer skulle resultera i en slags gåsmarsch.
För att uppfylla de tre reglerna cohension, alignment och seperation måste tillräcklig data från synfältet samlas in, agentens position, grannarnas position och riktningen. Eftersom grannarnas positioner är relativa för vilken agent som kontrolleras måste alla individer i flocken samla in den här informationen i varje upprepning av loopen. Detta kan bli väldigt CPU-tungt beroende på hur många agenter existerar i världen. Cohension (Sammanhang) För att uppfylla cohension regeln kontrollererar agenten först och främst var grannarna befinner sig och registrerar deras respektive positioner. Efter det börjar den räkna ut den genomsnittliga positionen av sina grannar. Detta gör den genom att ta vektor summan från alla grannars positioner och dividerar med antalet grannar i synfältet. Resultatet representerar den genomsnittliga positionen som agenten bör ha.
Exempel: I koden beskrivs Pave och vposition, vvelocity som Vektor typer. Och Units[j] representerar grannarna medans Units[i] själva agenten som kontrolleras. if (DoFlock&&(N>0)) //Här kontrolleras synfältet efter antalet grannar I närheten if (InView) if (d.magnitude() <= (Units[i].fLength * RadiusFactor)) Pave += Units[j].vPosition //Här summeras vektorerna för grannaras positioner Vave += Units[j].vVelovity //Här summeras riktninsvektorerna som kommer att användas i alignment regeln. Sedan räknas grannarnas genomsnittliga position ut genom att ta Pave/N, därefter beräknas vektor skillnaden mellan Units[i] och den genomsnittliga positionen av grannarna vilket resulterar i en positions vektor som representerar den optimala platsen för agenten att stå på. Pave = Pave / N u = Pave Units[i].vPosition Alignment (Uppställning) Här vill man att alla enheter i gruppen skall styra mot i princip samma riktning. För att satisfiera den här regeln behöver man hämta varje grannes riktningsvektor, det vill säga vektorn som bestämmer rörelse riktningen i en fysiskt simulerad värld. Med hjälp av den kan man bestämma i vilken riktning som grannarna färdas i. Sättet att bestämma den genomsnittliga riktningen är i princip identiskt med sättet att bestämma den genomsnittliga positionen, agenten delar grannarnas riktningsvektorer med antalet grannar och får ut den genomsnittliga riktningen som agenten bör ha för att få en bra uppställning. Pave = Pave / N u = Pave Units[i].vVelocity // vvelocity representerar riktningsvektorn Sedan jämför den sin riktning med grannarnas genomsnittliga riktning för att få ut vilken riktning agenten bör ha.
Seperation Seperation antyder att vi vill att agenterna ska hålla ett minimum avstånd från varandra även om de fortfarande är tillräckligt nära varandra för att de andra två reglerna skall vara uppfyllda. Här implementerar man en mindre båge runt agenten som representerar minimum avståndet till grannarna. Om grannarna är innanför minimun avståndet till agenten som bestäms av SEPERATION_FACTOR, bestäms riktnings korrektion beroende på vart grannen befinner sig i x led. (m =(+/-) 1) if (InView) if (d.magnitude() <= (units[i].flength * SEPERATION_FACTOR)) if (w.x < 0) m = 1 if (w.x > 0) m = -1
Diskussion Just möjligheten att kunna få sina agenter att röra sig, själva eller i grupp, är grunden för nästan alla datorspel. Även om ämnet inte är cutting edge finns det ett visst värde I att känna till de absoluta grunderna. Det finns säkert fler sätt att få en agent att ta sig fram i en värld än de som har tagits upp i rapporten men den ger en insikt över hur komplicerade även de mest grundläggande sakerna kan vara. Flockbeteende var från första början menat som ett komplement till chase and evade, vad händer om man lägger till fler agenter?, men under loppets gång insåg jag att det nästan borde ha varit en rapport i sig själv med tanke på hur komplicerat det var. Därför har jag utelämnat detaljer som hur fysik motorn fungerar och hur man applicerar krafter till sin agent och mer fokuserat på teorin bakom det hela. Jag tycker ändå att man får en bra förståelse för hur man löser problemen, utan att vara någon expert på att användningen av en fysik motor.
Referenser Text: AI for Game Developers, David M. Bourg and Glenn Seemann, O Reilly Game AI, Ming-Hwa Wang, Ph.D. COEN 396 Interactive Multimedia and Game Programming, Department of Computer Engineering, Santa Clara University http://oreilly.com/catalog/ai/chapter/ch04.pdf http://www.gamedev.net/page/resources/_/reference/programming/140/283/bresenhamr767 http://vimeo.com/9304844 http://www.falloutsoftware.com/tutorials/dd/dd4.htm Bilder: http://monkeysofwar.blogspot.com/2007/12/tile-based-game-line-of-sight.html http://www.falloutsoftware.com/tutorials/dd/dd4.htm http://oreilly.com/catalog/ai/chapter/ch04.pdf