Advertisement

Boids, a way not to check flock every update?

Started by December 15, 2017 07:57 PM
14 comments, last by Scouting Ninja 6 years, 10 months ago
2 hours ago, PSvils said:

but set it to TempInt + 1, and then process 2 boids at the same time:

I considered loop unrolling at the start but the problem is that zombie packs can be any size from 4 to 84. I don't know if there is a way to do loop unrolling with code during runtime; or if it would be faster than just using a for/while loop.

Currently my code isn't doing badly. I have a pack leader that decides overall movement and only updates it'self and pack members. Then when near a other zombie pack they will share the list of zombies to avoid each other.

Packs < 51% zombies of a large pack merges with the new pack and solo zombies always merge with a pack. All zombies respond to the NavMesh for now.

Here is a quick sketch of how it works.

BoiadsProgress.jpg.2af1716db8c9dd9bccdd477def89c3eb.jpg

 

I am making good progress on the game and I am thinking of making a gamedev blog for it, but I want to work on it a bit more before revealing to the public.

21 minutes ago, Scouting Ninja said:

I considered loop unrolling at the start but the problem is that zombie packs can be any size from 4 to 84. I don't know if there is a way to do loop unrolling with code during runtime; or if it would be faster than just using a for/while loop.

Not sure what you mean by loop unrolling. You're currently unnecessarily processing boid interactions twice, when you could visit each pair only once:


void Seperation (float MaxDistance){
	int TempInt = 0;
	//HiveMind is the parent of all the zombies
	while (TempInt < HiveMind.transform.childCount) {//So this will run once each child
		int SubCount = TempInt + 1;
		while (SubCount < HiveMind.transform.childCount){//The problem is now I need to run every zombie again to check there distance
			float Distance = GetDistanceOf(HiveMind.transform.getChild(TempInt),HiveMind.transform.getChild(SubCount));

			if (Distance < MaxDistance){
				AvoidFlockMember(HiveMind.transform.getChild(SubCount));//This moves away from the flock member.
				AvoidFlockMember(HiveMind.transform.getChild(TempInt));//This moves away from the flock member.
			}
			SubCount +=1;
		}
		TempInt +=1;
	}
}

But maybe I'm assuming incorrectly about how your code works outside of this function (for example how the AvoidFlockMember function works)

Advertisement
29 minutes ago, PSvils said:

float Distance = GetDistanceOf(HiveMind.transform.getChild(TempInt),HiveMind.transform.getChild(SubCount));

This only checks the member after the other and only in the array or list.

The boids needs to compare it self with all the other boids near it, not just one member.

31 minutes ago, PSvils said:

if (Distance < MaxDistance){ AvoidFlockMember(HiveMind.transform.getChild(SubCount));//This moves away from the flock member. AvoidFlockMember(HiveMind.transform.getChild(TempInt));//This moves away from the flock member. }

This looks promising because you move two at once, but how would you prevent the original loop affecting the ones that already calculated there avoid.

If I used something like this I could half my loops.

 

 

For those who wants to know how it all works:

Spoiler

 

Here is a example of my code back when I was still prototyping it. Here I was using rule sets and combining them to find the effect I liked.



	//Avoid others
	Vector3 RuleTwo(GameObject[] InZombieHord, GameObject InZombieBoid){
		Vector3 Avoid = new Vector3 (0f, 0f, 0f);
		foreach (GameObject Zombie in InZombieHord) {
			Vector3 Diffirence = Zombie.transform.position -InZombieBoid.transform.position;
			if (Diffirence.magnitude != 0 && Diffirence.magnitude < ViewDistance) {
				Avoid -= Diffirence;
				Avoid = Avoid * Time.deltaTime;
			}
		}
		return Avoid;
	}

So this made the boid check all other boids and then if they where in view distance it would move away from them.  This is how it was used:



	void UpdateAllBoids(GameObject[] InZombieHord){

		foreach (GameObject Zombie in InZombieHord) {
			Vector3 NewPosition = new Vector3 (0f, 0f, 0f);
			NewPosition += RuleOne (InZombieHord, Zombie); //This has a loop inside
			NewPosition += RuleTwo (InZombieHord, Zombie); //So does this
			NewPosition += ThreeTwo (InZombieHord, Zombie); //etc.
			NewPosition += FourTwo (InZombieHord, Zombie);
          
			Zombie.transform.position += NewPosition;

			WrapAround (Zombie);
		}
	}

 

My new code still follows this but the rules have all been worked into one:

[I decided to remove the main rule it is to bloated for the forum.]



foreach (GameObject Zombie in InPack) {
  TheFourZombieRules(Zombie); //This has a single loop inside that does all 4 rules I want, it's also a void now.
  }

The whole point of this loop is so that the new rules is done once for every zombie to compare it'self to the others.

A rule like this is also done by the pack leader but it moves to a goal instead of the perceived average.

MoreInDepth.jpg.8157a3533563a9c903a9a54da4b5f3e6.jpg

This sketch shows a bit in more depth how it works.

First pass all pack leaders are compared with all pack leaders, this is done using Broad Phase, so most of the time even if there is a 1000 pack leaders I get around 96 loops per update cycle (10-15 min) for the pack leaders.

In the second pass the zombie pack is calculated and if the zombie pack leaders are in the same zone they share there lists of zombies.

If the total zombies in a pack or combined pack is more than 24 a relative Broad Phase is used instead of just brute force loops. Leaders are also added to the Broad Phase loops because with so many zombies in such a small place they often try to overlap.

When off screen only the Pack leaders update and only there positions.

 

My math is also as optimal as I can get it, for example instead of calculating the perceived avrrage, the leader is the average position. So PerceivedAvrrage = Leader.transform.position - ( self.transform.position / PackList.length);

 

I am still looking for ways to improve this because it still consumes a lot of resources just to move the packs around.

I did reach my goal of a millions zombies so it is in working condition.

I'm simply talking about optimizing array comparison between elements, outside of any context. Think about it, if you have an array with length N, you only need to do !N comparisons, instead of N^N.

[1, 2, 3]

You have 2 loops, the first goes through each element, and an inner loop, which will compare this element with the other ones. If you start the inner loop at index 0 every time (as you are currently doing), you are comparing every single element twice instead of once. By visiting the first element in the outer loop, you will have compared that element to all other elements, which means when visiting the second element, the inner loop only needs to start with the third element, because comparing elements 1 and 2 already happened when visiting the first element.

I just thought I should explain this because either you're not understanding it, or I don't understand the current code / logic you have happening. The example function I wrote WILL visit and compare all elements, except only once instead of many times. :)

On 1/5/2018 at 12:47 AM, PSvils said:

I just thought I should explain this because either you're not understanding it, or I don't understand the current code / logic

My thanks, I remember seeing this in one of the Broad Phase explanations but didn't understand it so I skipped it. After you explained it I tried it and it helps a ton.

I just got home and tried it. Really thanks for this, now I have a neat little trick to help boost performance.

This topic is closed to new replies.

Advertisement