Thesis Game – Week_6_Work: Optimize the crows and fix the bug

This week I have done following things:

  1. Optimized the crows by disabling the audio script when the crow is generated. Only under the condition that the crow is close enough to the player and then the audio script will be enabled.
  2. Added the attack interval time to the crows’ attacking behavior. In the previous version, the crows would continuously attack the player. And now I added the interval  time to the attack function. Now the crows act like more real.
  3. Attached the send damage function to the crow manager script and now when crows attack the player, the player will lose the health point.
  4. Optimized the crows by changing the landAll() function. In previous project, the program would call landAll() function every frame once the player is out of the attacking range of the crows. Now I make it only call that function once.
  5. Fix the bug of the LandingManager Script. I made the default value of the crows flying variable as true.

Here is the short video of this week’s work:

Thesis Game – Week_5_Work: Landing Crows and Test Part2 Level

This week I created a new behavior of the crows, which is the landing crow. This behavior is handled by the LandingController Script. This script will grab a random crow and then make it land on the spot if the landing behavior is triggered. What I did to improve this script is adding a “LandingManager” script, which would automatically generate several landing spots at a certain amount. What we should pay attention to is now the game level is a terrain rather than a flat plane. So I need to make use of terrain

beginPosition.y = Terrain.activeTerrain.SampleHeight( beginPosition );

to get a random spot that is actually on the spot, not above nor under. This is what it looks like when you run it:

5.1

And now the crows will randomly land on these spot. When the player is closer enough to the crows, they will fly away. But if the player goes away, they will come back and land again.

Also I created the part 2 of our test level of the game, here is the design of that level:

5.2

And here is a brief video of this game level:

Thesis Game – Week_4_Work: Crows VS Player

This week I added the collision between the crows and player. Therefore, one more status of crow is also added to the crow controller, which is the dead status. After the crow is hit by the player, its status of death will be turn into true, then the crow’s movement behavior is disabled.

  1. function Dead(){
  2.             _deadTimer += Time.deltaTime;
  3.             if( gameObject.GetComponent(Rigidbody) == null ){
  4.                          var crowRigidBody:Rigidbody;
  5.                          crowRigidBody = gameObject.AddComponent (Rigidbody);
  6.                          crowRigidBody.mass = 5;
  7.              }
  8.             if( _deadTimer > 3.0f ){
  9.                          var animator:Animation;
  10.                         animator = gameObject.GetComponentInChildren(Animation);
  11.                         animator.enabled = false;
  12.             }
  13. }

From this video, you can the crows are attacking the player and the player is fighting back by using a mace ( just for a test, I know a mace for our game is weird ). If the crow gets hit, it will fall down on the ground but still flapping it wings. Then after a while, it totally dies and stops any movement. At last, the program will destroy this crow’s gameobject.

Mannequin AI – Just like Wolf

2.10-7

Based on the last two weeks’ work, this week I will improve the AI of Mannequin again, adding more animals behaviors into the game. And thanks to the resource that Travis provides to me, now I make Mannequin AI smarter, turning them into a creepy creature. From the article “Grey Wolf Optimizer”, it introduces an algorithm called “Grey Wolf Optimizer” (GWO), inspired by grey wolves. Before we start, I will briefly talk about GWO first.

1. Grey Wolf Optimizer( GWO )

Grey wolf (Canis lupus) belongs to Canidae family. Grey wolves
are considered as apex predators, meaning that they are at the top
of the food chain. Grey wolves mostly prefer to live in a pack. The
group size is 5–12 on average.

The main phases of grey wolf hunting are as
follows:

  • Tracking, chasing, and approaching the prey.
  • Pursuing, encircling, and harassing the prey until it stops
  • moving.
  • Attack towards the prey.

2.10-4

Pic-1 Hunting behavior of grey wolves: (A) chasing, approaching, and tracking prey (B–D) pursuiting, harassing, and encircling (E) stationary situation and attack

And the difference between the AI in our game Mannequin and GWO is that in our game, Mannequin knows where the player is, meaning that there is no need to search. Also, according to GWO, when we want to achieve Encircling prey, it should be done in this way:

2.10-5Pic-2 Encircling prey

Unluckily, I tried to add this function into our game, but it does not work well. Here is the code:

  1. Vector3 WolfPrey_Encircle( ){
  2.                          Vector3 C = new Vector3( 2.0f * Random.Range(0.0f,1.0f),
  3.                                                                         0.0f,
  4.                                                                         2.0f * Random.Range(0.0f,1.0f) );
  5.                          Vector3 C_Prey = new Vector3( C.x * lastKnownLocation.x ,
  6.                                                                                    C.y * lastKnownLocation.y ,
  7.                                                                                    C.z * lastKnownLocation.z );
  8.                          Vector3 D = ( C_Prey – transform.position );
  9.                           Wolf_Timer -= 0.01f;
  10.                           if( Wolf_Timer < 0.0f ) Wolf_Timer = 2.0f;
  11.                           Vector3 A = new Vector3( 2.0f * Random.Range(0.0f,1.0f) *
  12.                                                                          Wolf_Timer – Wolf_Timer,
  13.                                                                          0.0f,
  14.                                                                          2.0f * Random.Range(0.0f,1.0f) *
  15.                                                                          Wolf_Timer – Wolf_Timer );
  16.                          Vector3 A_Prey = new Vector3( A.x * D.x ,
  17.                                                                                    A.y * D.y ,
  18.                                                                                    A.z * D.z );
  19.                          Vector3 next_position = lastKnownLocation – A_Prey;
  20.                          return next_position;
  21. }

I think the reason why it could not work is that GWO equations is describing a discontinuous process, but for our game, the game is updating every frame. As several factors are just a random number, it cannot shows a continuous behaviors. Of course, this is my own understanding.

But I still learn another thing from GWO and it has been successfully added into our game.

2.  Alpha is calling Beta

Another thing I learned from GWO is that Wolves have a very strict social dominant hierarchy as show in Pic-3.

2.10-6Pic-3 . Hierarchy of grey wolf (dominance decreases from top down).

In a word, different wolves will behave differently according to their social identity. Some may act as leaders, other might behave like fighters.

In Mannequin, up to now we only have one type of Mannequin. Mannequin only chases player from behind. I call this type as Chaser.

And now I will add another type of Mannequin, which is Interceptors. For interceptor, it predict the future position of the player and then run to it.

Here is the code:

  1. Vector3 PrediectMovement( Vector3 i_position ){
  2.                          Vector3 predictLocation2 = i_position;
  3.                          if( Hunt_Type > 0.5f ){
  4.                                                  predictLocation2 = i_position + 120.0f *
  5.                                                  oculusObject.transform.forward;
  6.                          }
  7.                          else{};
  8.                          return predictLocation2;
  9. }

Here is the result of the new code:

2.10-1

Pic-4 Before the game starts

2.10-2

Pic-5 The Mannequins begin to chase player

2.10-3Pic-6 Chasers ( in blue ) are chasing behind the player, while Interceptors ( in red ) are run to the position where the player will go.

In general, I set the type of Chaser as 1, and type of Interceptor as 2.

Mannequin AI —— Flocking Behaviors ( Cohesion, and Separation )

To make the AI of Mannequin smarter, this week I add the cohesion and separation behaviors into the game project. Before I start, I will explain what is cohesion and separation.

Cohesion

2.08-1 2.08-2

 

Pic-1 & Pic-2 The left is cohesion weight is 0.1, and the right is 1.0.

Cohesion is a behavior that causes agents to steer towards the “center of mass” – that is, the average position of the agents within a certain radius.

The implementation is almost identical to that of the alignment behavior, but there are some key differences. First, instead of adding the velocity to the computation vector, the position is added instead.

My code is like this:

  1. Vector3 computeCohesion( Vector3 i_velocity )
  2. {
  3.            int neighborCount = 0;
  4.            for( int i = 0; i < numMannequin; i ++ )
  5.            {
  6.                        if( MannequinArray[i] == null ) break;
  7.                        if( transform != MannequinArray[i].transform )
  8.                        {
  9.                                      if( DistanceFrom( MannequinArray[ i ].transform.position )
  10.                                           < 200 )
  11.                                      {
  12.                                                  i_velocity.x += MannequinArray[ i].transform.position.x ;
  13.                                                  i_velocity.z += MannequinArray[ i ].transform.position.z ;
  14.                                                  neighborCount++;
  15.                                       }
  16.                        }
  17.            }
  18.            if( neighborCount == 0 )  return i_velocity;
  19.            else
  20.            {
  21.                           i_velocity.x /= neighborCount;
  22.                           i_velocity.z /= neighborCount;
  23.                           i_velocity = new Vector3( i_velocity.x – transform.position.x,0.0f,
  24.                                                                          i_velocity.y – transform.position.y );
  25.                           i_velocity.Normalize();
  26.                           return i_velocity;
  27.             }
  28. }

I implement  this function into the Chase script, and each time the program gets the lastKnowLocation of the player, it will use this function to create a new location which could avoid the Mannequin gather together.

Here is how it looks like in the game.

Before the game starts, the position of all Mannequins are like this:

2.08-3Pic-3 Before game starts

2.08-4Pic-4  After game starts (weight of cohesion is 0.1)

2.08-5

Pic-5 After game starts ( weight of cohesion is 3.0)

If we want the Mannequins steer towards the center of mass, we just decrease the weight of cohesion value.

Separation

2.08-6 2.08-7

 

Pic-6 & Pic-7  The left is separation weight is 0.1, and the right is 1.0.

Separation is the behavior that causes an agent to steer away from all of its neighbors.

The implementation of separation is very similar to that of alignment and cohesion, so I’ll only point out what is different. When a neighboring agent is found, the distance from the agent to the neighbor is added to the computation vector.

Here is my function which achieves the separation:

  1. Vector3 computeSeparation( Vector3 i_velocity )
  2. {
  3.            int neighborCount = 0;
  4.            for( int i = 0; i < numMannequin; i ++ )
  5.            {
  6.                        if( MannequinArray[i] == null ) break;
  7.                        if( transform != MannequinArray[i].transform )
  8.                        {
  9.                                      if( DistanceFrom( MannequinArray[ i ].transform.position )
  10.                                           < 200 )
  11.                                      {
  12.                                                 i_velocity.x += MannequinArray[ i ].transform.position.x – transform.position.x;
  13.                                                 i_velocity.z += MannequinArray[ i ].transform.position.z – transform.position.z;
  14.                                                  neighborCount++;
  15.                                       }
  16.                        }
  17.            }
  18.            if( neighborCount == 0 )  return i_velocity;
  19.            else
  20.            {
  21.                           i_velocity.x /= neighborCount;
  22.                           i_velocity.z /= neighborCount;
  23.                          i_velocity = -1 * i_velocity;
  24.                           i_velocity.Normalize();
  25.                           return i_velocity;
  26.             }
  27. }

Here is how it looks like in the game.

Before the game starts, the position of all Mannequins are like this:

2.08-3

Pic-8 Before game starts

2.08-8

Pic-9 After game starts (weight of separation is 0.1)

2.08-9Pic-10 After game starts (weight of separation is 3.0)

If we want to keep the Mannequins gather together, we should increase the value of separation’s weight.

After finishing Alignment, Cohesion and Separation, I use one function to combine them together.

  1. Vector3 EnmergenceMovement( Vector3 i_velocity )
  2. {
  3.              Vector3 alignment = computeAlignment( i_velocity );
  4.              Vector3 cohesion = computeCohesion( i_velocity );
  5.              Vector3 separation = computeSeparation( i_velocity );
  6.              float temp = separation_weight;
  7.              if( GetDistance() < 90 )  temp = 0.1f;
  8.              else  temp = 1.2f;
  9.                       i_velocity.x += alignment_weight * alignment.x + cohesion_weight * cohesion.x + temp * separation.x;
  10.                       i_velocity.z += alignment_weight * alignment.z + cohesion_weight * cohesion.z + temp * separation.z;
  11.                       i_velocity.Normalize();
  12.                       return i_velocity;
  13. }

Now we could change the Flock Behavior of Mannequins by adjusting three public values.

2.08-10Pic-11 Three public weight values

Mannequin AI —— Flocking Behaviors (Alignment )

OK, now I will work for our team’s Mannequin game project and currently I am responsible for achieving a smarter AI of Mannequin based on original version which Mark has achieved.

The first thing I will do is to keep Mannequins movement behaviors more like animals, meaning they are like flock, not robots that gather together. In order to achieve this, I will introduce a flock behavior into our project, called Flock Behaviors. And this week, I have achieved Alignment. 

In the natural world, organisms exhibit certain behaviors when traveling in groups. This phenomenon, also known as flocking, occurs at both microscopic scales (bacteria) and macroscopic scales (fish). Using computers, these patterns can be simulated by creating simple rules and combining them. This is known as emergent behavior, and can be used in games to simulate chaotic or life-like group movement.

Alignment

2.09-1 2.09-2

 

Pic-1 & Pic-2 The left is alignment weight is 0.1, and the right is 1.0.

Alignment is a behavior that causes a particular agent to line up with agents close by.

Here is the function I implement into Chase script:

  1. Vector3 computeCohesion( Vector3 i_velocity )
  2. {
  3.            int neighborCount = 0;
  4.            for( int i = 0; i < numMannequin; i ++ )
  5.            {
  6.                        if( MannequinArray[i] == null ) break;
  7.                        if( transform != MannequinArray[i].transform )
  8.                        {
  9.                                      if( DistanceFrom( MannequinArray[ i ].transform.position )
  10.                                           < 200 )
  11.                                      {
  12.                                               Vector3 temp = roomManager.NavigateTo(
  13.                                    MannequinArray[ i ].transform.position, lastKnownLocation);
  14.                                                  i_velocity.x += temp.x;
  15.                                                  i_velocity.z += temp.z;
  16.                                                  neighborCount++;
  17.                                       }
  18.                        }
  19.            }
  20.            if( neighborCount == 0 )  return i_velocity;
  21.            else
  22.            {
  23.                           i_velocity.x /= neighborCount;
  24.                           i_velocity.z /= neighborCount;
  25.                           i_velocity.Normalize();
  26.                           return i_velocity;
  27.             }
  28. }

I implement  this function into the Chase script, and each time the program gets the lastKnowLocation of the player, it will use this function to create a new location which could avoid the Mannequin gather together.

Here is how it looks like in the game.

Before the game starts, the position of all Mannequins are like this:

2.08-3Pic-3 Before game starts

2.09-3

Pic-4 After game starts (weight of alignment is 3.0) Mannequins’ direction is almost the same.

2.09-4Pic-5 After game starts (weight of alignment is 0.1) Mannequins’ direction is almost the different.

If I decrease the weight of the alignment value, the Mannequins’ movement directions will be more random, which creates a more real behaviors, leading to a better player’s experience.