Stuurgedrag is geweldig voor het creëren van realistische bewegingspatronen, maar ze zijn nog groter als je ze gemakkelijk kunt besturen, gebruiken en combineren. In deze tutorial zal ik bespreken en de implementatie van een bewegingsmanager voor al onze eerder besproken gedragingen bespreken.
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.
Zoals eerder besproken, produceert elk stuurgedrag een resulterende kracht (een "sturende kracht" genoemd) die wordt toegevoegd aan de snelheidsvector. De richting en de grootte van die kracht zullen het personage drijven, waardoor het beweegt volgens een patroon (zoeken, vluchten, zwerven, enzovoort). De algemene berekening is:
besturing = zoeken (); // dit kan elk gedrag zijn sturing = truncate (sturen, max_force) besturing = sturen / massasnelheid = trunceren (snelheid + sturen, max_snelheid) positie = positie + snelheid
Omdat de stuurkracht een vector is, kan deze worden toegevoegd aan elke andere vector (net als de snelheid). De echte "magie" ligt echter in het feit dat je verschillende stuurkrachten bij elkaar kunt voegen - het is zo simpel als:
besturing = niets (); // de nulvector, wat betekent "nulkrachtmagnitude" besturing = sturen + zoeken (); sturen = sturen + vluchten (); (...) besturing = afbreken (sturen, max_force) sturen = sturen / massasnelheid = afbreken (snelheid + sturen, max_snelheid) positie = positie + snelheid
De gecombineerde stuurkrachten resulteren in een vector die representeert allemaal die krachten. In het bovenstaande codefragment zal de resulterende stuurkracht ervoor zorgen dat het personage iets zoekt terwijl hij bezig is tegelijkertijd het zal iets ontvluchten anders.
Bekijk hieronder enkele voorbeelden van stuurkrachten gecombineerd om een enkele stuurkracht te produceren:
De combinatie van stuurkrachten zal uiterst complexe bewegingspatronen moeiteloos produceren. Stel je voor hoe moeilijk het zou zijn om code te schrijven om een personage iets te laten zoeken, maar tegelijkertijd een specifiek gebied te vermijden, zonder vectoren en krachten te gebruiken?
Dat zou de berekening van afstanden, gebieden, paden, grafieken en dergelijke vereisen. Als dingen in beweging zijn, moeten al deze berekeningen zo nu en dan worden herhaald, omdat de omgeving voortdurend verandert.
Met stuurgedrag zijn alle krachten dynamisch. Ze zijn bedoeld om elke game-update te berekenen, zodat ze van nature en naadloos reageren op veranderingen in de omgeving.
De demo hieronder toont schepen die de muiscursor zoeken, maar zal tegelijkertijd het midden van het scherm ontvluchten:
Om verschillende stuurgedragingen op hetzelfde moment op een eenvoudige en eenvoudige manier te gebruiken, bewegingsmanager komt van pas. Het idee is om een 'zwarte doos' te maken die kan worden aangesloten op een bestaande entiteit, waardoor deze in staat is om dat gedrag uit te voeren.
De manager heeft een verwijzing naar de entiteit waar het op is aangesloten (de "host"). De manager zal de host een reeks methoden bieden, zoals zoeken()
en vluchten()
. Telkens wanneer dergelijke methoden worden aangeroepen, werkt de manager zijn interne eigenschappen bij om een stuurkrachtvector te produceren.
Nadat de manager alle aanroepingen verwerkt, wordt de resulterende stuurkracht toegevoegd aan de snelheidsvector van de host. Dat zal de snelheidsvectoromvang en -richting van de gastheer volgens het actieve gedrag veranderen.
De onderstaande figuur demonstreert de architectuur:
De manager heeft een aantal methoden, die elk een ander gedrag vertegenwoordigen. Elk gedrag moet worden voorzien van verschillende stukken externe informatie om te kunnen werken.
Het zoekgedrag heeft bijvoorbeeld een punt in de ruimte nodig dat wordt gebruikt om de stuurkracht naar die plaats te berekenen; nastreven heeft verschillende informatie nodig van zijn doelwit, zoals huidige positie en snelheid. Een punt in de ruimte kan worden uitgedrukt als een instantie van Punt
of Vector2D
, beide vrij gewone klassen in elk kader.
Het doelwit dat wordt gebruikt in het achtervolgingsgedrag kan echter van alles zijn. Om de bewegingsmanager generiek genoeg te maken, moet hij een doelwit ontvangen dat, onafhankelijk van zijn type, enkele 'vragen' kan beantwoorden, zoals 'Wat is je huidige snelheid?"Door een aantal principes van objectgeoriënteerd programmeren te gebruiken, kan dit worden bereikt met interfaces.
Uitgaande van de interface IBoid
beschrijft een entiteit die kan worden afgehandeld door de bewegingsmanager, elke klasse in het spel kan stuurgedrag gebruiken, zolang het maar werkt IBoid
. Die interface heeft de volgende structuur:
openbare interface IBoid function getVelocity (): Vector3D; function getMaxVelocity (): Number; function getPosition (): Vector3D; function getMass (): Number;
Nu de manager op generieke wijze interactie kan hebben met alle game-entiteiten, kan de basisstructuur ervan worden gemaakt. De manager bestaat uit twee eigenschappen (de resulterende stuurkracht en de hostreferentie) en een set openbare methoden, één voor elk gedrag:
public class SteeringManager public var steering: Vector3D; public var host: IBoid; // De constructor public function SteeringManager (host: IBoid) this.host = host; this.steering = new Vector3D (0, 0); // De openbare API (één methode voor elk gedrag) public function seek (target: Vector3D, slowingRadius: Number = 20): void public function flee (target: Vector3D): void public function wander (): void openbare functie ontwijken (doel: IBoid): void openbare functie achtervolging (doel: IBoid): void // De updatemethode. // Moet worden genoemd nadat alle gedragingen zijn aangeroepen update van public function (): void // Reset de interne stuurkracht. publieke functie reset (): void // De interne API private functie doSeek (doel: Vector3D, slowingRadius: Number = 0): Vector3D private functie doFlee (doel: Vector3D): Vector3D private functie doWander (): Vector3D persoonlijke functie doEvade (doel: IBoid): Vector3D private functie doPursuit (doel: IBoid): Vector3D
Wanneer de manager wordt geïnstantieerd, moet deze een verwijzing ontvangen naar de host waarop deze is aangesloten. Het zal de manager in staat stellen om de gastheersnelheidvector volgens het actieve gedrag te veranderen.
Elk gedrag wordt vertegenwoordigd door twee methoden, een openbare en een private. Zoeken als voorbeeld gebruiken:
public function seek (target: Vector3D, slowingRadius: Number = 20): void private function doSeek (target: Vector3D, slowingRadius: Number = 0): Vector3D
Het publiek zoeken()
wordt aangeroepen om de manager te vertellen om dat specifieke gedrag toe te passen. De methode heeft geen retourwaarde en de bijbehorende parameters hebben betrekking op het gedrag zelf, zoals een punt in de ruimte. Onder de motorkap de privé-methode doSeek ()
wordt aangeroepen en de retourwaarde, de berekende stuurkracht voor dat specifieke gedrag, wordt toegevoegd aan de manager stuurinrichting
eigendom.
De volgende code toont de implementatie van seek:
// De publicatiemethode. // Ontvangt een doel om te zoeken en een vertragingsradius (gebruikt om uit te voeren). public function seek (target: Vector3D, slowingRadius: Number = 20): void steering.incrementBy (doSeek (target, slowingRadius)); // De echte implementatie van seek (inclusief aankomstcode) private function doSeek (target: Vector3D, slowingRadius: Number = 0): Vector3D var force: Vector3D; var distance: Number; desired = target.subtract (host.getPosition ()); afstand = gewenste lengte; desired.normalize (); als (afstand <= slowingRadius) desired.scaleBy(host.getMaxVelocity() * distance/slowingRadius); else desired.scaleBy(host.getMaxVelocity()); force = desired.subtract(host.getVelocity()); return force;
Alle andere gedragsmethoden worden op een vergelijkbare manier geïmplementeerd. De achtervolging ()
methode ziet er bijvoorbeeld als volgt uit:
openbare functie achtervolging (doel: IBoid): void steering.incrementBy (doPursuit (target)); private functie doPursuit (doel: IBoid): Vector3D distance = target.getPosition (). aftrekken (host.getPosition ()); var updatesNoodzakelijk: Number = distance.length / host.getMaxVelocity (); var tv: Vector3D = target.getVelocity (). clone (); tv.scaleBy (updatesNeeded); targetFuturePosition = target.getPosition (). clone (). add (tv); return doSeek (targetFuturePosition);
Gebruik makend van de code van eerdere tutorials, alles wat je hoeft te doen is om ze aan te passen in de vorm van gedrag()
en doBehavior ()
, zodat ze kunnen worden toegevoegd aan de bewegingsmanager.
Telkens wanneer de methode van een gedrag wordt aangeroepen, wordt de resulterende kracht die het produceert, toegevoegd aan de manager stuurinrichting
eigendom. Als gevolg hiervan zal eigendom alle stuurkrachten verzamelen.
Wanneer alle gedragingen zijn opgeroepen, moet de manager de huidige stuurkracht op de snelheid van de gastheren toepassen, zodat deze volgens het actieve gedrag wordt verplaatst. Het wordt uitgevoerd in de bijwerken()
methode van de bewegingsmanager:
public function update (): void var velocity: Vector3D = host.getVelocity (); var positie: Vector3D = host.getPosition (); afknotten (sturen, MAX_FORCE); steering.scaleBy (1 / host.getMass ()); velocity.incrementBy (steering); truncate (velocity, host.getMaxVelocity ()); position.incrementBy (velocity);
De bovenstaande methode moet worden aangeroepen door de host (of een andere game-entiteit) nadat alle gedragingen zijn aangeroepen, anders zal de host zijn snelheidsvector nooit wijzigen om overeen te komen met het actieve gedrag.
Laten we uitgaan van een klasse met de naam Prooi
moet bewegen met behulp van stuurgedrag, maar op dit moment heeft het geen stuurcode noch de bewegingsmanager. De structuur ziet er als volgt uit:
public class Prey public var position: Vector3D; public var velocity: Vector3D; openbare var-massa: Number; public function Prey (posX: Number, posY: Number, totalMass: Number) position = new Vector3D (posX, posY); velocity = new Vector3D (-1, -2); massa = totalMass; x = positie.x; y = position.y; public function update (): void velocity.normalize (); velocity.scaleBy (MAX_VELOCITY); velocity.scaleBy (1 / massa); truncate (velocity, MAX_VELOCITY); position = position.add (velocity); x = positie.x; y = position.y;
Met behulp van die structuur kunnen de klasseninstanties worden verplaatst met behulp van Euler-integratie, net als de allereerste demo van de zelfstudie. Om het in staat te stellen de manager te gebruiken, heeft het een eigenschap nodig die verwijst naar de bewegingsmanager en het moet de IBoid
interface:
public class Prey implementeert IBoid public var position: Vector3D; public var velocity: Vector3D; openbare var-massa: Number; openbare var-besturing: SteeringManager; public function Prey (posX: Number, posY: Number, totalMass: Number) position = new Vector3D (posX, posY); velocity = new Vector3D (-1, -2); massa = totalMass; besturing = nieuwe SteeringManager (dit); x = positie.x; y = position.y; public function update (): void velocity.normalize (); velocity.scaleBy (MAX_VELOCITY); velocity.scaleBy (1 / massa); truncate (velocity, MAX_VELOCITY); position = position.add (velocity); x = positie.x; y = position.y; // Hieronder staan de methoden die de IBoid-interface vereist. openbare functie getVelocity (): Vector3D return velocity; openbare functie getMaxVelocity (): Number return 3; openbare functie getPosition (): Vector3D returnpositie; openbare functie getMass (): Number return mass;
De bijwerken()
methode moet dienovereenkomstig worden gewijzigd zodat de manager ook kan worden bijgewerkt:
public function update (): void // Laat de prooi ronddwalen ... steering.wander (); // Update de manager zodat deze de prooivo snelheidvector zal veranderen. // De manager voert ook de Euler-integratie uit, waarbij de // de "positie" -vector wordt gewijzigd. steering.update (); // Nadat de manager zijn interne structuren heeft bijgewerkt, hoeven we alleen maar te doen onze positie bij te werken volgens de "positie" -vector. x = positie.x; y = position.y;
Alle gedragingen kunnen tegelijkertijd worden gebruikt, zolang alle methodeaanroepen vóór de manager worden gedaan bijwerken()
aanroep, die de verzamelde stuurkracht toepast op de snelheidsvector van de host.
De onderstaande code toont een andere versie van de Prey's bijwerken()
methode, maar deze keer zoekt het een positie op de kaart en ontwijkt een ander personage (beide op hetzelfde moment):
public function update (): void var destination: Vector3D = getDestination (); // de plaats om var jager te zoeken: IBoid = getHunter (); // haal de entiteit die op ons jaagt // Zoek de bestemming en ontwijk de jager (tegelijkertijd!) steering.seek (bestemming); steering.evade (jager); // Update de manager zodat deze de prooivo snelheidvector zal veranderen. // De manager voert ook de Euler-integratie uit, waarbij de // de "positie" -vector wordt gewijzigd. steering.update (); // Nadat de manager zijn interne structuren heeft bijgewerkt, hoeven we alleen maar te doen onze positie bij te werken volgens de "positie" -vector. x = positie.x; y = position.y;
De onderstaande demo toont een complex bewegingspatroon waarbij verschillende gedragingen worden gecombineerd. Er zijn twee soorten tekens in de scène: de Jager en de Prooi.
De jager zal na te streven een prooi als het dichtbij genoeg komt; het zal blijven zo lang als de uithoudingsvermogenlevering duurt; wanneer het uithoudingsvermogen opraakt, wordt de achtervolging onderbroken en de jager zal dwalen totdat het zijn uithoudingsvermogen niveaus herstelt.
Dit is de Hunter's bijwerken()
methode:
public function update (): void if (resting && stamina ++> = MAX_STAMINA) resting = false; if (prooi! = null &&! rust) steering.pursuit (prooi); uithoudingsvermogen - = 2; als (uithoudingsvermogen <= 0) prey = null; resting = true; else steering.wander(); prey = getClosestPrey(position); steering.update(); x = position.x; y = position.y;
De prooi zal dwalen voor onbepaalde tijd. Als de jager te dichtbij komt, zal het gebeuren ontwijken. Als de muiscursor in de buurt is en er geen jager rond is, zal de prooi dat doen zoeken de muiscursor.
Dit zijn de prooien bijwerken()
methode:
public function update (): void var distance: Number = Vector3D.distance (position, Game.mouse); jager = getHunterWithinRange (positie); if (jager! = null) steering.evade (jager); if (afstand <= 300 && hunter == null) steering.seek(Game.mouse, 30); else if(hunter == null) steering.wander(); steering.update(); x = position.x; y = position.y;
Het eindresultaat (grijs is zwerven, groen is zoeken, oranje is achtervolgen, rood is ontweken):
Een bewegingsmanager is erg handig voor het tegelijkertijd besturen van meerdere stuurgedragingen. De combinatie van dergelijk gedrag kan zeer complexe bewegingspatronen produceren, waardoor een game-entiteit één ding kan zoeken op hetzelfde moment dat het een ander ontwijkt.
Ik hoop dat je het beheersysteem dat besproken en geïmplementeerd is in deze tutorial leuk vond en in je games gebruikte. Bedankt voor het lezen! Vergeet niet om op de hoogte te blijven door ons te volgen op Twitter, Facebook of Google+.