Stel je een gamescène voor waar een kamer vol is met AI-gecontroleerde entiteiten. Om de een of andere reden moeten ze de kamer verlaten en door een deuropening gaan. In plaats van ze in een chaotische stroom over elkaar heen laten lopen, leer ze hoe ze beleefd kunnen vertrekken terwijl ze in de rij staan. Deze tutorial presenteert de wachtrij stuurgedrag met verschillende benaderingen om een menigte te laten bewegen terwijl rijen entiteiten worden gevormd.
Notitie: Hoewel deze tutorial geschreven is met behulp van AS3 en Flash, zou je in bijna elke game-ontwikkelomgeving dezelfde technieken en concepten moeten kunnen gebruiken. Je moet een basiskennis hebben van wiskundige vectoren.
Queuing, in de context van deze tutorial is het proces van in de rij staan, het vormen van een rij personages die geduldig wachten om ergens aan te komen. Terwijl de eerste in de rij beweegt, volgen de rest, waardoor een patroon ontstaat dat eruit ziet als een trein die wagens trekt. Tijdens het wachten moet een personage de lijn nooit verlaten.
Om het gedrag van de wachtrij te illustreren en de verschillende implementaties te laten zien, is een demo met een "wachtrijscène" de beste manier om te gaan. Een goed voorbeeld is een kamer vol met AI-gecontroleerde entiteiten, die allemaal proberen de kamer te verlaten en door de deuropening te gaan:
Deze scène is gemaakt met behulp van twee eerder beschreven gedragingen: zoeken en vermijden van botsingen.
De deuropening bestaat uit twee rechthoekige obstakels naast elkaar met een opening ertussen (de deuropening). De personages zoeken een punt daarachter. Als dat het geval is, worden de tekens opnieuw onder aan het scherm geplaatst.
Op dit moment lijkt de scène, zonder het rijgedrag, op een horde van wilden die op elkaars hoofden trappen om de bestemming te bereiken. Als we klaar zijn, zal de menigte soepel de zaal verlaten en rijen vormen.
De eerste eigenschap die een personage moet hebben om in de rij te staan, is om erachter te komen of er iemand voor hen staat. Op basis van die informatie kan het beslissen of het door zal gaan of moet stoppen met bewegen.
Ondanks het bestaan van geavanceerdere manieren om buren te controleren, gebruik ik een vereenvoudigde methode op basis van de afstand tussen een punt en een personage. Deze aanpak werd gebruikt bij het vermijden van botsingen om te controleren op obstakels die voor de boeg lagen:
Een punt genaamd verder
wordt geprojecteerd voor het personage. Als de afstand tussen dat punt en een buurpersonage kleiner is dan of gelijk aan MAX_QUEUE_RADIUS
, het betekent dat er iemand vooruit is en dat het personage moet stoppen met bewegen.
De verder
punt wordt als volgt berekend (pseudo-code):
// Zowel qa als vooruit zijn wiskundige vectoren qa = normaliseren (snelheid) * MAX_QUEUE_AHEAD; ahead = qa + positie;
De snelheid, die ook de richting van het personage aangeeft, wordt genormaliseerd en geschaald MAX_QUEUE_AHEAD
om een nieuwe vector te produceren met de naam qa
. Wanneer qa
is toegevoegd aan de positie
vector, het resultaat is een punt voor het personage en een afstand van MAX_QUEUE_AHEAD
eenheden er vanaf.
Dit alles kan worden ingepakt in de getNeighborAhead ()
methode:
private function getNeighborAhead (): Boid var i: int; var ret: Boid = null; var qa: Vector3D = velocity.clone (); qa.normalize (); qa.scaleBy (MAX_QUEUE_AHEAD); ahead = position.clone (). add (qa); voor (i = 0; i < Game.instance.boids.length; i++) var neighbor :Boid = Game.instance.boids[i]; var d :Number = distance(ahead, neighbor.position); if (neighbour != this && d <= MAX_QUEUE_RADIUS) ret = neighbor; break; return ret;
De methode controleert de afstand tussen de verder
punt en alle andere tekens, waarbij het eerste teken wordt teruggezonden waarvan de afstand kleiner of gelijk is aan MAX_QUEUE_AHEAD
. Als er geen teken wordt gevonden, wordt de methode geretourneerd nul
.
Zoals met alle andere gedragingen, is de wachtrij wordt berekend door een methode genaamd wachtrij()
:
private function queue (): Vector3D var neighbour: Boid = getNeighborAhead (); if (neighbor! = null) // TODO: actie ondernemen omdat buurman voor staat retourneer nieuwe Vector3D (0, 0);
Het resultaat van getNeighborAhead ()
in opgeslagen in de variabele buurman
. Als neighbor! = null
het betekent dat er iemand vooruit is; anders is het pad vrij.
De wachtrij()
, zoals alle andere gedragsmethoden, moet een kracht terugkeren die de stuurkracht is die gerelateerd is aan de methode zelf. wachtrij()
zal voorlopig een kracht zonder kracht teruggeven, dus het zal geen effecten produceren.
De bijwerken()
methode van alle karakters in de deuropeningscène, tot nu toe, is (pseudo-code):
public function update (): void var doorway: Vector3D = getDoorwayPosition (); besturing = zoeken (deuropening); // zoek de deurbesturing = sturen + botsingAvoidance (); // vermijd obstakels sturen = sturen + wachtrij (); // wachtrij onderweg sturen = inkorten (sturen, MAX_FORCE); sturen = sturen / massa; velocity = truncate (velocity + steering, MAX_SPEED); positie = positie + snelheid;
Sinds wachtrij()
geeft een nulkracht terug, de karakters zullen blijven bewegen zonder rijen te vormen. Het is tijd om ze wat actie te laten ondernemen wanneer een buurman wordt gedetecteerd.
Stuurgedrag is gebaseerd op krachten die voortdurend veranderen, dus het hele systeem wordt zeer dynamisch. Afhankelijk van de implementatie, hoe meer krachten er bij betrokken zijn, hoe moeilijker het wordt om een specifieke krachtvector te lokaliseren en te annuleren.
De implementatie die in deze serie stuurgedrag wordt gebruikt, voegt alle krachten samen. Als gevolg hiervan moet een kracht opnieuw worden berekend, omgekeerd en opnieuw aan de huidige stuurkrachtvector worden toegevoegd.
Dat is ongeveer wat er gebeurt in het aankomstgedrag, waarbij de snelheid wordt geannuleerd om het personage stil te houden. Maar wat gebeurt er wanneer meer krachten samenwerken, zoals aanvaringen met botsingen, vluchten, en meer?
De volgende secties bevatten twee ideeën om een personage te laten stoppen met bewegen. De eerste gebruikt een "harde stop" -aanpak die direct inwerkt op de snelheidsvector en alle andere stuurkrachten negeert. De tweede gebruikt een krachtvector, genaamd rem
, om alle andere stuurkrachten op elegante wijze te annuleren, waardoor het personage uiteindelijk niet meer beweegt.
Verschillende stuurkrachten zijn gebaseerd op de snelheidsvector van het personage. Als die vector verandert, worden alle andere krachten beïnvloed wanneer ze worden herberekend. Het idee van "harde stop" is vrij simpel: als er een personage vooruit is, "verkleinen" we de snelheidsvector:
private function queue (): Vector3D var neighbour: Boid = getNeighborAhead (); if (neighbor! = null) velocity.scaleBy (0.3); retourneer nieuwe Vector3D (0, 0);
In de bovenstaande code, de snelheid
vector is geschaald naar 30%
van zijn huidige grootte (lengte) terwijl een personage voor staat. Als gevolg hiervan neemt de beweging drastisch af, maar deze zal uiteindelijk weer normaal worden als het personage dat de weg blokkeert, beweegt.
Dat is gemakkelijker te begrijpen door te analyseren hoe beweging wordt berekend elke update:
velocity = truncate (velocity + steering, MAX_SPEED); positie = positie + snelheid;
Als het snelheid
de kracht blijft krimpen, zo ook de stuurinrichting
kracht, omdat het gebaseerd is op de snelheid
dwingen. Het creëert een vicieuze cirkel met een extreem lage waarde voor snelheid
. Dat is wanneer het personage stopt met bewegen.
Wanneer het krimpproces eindigt, zal elke update van het spel het snelheid
vector een beetje, van invloed op de stuurinrichting
ook dwingen. Uiteindelijk zullen verschillende updates na beide brengen snelheid
en stuurinrichting
vector terug naar hun normale magnitudes.
De "harde stop" -aanpak levert het volgende resultaat op:
Hoewel dit resultaat behoorlijk overtuigend is, voelt het als een "robotische" uitkomst. Een echte menigte heeft meestal geen lege ruimtes tussen hun leden.
De tweede benadering om beweging te stoppen probeert een minder "robotachtig" resultaat te creëren door alle actieve stuurkrachten te annuleren met behulp van a rem
dwingen:
persoonlijke functie wachtrij (): Vector3D var v: Vector3D = velocity.clone (); var brake: Vector3D = new Vector3D (); var neighbor: Boid = getNeighborAhead (); if (neighbor! = null) brake.x = -steering.x * 0.8; rem.y = -steering.y * 0.8; v.scaleBy (-1); rem = rem.add (v); retourrem;
In plaats van het maken van de rem
kracht door het herberekenen en inverteren van elk van de actieve stuurkrachten, rem
wordt berekend op basis van de stroom stuurinrichting
vector, die alle stuurkrachten bevat die aan het moment zijn toegevoegd:
De rem
force ontvangt beide zijn X
en Y
componenten van de stuurinrichting
kracht, maar omgekeerd en met een schaal van 0.8
. Het betekent dat rem
heeft 80% van de omvang van stuurinrichting
en wijst in de tegenovergestelde richting.
Tip: De ... gebruiken stuurinrichting
direct forceren is gevaarlijk. Als wachtrij()
is het eerste gedrag dat moet worden toegepast op een personage, de stuurinrichting
kracht zal "leeg" zijn. Als gevolg hiervan, wachtrij()
moet worden aangeroepen na alle andere stuurmethoden, zodat het de volledige en definitieve toegang kan krijgen stuurinrichting
dwingen.
De rem
force moet ook de snelheid van het personage annuleren. Dat is gedaan door toe te voegen -snelheid
naar de rem
dwingen. Daarna de methode wachtrij()
kan de finale retourneren rem
dwingen.
Het resultaat van het gebruik van de remkracht is de volgende:
De remaanpak levert een natuurlijker resultaat op vergeleken met de "robotachtige" oude omdat alle personages de lege ruimtes proberen te vullen. Het introduceert echter een nieuw probleem: tekens overlappen elkaar.
Om dat te verhelpen, kan de remaanpak worden verbeterd met een licht aangepaste versie van de "harde stop" -aanpak:
persoonlijke functie wachtrij (): Vector3D var v: Vector3D = velocity.clone (); var brake: Vector3D = new Vector3D (); var neighbor: Boid = getNeighborAhead (); if (neighbor! = null) brake.x = -steering.x * 0.8; rem.y = -steering.y * 0.8; v.scaleBy (-1); rem = rem.add (v); if (afstand (positie, buurpositie) <= MAX_QUEUE_RADIUS) velocity.scaleBy(0.3); return brake;
Een nieuwe test wordt gebruikt om buren in de buurt te controleren. Deze keer in plaats van de verder
om de afstand te meten, controleert de nieuwe test de afstand tussen de tekens positie
vector:
Deze nieuwe test controleert of er tekens in de buurt zijn in de MAX_QUEUE_RADIUS
straal, maar nu is het gecentreerd op de positie
vector. Als iemand zich binnen bereik bevindt, betekent dit dat de omgeving te druk wordt en tekens waarschijnlijk beginnen te overlappen.
De overlapping wordt gemitigeerd door het schalen van de snelheid
vector tot 30% van de huidige magnitude van elke update. Net als bij de "harde stop" -benadering, het verkleinen van de snelheid
vector vermindert drastisch de beweging.
Het resultaat lijkt minder 'robotachtig', maar het is niet ideaal, omdat de personages nog steeds overlappen bij de deuropening:
Hoewel de personages op een overtuigende manier proberen de doorgang te bereiken, vullen ze alle lege ruimtes wanneer het pad smal wordt, komen ze te dicht bij elkaar bij de deuropening.
Dit kan worden opgelost door een scheidingskracht toe te voegen:
persoonlijke functie wachtrij (): Vector3D var v: Vector3D = velocity.clone (); var brake: Vector3D = new Vector3D (); var neighbor: Boid = getNeighborAhead (); if (neighbor! = null) brake.x = -steering.x * 0.8; rem.y = -steering.y * 0.8; v.scaleBy (-1); rem = rem.add (v); rem = rem.add (scheiding ()); if (afstand (positie, buurpositie) <= MAX_QUEUE_RADIUS) velocity.scaleBy(0.3); return brake;
Voorheen gebruikt in de leider na gedrag, de scheidingskracht toegevoegd aan de rem
Kracht zorgt ervoor dat personages stoppen met bewegen op hetzelfde moment dat ze proberen van elkaar weg te blijven.
Het resultaat is een overtuigende menigte die de deur probeert te bereiken:
Door het gedrag van de wachtrij kunnen tekens in de rij gaan staan en geduldig wachten om op de bestemming aan te komen. Eenmaal in de rij probeert een karakter niet te "vals spelen" door posities te springen; het beweegt alleen als het personage er vlak voor komt.
De doorwayscene die in deze tutorial werd gebruikt, gaf aan hoe veelzijdig en aanpasbaar dit gedrag kan zijn. Enkele wijzigingen leveren verschillende resultaten op, die prima kunnen worden aangepast aan een breed scala aan situaties. Het gedrag kan ook worden gecombineerd met andere, zoals botsingsvermijding.
Ik hoop dat je dit nieuwe gedrag leuk vond en het begon te gebruiken om ontroerende mensen aan je spel toe te voegen!