Portrait of Marius Naasen Hi, I am Marius

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.

A GIF showcasing the fire mechanics of the battleship

Combat Engagement: The battleship uses physics-based calculations to determine the correct firing angle to hit its targets.

A GIF showcasing the patrolling of the battleship

Patrolling: The battleship patrols the designated area, and upon detecting an enemy vessel, it decides to engage.

A GIF showcasing the refueling of the battleship

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 calls SearchForTargets() every frame to continually check for nearby targets.
  • SearchForTargets() uses Physics.OverlapSphere to find all colliders within the detectionRadius. 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 the targets 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 the ScoreGiver.cs, Patrol.cs and TargetFinder.cs scripts.
  • The Update() method calls MakeDecision() 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 the GetScoreForAction() method.
  • EngageEnemy(), Refuel() and patrol() 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.