3
\$\begingroup\$

I am slowly creating a flow field pathfinding solution for Unity3D. I'm trying to follow Elijah Emerson's description, which appears in the book 'Game AI Pro' (volume 1).

The map is divided into sectors, and each sector houses three fields: cost, integration, flow, which need each other to calculate the flow field.

At one point I got fed up trying to reference locations in space between sectors and fields, so created a Coordinates object to handle this task. But I'm not sure if I've created an object which makes good sense for an optimal solution? I initially created a Struct, because the object is a data type which holds just world X and Y, along with a reference to the world this belongs to.

Because of this, I didn't want to load the object with more data fields, but I did need to reference various things described above. So I added get-only properties which call data from elsewhere. But is this fast and optimal? I don't know. I know there's an inevitable trade between memory and processing, but I just don't know what makes sense here for a fast solution.

using UnityEngine;

public struct Coordinates
{
    public readonly FlowFieldWorld world;
    public readonly int worldX, worldY;
    public int SectorX { get { return worldX % world.SectorSize; } }
    public int SectorY { get { return worldY % world.SectorSize; } }
    public FlowFieldSector Sector { get { return world.Sectors[worldX / world.SectorSize, worldY / world.SectorSize]; } }
    public CostTile Cost { get { return Sector.CostField[SectorX, SectorY]; } }
    public IntegrationTile Integration { get { return Sector.IntegrationField[SectorX, SectorY]; } }
    public FlowTile Flow { get { return Sector.FlowField[SectorX, SectorY]; } }
    public bool IsWalkable { get { return Cost.Value != 255; } }
    public readonly static Coordinates zero = new Coordinates(0, 0, null);
    public Vector3 Vector3 { get { return new Vector3(worldX, 0, worldY); } }

    public Coordinates(int worldX, int worldY, FlowFieldWorld world)
    {
        this.worldX = worldX;
        this.worldY = worldY;
        this.world = world;
    }

    public Coordinates(Coordinates coordinates)
    {
        this.worldX = coordinates.worldX;
        this.worldY = coordinates.worldY;
        this.world = coordinates.world;
    }

    public static bool operator ==(Coordinates left, Coordinates right)
    {
        return left.worldX == right.worldX && left.worldY == right.worldY;
    }

    public static bool operator !=(Coordinates left, Coordinates right)
    {
        return left.worldX != right.worldX || left.worldY != right.worldY;
    }

    public override bool Equals(object obj)
    {
        return obj is Coordinates coordinates && Equals(coordinates);
    }

    public bool Equals(Coordinates other)
    {
        return worldX == other.worldX && worldY == other.worldY;
    }

    public override int GetHashCode()
    {
        int hashCode = 1845962855;
        hashCode = hashCode * -1521134295 + worldX.GetHashCode();
        hashCode = hashCode * -1521134295 + worldY.GetHashCode();
        return hashCode;
    }

}
\$\endgroup\$
2
\$\begingroup\$

I have no knowledge about Unity3d, so here are some general comments.

If world.SectorSize is constant throughout the session, you can change some of the properties to readonly fields and calculate them in the constructor:

public readonly int SectorX;
public readonly int SectorY;
public readonly FlowFieldSector Sector;
...


public Coordinates(int worldX, int worldY, FlowFieldWorld world)
{
  this.worldX = worldX;
  this.worldY = worldY;
  this.world = world;
  SectorX = worldX % world.SectorSize;
  SectorY = worldY % world.SectorSize;
  Sector =  world.Sectors[worldX / world.SectorSize, worldY / world.SectorSize];
}

Doing so with Sector, Cost, Integration and Flow requires of cause that these objects are reference types and not replaced - if they are updated while the Coordinates instance lives. It is probably just a micro optimization - but everything counts?


public bool Equals(Coordinates other)
{
  return worldX == other.worldX && worldY == other.worldY;
}

This is actually an implementation of IEquatable<Coordinates> so you can add this interface to the inheritance list:

public struct Coordinates : IEquatable<Coordinates>

In respect to the DRY-principle you could do the following changes:

The copy constructor can call the other constructor:

public Coordinates(Coordinates source) : this(source.worldX, source.worldY, source.world)
{
}

The comparison operators can call Equals(Coordinates other):

public static bool operator ==(Coordinates left, Coordinates right)
{
  return left.Equals(right);
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.