Autonomous Battleship with Utility AI
This project showcases an autonomous battleship that navigates a map, searches for enemy vessels, engages in combat, and collects remains post-battle. The decision-making process is driven by utility AI, which uses mathematical functions and scoring systems to evaluate and prioritize actions. This article delves into the project’s structure, key components, and the algorithms that power its intelligent behavior.
Note that this is an incomplete project. Nevertheless, for a look at the codebase, please refer to the GitHub repository.
Combat Engagement: The battleship uses physics-based calculations to determine the correct firing angle to hit its targets.
Patrolling: The battleship patrols the designated area, and upon detecting an enemy vessel, it decides to engage.
Refueling: When the battleship’s fuel gets low, the utility functions determine that it should seek a refueling station.
Project Overview
The battleship project is designed to autonomously patrol a designated area, identify enemy vessels, engage in combat, and manage its resources effectively. The utility AI system ensures that the battleship makes intelligent decisions in various scenarios, including when to attack an enemy, when to search for enemies, and when to refuel.
This project consists of the following key features.
- Autonomous Navigation: The battleship navigates the map on its own, patrolling for enemy vessels.
- Combat Engagement: Upon detecting an enemy, the battleship engages in combat using physics-based calculations to determine the correct firing angle.
- Resource Management: The battleship monitors its fuel levels and seeks refueling stations when necessary.
Key Components
The project is built around several core scripts that handle different aspects of the battleship’s behavior.
-
ScoreGiver.cs
: This script is responsible for assigning scores to different actions based on their utility. The scores help the decision-making system prioritize actions. -
Patrol.cs
: Manages the patrolling behavior of the battleship. It defines the patrol routes and ensures the battleship covers the designated area effectively. -
TargetFinder.cs
: Implements the logic to detect and identify enemy vessels. It scans the area and flags potential targets for engagement. -
DecisionMaker.cs
: The central script that integrates all components. It makes decisions based on the utility scores assigned by the ScoreGiver.cs script, directing the battleship’s actions accordingly.
Code Overview
In this section we will highlight the essential parts of the code to the previously mentioned scripts.
ScoreGiver.cs
Script
The following code contains a simplified version of the ScoreGiver.cs
script to make it easier to understand the ideas and concepts used. The GetScoreForAction()
method receives an action as a string and returns a score based on the action's utility. For each case in the switch statement, a specific method is called to calculate scores for different actions, such as engaging an enemy or refueling.
public float GetScoreForAction(string action)
{
float score = 0f;
switch(action)
{
case "EngageEnemy":
score = CalculateEnemyEngagementScore();
break;
case "Refuel":
score = CalculateRefuelScore();
break;
// Other cases...
}
return score;
}
The CalculateEnemyEngagementScore()
and CalculateRefuelScore()
methods contain the logic to calculate the utility of engaging an enemy or refueling, respectively. These calculations consider factors like the proximity of enemies and current fuel levels. By comparing the utility values, the battleship will now be in a place to make educated decisions.
private float CalculateEnemyEngagementScore()
{
// Logic to calculate score based on enemy proximity
}
private float CalculateRefuelScore()
{
// Logic to calculate score based on current fuel levels
}
TargetFinder.cs
Script
The concepts for managing target detection behaviour are encapsulated in the following simplified code snippet and can be broken down by describing the following components.
targets
is a list containing all the current detected targets. This list gets cleared before every detection sweep.detectionRadius
defines the range within which the battleship can detect objects.- The
Update()
method callsSearchForTargets()
every frame to continually check for nearby targets. SearchForTargets()
usesPhysics.OverlapSphere
to find all colliders within thedetectionRadius
. It then iterates through these colliders to check if any are tagged as "Enemy" or "Player". If so, the collider corresponding to the detected object is added to thetargets
list.
public class TargetFinder : MonoBehaviour
{
List targets = new List();
public float detectionRadius;
void Update()
{
SearchForTargets();
}
IEnumerator SearchForTargets()
{
targets.Clear()
Collider[] hitColliders = Physics.OverlapSphere(transform.position, detectionRadius);
foreach (var hitCollider in hitColliders)
{
if (hitCollider.CompareTag("Enemy") || hitCollider.CompareTag("Player"))
{
targets.Add(hitCollider);
}
}
}
}
DecisionMaker.cs
Script
The DecisionMaker.cs
script as previously mentioned is the central component that integrates all the other scripts and directs the battleship’s actions based on utility scores. The following code is a simplified version of the actual script, making it easier to understand by breaking it down into the following components.
- The
Start()
method initializes references to theScoreGiver.cs
,Patrol.cs
andTargetFinder.cs
scripts. - The
Update()
method callsMakeDecision()
every frame to continually evaluate the best action to take. MakeDecision()
retrieves utility scores for patrolling, engaging an enemy (if there is one available) and refueling by querying theGetScoreForAction()
method.EngageEnemy()
,Refuel()
andpatrol()
methods contain the logic for each respective action, ensuring the battleship takes an action based on the highest utility score.
public class DecisionMaker : MonoBehaviour
{
private ScoreGiver scoreGiver;
private Patrol patrol;
private TargetFinder targetFinder;
void Start()
{
scoreGiver = GetComponent();
patrol = GetComponent();
targetFinder = GetComponent();
}
void Update()
{
MakeDecision();
}
private void MakeDecision()
{
float engageScore = scoreGiver.GetScoreForAction("EngageEnemy");
float refuelScore = scoreGiver.GetScoreForAction("Refuel");
float patrolScore = scoreGiver.GetScoreForAction("patrol");
if (highestScore(engageScore)) // Pseudocode
{
EngageEnemy();
}
else if (highestScore(refuelScore)) // Pseudocode
{
refuel();
}
else
{
patrol();
}
}
private void EngageEnemy()
{
// Logic to engage enemy
}
private void Refuel()
{
// Logic to refuel
}
private void Patrol()
{
// Logic to patrol
}
}
Conclusion
This project has been fascinating to work on, providing me with an opportunity to learn about utility AI. Despite its unfinished state, it serves as a valuable educational tool. The purpose of making it public is to allow others to benefit from exploring and drawing inspiration from the concepts and ideas discussed in this project, demonstrating the use of utility AI.