Soren Rogiers

Game developer

AI Programming


An autonomous AI made in C++ for my final assignment in the course Game Play Programming during my studies Digital Arts and entertainment.

The goal was to have an AI survive as long as possible in the given world. The given world was made up with houses and in those houses were items you could pick up.
As time went on in the world the difficulty rose which made enemies grow stronger and more determined to chase you down.

Code snippets below - full source on Github

Video


Code snippets


									
//HIDE
//****
class HideBehaviour : public ArriveBehaviour
{
public:
	HideBehaviour() {};
	HideBehaviour(std::vector obstacles) :m_pObstaclesArr(obstacles) {};
	virtual ~HideBehaviour() {};

	PluginOutput CalculateSteering(float deltaT, AgentInfo* pAgent) override;
	b2Vec2 GetBestHidingPosition(const b2Vec2& obstaclePosition, TargetInformation* pTarget);
protected:
	std::vector m_pObstaclesArr;
	float m_StartFleeDistance = 10.0f;

};

//OBSTACLE-AVOIDANCE
//******************
class ObstacleAvoidanceBehaviour : public SteeringBehaviour
{
public:
	ObstacleAvoidanceBehaviour(){}
	virtual ~ObstacleAvoidanceBehaviour() {};

	PluginOutput CalculateSteering(float deltaT, AgentInfo* pAgent) override;

	void UpdateObstacles(std::vector obstacles) { m_Obstacles = obstacles; }
	void SetMaxAvoidanceForce(float maxForce) { m_MaxAvoidanceForce = maxForce; }

private:
	std::vector m_Obstacles = {};
	float m_MaxAvoidanceForce = 30.f;

private:
	bool LineIntersectsCircle(b2Vec2 ahead, b2Vec2 ahead2, Obstacle obstacle) const;
	Obstacle FindMostThreateningObstacle(const b2Vec2& ahead, const b2Vec2& ahead2, const b2Vec2& currentPos);

};

//HIDE CALCULATE
//**************
PluginOutput HideBehaviour::CalculateSteering(float deltaT, AgentInfo* pAgent)
{
	float distanceToClosest = (std::numeric_limits::max)();
	b2Vec2 bestHidingspot = {};

	std::vector::iterator itr = m_pObstaclesArr.begin();
	while (itr != m_pObstaclesArr.end())
	{
		b2Vec2 hidingspot = GetBestHidingPosition((*itr)->GetPosition(), m_pTargetInfo);

		float distance = b2DistanceSquared(hidingspot, pAgent->Position);

		if (distance < distanceToClosest)
		{
			distanceToClosest = distance;
			bestHidingspot = hidingspot;
		}

		++itr;
	}

	//If we didnt find a suitable hiding spot flee from the hunter
	if (distanceToClosest == (std::numeric_limits::max)())
	{
		//Flee behaviour
		PluginOutput steeringOutput = {};

		auto targetVelocity = pAgent->Position - (*m_pTargetInfo).position;
		targetVelocity.Normalize();
		targetVelocity *= pAgent->MaxLinearSpeed;

		steeringOutput.LinearVelocity = targetVelocity - pAgent->LinearVelocity;

		return steeringOutput;
	}

	//Hide in the best spot possible
	auto newTarget = TargetInformation(bestHidingspot);
	ArriveBehaviour::m_pTargetInfo = &newTarget;

	return ArriveBehaviour::CalculateSteering(deltaT, pAgent);
}

b2Vec2 HideBehaviour::GetBestHidingPosition(const b2Vec2& obstaclePosition, TargetInformation* pTarget)
{
	const float distanceFromBoundary = 5.f;
	float distanceAway = 1.0f + distanceFromBoundary;

	b2Vec2 directionToObstacle = obstaclePosition - (*pTarget).position;
	directionToObstacle.Normalize();

	return (directionToObstacle * distanceAway) + obstaclePosition;

}
//AVOID-ENEMY CALCULATE
//*********************
PluginOutput ObstacleAvoidanceBehaviour::CalculateSteering(float deltaT, AgentInfo* pAgent)
{
	auto velocity = pAgent->LinearVelocity;
	velocity.Normalize();

	auto maxSeeAhead = 10.f;

	auto ahead = pAgent->Position + velocity * maxSeeAhead;
	auto ahead2 = pAgent->Position + velocity * maxSeeAhead * 0.5f;

	auto mostThreatening = FindMostThreateningObstacle(ahead, ahead2, pAgent->Position);

	b2Vec2 avoidance = {};

	if (!mostThreatening.IsEmpty())
	{
		avoidance.x = ahead.x - mostThreatening.position.x;
		avoidance.y = ahead.y - mostThreatening.position.y;

		avoidance.Normalize();
		avoidance *= m_MaxAvoidanceForce;
	}
	else
	{
		avoidance.SetZero();
	}

	PluginOutput steering = {};
	steering.LinearVelocity = avoidance;

	return steering;
}

Obstacle ObstacleAvoidanceBehaviour::FindMostThreateningObstacle(const b2Vec2& ahead, const b2Vec2& ahead2, const b2Vec2& currentPos)
{
	Obstacle mostThreatening = {};

	for(auto ob : m_Obstacles)
	{
		auto collision = LineIntersectsCircle(ahead, ahead2, ob);

		auto distancePosToEnemySqrt = b2DistanceSquared(currentPos, ob.position);
		auto distancePosToMtSqrt = b2DistanceSquared(currentPos, mostThreatening.position);

		if (collision && (mostThreatening.IsEmpty() || distancePosToEnemySqrt < distancePosToMtSqrt))
		{
			mostThreatening = ob;
		}
	}
	return mostThreatening;

}

bool ObstacleAvoidanceBehaviour::LineIntersectsCircle(b2Vec2 ahead, b2Vec2 ahead2, Obstacle obstacle) const
{
	auto distanceAheadSqrt = b2DistanceSquared(obstacle.position, ahead);
	auto distanceAhead2Sqrt = b2DistanceSquared(obstacle.position, ahead2);

	return distanceAheadSqrt <= obstacle.radius * obstacle.radius || distanceAhead2Sqrt <= obstacle.radius * obstacle.radius;
}
//CREATE GRID
//***********
m_pGrid = new Grid(WORLD_GetInfo());
m_pGrid->CreateGrid(4);

//BEHAVIOURS
//**********
m_pSeekBehaviour = new SeekBehaviour();
m_pObstacleAvoidance = new ObstacleAvoidanceBehaviour();
m_pWanderBehaviour = new WanderBehaviour();

BehaviourAndWeight seekWeight = BehaviourAndWeight(m_pSeekBehaviour, 1.f);
BehaviourAndWeight avoidWeight = BehaviourAndWeight(m_pObstacleAvoidance, 0.f);

m_pBlendedSteeringBehaviour = new BlendedSteeringBehaviour({ seekWeight,avoidWeight });

//STEERING PIPELINE
//*****************
m_pTargeter = new Targeter();
m_pActuator = new Actuator(m_pBlendedSteeringBehaviour);
m_pDecomposer = new Decomposer(this);

m_pSteeringpipeline = new SteeringPipeline();
m_pSteeringpipeline->SetTargeters({ m_pTargeter });
m_pSteeringpipeline->SetActuators(m_pActuator);
m_pSteeringpipeline->SetDecomposers({ m_pDecomposer });
m_pSteeringpipeline->SetFallBackBehaviour(m_pWanderBehaviour);

//BEHAVIOURTREE
m_pBehaviourTree = new BehaviourTree(pBlackboard,
	new BehaviourSelector
	({
		//Fill health
		new BehaviourSequence
		({
			new BehaviourConditional(IsHealthLow),
			new BehaviourConditional(IsHealthInInventory),
			new BehaviourAction(FillHealth)
		}),
		//Fill Energy
		new BehaviourSequence
		({
			new BehaviourConditional(IsEnergyLow),
			new BehaviourConditional(IsEnergyInInventory),
			new BehaviourAction(FillEnergy)
		}),
		//Deal with enemies
		new BehaviourSelector
		({
			new BehaviourSequence
			({
				new BehaviourConditional(EnemyInFov),
				new BehaviourConditional(HasWeapon),
				new BehaviourConditional(IsNotRunning),
				new BehaviourAction(SetEnemyTarget),
			})
		}),
		//Pickup items
		new BehaviourSelector
		({
			new BehaviourSequence
			({
				new BehaviourConditional(HasItemsToPickup),
				new BehaviourConditional(HasInventoryFreeslot),
				new BehaviourSelector
				({
					new BehaviourSequence
					({
						new BehaviourConditional(HasItemTarget),
						new BehaviourConditional(IsItemInGrabRange),
						new BehaviourConditional(IsItemInFov),
						new BehaviourAction(GrabItem)
					}),
					new BehaviourAction(SetItemTarget),
				})
			}),
		}),
		//Search houses part
		new BehaviourSelector
		({
			new BehaviourSequence
			({
				new BehaviourConditional(HasHouses),
				new BehaviourSelector
				({
					new BehaviourSequence({
						new BehaviourConditional(IsInsideHouse),
						new BehaviourConditional(IsHouseChecked),
						new BehaviourAction(Traverse)
					}),
					new BehaviourSequence({
						new BehaviourConditional(HasHousesToCheck),
						new BehaviourAction(SetHouseTarget)
					})
				})
			})
		}),
		//Traverse around the world
		new BehaviourSelector
		({
			new BehaviourSequence
			({
				new BehaviourAction(Traverse)
			})
		})
	})
);

void TestBoxPlugin::RememberHouses()
{
	auto housesList = FOV_GetHouses();
	int newId = m_HousesList.size() + 1;

	if(housesList.size() > 0)
	{
		for(auto h : housesList)
		{
			auto findHouse = [&](const House* house) {return house->GetCenter() == h.Center; };
			if(!(std::find_if(m_HousesList.begin(),m_HousesList.end(),findHouse) != m_HousesList.end()))
			{
				auto newHouse = new House(newId,h.Center,h.Size);
				m_HousesList.push_back(newHouse);
			}
		}
	}
}
void TestBoxPlugin::StoreEntities()
{
	auto entityList = FOV_GetEntities();
	m_EnemiesInFov.clear();
	if(entityList.size() > 0)
	{
		for(auto e:entityList)
		{
			if(e.Type == eEntityType::ITEM)
			{
				auto findItem = [&](const EntityInfo& entity) {return e.Position == entity.Position; };
				if(!(std::find_if(m_Items.begin(), m_Items.end(),findItem) != m_Items.end()))
				{
					m_Items.push_back(e);
				}
			} 
			else if (e.Type == eEntityType::ENEMY)
			{
				auto findEnemy = [&](const EntityInfo& entity) {return e.EntityHash == entity.EntityHash; };
				if (!(std::find_if(m_EnemiesInFov.begin(), m_EnemiesInFov.end(), findEnemy) != m_EnemiesInFov.end()))
				{
					m_EnemiesInFov.push_back(e);
				}
			}
		}
	}
}

//USE MEDKITS AND FOOD
//********************
bool fillHealth = false;
bool fillEnergy = false;

pBlackboard->GetData("FillHealth", fillHealth);
pBlackboard->GetData("FillEnergy", fillEnergy);

if (fillHealth)
{
	int healthIndex = -1;
	pBlackboard->GetData("HealthIndex", healthIndex);

	if (INVENTORY_UseItem(healthIndex))
	{
		INVENTORY_RemoveItem(healthIndex);
		std::cout << "Used item: MedKit - Removed from slotID: " << healthIndex << std::endl;

		SafeDelete(m_Inventory[healthIndex]);
		pBlackboard->ChangeData("Inventory", m_Inventory);
		pBlackboard->ChangeData("FillHealth", false);
	}
}

if (fillEnergy)
{
	int foodIndex = -1;
	pBlackboard->GetData("FoodIndex", foodIndex);

	if (INVENTORY_UseItem(foodIndex))
	{
		INVENTORY_RemoveItem(foodIndex);
		std::cout << "Used item: ENERGY - Removed from slotID: " << foodIndex << std::endl;

		SafeDelete(m_Inventory[foodIndex]);
		pBlackboard->ChangeData("Inventory", m_Inventory);
		pBlackboard->ChangeData("FillEnergy", false);
	}
}
								
							

Presentation


Go back