Hur man skapar ett spel-AI: en guide för nybörjare

Hur man skapar ett spel-AI: en guide för nybörjare

Jag snubblade över ett intressant material om artificiell intelligens i spel. Med en förklaring av grundlÀggande saker om AI genom enkla exempel, och dessutom mÄnga anvÀndbara verktyg och metoder för dess smidiga utveckling och design. Hur, var och nÀr de ska anvÀndas - det finns ocksÄ.

De flesta exemplen Àr skrivna i pseudokod, sÄ djupa programmeringskunskaper behövs inte. Under lÀnken finns 35 sidor text med bilder och GIF:ar, sÄ var beredd.

UPD. Jag ber om ursÀkt, men jag har redan gjort min egen översÀttning av denna artikel pÄ Habré. PatientZero. Du kan lÀsa hans version hÀr, men av nÄgon anledning har artikeln gÄtt mig förbi (jag anvÀnde sökfunktionen, men nÄgot gick fel). Eftersom jag skriver i en blogg som Àr dedikerad till spelutveckling, beslutade jag mig för att lÀmna min egen översÀttning för prenumeranterna (vissa moment har jag formulerat pÄ ett annat sÀtt, andra har jag medvetet utelÀmnat pÄ rÄd frÄn utvecklarna).

Vad Àr AI?

Spel-AI fokuserar pÄ vilka ÄtgÀrder ett objekt ska vidta baserat pÄ de förhÄllanden det befinner sig i. Detta kallas vanligtvis för att hantera 'intelligenta agenter', dÀr agenten Àr en spelkaraktÀr, ett fordon, en bot, och ibland nÄgot mer abstrakt: en hel grupp av entiteter eller till och med en civilisation. I varje fall Àr det nÄgot som ska kunna se sin omgivning, fatta beslut baserat pÄ den och agera i enlighet med detta. Detta kallas Sense/Think/Act-cykeln (KÀnna/TÀnka/Agera):

  • KĂ€nna: agenten hittar eller fĂ„r information om saker i sin miljö som kan pĂ„verka dess beteende (hot i nĂ€rheten, föremĂ„l att samla, intressanta platser att utforska).
  • TĂ€nka: agenten beslutar hur den ska reagera (övervĂ€ger om det Ă€r tillrĂ€ckligt sĂ€kert att samla föremĂ„l eller om den först mĂ„ste slĂ„ss/gömma sig).
  • Agera: agenten utför Ă„tgĂ€rder för att genomföra det föregĂ„ende beslutet (börjar röra sig mot motstĂ„ndaren eller objektet).
  • 
situationen har nu förĂ€ndrats pĂ„ grund av karaktĂ€rernas handlingar, sĂ„ cykeln börjar om med nya uppgifter.

AI fokuserar vanligtvis pÄ Sense-delen av cykeln. Till exempel gör sjÀlvkörande bilar bilder av vÀgen, kombinerar dem med radar- och lidar-data samt tolkar dem. Vanligtvis görs detta av maskininlÀrning, som bearbetar indata och ger dem mening, genom att extrahera semantisk information som "det finns en annan bil 20 meter framför dig". Detta kallas för klassificeringsproblem.

Spel behöver inte ett komplext system för att extrahera information, eftersom största delen av datan redan Ă€r en integrerad del av det. Det Ă€r inte nödvĂ€ndigt att köra algoritmer för bildigenkĂ€nning för att avgöra om det finns en fiende framför — spelet vet redan och överför informationen direkt under beslutsprocessen. DĂ€rför Ă€r Sense-delen av cykeln ofta mycket enklare Ă€n Think och Act.

BegrÀnsningar för spel-AI

AI har flera begrÀnsningar som mÄste följas:

  • AI behöver inte trĂ€nas i förvĂ€g, som en maskininlĂ€rningsalgoritm. Det Ă€r meningslöst att skriva ett neuralt nĂ€tverk under utvecklingen för att observera tiotusentals spelare och lĂ€ra sig det bĂ€sta sĂ€ttet att spela mot dem. Varför? För att spelet inte har slĂ€ppts Ă€nnu och det inte finns nĂ„gra spelare.
  • Spelet ska underhĂ„lla och utmana, sĂ„ agenterna fĂ„r inte hitta det bĂ€sta tillvĂ€gagĂ„ngssĂ€ttet mot mĂ€nniskor.
  • Agenterna mĂ„ste se realistiska ut, sĂ„ att spelarna kĂ€nner att de spelar mot riktiga mĂ€nniskor. AlphaGo övertrĂ€ffade mĂ€nniskor, men de valda dragen var lĂ„ngt ifrĂ„n traditionell förstĂ„else av spelet. Om spelet imiterar en mĂ€nsklig motstĂ„ndare ska det inte finnas den kĂ€nslan. Algoritmen mĂ„ste förĂ€ndras för att ta rimliga beslut snarare Ă€n perfekta idĂ©er.
  • AI mĂ„ste fungera i realtid. Det betyder att algoritmen inte kan monopoliserar CPU-anvĂ€ndningen under lĂ„ng tid för att ta beslut. Även 10 millisekunder för detta Ă€r för lĂ€nge, eftersom de flesta spel behöver mellan 16 och 33 millisekunder för att slutföra all bearbetning och gĂ„ vidare till nĂ€sta grafikram.
  • Det Ă€r idealiskt om Ă„tminstone en del av systemet styrs av data, sĂ„ att "icke-kodare" kan göra Ă€ndringar, och sĂ„ att justeringar sker snabbare.

LÄt oss övervÀga AI-ansatser som tÀcker hela cykeln Sense/Think/Act.

Beslut om grundlÀggande beslut

LĂ„t oss börja med det enklaste spelet – Pong. MĂ„let Ă€r att flytta plattan (paddle) sĂ„ att bollen studsar mot den istĂ€llet för att passera förbi. Det Ă€r som tennis dĂ€r du förlorar om du inte slĂ„r tillbaka bollen. HĂ€r har AI en relativt enkel uppgift – att avgöra i vilken riktning plattan ska flyttas.

Hur man skapar ett spel-AI: en guide för nybörjare

Villkorssatser

För AI i Pong finns det en uppenbar lösning – alltid försöka placera plattan under bollen.

Ett enkelt algoritm för detta, skrivet i pseudokod:

varje bilduppdatering medan spelet pÄgÄr:
om bollen Àr till vÀnster om plattan:
flytta plattan vÀnster
annars om bollen Àr till höger om plattan:
flytta plattan höger

Om plattan rör sig med bollens hastighet Àr det en perfekt algoritm för AI i Pong. Det Àr inget som behöver kompliceras, om det inte finns sÄ mÄnga data och möjliga handlingar för agenten.

Denna metod Àr sÄ enkel att hela cykeln Sense/Think/Act knappt mÀrks. Men den finns dÀr:

  • Delen Sense finns i de tvĂ„ if-satserna. Spelet vet var bollen och plattan Ă€r, sĂ„ AI vĂ€nder sig till den för denna information.
  • Delen Think ingĂ„r ocksĂ„ i de tvĂ„ if-satserna. De representerar tvĂ„ beslut som i detta fall Ă€r ömsesidigt uteslutande. Som ett resultat vĂ€ljs en av tre Ă„tgĂ€rder – flytta plattan Ă„t vĂ€nster, flytta Ă„t höger, eller inte göra nĂ„got om den redan Ă€r korrekt placerad.
  • Delen Act finns i satserna Flytta Plattan VĂ€nster och Flytta Plattan Höger. Beroende pĂ„ spelets design kan de flytta plattan omedelbart eller med en viss hastighet.

SĂ„dana metoder kallas reaktiva – det finns en enkel uppsĂ€ttning regler (i det hĂ€r fallet if-satserna i koden) som reagerar pĂ„ den aktuella vĂ€rldens tillstĂ„nd och agerar.

Besluts trÀd

Exemplet med spelet Pong Ă€r i princip likvĂ€rdigt med den formella AI-konceptet som kallas besluts trĂ€d. Algoritmen gĂ„r igenom det för att nĂ„ ett "blad" – beslutet om vilken Ă„tgĂ€rd som ska vidtas.

LÄt oss skapa ett flödesschema för besluts trÀdet för vÄr plattforms algoritm:

Hur man skapar ett spel-AI: en guide för nybörjare

Varje del av trĂ€det kallas node (nod) – AI anvĂ€nder grafteori för att beskriva sĂ„dana strukturer. Det finns tvĂ„ typer av noder:

  • Besluts noder: val mellan tvĂ„ alternativ baserat pĂ„ kontroll av ett visst villkor, dĂ€r varje alternativ representeras som en separat nod.
  • Slutnoder: Ă„tgĂ€rder att utföra, vilket representerar det slutgiltiga beslutet.

Algoritmen börjar med den första noden ("rotnoden" av trÀdet). Den antingen fattar ett beslut om vilken undernod att gÄ till, eller sÄ utför den en ÄtgÀrd som Àr lagrad i noden och avslutas.

Vad Àr dÄ fördelen om ett beslutstrÀd gör samma jobb som if-satserna i avsnittet ovan? HÀr finns ett gemensamt system dÀr varje beslut har endast ett villkor och tvÄ möjliga resultat. Detta gör det möjligt för utvecklaren att skapa AI utifrÄn data som representerar beslut i trÀdet, utan att hÄrdkoda det. LÄt oss förestÀlla oss det i en tabell:

Hur man skapar ett spel-AI: en guide för nybörjare

PÄ kodsidan fÄr du ett system för att lÀsa strÀngar. Skapa en nod för var och en av dem, koppla logik för beslutsfattande baserat pÄ den andra kolumnen och undernoder baserat pÄ den tredje och fjÀrde kolumnen. Du behöver fortfarande programmera villkor och ÄtgÀrder, men nu blir spelstrukturen mer komplex. Du lÀgger till fler beslut och ÄtgÀrder och konfigurerar hela AI:n genom att helt enkelt redigera en textfil med definitionen av trÀdet. DÀrefter överlÀmnar du filen till speldesignern, som kan Àndra beteendet utan att behöva rekonstruera spelet eller Àndra koden.

BeslutstrÀd Àr mycket anvÀndbara nÀr de byggs automatiskt baserat pÄ en stor mÀngd exempel (t.ex. med hjÀlp av ID3-algoritmen). Detta gör dem till ett effektivt och högpresterande verktyg för att klassificera situationer baserat pÄ insamlade data. Men vi gÄr bortom ett enkelt system för att vÀlja ÄtgÀrder för agenter.

Scenarier

Vi har gÄtt igenom systemet för beslutstrÀd som anvÀnde fördefinierade villkor och ÄtgÀrder. MÀnniskor som designar AI kan organisera trÀdet som de vill, men de mÄste fortfarande förlita sig pÄ en programmerare som har kodar det hela. Vad om vi kunde ge designern verktyg för att skapa egna villkor eller ÄtgÀrder?

För att programmet inte ska behöva skriva kod för villkoren Is Ball Left Of Paddle och Is Ball Right Of Paddle, kan det skapa ett system dÀr designern skriver villkoren för att kontrollera dessa vÀrden. DÄ skulle data i beslutstrÀdet se ut sÄ hÀr:

Hur man skapar ett spel-AI: en guide för nybörjare

I grunden Ă€r detta detsamma som i den första tabellen, men lösningarna inom sig har egen kod, som liknar den villkorliga delen av en if-sats. I koden skulle detta lĂ€sas i den andra kolumnen för beslutsknutar, men istĂ€llet för att söka efter ett specifikt villkor att utföra (Är bollen till vĂ€nster om paddeln), utvĂ€rderar det det villkorliga uttrycket och returnerar true eller false dĂ€refter. Detta görs med hjĂ€lp av skriptsprĂ„ket Lua eller Angelscript. Genom dessa kan utvecklaren ta objekt i sitt spel (boll och paddel) och skapa variabler som kommer att vara tillgĂ€ngliga i skriptet (boll.position). Dessutom Ă€r skriptsprĂ„ket enklare Ă€n C++. Det krĂ€ver inte en fullstĂ€ndig kompilering, vilket gör det perfekt för snabb justering av spel logik och lĂ„ter "icke-kodare" sjĂ€lva skapa nödvĂ€ndiga funktioner.

I det givna exemplet anvÀnds skriptsprÄket endast för att utvÀrdera det villkorliga uttrycket men kan ocksÄ anvÀndas för ÄtgÀrder. Till exempel kan data Move Paddle Right bli en skriptoperator (boll.position.x += 10). SÄledes kan ÄtgÀrden ocksÄ definieras i skriptet, utan behov av att programmera Move Paddle Right.

Man kan gÄ lÀngre och helt skriva besluts trÀdet i skriptsprÄket. Detta skulle vara kod i form av hÄrdkodade villkorssatser, men de skulle finnas i externa skriptfiler, vilket betyder att de kan Àndras utan att behöva kompilera om hela programmet. Ofta kan man Àndra skriptfilen mitt under spelet för att snabbt testa olika AI-reaktioner.

Reagera pÄ hÀndelser

Exemplen ovan passar perfekt i Pong. De kör kontinuerligt cykeln Sense/Think/Act och agerar baserat pÄ vÀrldens senaste tillstÄnd. Men i mer komplicerade spel behöver man reagera pÄ specifika hÀndelser, istÀllet för att utvÀrdera allt pÄ en gÄng. Pong Àr i sÄ fall redan ett dÄligt exempel. LÄt oss vÀlja ett annat.

FörestĂ€ll dig en shooter dĂ€r fienderna stĂ„r stilla tills de upptĂ€cker spelaren, varefter de agerar beroende pĂ„ sin "specialisering": vissa kommer att rush'a, andra kommer att attackera pĂ„ avstĂ„nd. Detta Ă€r fortfarande ett grundlĂ€ggande reaktionssystem — "om spelaren upptĂ€cktes, gör nĂ„got" — men det kan logiskt delas upp i hĂ€ndelsen Player Seen (spelare upptĂ€ckt) och reaktionen (vĂ€lj ett svar och utför det).

Det hĂ€r tar oss tillbaka till cykeln Sense/TĂ€nk/Utför. Vi kan programmera Sense-delen som varje ram kontrollerar om AI:n ser spelaren. Om inte hĂ€nder ingenting, men om den gör det skapas en hĂ€ndelse kallad Player Seen. Koden kommer att ha en separat sektion som sĂ€ger: "nĂ€r hĂ€ndelsen Player Seen intrĂ€ffar, gör ", dĂ€r Ă€r svaret du behöver för att anropa delarna TĂ€nk och Utför. PĂ„ sĂ„ sĂ€tt stĂ€ller du in reaktioner pĂ„ hĂ€ndelsen Player Seen: för en "rushande" karaktĂ€r – ChargeAndAttack, och för en snipervapen – HideAndSnipe. Dessa samband kan skapas i en databas för snabb redigering utan att behöva kompilera om. Och hĂ€r kan vi ocksĂ„ anvĂ€nda skriptsprĂ„k.

Att fatta komplexa beslut

Även om enkla reaktionssystem Ă€r mycket effektiva finns det mĂ„nga situationer dĂ€r de inte rĂ€cker. Ibland mĂ„ste olika beslut fattas beroende pĂ„ vad agenten gör för tillfĂ€llet, men att representera detta som ett villkor Ă€r svĂ„rt. Ibland finns det för mĂ„nga villkor för att effektivt representera dem i ett beslutstrĂ€d eller skript. Ibland mĂ„ste man förutse hur situationen kommer att förĂ€ndras innan man fattar beslut om nĂ€sta steg. För att lösa dessa problem behövs mer komplexa metoder.

Ändlig tillstĂ„ndsmaskin

Ändlig tillstĂ„ndsmaskin eller FSM (Finita tillstĂ„ndsmaskin) Ă€r ett sĂ€tt att sĂ€ga att vĂ„r agent för nĂ€rvarande befinner sig i ett av flera möjliga tillstĂ„nd och att han kan övergĂ„ frĂ„n ett tillstĂ„nd till ett annat. Det finns ett bestĂ€mt antal sĂ„dana tillstĂ„nd – dĂ€rav namnet. Det bĂ€sta exemplet frĂ„n verkligheten Ă€r trafikljuset. PĂ„ olika platser finns det olika sekvenser av ljus, men principen Ă€r densamma – varje tillstĂ„nd representerar nĂ„got (stanna, gĂ„, etc.). Trafikljuset befinner sig bara i ett tillstĂ„nd vid varje given tidpunkt och gĂ„r frĂ„n ett tillstĂ„nd till ett annat baserat pĂ„ enkla regler.

Med NPC:er i spel Àr det en liknande historia. LÄt oss ta en vakthavande med dessa tillstÄnd:

  • Patrullerande (Patrolling).
  • Angripande (Attacking).
  • Flyende (Fleeing).

Och med dessa villkor för att förÀndra hans tillstÄnd:

  • Om vakten ser en fiende, attackerar han.
  • Om vakten attackerar, men inte lĂ€ngre ser fienden, Ă„tergĂ„r han till att patrullera.
  • Om vakten attackerar, men Ă€r svĂ„rt skadad, flyr han.

Man kan ocksÄ skriva if-operatorer med en tillstÄndsvariabel för vakten och olika kontroller: finns det en fiende i nÀrheten, vad Àr NPC:ns hÀlsotillstÄnd osv. LÄt oss lÀgga till nÄgra fler tillstÄnd:

  • Inaktivitet (Idling) — mellan patruller.
  • Sökning (Searching) — nĂ€r en fiende har setts men har gömt sig.
  • Be om hjĂ€lp (Finding Help) — nĂ€r en fiende har setts, men Ă€r för stark för att slĂ„ss ensam.

Valet för var och en Ă€r begrĂ€nsat — till exempel kommer en vakt inte att söka efter en gömd fiende om han har lĂ„g hĂ€lsa.

Till slut kan en lång lista med "om och" bli för otymplig, så vi bör formaliserar en metod som gör det möjligt för oss att hålla reda på tillstånd och övergångar mellan tillstånd. <x Đž y, ĐœĐŸ ĐœĐ” z>, så <p>För att göra detta kommer vi att ta hänsyn till alla tillstånd och under varje tillstånd skriva upp alla övergångar till andra tillstånd, tillsammans med de nödvändiga villkoren för dem.

Hur man skapar ett spel-AI: en guide för nybörjare

Detta Ă€r en övergĂ„ngstabell för tillstĂ„nd — ett omfattande sĂ€tt att representera FSM. Vi ritar ett diagram och fĂ„r en fullstĂ€ndig översikt över hur NPC:ns beteende förĂ€ndras.

Hur man skapar ett spel-AI: en guide för nybörjare

Diagrammet visar kÀrnan i beslutsfattande för denna agent baserat pÄ den aktuella situationen. Varje pil visar en övergÄng mellan tillstÄnd, om villkoret bredvid Àr sant.

Vid varje uppdatering kontrollerar vi agentens aktuella tillstĂ„nd, gĂ„r igenom listan över övergĂ„ngar, och om villkoren för övergĂ„ngen Ă€r uppfyllda, antar han ett nytt tillstĂ„nd. Till exempel kontrolleras det varje bild om den 10-sekunders timer har löpt ut, och om ja, övergĂ„r vakten frĂ„n Inaktivitet till Patrullering. PĂ„ samma sĂ€tt kontrollerar tillstĂ„ndet Angripande agentens hĂ€lsa — om den Ă€r lĂ„g, övergĂ„r han till tillstĂ„ndet Fly.

Detta Àr bearbetning av övergÄngar mellan tillstÄnd, men hur Àr det med beteendet kopplat till sjÀlva tillstÄnden? NÀr det gÀller att implementera det faktiska beteendet för ett specifikt tillstÄnd finns det vanligtvis tvÄ typer av "hooks", dÀr vi tilldelar ÄtgÀrder till FSM:

  • ÅtgĂ€rder som vi utför regelbundet för det aktuella tillstĂ„ndet.
  • ÅtgĂ€rder som vi vidtar nĂ€r vi övergĂ„r frĂ„n ett tillstĂ„nd till ett annat.

Exempel för den första typen. I tillstÄndet Patrullering kommer agenten varje bild att röra sig lÀngs patrullvÀgen. I tillstÄndet Angripande kommer agenten varje bild att försöka pÄbörja en attack eller övergÄ till ett tillstÄnd nÀr det Àr möjligt.

För den andra typen kan vi övervÀga övergÄngen "om fienden Àr synlig och fienden Àr för stark, sÄ gÄ in i tillstÄndet Finding Help. Agenten mÄste vÀlja vart de ska gÄ för hjÀlp och spara denna information sÄ att tillstÄndet Finding Help vet vart de ska vÀnda sig. SÄ snart hjÀlpen har hittats, gÄr agenten tillbaka till tillstÄndet Attacking. Vid det tillfÀllet kanske de vill berÀtta för en allierad om hotet, sÄ en ÄtgÀrd kan intrÀffa: NotifyFriendOfThreat.

Och Ă„terigen kan vi se pĂ„ detta system genom linsen av cykeln Sense/Think/Act. Sense uttrycks i de data som anvĂ€nds av övergĂ„ngslogiken. Think — Ă€r de övergĂ„ngar som Ă€r tillgĂ€ngliga i varje tillstĂ„nd. Och Act sker genom Ă„tgĂ€rder som utförs periodiskt inom tillstĂ„ndet eller vid övergĂ„ngar mellan tillstĂ„nden.

Ibland kan kontinuerlig övervakning av övergĂ„ngsvillkor vara kostsam. Till exempel, om varje agent skulle utföra komplexa berĂ€kningar varje bildrute för att avgöra om de ser fiender och om de borde gĂ„ frĂ„n tillstĂ„ndet Patrolling till Attacking — skulle det ta mycket processortid.

Viktiga förÀndringar i vÀrldens tillstÄnd kan ses som hÀndelser som kommer att hanteras nÀr de intrÀffar. IstÀllet för att FSM varje bildrute kontrollerar övergÄngsvillkoret "kan min agent se spelaren?", kan vi konfigurera ett separat system för att utföra kontrollerna mer sÀllan (till exempel 5 gÄnger per sekund). Som resultat ges Player Seen nÀr kontrollen godkÀnns.

Detta skickas vidare till FSM, som nu mÄste gÄ in i tillstÄndet Player Seen event received och reaktionsmÀssigt agera. Det slutgiltiga beteendet Àr detsamma, förutom en nÀstan omÀrkbar fördröjning innan svaret ges. Resultatet Àr att prestandan har förbÀttrats genom att separera en del av Sense till en separat del av programmet.

Hierarkisk Àndlig tillstÄndsmaskin

Att arbeta med stora FSM Àr dock inte alltid bekvÀmt. Om vi vill expandera angreppstillstÄndet genom att ersÀtta det med separata MeleeAttacking (nÀrstrid) och RangedAttacking (distansangrepp), mÄste vi justera övergÄngarna frÄn alla andra tillstÄnd som leder till tillstÄndet Attacking (bÄde nuvarande och framtida).

Du har sÀkert lagt mÀrke till att vÄrt exempel har mÄnga dubblerade övergÄngar. De flesta övergÄngar i tillstÄndet Idling Àr identiska med övergÄngarna i tillstÄndet Patrolling. Det vore bra att undvika upprepningar, sÀrskilt om vi lÀgger till fler liknande tillstÄnd. Det Àr meningsfullt att gruppera Idling och Patrolling under en gemensam etikett "icke-strid", dÀr det bara finns ett gemensamt uppsÀttning övergÄngar till stridstillstÄnd. Om vi förestÀller oss denna etikett som ett tillstÄnd, skulle Idling och Patrolling bli del-tillstÄnd. Ett exempel pÄ anvÀndning av en separat övergÄngstabell för det nya icke-stridstillstÄndet:

HuvudtillstÄnd:
Hur man skapar ett spel-AI: en guide för nybörjare

Icke-stridstillstÄnd:
Hur man skapar ett spel-AI: en guide för nybörjare

Och i form av ett diagram:

Hur man skapar ett spel-AI: en guide för nybörjare

Det Ă€r samma system, men med ett nytt icke-stridstillstĂ„nd som inkluderar Idling och Patrolling. Med varje tillstĂ„nd som innehĂ„ller FSM med del-tillstĂ„nd (och dessa del-tillstĂ„nd i sin tur innehĂ„ller sina egna FSM — och sĂ„ vidare, sĂ„ mĂ„nga du behöver), fĂ„r vi ett Hierarchical Finite State Machine eller HFSM (hierarkisk Ă€ndlig automat). Genom att gruppera det icke-stridstillstĂ„ndet har vi skapat en hel del onödiga övergĂ„ngar. Detsamma kan vi göra för eventuella nya tillstĂ„nd med gemensamma övergĂ„ngar. Till exempel, om vi i framtiden utökar tillstĂ„ndet Attacking till tillstĂ„nden MeleeAttacking och MissileAttacking, kommer dessa vara del-tillstĂ„nd som övergĂ„r mellan varandra baserat pĂ„ avstĂ„ndet till fienden och tillgĂ„ngen pĂ„ ammunition. Som ett resultat kan komplexa beteendemodeller och delmodeller representeras med minimalt av dubblerade övergĂ„ngar.

BeteendetrÀd

Med HFSM skapas komplexa kombinationer av beteenden pĂ„ ett enkelt sĂ€tt. Det finns dock en liten svĂ„righet, att beslutsfattande i form av övergĂ„ngsregler Ă€r nĂ€ra kopplat till det aktuella tillstĂ„ndet. Och i mĂ„nga spel Ă€r detta just vad som behövs. En noggrann anvĂ€ndning av tillstĂ„ndshierarkier kan minska antalet upprepningar vid övergĂ„ngar. Men ibland behövs regler som fungerar oberoende av vilket tillstĂ„nd du Ă€r i, eller som tillĂ€mpas i nĂ€stan alla tillstĂ„nd. Till exempel, om agentens hĂ€lsa faller under 25%, vill du att den ska fly oavsett om den har varit i strid, slappnat av eller pratat — du mĂ„ste lĂ€gga till detta villkor i varje tillstĂ„nd. Och om din designer senare vill Ă€ndra tröskeln för lĂ„g hĂ€lsa frĂ„n 25% till 10%, mĂ„ste detta Ă„tgĂ€rdas igen.

I idealfall behövs ett system dÀr besluten om "vilket tillstÄnd man ska vara i" ligger utanför sjÀlva tillstÄnden, sÄ att förÀndringar endast behöver göras pÄ ett stÀlle utan att pÄverka övergÄngsvillkoren. HÀr kommer beteendetrÀd in i bilden.

Det finns flera sÀtt att implementera detta, men kÀrnan Àr ungefÀr densamma och liknar ett beslutstrÀd: algoritmen börjar med en "rot"-nod, och i trÀdet finns noder som representerar antingen beslut eller handlingar. Det finns dock nÄgra viktiga skillnader:

  • Nu returnerar noderna ett av tre vĂ€rden: Succeeded (om arbetet har slutförts), Failed (om det inte gĂ„r att starta) eller Running (om det fortfarande körs och inget slutresultat har nĂ„tts).
  • Det finns inte lĂ€ngre beslutsnoder för att vĂ€lja mellan tvĂ„ alternativ. IstĂ€llet finns det Decorator-noder, som har en enda undernod. Om de lyckas sĂ„ utför de sin enda undernod.
  • Noder som utför handlingar returnerar vĂ€rdet Running för att representera de pĂ„gĂ„ende handlingarna.

Denna lilla uppsÀttning noder kan kombineras för att skapa mÄnga komplexa beteendemodeller. LÄt oss förestÀlla oss HFSM:n frÄn det tidigare exemplet som ett beteendetrÀd:

Hur man skapar ett spel-AI: en guide för nybörjare

Med denna struktur bör det inte finnas nĂ„gon tydlig övergĂ„ng frĂ„n tillstĂ„nden Idling/Patrolling till tillstĂ„ndet Attacking eller nĂ„got annat. Om fienden Ă€r synlig och karaktĂ€rens hĂ€lsa Ă€r lĂ„g, kommer exekveringen att stanna pĂ„ noden Fleeing, oavsett vilken nod den tidigare exekverade — Patrolling, Idling, Attacking eller nĂ„gon annan.

Hur man skapar ett spel-AI: en guide för nybörjare

BeteendetrĂ€d Ă€r komplexa — det finns mĂ„nga sĂ€tt att konstruera dem, och att hitta rĂ€tt kombination av dekoratörer och kompositnoder kan vara utmanande. Det finns ocksĂ„ frĂ„gor om hur ofta man ska kontrollera trĂ€det — vill vi gĂ„ igenom varje del eller bara nĂ€r ett av villkoren har förĂ€ndrats? Hur ska man lagra tillstĂ„ndet som relaterar till noderna — hur vet vi nĂ€r vi har varit i tillstĂ„ndet Idling i tio sekunder, eller hur vet vi vilka noder som exekverades senast för att korrekt hantera sekvensen?

Det Àr just dÀrför det finns mÄnga olika implementeringar. Till exempel har i vissa system decorator-noder ersatts med inbyggda dekoratorer. Dessa omvÀrderar trÀdet nÀr dekoratorns villkor Àndras, hjÀlper till att koppla ihop noder och erbjuder periodiska uppdateringar.

Utility-baserat system

Vissa spel har mÄnga olika mekaniker. Det Àr önskvÀrt att de fÄr alla fördelar av enkla och allmÀnna regler för övergÄngar, men inte nödvÀndigtvis i form av ett fullt beteendetrÀd. IstÀllet för att ha en tydlig uppsÀttning val eller ett trÀd av möjliga handlingar Àr det enklare att lÀra sig alla handlingar och vÀlja den mest lÀmpliga i stunden.

Ett system baserat pÄ nytta hjÀlper just i det hÀr. Det Àr ett system dÀr agenten har mÄnga olika handlingar och sjÀlv vÀljer vilken han ska utföra, baserat pÄ den relativa nyttan av varje av dem. HÀr Àr nyttan ett godtyckligt mÄtt pÄ hur viktigt eller önskvÀrt det Àr för agenten att utföra denna handling.

Den berÀknade nyttan av en handling baserat pÄ det aktuella tillstÄndet och miljön kan agenten kontrollera och vÀlja den mest lÀmpliga andra tillstÄndet nÀr som helst. Detta liknar en FSM, förutom att övergÄngarna definieras av bedömningen för varje potentiellt tillstÄnd, inklusive det aktuella. Observera att vi vÀljer den mest anvÀndbara handlingen för att gÄ vidare (eller stanna kvar om vi redan har genomfört den). För mer variation kan detta vara ett viktat, men slumpmÀssigt val frÄn en liten lista.

Systemet tilldelar ett godtyckligt intervall av nyttovĂ€rden — till exempel frĂ„n 0 (helt oönskat) till 100 (fullt önskvĂ€rt). Varje handling har en rad parametrar som pĂ„verkar berĂ€kningen av detta vĂ€rde. Återigen med vĂ„rt exempel med vakten:

Hur man skapar ett spel-AI: en guide för nybörjare

ÖvergĂ„ngar mellan handlingar Ă€r oklara — vilket tillstĂ„nd som helst kan följa efter nĂ„got annat. Prioriteringarna av handlingar ligger i de Ă„terkommande nytta vĂ€rdena. Om fienden Ă€r synlig, och fienden Ă€r stark, medan karaktĂ€rens hĂ€lsa Ă€r lĂ„g, kommer bĂ„de Fleeing och FindingHelp att ge höga, icke-noll vĂ€rden. Samtidigt kommer FindingHelp alltid att vara högre. PĂ„ samma sĂ€tt ger icke-stridshandlingar aldrig mer Ă€n 50, sĂ„ de kommer alltid att vara lĂ€gre Ă€n stridshandlingar. Detta mĂ„ste beaktas nĂ€r man skapar handlingar och berĂ€knar deras nytta.

I vÄrt exempel returnerar ÄtgÀrder antingen ett fast konstant vÀrde eller ett av tvÄ fasta vÀrden. Ett mer realistiskt system skulle föreslÄ att man returnerar en bedömning frÄn ett kontinuerligt intervall av vÀrden. Till exempel returnerar ÄtgÀrden Fleeing högre nytta vÀrden om agentens hÀlsa Àr lÄg, medan ÄtgÀrden Attacking returnerar lÀgre vÀrden om fienden Àr för stark. PÄ grund av detta har ÄtgÀrden Fleeing prioritet över Attacking i alla situationer dÀr agenten kÀnner att han inte har tillrÀcklig hÀlsa för att besegra motstÄndaren. Detta möjliggör att Àndra prioriteringar baserat pÄ ett antal kriterier, vilket gör denna metod mer flexibel och variabel Àn beteendetrÀd eller FSM.

Varje ÄtgÀrd har mÄnga villkor för att berÀkna programmet. Dessa kan skrivas pÄ ett skriptsprÄk eller som en serie matematiska formler. I The Sims, som modellerar karaktÀrens dagliga rutiner, lÀggs en ytterligare nivÄ av berÀkningar till - agenten fÄr en rad "motivationer" som pÄverkar nytta vÀrderingarna. Om karaktÀren Àr hungrig kommer han sÄ smÄningom att bli Ànnu hungrigare, och nyttovÀrdet för ÄtgÀrden EatFood kommer att öka tills karaktÀren utför den, vilket minskar hungernivÄn och ÄterstÀller vÀrdet av EatFood till noll.

Idén att vÀlja ÄtgÀrder baserat pÄ ett bedömningssystem Àr ganska enkel, sÄ ett utility-baserat system kan anvÀndas som en del av AI: s beslutsprocesser snarare Àn som en fullstÀndig ersÀttning. BeslutstrÀdet kan be om bedömning av tvÄ barnknutar och vÀlja den som har högre vÀrde. PÄ liknande sÀtt kan ett beteendetrÀd ha en sammansatt nod Utility för att bedöma nyttan av ÄtgÀrder för att avgöra vilken barnelement som ska utföras.

Rörelse och navigering

I de tidigare exemplen hade vi en plattform som vi flyttade Ät vÀnster eller höger, och en vÀktare som patrullerade eller attackerade. Men hur hanterar vi sjÀlva agentens rörelse över en viss tidsperiod? Hur stÀller vi in hastigheten, hur undviker vi hinder och hur planerar vi rutten om det Àr svÄrare att nÄ destinationen Àn att bara röra sig i en rak linje? LÄt oss ta en titt pÄ detta.

Hantering

I början kommer vi att anta att varje agent har ett hastighetsvĂ€rde, som inkluderar hur snabbt den rör sig och i vilken riktning. Det kan mĂ€tas i meter per sekund, kilometer per timme, pixlar per sekund, osv. Med minnet av cykeln Sinna/TĂ€nka/Handla kan vi förestĂ€lla oss att delen TĂ€nka vĂ€ljer hastigheten, medan delen Handla tillĂ€mpar den hastigheten pĂ„ agenten. Vanligtvis finns det ett fysiksystem i spel som utför den hĂ€r uppgiften Ă„t dig, genom att studera hastighetsvĂ€rdet för varje objekt och justera det. DĂ€rför kan vi överlĂ„ta AI:t en enda uppgift – att avgöra vilken hastighet agenten ska ha. Om vi vet var agenten ska vara, mĂ„ste vi flytta den i rĂ€tt riktning med den angivna hastigheten. En mycket trivial ekvation:

desired_travel = destination_position – agent_position

FörestĂ€ll dig en 2D-vĂ€rld. Agenten befinner sig vid punkten (-2,-2), destinationen ligger nĂ„gonstans nordost i punkten (30, 20), och den nödvĂ€ndiga vĂ€gen för agenten att komma dit Ă€r (32, 22). Anta att dessa positioner mĂ€ts i meter – om vi antar att agentens hastighet Ă€r 5 meter per sekund, kommer vi att skala vĂ„r rörelsevektor och fĂ„ en hastighet pĂ„ ungefĂ€r (4.12, 2.83). Med dessa parametrar skulle agenten nĂ„ destinationen efter nĂ€stan 8 sekunder.

VÀrden kan rÀknas om nÀr som helst. Om agenten var halvvÀgs till mÄlet, skulle rörelsen vara hÀlften av lÀngden, men eftersom agentens maximala hastighet Àr 5 m/s (vilket vi bestÀmde tidigare), kommer hastigheten att vara densamma. Detta fungerar ocksÄ för rörliga mÄl, vilket gör det möjligt för agenten att göra smÄ justeringar nÀr de rör sig.

Men vi vill ha mer variation – till exempel att lĂ„ngsamt öka hastigheten för att simulera en karaktĂ€r som rör sig frĂ„n stillastĂ„ende till löpande. Samma sak kan göras i slutet innan stopp. Dessa funktioner kallas steering behaviours, dĂ€r varje har specifika namn: Seek (sök), Flee (fly), Arrival (ankomst) osv. IdĂ©n Ă€r att accelerationer kan tillĂ€mpas pĂ„ agentens hastighet, baserat pĂ„ en jĂ€mförelse mellan agentens position och nuvarande hastighet med destinationen, för att anvĂ€nda olika sĂ€tt att röra sig mot mĂ„let.

Varje beteende har ett nÄgot annat syfte. Seek och Arrival Àr sÀtt att flytta agenten till destinationspunkten. Obstacle Avoidance (undvikande av hinder) och Separation (separation) justerar agentens rörelse för att kringgÄ hinder pÄ vÀgen till mÄlet. Alignment (samordning) och Cohesion (sammanslutning) hÄller agenterna tillsammans nÀr de rör sig. Ett antal olika styrningsbeteenden kan summeras för att fÄ en enda vÀgvektor med hÀnsyn till alla faktorer. En agent anvÀnder beteendena Arrival, Separation och Obstacle Avoidance för att hÄlla sig borta frÄn vÀggar och andra agenter. Denna metod fungerar bra i öppna miljöer utan överflödiga detaljer.

Under svĂ„rare förhĂ„llanden fungerar sammanslagningen av olika beteenden sĂ€mre — till exempel kan agenten fastna i vĂ€ggen pĂ„ grund av konflikten mellan Arrival och Obstacle Avoidance. DĂ€rför mĂ„ste vi övervĂ€ga alternativ som Ă€r mer komplexa Ă€n bara en enkel summering av alla vĂ€rden. En sĂ„dan metod Ă€r: istĂ€llet för att summera resultaten av varje beteende kan vi övervĂ€ga rörelser i olika riktningar och vĂ€lja den bĂ€sta.

Men i en komplex miljö med ÄtervÀndsgrÀnder och val av vilken riktning man ska gÄ, behöver vi nÄgot Ànnu mer avancerat.

SökvÀg

Steering behaviours Àr utmÀrkta för enkel rörelse pÄ öppen mark (fotbollsplan eller arena), dÀr vÀgen frÄn A till B Àr en rak linje med smÄ avvikelser runt hinder. För komplexa rutter behöver vi pathfinding (sökvÀg), som Àr ett sÀtt att utforska vÀrlden och fatta beslut om rutten genom den.

Det enklaste sĂ€ttet Ă€r att lĂ€gga ett rutnĂ€t över varje ruta intill agenten och bedöma i vilka av dem det Ă€r tillĂ„tet att röra sig. Om nĂ„gon av dem Ă€r en destination, följ vĂ€gen frĂ„n varje ruta tillbaka till föregĂ„ende, tills du nĂ„r början. Det Ă€r vĂ€gen. Om inte upprepa processen med nĂ€rmaste andra rutor, tills du hittar destinationen eller rutorna tar slut (detta innebĂ€r att det inte finns nĂ„gon möjlig vĂ€g). Detta kallas formellt för Breadth-First Search eller BFS (bredd först-sökning). PĂ„ varje steg tittar det Ă„t alla hĂ„ll (dĂ€rav bredd). SökomrĂ„det liknar en vĂ„gfront som rör sig tills den nĂ„r mĂ„let — sökomrĂ„det expanderar för varje steg tills slutpunkten inkluderas, varefter vĂ€gen tillbaka kan spĂ„ras till början.

Hur man skapar ett spel-AI: en guide för nybörjare

Som ett resultat fĂ„r du en lista över rutor som utgör den önskade vĂ€gen. Detta Ă€r vĂ€gen (dĂ€rav pathfinding) — en lista över stĂ€llen som agenten kommer att besöka pĂ„ vĂ€g till destinationen.

Eftersom vi kĂ€nner till positionen för varje ruta i vĂ€rlden kan vi anvĂ€nda steering behaviors för att röra oss lĂ€ngs vĂ€gen — frĂ„n nod 1 till nod 2, sedan frĂ„n nod 2 till nod 3 och sĂ„ vidare. Det enklaste alternativet Ă€r att rikta in sig pĂ„ mitten av nĂ€sta ruta, men det Ă€r Ă€nnu bĂ€ttre att stanna vid mitten av kanten mellan den nuvarande rutan och den nĂ€sta. Detta gör att agenten kan skĂ€ra hörn vid branta svĂ€ngar.

BFS-algoritmen har Ă€ven nackdelar — den utforskar lika mĂ„nga rutor i "fel" riktning som i "rĂ€tt". HĂ€r framtrĂ€der en mer avancerad algoritm kallad A* (A star). Den fungerar pĂ„ liknande sĂ€tt men istĂ€llet för att blint utforska grannrutor (sen grannarnas grannar, sedan grannarnas grannars grannar och sĂ„ vidare), samlar den noder i en lista och sorterar dem sĂ„ att nĂ€sta utforskade nod alltid Ă€r den som leder till den kortaste vĂ€gen. Noder sorteras baserat pĂ„ en heuristik som beaktar tvĂ„ saker — "kostnaden" för den hypotetiska vĂ€gen till den önskade rutan (inklusive eventuella rörlighetskostnader) och en bedömning av hur lĂ„ngt denna ruta Ă€r frĂ„n destinationen (och flyttar sökningen i rĂ€tt riktning).

Hur man skapar ett spel-AI: en guide för nybörjare

I det hÀr exemplet visar vi hur agenten undersöker ett avsnitt i taget, varje gÄng vÀljer det det mest lovande grannfÀltet. Den resulterande vÀgen Àr densamma som vid BFS, men under processen har fÀrre avsnitt övervÀgts - vilket Àr av stor betydelse för spelprestanda.

Rörelse utan rutnÀt

Men de flesta spel Àr inte uppbyggda pÄ ett rutnÀt, och ofta gÄr det inte att skapa ett utan att kompromissa med verklighetstrogenhet. Det krÀvs kompromisser. Vilka storlekar bör avsnitten ha? För stora - och de kan inte korrekt representera smala korridorer eller svÀngar, för smÄ - det kommer att vara för mÄnga avsnitt att söka igenom, vilket kan ta mycket tid.

Det första att förstÄ Àr att ett rutnÀt ger oss en graf av sammankopplade noder. A* och BFS-algoritmer fungerar faktiskt pÄ grafer och bryr sig överhuvudtaget inte om vÄrt rutnÀt. Vi skulle kunna placera noder var som helst i spelvÀrlden: om det finns en förbindelse mellan tvÄ sammankopplade noder, sÄvÀl som mellan start- och slutpunkt och minst en av noderna - algoritmen kommer att fungera lika bra som tidigare. Detta kallas ofta en waypoint-system, eftersom varje nod representerar en betydelsefull position i vÀrlden, vilket kan vara en del av ett antal hypotetiska vÀgar.

Hur man skapar ett spel-AI: en guide för nybörjare
Exempel 1: en nod i varje avsnitt. Sökningen börjar frÄn noden dÀr agenten befinner sig och slutar i noden för det avsnitt som behövs.

Hur man skapar ett spel-AI: en guide för nybörjare
Exempel 2: en mindre uppsÀttning noder (waypoints). Sökningen börjar i avsnittet med agenten, passerar igenom nödvÀndiga noder och fortsÀtter sedan till mÄlet.

Detta Àr ett ganska flexibelt och kraftfullt system. Men det krÀvs viss försiktighet nÀr det gÀller beslut om var och hur man placerar waypoints, annars kan agenterna helt enkelt missa den nÀrmaste punkten och inte kunna börja sin vÀg. Det skulle vara enklare om vi kunde automatiskt placera waypoints baserat pÄ vÀrldens geometri.

HÀr kommer navigation mesh eller navmesh (navigationsnÀt) in. Detta Àr vanligtvis ett 2D-nÀt av trianglar som lÀggs pÄ vÀrldens geometri - överallt dÀr agenterna har tillÄtelse att gÄ. Varje triangel i nÀtet blir en nod i grafen och har upp till tre angrÀnsande trianglar som blir grannnoder i grafen.

Den hĂ€r bilden Ă€r ett exempel frĂ„n Unity-motorn – den analyserade geometrin i vĂ€rlden och skapade en navmesh (visad i ljusblĂ„tt pĂ„ skĂ€rmdumpen). Varje polygon i navmesh Ă€r ett omrĂ„de dĂ€r en agent kan stĂ„ eller röra sig frĂ„n en polygon till en annan. I det hĂ€r exemplet Ă€r polygonerna mindre Ă€n de vĂ„ningar dĂ€r de ligger – detta görs för att ta hĂ€nsyn till agentens storlek, som kan strĂ€cka sig utanför dess nominella position.

Hur man skapar ett spel-AI: en guide för nybörjare

Vi kan söka efter en rutt genom detta nÀtverk, Äterigen med hjÀlp av A*-algoritmen. Detta ger oss en nÀstan perfekt rutt i vÀrlden som tar hÀnsyn till all geometri och samtidigt inte krÀver onödiga noder eller skapande av waypoints.

Pathfinding Àr ett för stort Àmne för att tÀckas av ett enda avsnitt. Om du vill studera det mer detaljerat kan detta hjÀlpa dig Amita Patels webbplats.

Planering

Vi har bekrĂ€ftat med pathfinding att det ibland inte rĂ€cker med att bara vĂ€lja en riktning och gĂ„ – vi mĂ„ste vĂ€lja en rutt och göra nĂ„gra svĂ€ngar för att nĂ„ vĂ„rt mĂ„l. Vi kan sammanfatta denna idĂ©: att nĂ„ ett mĂ„l Ă€r inte bara nĂ€sta steg, utan en hel sekvens dĂ€r vi ibland mĂ„ste titta framĂ„t flera steg för att veta hur det första ska vara. Detta kallas planering. Pathfinding kan betraktas som en av flera tillĂ€gg till planering. Ur vĂ„rt perspektiv av cykeln Sense/Think/Act Ă€r detta dĂ€r Think-planerade delar Act för framtiden.

LÄt oss ta ett exempel frÄn kortspelet Magic: The Gathering. Vi spelar först med följande kort i handen:

  • Swamp – ger 1 svart mana (landskort).
  • Forest – ger 1 grön mana (landskort).
  • Fugitive Wizard – krĂ€ver 1 blĂ„ mana för att Ă„kalla.
  • Elvish Mystic – krĂ€ver 1 grön mana för att Ă„kalla.

Vi ignorerar de tre ÄterstÄende korten för enkelhetens skull. Enligt reglerna fÄr spelaren spela ett landkort per tur, de kan "tappa" detta kort för att extrahera mana frÄn det och sedan anvÀnda besvÀrjelser (inklusive Äkallande av varelser) baserat pÄ mÀngden mana. I den hÀr situationen vet den mÀnskliga spelaren att de behöver spela Forest, "tappa" 1 grön mana och sedan Äkalla Elvish Mystic. Men hur ska spelarkompjuter förstÄ detta?

Enkel planering

Det triviala tillvÀgagÄngssÀttet Àr att prova varje ÄtgÀrd i tur och ordning tills inga lÀmpliga alternativ ÄterstÄr. NÀr AI tittar pÄ korten ser det att det kan spela Swamp. Och det spelar det. Finns det nÄgra andra ÄtgÀrder kvar att göra den hÀr turen? Det kan inte spela varken Elvish Mystic eller Fugitive Wizard, eftersom det krÀvs grön och blÄ mana för att spela dem och Swamp ger endast svart mana. Och den kan inte spela Forest eftersom den redan har spelat Swamp. SÄ spel-IAn har följt reglerna, men gjort det dÄligt. Det kan förbÀttras.

Planering kan hitta en lista över ÄtgÀrder som leder spelet till det önskade tillstÄndet. Precis som varje ruta pÄ vÀgen hade grannar (i pathfinding) har varje ÄtgÀrd i planen ocksÄ grannar eller efterföljande ÄtgÀrder. Vi kan söka efter dessa ÄtgÀrder och följande ÄtgÀrder tills vi nÄr det önskade tillstÄndet.

I vÄrt exempel Àr det önskade resultatet "Äberopa en varelse om möjligt". I början av turen ser vi bara tvÄ möjliga ÄtgÀrder som Àr tillÄtna enligt spelets regler:

1. Spela Swamp (resultat: Swamp i spelet)
2. Spela Forest (resultat: Forest i spelet)

Varje Ă„tgĂ€rd som vidtas kan leda till ytterligare Ă„tgĂ€rder och stĂ€nga andra, beroende pĂ„ spelets regler. TĂ€nk dig att vi spelade Swamp — det skulle ta bort Swamp som steg (vi har redan spelat det), det skulle ocksĂ„ ta bort Forest (eftersom man fĂ„r spela en landkort per tur enligt reglerna). Efter detta lĂ€gger AI till som nĂ€sta steg — att fĂ„ 1 svart mana, eftersom det inte finns nĂ„gra andra alternativ. Om den gĂ„r vidare och vĂ€ljer att aktivera Swamp, fĂ„r den 1 enhet svart mana och kan ingenting göra med den.

1. Spela Swamp (resultat: Swamp i spelet)
1.1 «Aktivera» Swamp (resultat: Swamp «aktiverad», +1 enhet svart mana)
Inga tillgĂ€ngliga Ă„tgĂ€rder – SLUT
2. Spela Forest (resultat: Forest i spelet)

Listan över Ă„tgĂ€rder blev kort, vi har kört fast. Vi upprepar processen för nĂ€sta Ă„tgĂ€rd. Vi spelar Forest, öppnar Ă„tgĂ€rden "fĂ„ 1 grön mana", vilket i sin tur öppnar den tredje Ă„tgĂ€rden — Ă„kalla Elvish Mystic.

1. Spela Swamp (resultat: Swamp i spelet)
1.1 «Aktivera» Swamp (resultat: Swamp «aktiverad», +1 enhet svart mana)
Inga tillgĂ€ngliga Ă„tgĂ€rder – SLUT
2. Spela Forest (resultat: Forest i spelet)
2.1 «Aktivera» Forest (resultat: Forest «aktiverad», +1 enhet grön mana)
2.1.1 Åkalla Elvish Mystic (resultat: Elvish Mystic i spelet, -1 enhet grön mana)
Inga tillgĂ€ngliga Ă„tgĂ€rder – SLUT

Slutligen har vi studerat alla möjliga ÄtgÀrder och hittat en plan för att Äkalla en varelse.

Detta Ă€r ett mycket förenklat exempel. Det Ă€r önskvĂ€rt att vĂ€lja den bĂ€sta möjliga planen, snarare Ă€n nĂ„gon som uppfyller vissa kriterier. Vanligtvis kan man bedöma potentiella planer baserat pĂ„ slutresultatet eller den sammanlagda vinsten frĂ„n att genomföra dem. Man kan ge sig sjĂ€lv 1 poĂ€ng för att spela en landkort och 3 poĂ€ng för att kalla pĂ„ en varelse. Att spela Swamp skulle ge 1 poĂ€ng. Men att spela Forest → Tappa Forest → kalla pĂ„ Elvish Mystic — skulle ge totalt 4 poĂ€ng.

SÄ hÀr fungerar planering i Magic: The Gathering, men samma logik kan tillÀmpas i andra situationer. Till exempel, flytta en bonde för att frigöra utrymme för en löparens drag i schack. Eller gömma sig bakom en vÀgg för att sÀkert skjuta i XCOM sÄ. I allmÀnhet förstÄr du poÀngen.

FörbÀttrad planering

Ibland finns det för mĂ„nga potentiella Ă„tgĂ€rder för att övervĂ€ga varje möjlig variant. Återigen till exemplet med Magic: The Gathering: antag att det i spelet finns flera landkort och varelser i din hand — antalet möjliga dragkombinationer kan uppgĂ„ till tiotals. Det finns flera lösningar pĂ„ problemet.

Det första sĂ€ttet Ă€r backwards chaining (bakĂ„t kedjande). IstĂ€llet för att gĂ„ igenom alla kombinationer, Ă€r det bĂ€ttre att börja med det slutliga resultatet och försöka hitta en direkt vĂ€g. IstĂ€llet för att gĂ„ frĂ„n roten av trĂ€det till ett visst blad, rör vi oss bakĂ„t — frĂ„n bladet till roten. Denna metod Ă€r enklare och snabbare.

Om motstÄndaren har 1 hÀlsa, kan man hitta en plan "att orsaka 1 eller fler skador". För att uppnÄ detta mÄste en serie villkor uppfyllas:

1. Skadan kan orsakas av en trollformel — den mĂ„ste vara pĂ„ handen.
2. För att spela trollformeln — behövs mana.
3. För att fĂ„ mana — mĂ„ste man spela ett landkort.
4. För att spela ett landkort — mĂ„ste man ha det i handen.

Ett annat sĂ€tt Ă€r best-first search (bĂ€sta först sökning). IstĂ€llet för att gĂ„ igenom alla vĂ€gar, vĂ€ljer vi den mest passande. Detta sĂ€tt ger oftast en optimal plan utan onödiga sökningar. A* Ă€r en typ av bĂ€sta första sökning — genom att undersöka de mest lovande vĂ€garna frĂ„n början kan den redan hitta den bĂ€sta vĂ€gen utan att behöva kontrollera andra alternativ.

En intressant och alltmer populÀr variant av best-first search Àr Monte Carlo Tree Search. IstÀllet för att gissa vilka planer som Àr bÀttre Àn andra vid valet av varje efterföljande ÄtgÀrd, vÀljer algoritmen slumpmÀssiga efterföljare vid varje steg tills den nÄr slutet (nÀr planen resulterar i vinst eller förlust). Slutresultatet anvÀnds sedan för att öka eller minska bedömningen av "vikten" av de föregÄende alternativen. Genom att upprepa denna process flera gÄnger ger algoritmen en bra uppskattning av vilket nÀsta steg som Àr bÀttre, Àven om situationen förÀndras (om motstÄndaren vidtar ÄtgÀrder för att hindra spelaren).

I berĂ€ttelsen om planering i spel kan vi inte undvika Goal-Oriented Action Planning eller GOAP (mĂ„linriktad handlingsplanering). Detta Ă€r en mycket anvĂ€nd och diskuterad metod, men förutom nĂ„gra distinkta detaljer Ă€r det i grunden en metod för backwards chaining, som vi redan har pratat om. Om uppgiften Ă€r att "eliminera spelaren", och spelaren gömmer sig, kan planen vara: kasta granat → ta fram den → kasta.

Det finns vanligtvis flera mÄl, var och en med sin egen prioritet. Om mÄlet med högst prioritet inte kan uppfyllas (ingen kombination av ÄtgÀrder skapar planen "eliminera spelaren" eftersom spelaren inte Àr synlig), kommer AI:n att ÄtergÄ till mÄl med lÀgre prioritet.

InlÀrning och anpassning

Vi har redan nÀmnt att spel-AI vanligtvis inte anvÀnder maskininlÀrning, eftersom det inte passar för att styra agenter i realtid. Men det betyder inte att man inte kan lÄna nÄgot frÄn detta omrÄde. Vi vill ha en motstÄndare i en shooter som vi kan lÀra oss nÄgot av. Till exempel, att fÄ veta om de bÀsta positionerna pÄ kartan. Eller en motstÄndare i en fighting som skulle blockera ofta anvÀnda komboattacker, vilket motiverar spelaren att anvÀnda andra. SÄ maskininlÀrning kan vara mycket anvÀndbart i sÄdana situationer.

Statistik och sannolikheter

Innan vi gĂ„r vidare till mer komplexa exempel, lĂ„t oss övervĂ€ga hur lĂ„ngt vi kan gĂ„ genom att ta nĂ„gra enkla mĂ€tningar och anvĂ€nda dem för beslutsfattande. Till exempel, strategin i realtid – hur kan vi bestĂ€mma om en spelare kan starta en attack inom de första minuterna av spelet och vilken försvar som ska förberedas mot detta? Vi kan studera spelarens tidigare erfarenheter för att förstĂ„ vilken potentiell reaktion som kan komma i framtiden. LĂ„t oss börja med att vi inte har nĂ„gon sĂ„dant utgĂ„ngsmaterial, men vi kan samla det – varje gĂ„ng AI spelar mot en mĂ€nniska kan den registrera tiden för den första attacken. Efter nĂ„gra sessioner kommer vi att fĂ„ ett genomsnittligt vĂ€rde för hur lĂ„ng tid det tar innan spelaren attackerar i framtiden.

Det finns ocksĂ„ ett problem med medelvĂ€rden: om en spelare har "rushat" 20 gĂ„nger och spelat lĂ„ngsamt 20 gĂ„nger, sĂ„ kommer de nödvĂ€ndiga vĂ€rdena att ligga nĂ„gonstans i mitten, vilket inte ger oss nĂ„got anvĂ€ndbart. En lösning Ă€r att begrĂ€nsa ingĂ„ngsdata – man kan övervĂ€ga de senaste 20 styckena.

En liknande metod anvÀnds för att bedöma sannolikheten för vissa handlingar, som antar att spelarens tidigare preferenser kommer att vara desamma i framtiden. Om en spelare attackerar oss fem gÄnger med en eldboll, tvÄ gÄnger med blixt och en gÄng i nÀrstrid, Àr det uppenbart att han föredrar eldboll. LÄt oss extrapolera och se sannolikheten för anvÀndning av olika vapen: eldboll=62,5%, blixt=25% och nÀrstrid=12,5%. VÄr spel-AI behöver förbereda sig pÄ att skydda sig mot eld.

En annan intressant metod Ă€r att anvĂ€nda Naive Bayes Classifier (naiv bayesisk klassificerare) för att studera stora mĂ€ngder ingĂ„ngsdata och klassificera situationen sĂ„ att AI reagerar pĂ„ rĂ€tt sĂ€tt. Bayesianska klassificerare Ă€r mest kĂ€nda för sin anvĂ€ndning i skrĂ€ppostfilter för e-post. DĂ€r undersöker de ord, jĂ€mför dem med var dessa ord tidigare förekommit (i skrĂ€ppost eller inte), och drar slutsatser om inkommande e-post. Vi kan göra samma sak Ă€ven med mindre ingĂ„ngsdata. Baserat pĂ„ all anvĂ€ndbar information som AI ser (till exempel vilka fiendens enheter som skapades, eller vilka besvĂ€rjelser de anvĂ€nder, eller vilka teknologier de forskar pĂ„), och det slutliga resultatet (krig eller fred, "rush" eller försvara, osv.) – vĂ€ljer vi det beteende som AI ska ha.

Alla dessa sÀtt att lÀra sig Àr tillrÀckliga, men det Àr att föredra att anvÀnda dem baserat pÄ data frÄn tester. AI kommer att lÀra sig att anpassa sig till olika strategier som dina playtestare har anvÀnt. AI som anpassar sig efter spelaren efter slÀppet kan bli förutsÀgbar eller Ä sin sida för svÄr att besegra.

Anpassning baserat pÄ vÀrden

Med tanke pÄ innehÄllet i vÄr spelvÀrld och regler kan vi Àndra uppsÀttningen vÀrden som pÄverkar beslutsfattandet, istÀllet för att bara anvÀnda indata. Vi gör sÄ hÀr:

  • LĂ„t AI samla in data om vĂ€rldens tillstĂ„nd och nyckelhĂ€ndelser under spelets gĂ„ng (som angivet ovan).
  • Vi Ă€ndrar nĂ„gra viktiga vĂ€rden baserat pĂ„ dessa data.
  • Vi genomför vĂ„ra beslut baserat pĂ„ bearbetningen eller utvĂ€rderingen av dessa vĂ€rden.

Till exempel, agenten har flera rum att vÀlja pÄ i en förstapersonsskjutare-karta. Varje rum har ett vÀrde som avgör hur önskvÀrt det Àr att besöka. AI vÀljer slumpmÀssigt vilket rum den ska gÄ till, baserat pÄ vÀrdet. DÀrefter minns agenten vilket rum han blev dödad i och minskar dess vÀrde (sannolikheten att han gÄr dit igen). Detsamma gÀller för den omvÀnda situationen - om agenten förstör mÄnga motstÄndare ökar vÀrdet pÄ rummet.

Markovmodell

Vad om vi anvÀnder de insamlade data för att förutsÀga? Om vi kommer ihÄg varje rum dÀr vi ser spelaren under en viss tidsperiod, kan vi förutsÀga vilket rum spelaren kan gÄ till. Genom att spÄra och registrera spelarens rörelser mellan rummen (vÀrden) kan vi göra prognoser.

LÄt oss ta tre rum: röd, grön och blÄ. Och Àven observationerna som vi har registrerat under visningen av spelsessionen:

Hur man skapar ett spel-AI: en guide för nybörjare

Antalet observationer av varje rum Ă€r nĂ€stan lika — var vi ska göra en bra plats för en överraskning vet vi fortfarande inte. Insamling av statistik försvĂ„ras ocksĂ„ av respawn av spelare som dyker upp jĂ€mnt över hela kartan. Men data om nĂ€sta rum som de gĂ„r in i efter att ha dykt upp pĂ„ kartan Ă€r redan anvĂ€ndbar.

Det Ă€r tydligt att det gröna rummet tilltalar spelarna – de flesta frĂ„n det röda gĂ„r över dit, och 50 % av dem stannar kvar dĂ€r. Det blĂ„ rummet dĂ€remot Ă€r inte populĂ€rt, nĂ€stan ingen gĂ„r dit, och om de gör det, stannar de inte lĂ€nge.

Men uppgifterna sĂ€ger oss nĂ„got mer viktigt – nĂ€r spelaren befinner sig i det blĂ„ rummet, Ă€r nĂ€sta rum dĂ€r vi troligen kommer att se dem det röda, inte det gröna. Trots att det gröna rummet Ă€r populĂ€rare Ă€n det röda, förĂ€ndras situationen om spelaren befinner sig i det blĂ„. Det nĂ€sta tillstĂ„ndet (alltsĂ„ rummet som spelaren kommer att gĂ„ till) beror pĂ„ det föregĂ„ende tillstĂ„ndet (det rum dĂ€r spelaren befinner sig just nu). Genom att studera beroenden kommer vi att göra förutsĂ€gelser som Ă€r mer exakta Ă€n om vi bara rĂ€knade observationer oberoende av varandra.

Att förutsÀga framtida tillstÄnd baserat pÄ data frÄn tidigare tillstÄnd kallas en Markov-modell, och sÄdana exempel (med rum) kallas Markovkedjor. Eftersom modellerna representerar sannolikheten för förÀndringar mellan konsekutiva tillstÄnd, visas de visuellt i form av FSM med sannolikhet för varje övergÄng. Tidigare anvÀnde vi FSM för att representera det beteendemÀssiga tillstÄndet hos agenten, men detta koncept strÀcker sig till vilket tillstÄnd som helst, oavsett om det Àr kopplat till agenten eller inte. I detta fall representerar tillstÄnden rummet som agenten befinner sig i:

Hur man skapar ett spel-AI: en guide för nybörjare

Detta Àr en enkel form av att representera relativ sannolikhet för förÀndringar av tillstÄnd, vilket ger AI en viss möjlighet att förutsÀga nÀsta tillstÄnd. Flera steg framÄt kan förutsÀgas.

Om spelaren Àr i det gröna rummet finns det 50 % chans att de stannar kvar dÀr vid nÀsta observation. Men vad Àr sannolikheten att de fortfarande kommer att vara dÀr Àven efterÄt? Det finns inte bara en chans att spelaren stannade i det gröna rummet efter tvÄ observationer, utan ocksÄ en chans att de lÀmnat och kommit tillbaka. HÀr Àr en ny tabell med hÀnsyn till de nya uppgifterna:

Hur man skapar ett spel-AI: en guide för nybörjare

Av den framgĂ„r det att chansen att se spelaren i det gröna rummet efter tvĂ„ observationer kommer att vara 51 % – 21 % att de kommer frĂ„n det röda rummet, 5 % av dem att spelaren besöker det blĂ„ rummet mellan dem, och 25 % att spelaren överhuvudtaget inte lĂ€mnar det gröna rummet.

Tabellen Ă€r ett enkelt visuellt verktyg – proceduren krĂ€ver endast att man multiplicerar sannolikheter vid varje steg. Det innebĂ€r att du kan se lĂ„ngt in i framtiden med ett förbehĂ„ll: vi förutsĂ€tter att chansen att komma in i ett rum helt beror pĂ„ det aktuella rummet. Detta kallas Markov-egenskapen (Markov Property) – framtida tillstĂ„nd beror endast pĂ„ nuvarande. Men detta Ă€r inte hundraprocentigt exakt. Spelare kan Ă€ndra beslut baserat pĂ„ andra faktorer: hĂ€lsonivĂ„ eller mĂ€ngd ammunition. Eftersom vi inte faststĂ€ller dessa vĂ€rden kommer vĂ„ra prognoser att vara mindre exakta.

N-Grams

Och vad sÀgs om exemplet med fightingspel och förutsÀgelse av spelarens kombinationer? Detsamma! Men istÀllet för ett tillstÄnd eller en hÀndelse kommer vi att undersöka hela sekvenser som kombinationsslaget bestÄr av.

Ett sÀtt att göra detta Àr att spara varje input (t.ex. Kick, Punch eller Block) i en buffer och registrera hela bufferten som en hÀndelse. SÄ spelaren trycker flera gÄnger pÄ Kick, Kick, Punch för att anvÀnda attacken SuperDeathFist, AI-systemet sparar alla inputs i bufferten och kommer ihÄg de senaste tre som anvÀnds vid varje steg.

Hur man skapar ett spel-AI: en guide för nybörjare
(Fetmarkerade rader indikerar nÀr spelaren startar attacken SuperDeathFist.)

AI:n kommer att se alla alternativ nÀr spelaren valde Kick, följt av en annan Kick, och dÀrefter mÀrk att nÀsta input alltid Àr Punch. Detta gör att agenten kan förutsÀga kombinationen SuperDeathFist och blockera den om möjligt.

Dessa sekvenser av hÀndelser kallas N-gram (N-grams), dÀr N Àr antalet lagrade element. I det tidigare exemplet var det en 3-gram (trigram), vilket betyder att de första tvÄ inspelningarna anvÀnds för att förutsÀga den tredje. PÄ samma sÀtt i en 5-gram förutsÀger de första fyra inspelningarna den femte och sÄ vidare.

Utvecklaren mÄste noggrant vÀlja storleken pÄ N-gram. Ett mindre antal N krÀver mindre minne, men lagrar ocksÄ en kortare historia. Till exempel, en 2-gram (bigram) kommer att registrera Kick, Kick eller Kick, Punch, men kan inte lagra Kick, Kick, Punch, sÄ AI:n kommer inte att reagera pÄ kombinationen SuperDeathFist.

Å andra sidan krĂ€ver större siffror mer minne och det blir svĂ„rare för AI:n att lĂ€ra sig eftersom det kommer att finnas mĂ„nga fler möjliga alternativ. Om du hade tre möjliga inputs Kick, Punch eller Block, och vi anvĂ€nde en 10-gram, skulle det ge cirka 60 000 olika alternativ.

Bigrammodellen Àr en enkel Markovkedja - varje par "tidigare tillstÄnd/nuvarande tillstÄnd" Àr ett bigram, och du kan förutsÀga det andra tillstÄndet baserat pÄ det första. Trigram och större N-gram kan ocksÄ betraktas som Markovkedjor, dÀr alla element (förutom den sista i N-grammet) tillsammans bildar det första tillstÄndet, medan det sista elementet Àr det andra. Ett exempel med fighting visar övergÄngschanser frÄn tillstÄndet Kick och Kick till tillstÄndet Kick och Punch. Genom att betrakta flera poster i ingÄngshistoriken som en enhet omvandlar vi i praktiken ingÄngssekvensen till en del av ett helt tillstÄnd. Detta ger oss Markovegenskapen, vilket gör det möjligt att anvÀnda Markovkedjor för att förutsÀga nÀsta ingÄng och gissa vilken kombo-move som kommer att följas.

Slutsats

Vi har diskuterat de vanligaste verktygen och metoderna inom utveckling av artificiell intelligens. Vi har ocksÄ gÄtt igenom situationer dÀr de ska tillÀmpas och dÀr de Àr sÀrskilt anvÀndbara.

Detta bör vara tillrÀckligt för att förstÄ de grundlÀggande aspekterna av spel-AI. Men sjÀlvklart Àr detta lÄngt ifrÄn alla metoder. Bland de mindre populÀra, men lika effektiva, finns:

  • optimeringsalgoritmer, inklusive hill climbing, gradientnedstigning och genetiska algoritmer
  • konkurrerande sök-/planeringsalgoritmer (minimax och alpha-beta pruning)
  • klassificeringsmetoder (perceptron, neurala nĂ€tverk och stödvektormaskiner)
  • system för att bearbeta perception och minne hos agenter
  • arkitektoniska angreppssĂ€tt för AI (hybrida system, delmĂ€ngd av arkitekturer och andra sĂ€tt att överlagra AI-system)
  • animationsverktyg (planering och samordning av rörelse)
  • prestandafaktorer (detaljnivĂ„, anytime-algoritmer, och tidsdelning)

Internetresurser om Àmnet:

1. PĂ„ GameDev.net finns det ett avsnitt med artiklar och handledningar om AI, samt forum.
2. AiGameDev.com innehÄller mÄnga presentationer och artiklar om ett brett spektrum av relaterade Àmnen inom utveckling av spel-AI.
3. GDC Vault inkluderar Àmnen frÄn GDC AI-summit, mÄnga av vilka Àr tillgÀngliga gratis.
4. AnvÀndbara material kan ocksÄ hittas pÄ webbplatsen AI Game Programmers Guild.
5. Tommy Thompson, AI-forskare och spelutvecklare, gör videor pÄ YouTube-kanalen AI and Games med förklaringar och hypoteser kring AI i kommersiella spel.

Böcker om Àmnet:

1. Serien av böckerna Game AI Pro Àr samlingar av korta artiklar som förklarar hur man implementerar specifika funktioner eller hur man löser specifika problem.

Game AI Pro: Insamlad visdom frÄn spel-AI-professionella
Game AI Pro 2: Insamlad visdom frÄn spel-AI-professionella
Game AI Pro 3: Insamlad visdom frÄn spel-AI-professionella

2. Serien AI Game Programming Wisdom Àr föregÄngaren till serien Game AI Pro. Den innehÄller Àldre metoder, men nÀstan alla Àr fortfarande relevanta idag.

AI Game Programming Wisdom 1
AI Game Programming Wisdom 2
AI Game Programming Wisdom 3
AI Game Programming Wisdom 4

3. Artificial Intelligence: A Modern Approach — det Ă€r en av de grundlĂ€ggande texterna för alla som vill förstĂ„ det allmĂ€nna omrĂ„det för artificiell intelligens. Denna bok handlar inte om spelutveckling — den lĂ€r ut de grundlĂ€ggande principerna för AI.

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster