Node-based pathfinding while following traffic. Images to help.

Ok. So I have this idea for a larger project but I’m attempting a small prototype of one of the major systems in the game.

Essentially I want to use node path ai to simulate electrical current through a “wire”.

So lets say I have a path like so:

Comprised of two Nodes; Node A and Node B.

I want to be able to spawn an ai object on one Node and have it move towards the connecting Node like in this .gif I created: http://gph.is/2lA1rrC

If a Node has two connections then it should spawn two ai that traverse towards both connecting nodes respectively.

This is incredibly easy to implement and node path tutorials are abundant. The difficulty comes when I spawn an ai on one Node and there is already an ai object moving towards the same Node then I dont want to spawn the ai. I never want two ai to cross paths with each other.

So lets say I have 3 Nodes: A, B and C which are connected in a line A->B->C.

I spawn an ai object on Node A and it starts moving towards Node B.

During the ai’s transit between the Nodes I want to spawn an ai object on Node B. But because the “direction of traffic” goes from Node A to B then an ai is not spawned to travel down that path, but since there is no ai traveling from Node C to B an ai is spawned on Node B and starts moving towards Node C.

If there are no ai traveling towards Node B OR if ai are currently traveling away from Node B then two ai would spawn to travel to both Node A and C.

How can I restrict ai from going against the flow of traffic?

Put simply, you need a way to ‘claim’ a link. When an AI starts traversing a link it registers itself on that link, then de-registers itself when it reaches the end. When a node is triggered to spawn ai’s it checks the links connecting to it, sees if there are any AIs registered on them already and their direction.

The way i would do this depends on how your links are set up. Do you simply have nodes that have references to other nodes, or do you have some kind of link object that represents a connection between 2 nodes.

Also. Can multiple ai’s travel down the same link at the same time if they’re going in the same direction. Or are links strictly limited to one ai at a time.

1 Like

Right now I just have Nodes that each have a List of other Nodes. I thought of creating a Segment class that would have two Nodes, and then perhaps a Path class that represents a set of Segments. So in my example in the OP there would be 1 Path(ABC) with two Segments(AB and BC) and each Segment referencing it’s respective Nodes. Though I have not attempted to implement this.

I don’t necessarily want any code written for me, unless someone is inclined to. Really what I’d prefer is giving me a direction to head in. For example, what WOULD be the best way to model the links between nodes? What classes should I start with, what information should they hold and what kind of interactivity should these classes have with each other?

To answer your question yes two ai’s can travel down the same link/segment as long as they are going in the same direction and in fact this is a requirement.

Eventually there will be two seperate ai’s, or have the ai hold a flag(on/off), that mark the beginning and end of “current” through the wire. So these “Wires” will be connected to a Component, such as a Light Bulb, and when each ai reaches the Component it will tell it to either turn on or off. Kind of like this:

OK, so basically you have a graph (in this case ABC, but you could have a graph much larger than this).

And you have AI units that ‘path find’ through this graph? Finding a path to get from one node to someother node. Like dijkstra or A* pathfinding or something?

BUT, you want it so that certain edges (link between nodes) are 1-way only.

Well usually edges are defined as the relationship between nodes. You have a node, and the node describe what nodes are traversable to from it.

How a 1way edge would be defined is that the node that can be exited towards another has the other as a link, but the other does not have the origin as a link.

So like if A->B is one way, A has a link to B, but B does NOT have a link to A.

public class Node
{

    public Node[] Neighbours;

}

var a = new Node();
var b = new Node();
var c = new Node();
a.Neighbours = new Node[] {b};
b.Neighbours = new Node[] {c};
c.Neighbours = new Node[] {b};//b->c is 2-way

var path = ReducePath(start = a, goal = c);

In this case, you also need to account for what happens if the ‘start’ of the current finishes traversing a link before the ‘end’ of the current starts traversing it. Which will happen if the length of the gap between the 2 is longer then the length of any of your links. There will be a period where there are no units traveling down the link but we still shouldn’t allow an AI to spawn going the other way because even thou there are no units traveling down it now, the end ai is about to need to.

There’s also the decision about what happens if 2 currents try to go down the same link at the same time. Lets say that the start of one current starts down a link. Before the end of that current starts going down the link the start of another current arrives at the node and starts going down the same link in the same direction. You now have 2 currents with overlapping start/end nodes going down the same link at the same time. Is this allowed? And if it’s not how do we handle it, do we terminate one of the currents? We could merge them so there’s now one current of equivalent length but If they’re both following different paths to different destinations then that’s not viable. Alternatively you could selectively ‘hide’ overlapping units so it looks like they merge as they enter the start of the link and unmerge as the exit it.

(I hate it when i start to think about what ‘can’ happen in a system, it always takes ‘simple’ and turns it into ‘complicated’ :p, unfortunately it has to be done or there are problems later)

There are lots of different ways to do this. The simplest way i can think of is to have a link object. The link has a counter that keeps track of how many units are traversing it and a variable recording what the current direction of travel is. When a unit starts going down it it adds one, when it reaches the end it subtracts one. You can check if a direction is allowed with 2 simple if checks. If the counter == 0 then there are no units traveling the link and any direction is allowed. Else, check if the current direction matches the direction you want to travel in.

To solve the problem i mentioned before where there’s a gap between the start of a current and the end of a current, you simply increment the counter when the start begins traversing the link but don’t decrement it until the END has finished traversing it, so the direction remains set until the entire current has traversed it.

I’ve probably set this up a bit weirdly, i was just trying to keep it as short and simple as possible. The link is another monobehaviour that you stick somewhere and assign the 2 end nodes in the editor. A scriptable object would probably be more suitable but that’s a touch more setup and i wanted to keep this as simple as possible.

Comes with my usual disclaimer: Example code only, not a finished product, no testing of any kind was done :p.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

class Link : MonoBehaviour
{
    public enum DirectionEnum
    {
        AtoB,
        BtoA,
        NoUnitsTraversing
    }

    [SerializeField]
    protected Node A;
    [SerializeField]
    protected Node B;
    public int NumberOfUnitsTraversing
    {
        get;
        protected set;
    }
    public DirectionEnum CurrentDirection
    {
        get;
        protected set;
    }

    private void Start()
    {
        A.Links.Add(this);
        B.Links.Add(this);
    }

    public DirectionEnum GetDirection(Node StartingFrom)
    {
        if (StartingFrom == A)
            return DirectionEnum.AtoB;
        else if (StartingFrom == B)
            return DirectionEnum.BtoA;
        else
            throw new Exception("StartingFrom is not one of the nodes in this link");
    }
 
    public bool Traverse(DirectionEnum Direction)
    {
        if (NumberOfUnitsTraversing == 0 || CurrentDirection == Direction)
        {
            // No units traversing, or there are but they're going the same direction.
            CurrentDirection = Direction; // Make sure the direction of the link is current
            NumberOfUnitsTraversing += 1; // Increase the counter
            return true;
        }
        // No allowed to travel in this direction, there is a unit going the other way
        return false;
    }
    public void EndTraverse()
    {
        NumberOfUnitsTraversing -= 1; // Subtract from the counter
        if (NumberOfUnitsTraversing == 0)
            CurrentDirection = DirectionEnum.NoUnitsTraversing;
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

class Node : MonoBehaviour
{
    // List of links leaving this node
    public List<Link> Links
    {
        get;
        protected set;
    }

    private void Awake()
    {
        Links = new List<Link>();
    }

    // Get all outgoing links that are currently available to travel FROM.
    public List<Link> AvailableLinks()
    {
       List<Link> Result = new List<Link>();
        foreach (Link Item in Links)
        {
            if (Item.NumberOfUnitsTraversing == 0)
            {
                // There are no units traversing this link,
                Result.Add(Item);
            }
            else if (Item.CurrentDirection == Item.GetDirection(this))
            {
                // We compared the direction the link is currently set to, to the direction leaving from this node to the other end.
                // If they match then we're allowed to go.
                Result.Add(Item);
            }
        }
        return Result;
    }
}