Featherfall

Futuregames Game Project

Platforms:

PC

Engine:

Unity

Language:

C#

VCS:

Perforce

PM Tool:

HacknPlan

Date:

2019

Duration of the project:

7 weeks

Type of game:

Third Person, hack and slash

The AI has 3 different states: Idle, Alerted, Searching. In this video you can see how it behaves:
It has 4 differents attacks: Left, Right, Double, Heavy;
Left and right deals the same amount of damage but it's to make some variantions of the basic attack.
Double attack it's a left and right attack consecutively. Basically 2 fast basic attacks.
The Heavy attack it's the one that deals more damage.
I had at my disposal only 2 animations: a swing of the left arm from right to left, and an animation were he attacks with both arms. So I what I did is reverse the basic attack animation, so now it would look he is moving is left arm form left to right instead of right to left. Doing so I created 2 version for the basic attack. Then I combined both animations (the basic attack, and the reverse basic attack) and made the Double attack.
The system for the attacks it works like this:
- You can choose the min and max time for a random cooldown of when the AI attacks.
- You can choose the percentage for the chance of his next attack (if a basic, double or heavy attack). The basic attack will alternate from the left and right animation.
The GameManager holds the GameMode and the AISystem. The GameMode lets you control where the enemies spawn. With a variety of options the GameMode lets you choose where the enemy will spawn(white sphere), the range of where he will patrol(green sphere) and the range the player needs to be to activate the the wave spawn(red sphere). If you are inside the red sphere and the enemy are alerted, enemies will start spawning one after one.
The AISystem it's a setup for the AI where the states are choosen. The enemy file it only contains his data, and there is no function inside, that's because the AISystem handles everything (sight, attacks etc.)
There is also included the AggroSystem(System that lets the enemies run in a circle).
The GameManager also has an ObjectPool, so the enemies will be initialized just at the beginning when you start the game. The enemies, when they die, they will be reused and spawned when needed for the waves (when the enemies spawn from the nest). The active enemies will always be at the beginning of the array and poolPos will save the index where the first inactive enemy will be. So the array will be devided in: left part, containing active enemies and the right part containing inactive enemies. When an enemy dies, it needs to be deactivated, so now the inactive enemy needs to go to the right part of the array and by doing a quick search with IndexOf, it will find the enemy faster because it was active, so it's in the left part of the array (beginning of array). By sorting the array it's more optimized and easier to handle. A dead enemy will be replaced with the last active enemy and poolPos will be decreased.

SpawnFromPool SpawnFromPool


public GameObject SpawnFromPool(string tag, Vector3 position, Quaternion rotation, Vector3? scale = null)

{

	if (!poolDictionary.ContainsKey(tag))

	{

		Debug.LogWarning("Wrong tag");

		return null;

	}



	if (poolPos[tag] == poolDictionary[tag].Count)

		return null;

						

	if (scale == null)

		scale = new Vector3(1, 1, 1);

		

	GameObject objectToSpawn = poolDictionary[tag][poolPos[tag]];

	poolPos[tag]++;

								

	objectToSpawn.transform.position = position;

	objectToSpawn.transform.rotation = rotation;

	objectToSpawn.transform.localScale = (Vector3)scale;

	objectToSpawn.SetActive(true);

								

	return objectToSpawn;

}

						
DespawnFromPool DespawnFromPool


public void DespawnFromPool(string tag, GameObject gameObject_false)

{

    if (!poolDictionary.ContainsKey(tag))

    {

        Debug.LogWarning("Wrong tag");

         return;

    }

    if (!gameObject_false)

         return;



    gameObject_false.SetActive(false);

    int gameObjectPos_false = poolDictionary[tag].IndexOf(gameObject_false);

	

    GameObject savedGameObject = gameObject_false;

    poolDictionary[tag][gameObjectPos_false] = poolDictionary[tag][poolPos[tag] - 1];

    poolDictionary[tag][poolPos[tag] - 1] = savedGameObject;



    poolPos[tag]--;

}

							

The State class it's a ScriptableObject so it can be created in the editor and can be choosen in the AISystem as a State of the AI.

State State


[CreateAssetMenu(menuName = "AI/State")]

public class State : ScriptableObject

{

	[System.Serializable]

	public class Behaviour

	{

	  public Decision decision;

	  Action trueAction;



          [Tooltip("If true it will execute the False Action")] public bool FalseAction = false;

	  public Action falseAction;

	}



	public Behaviour[] behaviour;



	public void UpdateState(AIController controller, NavMeshAgent navMeshAgent)

	{

		Assert.IsNotNull(behaviour, "behavior is NULL");



		for (int i = 0; i < behaviour.Length; i++)

		{

			if (behaviour[i].decision.Decide(controller))

			{

				behaviour[i].trueAction.Act(controller, navMeshAgent);

			}

			else

			{	

				if (behaviour[i].FalseAction)

				{

					behaviour[i].falseAction.Act(controller, navMeshAgent);

				}

			}

		}

 	}

}																					

							

A State can have mulitple behaviors, such as the AlertedState which has two, one for deciding if following the player or if it's false to roam around the player. The second behavior is to decide if it should attack or not.