Survival AI


An AI written is C++ that survives in a world full of enemies

About the project

The goal of this project was to make an AI that would survive as long as possible in a world full of enemies and pickups. These pickups spawn randomly in randomly generated houses and can be one of 4 types of objects. There are guns, medkits, food and garbage. The AI uses a behaviour tree to decide what action it is going to undertake next. In general it starts of by exploring the whole world, so it knows where all the houses are. It is aided by a couple of trackers that hold information about the world, like house locations and how long it has been since the last visit and all known other pickups and their respective strength so the bot knows where the strongest objects are in the world. It’s inventory consists of 5 spots, one of these is reserved for a gun and both the medkits and the food have 2 slots reserved for them. The overall tactic to survive is to run away from danger until that is no longer an option and the bot shoot all of the surrounding enemies. The whole AI was written in C++ as a plugin. Because it is a plugin it can’t access all the world data, it relies on an interface provided by the main application to see only the parts of the world the actual bot can observe.

Code snippets

Plugin initialisation

The following code snippet shows the initialization code for the plugin. In this function the data required by the behaviour tree gets added to the blackboard and the actual tree gets built. Some general information about the bot is filled in and all managers get initialized as well.

  1//Called only once, during initialization
  2void Plugin::Initialize(IBaseInterface* pInterface, PluginInfo& info)
  3{
  4//Retrieving the interface
  5//This interface gives you access to certain actions the AI_Framework can perform for you
  6m_pInterface = static_cast<IExamInterface*>(pInterface);
  7
  8//Bit information about the plugin
  9//Please fill this in!!
 10info.BotName = "Run, Escape";
 11info.Student_FirstName = "Mathias";
 12info.Student_LastName = "Dierickx";
 13info.Student_Class = "2DAE1";
 14
 15m_pHouseTracker = new HouseTracker{ 30.0f, 480.0f, m_pInterface};
 16m_pEnemyTracker = new EnemyTracker(7.5f, m_pInterface);
 17m_pPickupTracker = new PickupTracker(m_pInterface);
 18m_pWorldExplorer = new WorldExplorer(m_pInterface, 12.5f);
 19//Init of the behaviorTree
 20auto pBlackBoard = new Blackboard();
 21pBlackBoard->AddData("EnemyTracker", m_pEnemyTracker);
 22pBlackBoard->AddData("PickupTracker", m_pPickupTracker);
 23pBlackBoard->AddData("HouseTracker", m_pHouseTracker);
 24pBlackBoard->AddData("CurrentHouse", m_pCurrentHouse);
 25pBlackBoard->AddData("Interface", m_pInterface);
 26pBlackBoard->AddData("MoveTargetSet", false);
 27pBlackBoard->AddData("AimTargetSet", false);
 28pBlackBoard->AddData("MoveTarget", Elite::Vector2{});
 29pBlackBoard->AddData("AimTarget", Elite::Vector2{});
 30pBlackBoard->AddData("RunState", false);
 31//Add pointer to waypoint vector to waypoints
 32pBlackBoard->AddData("WaypointPointer", &m_Waypoints);
 33pBlackBoard->AddData("NewPickupLocation", EntityInfo());
 34pBlackBoard->AddData("WasInHouse", false);
 35//Add pointer to array of pickups in FOV
 36pBlackBoard->AddData("PickupsInFOV", &m_PickupsInFOV);
 37//Priority target data
 38pBlackBoard->AddData("PriorityTarget", Elite::Vector2{});
 39pBlackBoard->AddData("PriorityTargetSet", false);
 40//Add the worldExplorer to the blackboard
 41pBlackBoard->AddData("WorldExplorer", m_pWorldExplorer);
 42
 43m_pBehaviorTree = new BehaviorTree(pBlackBoard,
 44new BehaviorSelector
 45({
 46new BehaviorAction(SetPriorityTarget),
 47new BehaviorSequence
 48({
 49  new BehaviorConditional(GetNotInHouse),
 50  new BehaviorAction(ResetLeaving)
 51}),
 52new BehaviorSequence
 53({
 54  new BehaviorConditional(GetIsInHouse),
 55  new BehaviorConditional(GetNotWasInHouse),
 56  new BehaviorAction(SetCurrentHouse)
 57}),
 58new BehaviorSequence
 59({
 60new BehaviorConditional(GetIsInHouse),
 61new BehaviorConditional(IsInDanger),
 62new BehaviorSelector
 63  ({
 64  new BehaviorSequence
 65    ({
 66      new BehaviorConditional(HasGun),
 67      new BehaviorAction(SetKill)
 68    }),
 69  new BehaviorSequence
 70    ({
 71      new BehaviorAction(MarkUnsafe),
 72      new BehaviorAction(SetRun)
 73    })
 74  })
 75}),
 76new BehaviorSequence
 77({
 78new BehaviorConditional(GetNotInHouse),
 79new BehaviorConditional(IsInDanger),
 80new BehaviorSelector
 81({
 82  new BehaviorSequence
 83  ({
 84    new BehaviorConditional(HasGun),
 85    new BehaviorAction(SetRun),
 86    new BehaviorAction(SetKill)
 87  }),
 88  new BehaviorAction(SetRun)
 89})
 90}),
 91new BehaviorSequence
 92({
 93new BehaviorConditional(GetIsInHouse),
 94new BehaviorConditional(CheckAndMarkWaypoints),
 95new BehaviorSelector
 96  ({
 97    new BehaviorSequence
 98      ({
 99        new BehaviorConditional(SeesNewPickup),
100        new BehaviorAction(MoveAndMark)
101      }),
102    new BehaviorAction(SetAndMoveToWaypoint)
103  })
104})
105}));
106}

Shooting

Next, we have the shoot function for the AI. This function checks if there is an enemy in front of us, using the size of the enemy a maximum offset is calculated. If the angle between the bot’s forward vector and the vector from the bot to the enemy is smaller than the calculated maximum offset for that enemy, we know for sure that we will hit the enemy and the gun gets triggered. Because the interface limits us to one shot per frame, once the gun has been shot we return out of this function as consecutive shots in the same frame decrease our score and give us an invalid function call. This function is not in charge of rotating towards the enemies, this is handled by the move function shown in the next snippet.

 1void Plugin::Shoot(const std::vector<Elite::Vector2>& enemies)
 2{
 3//For every enemy in field of vision: Calculate the angle between the vector from me to the enemy and the negative Y axis
 4//If the angle is nearly equal to the agent orientation, we can shoot the gun
 5
 6Elite::Vector2 axis{ 0, -1 };
 7auto agent = m_pInterface->Agent_GetInfo();
 8
 9for (size_t i = 0; i < enemies.size(); ++i)
10{
11  Elite::Vector2 lookVector = enemies.at(i) - agent.Position;
12  float distance = lookVector.Magnitude();
13  lookVector.Normalize();
14  float size = agent.AgentSize * 0.8f;
15
16  float angleOffset = atan(size * 0.5f / distance);
17
18  float angle = atan2(axis.x * lookVector.y - axis.y * lookVector.x, axis.x * lookVector.x + axis.y * lookVector.y);
19  Elite::Vector2 orientVec = Elite::OrientationToVector(agent.Orientation);
20  float lookAngle = atan2(axis.x * orientVec.y - axis.y * orientVec.x, axis.x * orientVec.x + axis.y * orientVec.y);
21
22  if(abs(angle - lookAngle) < abs(angleOffset))
23  {
24    m_pPickupTracker->ShootGun();
25    //We can only shoot the gun once
26    return;
27  }
28}

Movement

The Move function is the core of the AI movement, it requests the latest data set by the behaviour tree to determine where it has to move towards. The behaviour tree can set a priority target to move to and a target to rotate towards. The first is used when the bot determines it needs to go get a pickup it needs and knows it is nearby. The rotation target is used to point the bot towards an enemy that has to be shot in order to survive. If none of these targets are set, the bot just auto rotates towards it moving direction and uses the GetHouseToSearch() function to move towards a house that has to be searched. This last part is determined by the house tracker class.

 1void Plugin::Move(SteeringPlugin_Output& steering, const std::vector<Elite::Vector2>& enemies)
 2{
 3	auto pBlackBoard = m_pBehaviorTree->GetBlackboard();
 4	auto agentInfo = m_pInterface->Agent_GetInfo();
 5
 6	pBlackBoard->GetData("RunState", m_CanRun);
 7	bool targetSet;
 8	bool priorTargetSet;
 9	bool aimTargetSet;
10	Elite::Vector2 aimTarget;
11	Elite::Vector2 priorTarget;
12	if (!pBlackBoard->GetData("MoveTargetSet", targetSet)
13		|| !pBlackBoard->GetData("PriorityTargetSet", priorTargetSet)
14		|| !pBlackBoard->GetData("PriorityTarget", priorTarget)
15		|| !pBlackBoard->GetData("AimTarget", aimTarget)
16		|| !pBlackBoard->GetData("AimTargetSet", aimTargetSet))
17		std::cout << "Could not retreive MoveTargetSet from blackboard";
18
19	//Simple seek to the target
20	if (priorTargetSet)
21	{
22		m_Target = priorTarget;
23	}
24	else if (!targetSet)
25	{
26		m_pHouseTracker->GetHouseToSearch(agentInfo.Position, m_Target);
27	}
28	steering.AutoOrientate = true;
29
30
31	auto nextTargetPos = m_pInterface->NavMesh_GetClosestPathPoint(m_Target);
32
33	//If we have an aim target, rotate towards it and call the shoot function
34	if(aimTargetSet)
35	{
36		//calculate what direction we have to rotate in to aim at the target
37		steering.AutoOrientate = false;
38		Elite::Vector2 forwardVec = Elite::OrientationToVector(agentInfo.Orientation);
39		Elite::Vector2 targetVec = aimTarget - agentInfo.Position;
40		float distance = targetVec.Magnitude();
41		targetVec /= distance;
42		float angle = atan2(forwardVec.x * targetVec.y - forwardVec.y * targetVec.x, forwardVec.x * targetVec.x + forwardVec.y * targetVec.y);
43		angle /= abs(angle);
44
45		steering.AngularVelocity = angle * agentInfo.MaxAngularSpeed;
46
47		//Call shoot function, this function will check if any of the enemies in FOV are in range of the gun
48		//If we are looking at one, we shoot and flag the pickup manager an item has been used this frame so it doesn't use food or medkits (avoid the penalty)
49		Shoot(enemies);
50	}
51
52	if( !agentInfo.IsInHouse && Elite::DistanceSqrt(m_Target, agentInfo.Position) < 1600.0f)
53		steering.RunMode = true;
54	else
55		steering.RunMode = m_CanRun;
56
57	steering.LinearVelocity = nextTargetPos - agentInfo.Position; //Desired Velocity
58	steering.LinearVelocity.Normalize(); //Normalize Desired Velocity
59
60	float distSqrt = Elite::DistanceSqrt(m_pInterface->Agent_GetInfo().Position, m_Target);
61	if ( distSqrt < 16.0f)
62	{
63		steering.RunMode = false;
64		steering.LinearVelocity *= (agentInfo.MaxLinearSpeed * (distSqrt / 16.0f));
65	}
66	else
67		steering.LinearVelocity *= agentInfo.MaxLinearSpeed; //Rescale to Max Speed
68
69
70	if (!enemies.empty() && !aimTargetSet)
71		AddOffset(steering, enemies, agentInfo);
72}
Cpp  AI