Linköpings Universitet. ProjectBot. En bot för spelet Rocket League. Jesper Pettersson

Relevanta dokument
Lär dig programmera! Prova på programmering med enkla exempel! Björn Regnell

OzoCodes FÄRG KODER VINST/ UTGÅNG FART RÄKNARE RIKTNING TIMER COOLA RÖRELSER REFERENSSCHEMA VINST/UTGÅNG (SPELA IGEN) CRUISE SNABB TURBO NITRO BOOST

Konvexa höljet Laboration 6 GruDat, DD1344

Användarhandledning Version 1.2

Lektion 2: Sökagenter. Robin Keskisärkkä

Introduktion till programmering D0009E. Föreläsning 5: Fruktbara funktioner

Tentamen OOP

Programmering. Scratch - grundövningar

Kurslitteraturen. C-nivå Villkorssatser [kap 8] if & elif & else and & or Loopar [kap 9] for

Sphero SPRK+ Appen som används är Sphero Edu. När appen öppnas kommer man till denna bild.

3. Välj den sprajt (bild) ni vill ha som fallande objekt, t ex en tårta, Cake. Klicka därefter på OK.

Föreläsning 2 Programmeringsteknik och C DD1316. Mikael Djurfeldt

Programmering I Tobias Wrigstad fredag, 2009 augusti 28

Alla datorprogram har en sak gemensam; alla processerar indata för att producera något slags resultat, utdata.

EnKlass. Instans 3 av EnKlass. Instans 2 av EnKlass

Föreläsning 2 Programmeringsteknik DD1310. Programmering. Programspråk

Uppgifter teknik HT17 (uppdaterad v. 40)

Föreläsning 2 Programmeringsteknik DD1310. Programmering. Programspråk

Nu lär vi oss tre i rad

Föreläsning 3. Iteration while-satsen

Introduktion till programmering SMD180. Föreläsning 5: Fruktbara funktioner

Helgträning

Föreläsning 10 Datalogi 1 DA2001. Utskrift på skärmen. Syntax. print( Hej ) Hur är det? Hej. print( Hej,end= ) print( Hur är det? ) HejHur är det?

F3C HELIKOPTER SPORT PROGRAM (Ny manöver 2 ersätter tidigare, fr.o.m. 2001)

Objektorientering i liten skala

TENTAMEN PROGRAMMERING I JAVA, 5P SOMMARUNIVERSITETET

Inom datalogin brukar man använda träd för att beskriva vissa typer av problem. Om man begränsar sig till träd där varje nod förgrenar sig högst två

Föreläsning 2 Programmeringsteknik och C DD1316. Programmering. Programspråk

PROGRAMMERING-Java Omtentamina

Åkersberga P05: Röd & Blå

Planering av ett större program, del 2 - for och listor. Linda Mannila

Anmälningskod: Lägg uppgifterna i ordning. Skriv uppgiftsnummer (gäller B-delen) och din kod överst i högra hörnet på alla papper

Platser för att skriva och testa kod online. Workshop om programmering i matematikkurser, version 0.7 senast sparat

Laboration: Whitebox- och blackboxtesting

Introduktion till programmering SMD180. Föreläsning 3: Funktioner

16. VOLLEY Volley är tillåtet dock inte på serven.

Classes och Interfaces, Objects och References, Initialization

Tentamen i Introduktion till programmering

Iteration while-satsen

Detaljbeskrivning av Player

Python. Python är, som Scheme, ett interpreterat språk men det finns kompilatorer för Python.

Python. Python är, som Scheme, ett interpreterat språk men det finns kompilatorer för Python.

Hej idrottslärare i årskurs 7-9.

Föreläsning 2 Programmeringsteknik och C DD1316

Fotbollsövningar UPPVÄRMING:

TDDD78 projekt: Tower Defence

F4. programmeringsteknik och Matlab

Objektorienterad programmering. Fält som funktionsresultat. Mer om fält: att uppdatera ett parameterfält. Kontrast: Parametrar av primitiv typ

Programmering med Java. Grunderna. Programspråket Java. Programmering med Java. Källkodsexempel. Java API-exempel In- och utmatning.

Tor Sterner-Johansson Thomas Johansson Daniel Henriksson

729G04 Programmering och diskret matematik

Extramaterial till Matematik Y

732G Linköpings universitet 732G11. Johan Jernlås. Översikt. Repetition. Exempelduggan. Luffarschack. Koda spel

Föreläsning 3-4 Innehåll. Diskutera. Metod. Programexempel med metod

Föreläsning 5-6 Innehåll. Exempel på program med objekt. Exempel: kvadratobjekt. Objekt. Skapa och använda objekt Skriva egna klasser

PROGRAMMERING-Java TENTAMINA

Fredag 10 juni 2016 kl 8 12

Static vs Dynamic binding Polymorfism. Objekt-orienterad programmering och design Alex Gerdes, 2016

Övning 1 - Abstrakta datatyper

TDP002 Imperativ programmering. Laborationsmaterial emacs python-mode

TENTAMEN OOP

Multipel tilldelning. Introduktion till programmering D0009E. Föreläsning 6: Iteration. while-satsen. Kom ihåg. Snurror kontra rekursion

Föreläsning 5-6 Innehåll

Föreläsningsanteckningar, Introduktion till datavetenskap HT S4 Datastrukturer. Tobias Wrigstad

Introduktion till programmering SMD180. Föreläsning 2: Variabler, uttryck och satser

Robotarm och algebra

Föreläsning 3-4 Innehåll

TENTAMEN OOP

Neurala nätverk och språkigenkänning. Henrik Linnarsson. Linköping University

Lite om reella tal. Programmering. I java. Om operatorers associativitet och prioritet

Tentamen Programmeringsteknik II Skrivtid: Hjälpmedel: Java-bok (vilken som helst) Skriv läsligt! Använd inte rödpenna!

Tentamen ID1004 Objektorienterad programmering April 7, 2015

Fotbollsövningar 3 P08 Mariaskolan

732G Linköpings universitet 732G11. Johan Jernlås. Översikt. Repetition. Felsökning. Datatyper. Referenstyper. Metoder / funktioner

Genetisk programmering i Othello

Föreläsning 2 Programmeringsteknik och Matlab DD1312. Programspråk. Utskrift på skärmen

Försvar. 1. Förberedande försvarsträinng (5 eller 6 spelare). 2. Försvar mot 1:a-tempo följt av 3:e-tempo (minst 6 spelare).

Presentation av trafiksimuleringsprojektet

Sätt att skriva ut binärträd

Skillnader mellan Python och Java

Static vs Dynamic binding Polymorfism. Objekt-orienterad programmering och design (DIT953) Niklas Broberg, 2018

Flaxande Fågel. Introduktion. Level

Kvarterslag driva finta dribbla. Created with Sideline XPS Trainer -

Fotbollsskolan. skott.indd

[ÖVNINGSBANK] Sollentuna FK. Expressbollen

Mikael Bondestam Johan Isaksson. Spelprogrammering. med CDX och OpenGL

729G04 Programmering och diskret matematik. Python 2: Villkorssatser, sanningsvärden och logiska operatorer

729G04 Programmering och diskret matematik

Uppdrag för LEGO projektet Hitta en vattensamling på Mars

Tentamen DE12, IMIT12, SYST12, ITEK11 (även öppen för övriga)

Kort om klasser och objekt En introduktion till GUI-programmering i Java

Spel. 1 mot 1 på en spelplan som omfattar ca 2 m². Endast fingerslag (eller bagger) är tillåtet. Alternativt kan man tillåta tre beröringar "per lag".

Legorobot. Lär dig programmera en legorobot. Teknikåttan Ola Ringdahl Lena Kallin Westin

Föreläsning 8 SLUMPTAL, SIMULERING + INTRODUKTION TILL VEKTORER

Corioliseffekter. Uppdaterad: Om bildsekvenserna Bildsekvens 1: Boll far förbi rymdstationen längs en rät linje Bildsekvens 2:...

Några inbyggda funktioner (med resultat!) Introduktion till programmering D0009E. Föreläsning 4: Villkor och rekursion. Modulus-operatorn.

Låt eleverna lösa uppgifterna med huvudräkning och sedan jämföra med resultatet av ett program, t.ex. print(6 + 4 * 3)

Instuderingsfrågor Krafter och Rörelser

Grundläggande datalogi - Övning 4

Föreläsning 6: Introduktion av listor

Transkript:

Linköpings Universitet ProjectBot En bot för spelet Rocket League Jesper Pettersson 2019-01-11

Innehållsförteckning 1. Introduktion... 3 1.2 Rocket League... 3 1.2.1 Spelplanen... 3 1.2.2 RLBot... 4 1.3 Spelet... 5 1.3.1 Bot... 5 1.3.2 Environment... 6 2. Implementation... 7 2.1 ProjectBot... 8 2.1.1 init... 8 2.1.2 aim... 9 2.1.3 check_for_dodge... 10 2.1.4 closest_point... 10 2.1.5 get_output... 10 3. Resultat... 11 4. Reflektioner... 12 5. Referenslista... 14 6. Bilaga... 15 6.1 Bilaga 1 Python kod... 15 1

Abstract This report concerns the creation and implementation of a bot in the game Rocket League, based on Millington and Funges model of Artificial Intelligence for games. Due to the complexity of the multiagent environment, the goal of the project was to create a bot that would play evenly against the games easiest built-in bot. The created bot managed to achieve a 66%-win rate, after having played 15 games against its opponent. 2

1. Introduktion Fotboll är en av världens mest populära sporter med miljontals fans världen över. Vad händer när man kombinerar fotboll med motorsport? Och inte bara det, utan ger fordonen möjlighet att flyga? Då får man spelet Rocket League. Syftet med detta projekt är att skapa en bot som sedan ska implementeras i detta spel. Målet är att den konstruerade boten ska klara av att spela mot spelets lättaste förprogrammerade bot som är designad för nya spelare. Ordet bot kommer att användas genom arbetet och syftar till i det här fallet en intelligent agent, bot är den vanligaste benämningen på en intelligent agent inom spelbranschen och kommer såldes att även användas här. Boten kommer att programmeras i Python 3 och sedan implementeras med hjälp av RLBot. 1.2 Rocket League Rocket League är ett fotbolls liknande spel utvecklat av Psyonix och gavs ut 2015. Likt fotboll möter två lag varandra i en match som normalt varar i fem minuter. Varje lag består utav en till fyra spelare eller botar, där laget med flest mål vid slutsignalen vinner. Står det lika vid matchensslut går det vidare till övertid där första laget som gör ytterligare ett mål vinner. Tillskillnad från traditionella fotbollsspel där spelaren kontrollerar en mänskligfotbollsspelare, kontrollerar en spelare i Rocket League istället en raketdriven bil som kan hoppa och flyga med hjälp av boost. Boost är raketbränslet som tillåter spelaren att uppnå en högre hastighet som dels möjliggör att förstöra motståndarens bil vid kollision, samt tillåter det även spelaren att flyga. Förutom spelarna finns det även en boll, ett lag får ett poäng om de lyckas att skjuta bollen in i motståndarens mål (Rocket League, 2019). 1.2.1 Spelplanen Planen är 10240 x 8192 unreal units, där en unreal unit motsvarar en cm, vilket ger en spelplan som är 102,40 meter lång och 81,92 meter bred, planen har även väggar och ett tak. Rocket League använder sig utav ett koordinatsystem (X, Y, Z) där Z är upp mot taket och negativa y-värden går mot det blåa lagets planhalva. Planens dimensioner har följande värden: Golv: 0 Mittpunkt: (0, 0) Sidvägg: 4096 Bakrevägg: 5120 Tak: 2044 3

På planen finns det totalt 34 boost pads, dessa dynor är uppdelade i stora och små. De små utgör 28 av de 34 dynorna och ger spelaren 12 boost, medan de sex stora dynorna ger spelaren 100 boost, vilket är den maximala boosten en spelare kan lagra. Den vänstra bilden nedan illustrerar spelplanen och dess dimensioner, den högra bilden illustrerar var på planen boost pads finns, talen i fetstil representerar de stora dynorna (Game Values, 2019). Bild 1 & 2, bild 1 illustrerar spelplanens dimensioner och bild 2 utplaceringen av boost pads 1.2.2 RLBot RLBot är ett ramverk utvecklat av spelare för att underlätta skapandet av botar inom Rocket League. Ramverket stödjer flera programmeringsspråk, exmeplevis Scratch, Python och Java (RLBot, 2019). RLBot gör det möjligt att testa sin kod direkt i spelet och förser även boten med GameTickPacket. GTP innehåller information om matchen som exempelvis dynamiska element så som varje spelares och bollens nuvarande position och hastighet. GTP innehåller även statiska element som platserna för väggarna, målen och boost pads. Hur informationen i GTP representeras för exempelvis bilarna under matchen kan ses nedan (Input & Output, 2019): packet: { 'game_cars': [ { 'physics': { 'location': {'x': 0.0, 'y': 0.0, 'z': 0.0}, 'rotation': {'pitch': 0.0, 'yaw': 0.0, 'roll': 0.0}, 4

1.3 Spelet } 'velocity': {'x': 0.0, 'y': 0.0, 'z': 0.0}, 'angular_velocity': {'x': 0.0, 'y': 0.0, 'z': 0.0} 1.3.1 Bot Rocket League är ett relativt komplext spel då bilarna inte enbart kan färdas på golvet av planen utan dessutom på planens väggar och tak, boost tillåter även bilarna att flyga. Detta innebär att det finns ett stort utrymme att bemästra inom en dynamisk miljö. Som följd kommer boten att limiteras till att inte försöka att flyga, något som botens motståndare, dvs Psyonix lättaste bot inte heller kan. Octane är bilen som boten kommer att styra, denna bil valdes då det är den mest använda bilen i spelet. Boten kommer att använda sig utav Millington och Funges modell för AI inom spel som riktlinje där det som är markerat i grått kommer att implementeras (se bild 3). GameTickPacket kommer att motsvara World Interface, botens källa för information om världen och fungerar som dess percept. Botens strategi kommer att bestå huvudsakligen utav två lägen, när bollen är mellan boten och motståndarens mål kommer boten att jaga bollen och försöka skjuta in bollen i motståndarens mål. Om bollen är närmare botens egna mål än vad boten är kommer den att åka hemåt mot sitt egna mål. När botens position sedan är mellan sitt egna mål och bollen igen, kommer den att anfalla, detta är för att undvika att boten skjuter bollen mot sitt egna mål. Botens beslutsfattning kommer att bero på dess egna position samt bollens position. Den har åtta effektorer tillgängliga för att röra sig i världen (Millington & Funge, 2009): Throttle - -1 för full fart bakåt, 1 för full fart framåt. Handbrake True om handbromsen ska användas. Steer - -1 för fullt åt vänster, 1 för fullt åt höger. Pitch - -1 för nos neråt, 1 för nos uppåt. Yaw -1 för nos åt vänster, 1 för nos åt höger. Roll - -1 för rotation åt vänster, 1 för rotation åt höger. Jump True om agenten ska hoppa. Boost True om agenten ska använda boost. 5

Bild 3. Millington och Funges modell för AI inom spel 1.3.2 Environment Med hjälp av Norvig och Russel s definition av task environments har följande egenskaper hos Rocket Leagues miljö identifierats (Norvig & Russel, 2010): Fully observable GameTickPacket som boten får tillgång till innehåller all relevant information som boten behöver för att utföra en handling. Informationen som är relevant i det här fallet är botens nuvarande yaw, dess position, bollens position, samt vilket lag den tillhör. Försedd med denna data kan boten avgöra om den ska jaga efter bollen eller åka hemåt för att försäkra sig om att den inte skjuter bollen mot sitt egna mål. Multiagent Miljön som boten befinner sig i kan beskrivas som en competetive multiagent, detta innebär att boten inte agerar ensam i världen utan det finns andra botar eller spelare som agerar samtidigt. Det innebär även att både boten och dess motståndare försöker att maximera sitt performance measure, vilket i Rocket League lättast uppnås genom att göra mål. Stochastic Botens och de andra botarna eller spelarnas handlingar påverkar det nuvarande tillståndet i denna multiagent-miljö. Resultatet av detta är att boten inte kan vara helt säker på utfallet av en genomförd handling, motståndarens handlingar går inte heller med säkerhet att förutse vilket gör miljön som boten agerar i till stokastisk. Sequential I miljön som boten agerar i påverkar dess utförda handlingar vilken handling som är mest lämplig att utföra vid nästa tillstånd, exempelvis kan en missad voltning mot bollen vid motståndarens mål resultera i att motståndaren får ett bra läge att göra mål, under tiden måste boten vända om och åka tillbaka innan den kan försvara. Faktumet att botens handlingar kan ha framtida konsekvenser gör miljön till sekventiell. 6

Dynamic Spelets utformning med flera aktiva botar eller spelare vars mål är att vinna, samt en tidsgräns på fem minuter innebär att boten måste dels ta hänsyn till att exempelvis bollen kan flytta sig som ett resultat av att motståndaren åker på bollen. Beslutstagandet behöver således ske i realtid, vilket innebär att boten inte har råd att stå och planera en sekvens av handlingar utan den måste istället ta snabba beslut vid varje nytt tillstånd. Dessa faktorer som ständigt uppdateras gör att miljön klassas som dynamisk. Continuous Miljön har inte ett distinkt och ändligt antal potentiella tillstånd och percept, utan miljön i Rocket League är ett kontinuerligt tillstånds- och tidsproblem; där hastigheten och positionen för bilarna och bollen ständigt uppdateras över tid. Även botens handlingar är kontinuerliga där exempelvis styrvinklarna och rotation förändras över tid. 2. Implementation Implementationen består utav boten och ramverket RLBot. Detta ramverk presenterade möjligheten att ladda in boten i en offline match mot antingen en kopia av sig själv, någon utav de förprogrammerade botar som ingår i spelet, eller mot en mänsklig spelare. Utifrån ramverket användes BaseAgent, vilket gör det möjligt att styra boten under matchen med hjälp av Python-filen project_bot.py. Från RLBot användes även SimpleControllerState, detta fungerar som en simulation av en spelkontroll och resulterar i att någon av botens tillgängliga handlingar ges ut som output i form av input till kontrollen. Exempelvis betyder koden nedan att knappen för att gasa ska hållas ned, vilket resulterar i att boten accelererar och således kör framåt: self.controller.throttle = 1 RLBot ger även, som tidigare nämnt, tillgång till GameTicketPacket som uppdateras 60 gånger per sekund. Detta paket innehåller all relevant data om matchen för varje tillstånd. Med hjälp av GTP får boten tillgång till de percept som krävs för att uppfatta och agera i omgivningen. Dessa percept består dels utav dynamiska element, exempelvis alla agenters nuvarande position, deras rotation och hastighet. Samt bollens egenskaper i dess nuvarande tillstånd, något som är nödvändigt för att boten ska veta om den ska anfalla eller försvara. Boten består huvudsakligen utav en klass, ProjectBot, samt två fristående funktioner, distance och draw_debug. Distance gör det möjligt att räkna ut avståndet mellan två punkter, exempelvis boten och bollen. Draw_debug användes för att rita ut en linje mellan 7

boten och bollen, samt att skriva ut vilka handlingar boten använde för att styra mot ett mål, vilket underlättade kodningsprocessen. 2.1 ProjectBot Inledningsvis i init metoden tillskrivs dels konstanta värden till variablerna med enbart stora bokstäver, dessa värden agerar som riktlinjer för boten vid utförandet av vissa handlingar. Samt tillskrivs resterande variabler ett initialt värde som kommer att förändras under matchens gång. 2.1.1 init self.controller = SimpleControllerState() Denna variabel kommer att representera spelkontrollens tillstånd och används för att simulera input till spelkontrollen. self.dodge_time = 0.2 Konstanten 0,2 syftar till att boten kommer att vänta 0,2 sekunder innan den hoppar igen när den utför en volt. self.distance_from_ball_to_boost = 1500 Denna konstant är det avstånd mellan boten och bollen som krävs för att boten ska använda sig utav boost. self.powerslide_angle = math.radians(140) Denna variabel använder sig utav math.radians() funktionen för att konvertera grader till radianer. Detta konstanta värde kommer sedan att användas för att avgöra när boten ska använda sig utav handbromsen vid styrning för att svänga snabbare. self.maximum_distance_to_chase_target = 750 Detta är det längsta tillåtna avståndet mellan boten och en annan punkt för att den ska kunna åka till punkten. Används för att försöka hamna i en bättre skottlinje mot motståndarens mål. self.bot_pos = None Denna variabel kommer representera botens position i form av ett x-, y- och z-värde. self.bot_yaw = None Denna variabel kommer att representera vilken riktning som botens nos pekar åt. 8

self.should_dodge = False Denna variabel kan sättas till True om boten ska volta framåt. self.on_second_jump = False Denna variabel används för att kontrollera om boten har hoppat en gång redan. Något som är nödvändigt vid utförandet av en volt där boten hoppar två gånger, samtidigt som den tiltar styrspaken på kontrollen uppåt. self.next_dodge_time = 0 Denna variabel används för att avgöra om boten ska inleda en volt eller inte. self.action_display = Denna variabeln används för att skriva ut vilken handling som boten använder sig utav vid det nuvarande tillståndet. 2.1.2 aim Metoden aim syftar till att få boten att justera sin riktning och sedan sikta mot ett mål, vilket kan antingen vara bollen eller en annan punkt på spelplanen. Detta är möjligt givet målets x- och y-värde. Första steget är att räkna ut vinkeln mellan boten och dess mål (engelskans target och inte goal), detta sker med hjälp av funktionen math.atan2() från biblioteket math. Funktionen atan2(y, x), tillskillnad från funktionen atan(x) som enbart returnerar en arc tangent för x, så returnerar atan2 istället en arc tangent för y/x. Detta är nödvändigt då både x- och y-värdet är av intresse. Funktionen tar således in två argument, det ena är målets y-värde minus botens y-värde, och det andra argumentet är målets x-värde minus botens x-värde. Den returnerar sedan ett radianvärde mellan -pi och pi som sparas i variabeln angle_between_bot_and_target (Python, 2019). Nästa steg är att räkna ut vinkeln mellan botens nos och målet, detta sker genom att ta angle_between_bot_and_target minus self.bot_yaw. Med detta värde som riktlinje kan boten sedan korrigera bilen med hjälp av self.controller.steer, vilket styr kontrollens styrspak antingen till höger eller vänster om nosen på bilen är vinklad mer än tio grader från målet. 9

2.1.3 check_for_dodge Denna metod kontrollerar om boten ska utföra en volt, om så är fallet blir resultatet att self.controller.jump sätts till True och self.controller.pitch sätts till -1. Detta är ekvivalent till att hoppknappen på spelkontrollen trycks ner samtidigt som att styrspaken riktas uppåt, för att sedan trycka på hoppknappen igen. 2.1.4 closest_point Denna metod tar in sex argument, första värdet är 0 och representerar x-värdet för mittpunkten i motståndarens mål. Det andra värdet är y-värdet för motståndarens mål som antingen är -5000 eller 5000. Det tredje och fjärde argumentet är bollens x- och y-värde, det femte och sjätte argumentet är botens x- och y-värde. Den returnerar sedan den närmaste punkten på linjen mellan boten och målet, detta används för att försöka skjuta bollen mot motståndarens mål. 2.1.5 get_output Metoden get_output är den huvudsakliga loopen i klassen ProjectBot och avgör vilka spelkontroll inputs som ska användas. Inledningsvis används GameTickPacket för att uppdatera variablerna som är relevanta för matchen och för botens beteende. Först uppdateras botens percept: self.bot_yaw = packet.game_cars[self.team].physics.rotation.yaw Denna variabel uppdateras med botens nuvarande riktning på nosen. self.bot_pos = packet.game_cars[self.index].physics.location Denna variabel uppdateras med botens nuvarande position, i form av ett x-, y- och z- värde. self.bot = packet.game_cars[self.index] Denna variabel används av draw_debug funktionen för att rita ut en linje mellan boten och bollen. ball_pos = packet.game_ball.physics.location Denna variabel uppdateras med bollens nuvarande position, i form av ett x-, y- och z- värde. Sedan sätts en parameter self.controller.boost som resulterar i att boten använder sig utav boost för att åka snabbare. Detta sker enbart om avståndet mellan boten och bollen är större än det givna avståndet self.distance_from_ball_to_boost och att bollen är 10

mellan boten och motståndarens mål. Detta är för att förhindra att boten boostar vid fel tillfälle, vilket kan resultera i att boten hamnar i en dålig position. Variabeln self.controller.throttle sätts till 1, vilket gör att boten accelererar och således kör framåt. Sedan kontrolleras vilket lag boten tillhör för att dels ge variabeln enemy_goal ett värde, denna variabel används sedan, som tidigare nämnt av metoden closest_point. Vilket lag boten tillhör är även viktigt för att veta när den ska anfalla, med andra ord jaga bollen, eller när den ska åka hemåt mot sitt mål för att försvara. Detta kontrolleras genom if-satsen: if (self.index == 0 and self.bot_pos.y < ball_pos.y) or (self.index == 1 and self.bot_pos.y > ball_pos.y) Om boten har index 0 tillhör den det blåa laget, vilket innebär att den är mellan sitt egna mål och bollen om botens y-axel är mindre än bollens y-axel. Tillhör boten lag orange är den mellan sitt egna mål och bollen när dess y-axel är större än bollens y-axel. Uppfylls denna ifsats vet boten att den kan anfalla. Om boten anfaller kommer den att volta in i bollen om den är inom det givna avståndet 500; givet att boten är närmare sitt egna mål än bollen och att bollens z-värde inte är för högt. Befinner sig bollen närmarare botens egna mål kommer den att sikta mot sitt egna mål och åka hemåt till dess att den har passerat bollen. Ytterligare en kontroll utförs för att se om bollens nuvarande position är exakt (0, 0), vilket innebär att bollen är i mitten av planen. Detta sker nästan enbart om det är avspark, detta gör att boten siktar in sig på bollen och boostar för att hinna till bollen så fort som möjligt. Sist returneras self.controller vilket är de spelkontroll inputs som ska användas för varje tillstånd. 3. Resultat Efter 15 spelade matcher mot spelets lättaste bot hade ProjectBot vunnit 10 utav 15 matcher. En sammanställning av ProjectBot och dess motståndare PsyonixBots prestation kan ses nedan: ProjectBot Vinstfrekvens % Skott på mål Medel Räddningar Medel Mål Medel Totalpoäng Medel 66 3,67 0,33 3,47 543 Tabell 1. Genomsnitt för ProjectBot efter 15 spelade matcher 11

PsyonixBot Vinstfrekvens Skott på mål Räddningar Mål Totalpoäng % Medel Medel Medel Medel 34 4,40 0,06 2,93 563 Tabell 2. Genomsnitt för Psyonixs lättaste bot efter 15 spelade matcher 4. Reflektioner Rocket League upplevs av de flesta som spelar det som ett väldigt svårt och komplext spel med en hög inlärningskurva. Dessa faktorer resulterar i att spelet presenterar en intressant utmaning för AI och skapandet av botar. Inledningsvis var tanken att ett neuralt nätverk skulle implementeras som sedan skulle tränas med hjälp av Reinforcement learning, närmare bestämt Q-learning. Maskininlärning visade sig dock vara ett outforskat område vid skapandet av botar inom Rocket League, då 28 av 29 botar som ställde upp i en turnering för botar i augusti 2018, var hårdkodade. Enbart 1 av 29 botar använde sig utav maskininlärning. En av anledningarna till detta kan bero på poängsystemet i Rocket League. I spelet får en spelare poäng genom att nudda bollen, skjuta bollen mot motståndarens mål, rädda framför sitt egna mål, göra mål från marken, samt från luften vilket ger mer poäng, för att nämna några poänggivande handlingar. Detta poängsystem kan tänkas försvåra problemet att lära nätverket vilka handlingar som är bäst givet ett visst tillstånd. Exempelvis illustrerar resultatet att Psyonixs egna lättaste bot hade ett högre medelvärde i totalpoäng jämfört med boten som skapades för detta projekt, något som tyder på att det inte enbart är slutpoängen i matchen som spelar roll utan det är antalet mål. Skapandet av en hårdkodad bot visade sig vara betydligt svårare än vad jag hade föreställt mig. Målet var att boten skulle spela jämnt mot spelets lättaste förprogrammerade bot, vilket uppnåddes. Men bristerna i min bot är många och det finns extremt stort utrymme för förbättring. En tänkbar förbättring hade varit att implementera en förmåga hos boten att förutse var bollen kommer att vara flera tillstånd i framtiden. Något som teoretiskt sett borde vara möjligt med hjälp av bollens hastighet, dess riktning och hur den påverkas av spelets tyngdkraft. Denna kunskap hade även öppnat upp möjligheten att skapa en bot som kan flyga, då förmågan att förutse bollens framtida position är kritisk för att träffa den i luften. Denna implementation hade dock gjort att metoden aim hade behövts uppdateras. Anledningen är 12

att atan2 enbart tar hänsyn till x- och y-värdet vid uträkningen av en vinkel, vilket fungerade väl i detta fall då boten inte hade förmågan att flyga kontrollerat. Däremot skulle det krävas en annan metod som tar hänsyn till inte bara x- och y-värdet men också z-värdet för att exempelvis förutse bollens bana när den är i luften. En annan förbättring hade varit att skapa flera klasser som kallas på beroende på det nuvarande tillståndet. Exempelvis har botarna som ställer upp i turneringar ofta en stor uppsättning av klasser, exempelvis en klass för att försvara sitt egna mål, en klass för att anfalla på marken och en klass för att kunna flyga. De botar som tävlar i turneringar består till större del av flera tusen rader kod, vilket är det stora problemet med att det inte finns någon etablerad maskininlärning för spelet. Istället för att boten kan lära sig spelet på egen hand, så behöver skaparen av boten tänka ut alla tänkbara scenarion och tillstånd som en match kan innehålla och utefter detta programmera lämpligt beteende. Vilket är ett stort- och tidskrävande problem att lösa. Syftet med projektet var att skapa en bot som kunde spela jämnt mot Rocket Leagues lättaste förprogrammerade bot, ett mål som jag anser uppnåddes. Dock finns det, som tidigare nämnt, ett stort utrymme för förbättring, något som kan uppnås med en djupare förståelse för spelets fysik och potentiellt en implementation av maskininlärning. 13

5. Referenslista Input & Output Data. (2019). Retrieved from https://github.com/rlbot/rlbotpythonexample/wiki/input-and-output-data Millington, I., & Funge, J. (2009). Artificial Intelligence for Games (2nd ed.). London: CRC Press. Python 3.7.2 documentation. (2019). Retrieved from https://docs.python.org/3/library/math.html RLBot. (2019). Retrieved from http://www.rlbot.org/ Rocket League. (2019). Retrieved from https://www.rocketleague.com/game-info/ Russell, S., & Norvig, P. (2010). Artificial Intelligence: A Modern Approach (3rd ed.). New Jersey: Pearson Education Inc. Useful Game Values. (2019). Retrieved from https://github.com/rlbot/rlbot/wiki/useful- Game-Values 14

6. Bilaga 6.1 Bilaga 1 Python kod # -*- coding: utf-8 -*- from rlbot.agents.base_agent import BaseAgent, SimpleControllerState from rlbot.utils.structures.game_data_struct import GameTickPacket import math import time def distance(x1, y1, x2, y2): # Metoden gör det möjligt att räkna ut avståndet mellan två punkter return math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) def draw_debug(renderer, car, ball, action_display): renderer.begin_rendering() # rita en linje från boten till bollen renderer.draw_line_3d(car.physics.location, ball.physics.location, renderer.white()) # printa handlingen som boten utför renderer.draw_string_3d(car.physics.location, 2, 2, action_display, renderer.white()) renderer.end_rendering() class ProjectBot(BaseAgent): def init (self, name, team, index): # super() gör så att init för BaseAgent körs först och sedan koden super(). init (name, team, index) # representerar "spelkontrollens" tillstånd, används för kontroller input self.controller = SimpleControllerState() # Konstanter i caps # Agenten väntar 0.2 sekunder innan den hoppar andra gången self.dodge_time = 0.2 # Vilket avstånd agenten behöver ha till bollen för att volta self.distance_to_dodge = 500 # Minsta avståndet mellan agenten och bollen för att agenten ska boosta self.distance_from_ball_to_boost = 1500 # Vinkeln (från nosen av agenten mot bollen) som krävs för att agenten ska använda powerslide self.powerslide_angle = math.radians(140) self.maximum_distance_to_chase_target = 750 # Agentens position self.bot_pos = None # Vilken direction agentens nos pekar åt self.bot_yaw = None # Om agenten ska volta framåt self.should_dodge = False 15

# Om agenten har hoppat en gång self.on_second_jump = False self.next_dodge_time = 0 self.action_display = "" def aim(self, target_x, target_y): #vinkel mellan agenten och målet (bollen eller en annan punkt) angle_between_bot_and_target = math.atan2(target_y - self.bot_pos.y, target_x - self.bot_pos.x) # vinkel mellan agentens nos och målet (bollen eller annan punkt) angle_front_to_target = angle_between_bot_and_target - self.bot_yaw # Korrigera värdena # Om vinkeln är mindre än -180 if angle_front_to_target < -math.pi: angle_front_to_target += 2 * math.pi # Om vinkeln är större än 180 if angle_front_to_target > math.pi: angle_front_to_target -= 2 * math.pi if angle_front_to_target < math.radians(-10): # Om målet är mer än 10 grader till höger från mitten, styr vänster self.controller.steer = - 1 self.action_display = "turn left" elif angle_front_to_target > math.radians(10): # Om målet är mer än 10 grader till vänster från mitten, styr höger self.controller.steer = 1 self.action_display = "turn right" else: # Om målet är mindre än 10 grader från mitten, styr rakt framåt self.controller.steer = 0 self.action_display = "straight ahead" self.controller.handbrake = abs(math.degrees(angle_front_to_target)) < self.powerslide_angle def check_for_dodge(self): # Om agenten ska hoppa, så sätts jump till true och pitch blir -1 # Där pitchen -1 resulterar i att styrspaken är i högsta läget (uppåt) if self.should_dodge and time.time() > self.next_dodge_time: self.controller.jump = True self.controller.pitch = - 1 # Kontrollerar om agenten har hoppat en gång redan if self.on_second_jump: self.on_second_jump = False self.should_dodge = False 16

else: self.on_second_jump = True self.next_dodge_time = time.time() + self.dodge_time def closest_point(self, x1, y1, x2, y2, x3, y3): k = ((y2 - y1) * (x3 - x1) - (x2 - x1) * (y3 - y1)) / ((y2 - y1) ** 2 + (x2 - x1) ** 2) x4 = x3 - k * (y2 - y1) y4 = y3 + k * (x2 - x1) # returnerar närmaste punkten på linjen mellan boten och målet return x4, y4 def get_output(self, packet: GameTickPacket) -> SimpleControllerState: # Uppdatera speldata variablerna mha GameTickPacket self.bot_yaw = packet.game_cars[self.team].physics.rotation.yaw self.bot_pos = packet.game_cars[self.index].physics.location self.bot = packet.game_cars[self.index] # Bollens position ball_pos = packet.game_ball.physics.location # Agenten boostar om bollen är tillräckligt långt borta self.controller.boost = (distance(self.bot_pos.x, self.bot_pos.y, ball_pos.x, ball_pos.y) > self.distance_from_ball_to_boost) and (self.bot_pos.y < ball_pos.y) # Om agenten ska köra rakt fram = 1, bakåt = -1 och stå stilla = 0 self.controller.throttle = 1 # Lag blå har sitt mål vid -5000 (y-axeln) och det orangea laget har sitt mål # vid 5000 (y-axeln). Detta innebär att om agenten tillhör lag blå så är agenten # bakom bollen om bollens y-axel är större än agentens y-axel. # Om agenten tillhör lag orange så är agenten bakom bollen om bollens y- axel # är mindre än agentens y-axel if (self.index == 0 and self.bot_pos.y < ball_pos.y) or (self.index == 1 and self.bot_pos.y > ball_pos.y): if self.team == 0: enemy_goal = 5000 else: enemy_goal = - 5000 target = self.closest_point(0, enemy_goal, ball_pos.x, ball_pos.y, self.bot_pos.x, self.bot_pos.y) if (distance(target[0], target[1], self.bot_pos.x, self.bot_pos.y) > self.maximum_distance_to_chase_target) and (self.bot_pos.y > - 4800) and (self.bot_pos.z < 200): self.aim(target[0], target[1]) else: 17

# Förhindra boten från att fastna i sitt eget mål if abs(self.bot_pos.y) > 4800: self.aim(0, 4800 * (1 if self.team else -1)) else: self.aim(ball_pos.x, ball_pos.y) if (distance(self.bot_pos.x, self.bot_pos.y, ball_pos.x, ball_pos.y) < self.distance_to_dodge) and (self.bot_pos.y < ball_pos.y) and (ball_pos.z < 200): self.should_dodge = True else: if self.team == 0: # 0 i det här fallet är lag blå, deras mål ligger vid nedstående koordinater self.aim(0, -5000) if self.bot_pos.y > 0 and ball_pos.y < 0: self.controller.boost = True else: self.controller.boost = False else: # 1 är lag orange, deras mål ligger vid nedstående koordinater self.aim(0, 5000) if self.bot_pos.y < 0 and ball_pos.y > 0: self.controller.boost = True else: self.controller.boost = False # Om det är avspark (bollen ligger exakt i mitten) kommer agenten att boosta if ball_pos.x == 0 and ball_pos.y == 0: self.aim(ball_pos.x, ball_pos.y) self.controller.boost = True # Self.jump blir enbart aktiv för 1 frame self.controller.jump = 0 self.check_for_dodge() draw_debug(self.renderer, self.bot, packet.game_ball, self.action_display) return self.controller 18