In de natuurlijke wereld vertonen organismen bepaalde gedragingen wanneer ze in groepen reizen. Dit fenomeen, ook bekend als stroomden, komt voor op zowel microscopische schalen (bacteriën) en macroscopische schalen (vissen). Met behulp van computers kunnen deze patronen worden gesimuleerd door eenvoudige regels te maken en te combineren. Dit staat bekend als opkomend gedrag, en kan worden gebruikt in games om chaotische of levensechte groepsbewegingen te simuleren.
Notitie: Hoewel deze tutorial geschreven is met behulp van Flash en AS3, zou je in bijna elke game-ontwikkelomgeving dezelfde technieken en concepten moeten kunnen gebruiken.
In deze zelfstudie zal ik de drie belangrijkste regels bespreken die worden gebruikt om het massaal te simuleren en uit te leggen hoe deze moeten worden geïmplementeerd. Voordat we beginnen, is hier een terminologie die ik ga gebruiken:
Deze demo toont de effecten van de drie stroomsnormen die ik in deze tutorial zal uitleggen: opstelling, samenhang, en scheiding.
De volledige broncode voor deze demo kan hier worden gedownload, dus dit artikel zal alleen de belangrijkste aspecten van de implementatie benadrukken. Download de bron als je meer wilt weten.
Afstemming is een gedrag dat ervoor zorgt dat een bepaalde agent in de buurt komt van agenten.
Eerst maken we een functie die een agent nodig heeft en een snelheidsvector retourneert.
public function computeAlignment (myAgent: Agent): Point
We hebben twee variabelen nodig: één voor het opslaan van de vector die we willen berekenen en een andere voor het bijhouden van het aantal buren van de agent.
var v: Point = new Point (); var neighborCount = 0;
Met onze geïnitialiseerde variabelen, itereren we nu alle agents en vinden die binnen de buurradius - dat wil zeggen degenen die dichtbij genoeg zijn om als buren van de aangegeven agent te worden beschouwd. Als een agent binnen de straal wordt gevonden, wordt de snelheid aan de berekeningsvector toegevoegd en wordt de buurtelling opgehoogd.
voor elke (var-agent: Agent in agentArray) if (agent! = myAgent) if (myAgent.distanceFrom (agent) < 300) v.x += agent.velocity.x; v.y += agent.velocity.y; neighborCount++;
Als er geen buren zijn gevonden, retourneren we eenvoudig de nulvector (de standaardwaarde van de berekeningsvector).
als (neighborCount == 0) return v;
Als laatste delen we de berekeningsvector door de buurtelling en normaliseren deze (deel deze op lengte om een vector met lengte te krijgen 1
), het verkrijgen van de uiteindelijke resulterende vector.
v.x / = neighborCount; v.y / = neighborCount; v.normalize (1); return v;
Cohesie is een gedrag dat ervoor zorgt dat agenten richting het "zwaartepunt" gaan - dat wil zeggen, de gemiddelde positie van de agenten binnen een bepaalde straal.
De implementatie is bijna identiek aan die van het aligneringsgedrag, maar er zijn enkele belangrijke verschillen. Ten eerste, in plaats van het toevoegen van de snelheid naar de berekeningsvector, de positie is toegevoegd.
v.x + = agent.x; v.y + = agent.y;
Net als hiervoor wordt de berekeningsvector gedeeld door de buurtelling, resulterend in de positie die overeenkomt met het massamiddelpunt. We willen echter niet het zwaartepunt zelf, we willen de richting naar het zwaartepunt, dus herverwerken we de vector als de afstand van het middel tot het massamiddelpunt. Ten slotte wordt deze waarde genormaliseerd en geretourneerd.
v.x / = neighborCount; v.y / = neighborCount; v = nieuw punt (v.x - myAgent.x, v.y - myAgent.y); v.normalize (1); return v;
Scheiding is het gedrag dat een agent van al zijn buren afleidt.
De implementatie van scheiding lijkt erg op die van afstemming en samenhang, dus ik zal alleen wijzen op wat anders is. Wanneer een naburige agent wordt gevonden, wordt de afstand van de agent tot de buur aan de berekeningsvector toegevoegd.
v.x + = agent.x - myAgent.x; v.y + = agent.y - myAgent.y
De berekeningsvector wordt gedeeld door de overeenkomstige buurtelling, maar voordat er wordt genormaliseerd, is er nog een cruciale stap bij betrokken. De berekende vector moet worden genegeerd zodat de agent op de juiste manier van zijn buren weg kan sturen.
v.x * = -1; v.y * = -1;
Zodra deze drie regels zijn geïmplementeerd, moeten ze samenkomen. De eenvoudigste manier om dit te doen is als volgt:
var alignment = computeAlignment (agent); var cohesion = computeCohesion (agent); var separation = computeSeparation (agent); agent.velocity.x + = alignment.x + cohesion.x + separation.x; agent.velocity.y + = alignment.y + cohesion.y + separation.y; agent.velocity.normalize (AGENT_SPEED);
Hier bereken ik gewoon de drie regels voor een bepaalde agent en voeg deze toe aan de snelheid. Vervolgens normaliseer ik de snelheid en vervolgens vermenigvuldig met een constante die de standaardsnelheid voor een agent weergeeft. Het is mogelijk om dit verder te verbeteren door gewichten toe te voegen voor elke regel om het gedrag aan te passen:
agent.velocity.x + = alignment.x * alignmentWeight + cohesion.x * cohesionWeight + separation.x * separationWeight; agent.velocity.y + = alignment.y * alignmentWeight + cohesion.y * cohesionWeight + separation.y * separationWeight;
Door deze gewichten te wijzigen, verandert de manier waarop agenten elkaar ontmoeten. Experimenteer met de cijfers tot je iets vindt dat je leuk vindt.
Hier is de demo opnieuw, zodat je hem kunt uitproberen:
Stroomden is eenvoudig te implementeren, maar het heeft een aantal krachtige resultaten. Als je een spel maakt met AI, vooral grote groepen AI die met elkaar communiceren, kan flocking van pas komen. Gebruik het goed.