A collection of my personal projects

A* Pathfinding with Waypoints

·

This project demonstrates the implementation of the A* pathfinding algorithm using waypoints to simulate roads. The waypoints form a graph, and the tank navigates through the terrain to reach a user-defined endpoint by following the shortest path calculated by the A* algorithm.

This project demonstrates the application of the A* pathfinding algorithm using waypoints to simulate roads for a tank navigating a terrain. It showcases both the algorithm’s efficiency and the ability to dynamically change the destination points, providing an interactive and visual experience.

Core Features

  • Waypoint Graph Setup: Defines nodes (waypoints) and edges (connections) to create a graph structure representing roads.
  • A Pathfinding Algorithm*: Implements the A* algorithm to find the shortest path between nodes in the graph.
  • Real-Time Navigation: The tank dynamically follows the calculated path to reach the user-defined endpoint.
  • Interactive Controls: Users can set different destination points for the tank using buttons.

1. Graph Structure (Graph.cs)

This script manages the graph of nodes and edges, and implements the A* algorithm to find the shortest path.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Graph
{
    private List<Edge> edges = new List<Edge>();
    private List<Node> nodes = new List<Node>();
    public List<Node> pathList = new List<Node>();
    
    public Graph() { }

    public void AddNode(GameObject _id)
    {
        Node node = new Node(_id);
        nodes.Add(node);
    }

    public void AddEdge(GameObject fromNode, GameObject toNode)
    {
        Node from = FindNode(fromNode);
        Node to = FindNode(toNode);

        if (from != null && to != null)
        {
            Edge e = new Edge(from, to);
            edges.Add(e);
            from.edgeList.Add(e);
        }
    }

    Node FindNode(GameObject _id)
    {
        foreach (Node n in nodes)
        {
            if (n.GetID() == _id) return n;
        }

        return null;
    }

    public bool AStar(GameObject startID, GameObject endID)
    {
        if (startID == endID)
        {
            pathList.Clear();
            return false;
        }
        
        Node start = FindNode(startID);
        Node end = FindNode(endID);

        if (start == null || end == null) return false;

        List<Node> open = new List<Node>();
        List<Node> closed = new List<Node>();

        float tentativeGSscore = 0;
        bool tentativeIsBetter;

        start.g = 0;
        start.h = distance(start, end);
        start.f = start.h;
        
        open.Add(start);

        while (open.Count > 0)
        {
            int i = LowestCost(open);
            Node thisNode = open[i];
            if (thisNode.GetID() == endID)
            {
                ReconstructPath(start, end);
                return true;
            }
            
            open.RemoveAt(i);
            closed.Add(thisNode);
            Node neighbour;
            foreach (Edge e in thisNode.edgeList)
            {
                neighbour = e.endNode;

                if (closed.IndexOf(neighbour) > -1) continue;

                tentativeGSscore = thisNode.g + distance(thisNode, neighbour);
                if (open.IndexOf(neighbour) == -1)
                {
                    open.Add(neighbour);
                    tentativeIsBetter = true;
                }
                else if (tentativeGSscore < neighbour.g)
                {
                    tentativeIsBetter = true;
                }
                else tentativeIsBetter = false;

                if (tentativeIsBetter)
                {
                    neighbour.cameFrom = thisNode;
                    neighbour.g = tentativeGSscore;
                    neighbour.h = distance(thisNode, end);
                    neighbour.f = neighbour.g + neighbour.h;
                }
            }
        }

        return false;
    }

    public void ReconstructPath(Node startID, Node endID)
    {
        pathList.Clear();
        pathList.Add(endID);

        var p = endID.cameFrom;
        while (p != startID && p != null)
        {
            pathList.Insert(0, p);
            p = p.cameFrom;
        }
        pathList.Insert(0, startID);
    }

    float distance(Node a, Node b)
    {
        return (Vector3.SqrMagnitude(a.GetID().transform.position - b.GetID().transform.position));
    }

    int LowestCost(List<Node> l)
    {
        float lowestCost = 0;
        int count = 0;
        int iteratorCount = 0;

        lowestCost = l[0].f;
        
        for (int i = 1; i < l.Count; i++)
        {
            if (l[i].f < lowestCost)
            {
                lowestCost = l[i].f;
                iteratorCount = count;
            }
            count++;
        }

        return iteratorCount;
    }
}

2. Node Definition (Node.cs)

This script defines the node structure, representing each waypoint in the graph.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Node
{
    public List<Edge> edgeList = new List<Edge>();
    public Node path = null;
    
    private GameObject id;
    public float x, y, z;
    
    public float f, g, h;
    public Node cameFrom;

    public Node(GameObject i)
    {
        id = i;
        x = i.transform.position.x;
        y = i.transform.position.y;
        z = i.transform.position.z;
    }

    public GameObject GetID()
    {
        return id;
    }
}

3. Edge Definition (Edge.cs)

This script defines the edges connecting nodes in the graph.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Edge
{
    public Node startNode;
    public Node endNode;

    public Edge(Node from, Node to)
    {
        startNode = from;
        endNode = to;
    }
}

4. Waypoint Manager (WPManager.cs)

This script sets up the waypoints and links, initializing the graph for pathfinding.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;



[System.Serializable]
public struct Link
{
    public enum direction { UNI, BI }
    public GameObject node1;
    public GameObject node2;
    public direction dir;
}

public class WPManager : MonoBehaviour
{
    public GameObject[] waypoints;
    public Link[] links;
    public Graph graph = new Graph();
    
    // Start is called before the first frame update
    void Start()
    {
        if (waypoints.Length > 0)
        {
            foreach (GameObject wp in waypoints)
            {
                graph.AddNode(wp);
            }

            foreach (Link l in links)
            {
                graph.AddEdge(l.node1, l.node2);
                if (l.dir == Link.direction.BI)
                {
                    graph.AddEdge(l.node2, l.node1);
                }
            }
        }
    }
}

5. Tank Navigation (FollowWP.cs)

This script handles the tank’s movement along the path calculated by the A* algorithm, allowing the user to set different destination points.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FollowWP : MonoBehaviour
{
    private Transform goal;
    public float speed;
    public float accuracy;
    public float rotSpeed;

    public GameObject wpManager;
    private GameObject[] waypoints;
    private GameObject currentNode;
    private int currentWP = 0;
    private Graph g;
    
    // Start is called before the first frame update
    void Start()
    {
        waypoints = wpManager.GetComponent<WPManager>().waypoints;
        g = wpManager.GetComponent<WPManager>().graph;
        currentNode = waypoints[0]; // de acolo incepe ca pe nodu ala se afla tanku
    }

    public void GoToHeli()
    {
        g.AStar(currentNode, waypoints[0]);
        currentWP = 0;
    }
    
    public void GoToRuin()
    {
        g.AStar(currentNode, waypoints[11]);
        currentWP = 0;
    }
    
    public void GoToFactory()
    {
        g.AStar(currentNode, waypoints[10]);
        currentWP = 0;
    }
    
    public void GoToHangar()
    {
        g.AStar(currentNode, waypoints[3]);
        currentWP = 0;
    }

    // Update is called once per frame
    void LateUpdate()
    {
        if (g.pathList.Count == 0 || currentWP == g.pathList.Count) return;
        
        if (Vector3.Distance(g.pathList[currentWP].GetID().transform.position, this.transform.position) < accuracy)
        {
            currentNode = g.pathList[currentWP].GetID();
            currentWP++;
        }

        if (currentWP < g.pathList.Count)
        {
            goal = g.pathList[currentWP].GetID().transform;
            Vector3 lookAtGoal = new Vector3(goal.position.x, this.transform.position.y, goal.position.z);
            Vector3 direction = lookAtGoal - this.transform.position;
            
            this.transform.rotation = Quaternion.Slerp(this.transform.rotation, Quaternion.LookRotation(direction), rotSpeed * Time.deltaTime);
            
            this.transform.Translate(0, 0, speed * Time.deltaTime) ;
            
        }
    }
}

¶¶¶¶¶

¶¶¶¶¶

¶¶¶¶¶

Leave a Reply

Your email address will not be published. Required fields are marked *