Dijkstra's Algorithm Ineffeciencies on a Hex Grid, C#, Unity3D - c#

I'm attempting to create a turn based strategy game using a 3D HexGrid map, I've implemented dijkstra's algorithm but it doesn't run 100% efficiently and I can't work out why. I also attempted to implement A* but have had to stop as I can't work out how to properly implement it, so any help with that would also be massively appreciated.
My unit passes it's GameObject and the Vector3 of it's target to the generate path function and each Node in the graph list is populated with its x,y,z and all of it's neighbors.
The inefficiencies are such that when moving; in a -X direction when on an odd Z plane or in a +X when on an even Z plane, an extra step is made, shown in the screenshots. Another Inefficiency is that when moving in the Z plane an extra step is often taken as the code seems to prefer keeping it's X value the same for as long as possible before approaching on the Z plane. This is leading to the unit being 1 tile further from the goal when it starts it's Z movement than it would have been has it moved 1 X negatively to start with.
I'll add my path generation code, neighbor generation code and my node class code as well as screenshots of where the inefficiencies are occurring as I know my explanations are unclear at best. The neighbor code ensures that the highest adjacent tile is the one stored as the neighbor (it also has to search through types as i have a variety of tile types.
Thank you so much in advance, to anyone that might be able to offer some help or insight in to what is going wrong.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
using System;
using System.Linq;
public class UnitMasterScript : MonoBehaviour {
// private int Number_of_neighbours = 6;
private int Number_of_neighbours = 6;
private GameObject[] Neighbours;
Node[,,] graph;
public bool MapMakerDone = false;
void Update()
{
if (MapMakerDone == true)
{
// Wait for MapMaker to change MapMakerDone to true then allow rest of script to run
GameObject Map = GameObject.Find("MapMaker");
int WidthVal = Map.GetComponent<MapMakerFromFile>().WidthVal;
int HeightVal = Map.GetComponent<MapMakerFromFile>().HeightVal;
int DepthVal = Map.GetComponent<MapMakerFromFile>().DepthVal;
// Graph of node generation code
// Code to find which hex is to each side of this hex
// Need to find hex to left, right, ul, ur, ll, lr
// Need to find hex at the top of the stack adjacent
// 0:L 1:R 2:UL 3:UR 4:LL 5:LR
graph = new Node[WidthVal, HeightVal, DepthVal];
for (int x = 0; x < WidthVal; x++)
{
for (int y = 0; y < HeightVal; y++)
{
for (int z = 0; z < DepthVal; z++)
{
graph[x, y, z] = new Node();
graph[x, y, z].x = x;
graph[x, y, z].y = y;
graph[x, y, z].z = z;
}
}
}
for (int x = 0; x < WidthVal; x++)
{
for (int y = 0; y < HeightVal; y++)
{
for (int z = 0; z < DepthVal; z++)
{
// Set up the x and y for the neighbour as the source so it can be used
int neighbourX = x;
int neighbourY = 0; // must always start from 0 to ensure a downstep isn't missed
int neighbourZ = z;
int neighbourType = 0;
int correct_type = 0;
GameObject Hex_Present_checker = null;
// First needs to check if there even is a tile at this coordinate location
for (neighbourType = 0; neighbourType < 5; neighbourType++)
{
Hex_Present_checker = GameObject.Find("Hex_" + x + "_" + y + "_" + z + "_" + neighbourType);
if (Hex_Present_checker != null)
{
correct_type = neighbourType;
}
Hex_Present_checker = null;
}
if (correct_type != 0)
{
neighbourType = correct_type;
// int Number_of_neighbours = 6;
// GameObject[] Neighbours;
Neighbours = new GameObject[Number_of_neighbours];
// For each value of each tile in neighbours find what the tile coordinates are in XYZ
for (int q = 0; q < Number_of_neighbours; q++)
{
// Finds X and Z values of the neighbours
if (q < 2)
{
if (q == 0) { neighbourX = x + 1; }
if (q == 1) { neighbourX = x - 1; }
}
if (z % 2 == 1)
{
if (q == 2) { neighbourX = x; neighbourZ = z + 1; }
if (q == 3) { neighbourX = x + 1; neighbourZ = z + 1; }
if (q == 4) { neighbourX = x; neighbourZ = z - 1; }
if (q == 5) { neighbourX = x + 1; neighbourZ = z - 1; }
}
else
{
if (q == 2) { neighbourX = x - 1; neighbourZ = z + 1; }
if (q == 3) { neighbourX = x; neighbourZ = z + 1; }
if (q == 4) { neighbourX = x - 1; neighbourZ = z - 1; }
if (q == 5) { neighbourX = x; neighbourZ = z - 1; }
}
// Checks for the highest tile for the XZ coordinate and gets its Y value
GameObject potential_highest_ring;
int highest_Y = 0;
int correct_neighbour_type = 0;
for (neighbourY = 0; neighbourY < HeightVal; neighbourY++)
{
for (neighbourType = 0; neighbourType < 5; neighbourType++)
{
potential_highest_ring = null;
potential_highest_ring = GameObject.Find("Hex_" + neighbourX + "_" + neighbourY + "_" + neighbourZ + "_" + neighbourType);
if (potential_highest_ring != null)
{
highest_Y = neighbourY;
correct_neighbour_type = neighbourType;
}
}
}
// Need to check if there is a neighbour at the given coordinates
// Debug.Log("Hex_" + neighbourX + "_" + highest_Y + "_" + neighbourZ + "_" + neighbourType);
Neighbours[q] = GameObject.Find("Hex_" + neighbourX + "_" + highest_Y + "_" + neighbourZ + "_" + correct_neighbour_type);
// While there is a neighbour in the neighbours array
// add it's coordinates to the graph node as a part of its neighbours sublist
if (Neighbours[q] != null)
{
graph[x, y, z].neighbours.Add(graph[neighbourX, highest_Y, neighbourZ]);
}
}
}
}
}
}
MapMakerDone = false;
}
}
// List<Node> currentPath = null;
public List<Node> GeneratePathTo(GameObject SelectedUnit, Vector3 targetVec)
{
// Dijkstra's Algorithm Implementation
Dictionary<Node, float> dist = new Dictionary<Node, float>();
Dictionary<Node, Node> prev = new Dictionary<Node, Node>();
List<Node> unvisited = new List<Node>();
Node source = graph[
SelectedUnit.GetComponent<UnitBasicScript>().HexX,
SelectedUnit.GetComponent<UnitBasicScript>().HexY,
SelectedUnit.GetComponent<UnitBasicScript>().HexZ];
// TargetNode float to int conversion
int targetVecXInt = (int)targetVec.x;
int targetVecYInt = (int)targetVec.y;
int targetVecZInt = (int)targetVec.z;
Node targetNode = graph[
targetVecXInt,
targetVecYInt,
targetVecZInt];
// Debug.Log(targetVecXInt + "_" + targetVecYInt + "_" + targetVecZInt);
dist[source] = 0;
prev[source] = null;
// Initialise everything to have infinity distance since no other
information available
// Some nodes might not eb erachable therefore infinity is reasonable
foreach (Node v in graph)
{
if (v != source)
{
dist[v] = Mathf.Infinity;
prev[v] = null;
}
unvisited.Add(v);
}
while (unvisited.Count > 0)
{
// u is unvisited node with shortest distance
Node u = null;
foreach (Node possibleU in unvisited)
{
if (u == null || dist[possibleU] < dist[u])
{
u = possibleU;
}
}
unvisited.Remove(u);
if (u == targetNode)
{
break;
}
foreach (Node v in u.neighbours)
{
float alt = dist[u] + u.Distanceto(v);
if (alt < dist[v])
{
dist[v] = alt;
prev[v] = u;
}
}
}
if (prev[targetNode] == null)
{
// No route to target
return null;
}
List<Node> currentPath = new List<Node>();
Node curr = targetNode;
while (prev[curr] != null)
{
currentPath.Add(curr);
curr = prev[curr];
}
currentPath.Reverse();
return currentPath;
} // End of generate path function
public class Node
{
public int x = 0;
public int y = 0;
public int z = 0;
public List<Node> neighbours;
public Node()
{
neighbours = new List<Node>();
}
public float Distanceto(Node n)
{
if (n == null)
{
Debug.Log("Error");
}
return Vector2.Distance(
new Vector3(x, y, z),
new Vector3(n.x, n.y, n.z));
}
}
}
That concludes the code, I understand everything within monobehaviour has to be indented and it is in my code but upon copying into stackoverflow it lost that indentation. Next are the pictures showing the incorrect paths the units take.
https://imgur.com/a/wEChdq3
If any other information is needed please let me know and I will be more than happy to provide it. Thank you so much again!

You are using a List instead of a priority queue, which is massively inefficient. Also, since your grid has a simple heuristic, you should consider using A* which will be much faster.

Despite all the glaring inefficiencies which i still haven't resolved, I have resolved the problems with the algorithm implementation. I was getting the distance between the tile grid coordinates which didn't take into account that on a hex grid a diagonal movement has the exact same distance cost as a horizontal movement. Therefore the solution was to get the distance once the node grid coordinates had been converted to world coordinates as this will ensure all distances between adjacent tiles are equal.
Hope this helps if anyone gets stuck with the same problem!

Related

Algorithm - Group Geographic Points in Rows

I have a set of x,y coordinates (in meters) for an orchard. I'm trying to automatically group rows and to number the trees inside the groups (rows) from top to bottom (based on a definition of what top and bottom are). Unfortunately, I have not been able to come up with a solution. See below for a picture as well as a link to a dataset.
An example of a number for a tree would be:
5-1
5-2
where 5 is the row number and 1 and 2 are the number of the tree inside the row.
The distance between trees in a row is about 6 meters, and between the rows about 12 meters. Thus a row can be defined where the neighboring trees are less than 7 meters away using Euclidean distance. Organizing the data by y coordinates does not work as the rows are not straight lines.
To make things more complicated, the rows need to be in order from either left to right or right to left.
Is there an existing algorithm that I can use? If not, what can I do to make this work? Some direction will be appreciated!
Data:
https://drive.google.com/file/d/1csLM4IpP3tMF0fqQkql6gIANHngX9A3c/view?usp=sharing
Thanks for all your help. See below for my solution. It is very messy, but I'm sure the idea will come through:
public class Group
{
public int group;
public int row;
public double highestRelDistance;
public Group(int _group)
{
group = _group;
}
}
public class Tree
{
public string name;
public Group group = new Group(0);
public int orderInGroup;
public double x;
public double y;
public string type;
public double relDistance;
}
public static void FitTreesToLine(List<Tree> treesList, out double m, out double c)
{
double[] xdata = treesList.Select(x => x.x).ToArray();
double[] ydata = treesList.Select(x => x.y).ToArray();
Tuple<double, double> p = Fit.Line(xdata, ydata);
double a = p.Item1; // == 10; intercept
double b = p.Item2; // == 0.5; slope
m = b;
c = a;
}
public static double FindDistanceBetweenPointAndLine(double m, double c, double point_x, double point_y )
{
double line_start_x = point_x * 0.5;
double line_start_y = m * line_start_x + c;
double line_end_x = point_x * 1.5;
double line_end_y = m * line_end_x + c;
double distance = Math.Abs((line_end_x - line_start_x) * (line_start_y - point_y) - (line_start_x - point_x) * (line_end_y - line_start_y)) /
Math.Sqrt(Math.Pow(line_end_x - line_start_x, 2) + Math.Pow(line_end_y - line_start_y, 2));
return (distance);
}
public static void DoCalculations(List<Tree> treeList)
{
//Calculate groups
Group curGroup = new Group(1);
groupList.Add(curGroup);
int searchFailures = 0;
treeGrouping:
List<Tree> noGroupList = treeList.Where(x => x.group.group == 0).ToList();
List<Tree> closeTreeList = new List<Tree>();
if (noGroupList.Count() >= 3 && searchFailures < 1000)
{
var refTree = noGroupList[0];
closeTreeList.Add(refTree);
for (int i = 1; i < noGroupList.Count(); i++)
{
double distance = Math.Sqrt(Math.Pow(refTree.x - noGroupList[i].x, 2) + Math.Pow(refTree.y - noGroupList[i].y, 2));
if (distance <= 7)
{
closeTreeList.Add(noGroupList[i]);
if (closeTreeList.Count() == 2)
{
//Fit linear curve
double m = 0;
double c = 0;
FitTreesToLine(closeTreeList, out m, out c);
//Find all points that is close to the line in original tree list
for (int j = 0; j < noGroupList.Count(); j++)
{
double distanceFromLine = FindDistanceBetweenPointAndLine(m, c, noGroupList[j].x, noGroupList[j].y);
if (distanceFromLine <= 8)
{
noGroupList[j].group = curGroup;
}
}
//Iterate current group
curGroup = new Group(curGroup.group + 1);
groupList.Add(curGroup);
goto treeGrouping;
}
}
}
refTree.group.group = 9999999;
//curGroup = new Group(curGroup.group + 1);
//groupList.Add(curGroup);
searchFailures++;
goto treeGrouping;
}
//Order trees within their groups
foreach (var group in groupList)
{
var groupTreeList = treeList.Where(x => x.group == group).OrderBy(x => x.y).ToList();
for (int i = 0;i < groupTreeList.Count();i++)
{
groupTreeList[i].orderInGroup = i + 1;
}
}
//Get max group rel distance
foreach (var group in groupList)
{
var items = treeList.Where(x => x.group == group);
if (items.Count() > 0)
{
group.highestRelDistance = items.OrderBy(x=>x.orderInGroup).Last().x;
}
}
//Order tree groups into rows
groupList = groupList.OrderBy(x => x.highestRelDistance).ToList();
for (int i = 0;i < groupList.Count();i++)
{
var items = treeList.Where(x => x.group == groupList[i]).ToList();
foreach (var item in items)
{
item.group.row = i;
}
}
//Generate tree names
foreach (var tree in treeList)
{
tree.name = "(" + tree.group.row.ToString().PadLeft(2,'0') + "-" + tree.orderInGroup.ToString().PadLeft(3, '0') + ")";
}
//Order list
treeList = treeList.OrderBy(x => x.group.row).ThenBy(x => x.orderInGroup).ToList();
}

IndexOutOfRangeException: Array Index is out of Range Unity

I was trying to build a Pathfinding A* code, but keep getting an Array Index error. I have tried tweaking with the x and y, but no luck so far.
Here is the code.
public int columns = 9; //Columns and rows to set up the board
public int rows = 9; //This number are the usable tiles, the complete board is 11 by 11
//With a border of 1 tile
public Node[,] graph;
//Class Node
public class Node
{
public List<Node> neighbours; //List of all the neighbours nodes (4Directions)
public Node()
{
neighbours = new List<Node>();
}
}
//Sets up the outer walls and floor (background) of the game board.
void BoardSetup()
{
for (int x = -1; x < columns + 1; x++)
{
for (int y = -1; y < rows + 1; y++)
{
Edited out Code, don't think has anything to do with my problem.
}
}
}
//Creates the graph to use Pathfinding
void GeneratePathfindingGraph()
{
//Create graph
graph = new Node[columns, rows];
for (int x = -1; x < columns + 1; x++)
{
for (int y = -1; y < rows + 1; y++)
{
graph[x, y] = new Node();
Add the 4 cardinal adjacent tiles
if (x != - 1)
graph[x, y].neighbours.Add(graph[x - 1, y]);
if (x != columns - 1)
graph[x, y].neighbours.Add(graph[x + 1, y]); //Where I am getting the error
if (y != - 1)
graph[x, y].neighbours.Add(graph[x, y - 1]);
if (y != rows - 1)
graph[x, y].neighbours.Add(graph[x, y + 1]);
}
}
}
//SetupScene initializes our level and calls the previous functions to lay out the game board
public void SetupScene(int level)
{
//Creates the outer walls and floor.
BoardSetup();
//Creates the Node map to use Pathfinding
GeneratePathfindingGraph();
}
}
I have edited out most of the code in this class, I don't think it has much to do with the problem.
Can you change all the initialization in the loop like x = -1;to x = 0;
Arrays always start's with a ZERO.

Ray/AABB Intersection Incorrect

I've tried to reimplement the Fast Graphics Gems Ray/AABB Intersection Method in C#:
// Based on "Fast Ray-Box Intersection" algorithm by Andrew Woo, "Graphics Gems", Academic Press, 1990
public unsafe Vector? IntersectionWith(Cuboid other) {
const int NUM_DIMENSIONS = 3;
Assure.Equal(NUM_DIMENSIONS, 3); // If that value is ever changed, this algorithm will need some maintenance
const byte QUADRANT_MIN = 0;
const byte QUADRANT_MAX = 1;
const byte QUADRANT_BETWEEN = 2;
// Step 1: Work out which direction from the start point to test for intersection for all 3 dimensions, and the distance
byte* quadrants = stackalloc byte[NUM_DIMENSIONS];
float* candidatePlanes = stackalloc float[NUM_DIMENSIONS];
float* cuboidMinPoints = stackalloc float[NUM_DIMENSIONS];
float* cuboidMaxPoints = stackalloc float[NUM_DIMENSIONS];
float maxDistance = Single.NegativeInfinity;
byte maxDistanceDimension = 0;
bool startPointIsInsideCuboid = true;
cuboidMinPoints[0] = other.X;
cuboidMinPoints[1] = other.Y;
cuboidMinPoints[2] = other.Z;
cuboidMaxPoints[0] = other.X + other.Width;
cuboidMaxPoints[1] = other.Y + other.Height;
cuboidMaxPoints[2] = other.Z + other.Depth;
for (byte i = 0; i < NUM_DIMENSIONS; ++i) {
if (StartPoint[i] < cuboidMinPoints[i]) {
quadrants[i] = QUADRANT_MIN;
candidatePlanes[i] = cuboidMinPoints[i];
startPointIsInsideCuboid = false;
}
else if (StartPoint[i] > cuboidMaxPoints[i]) {
quadrants[i] = QUADRANT_MAX;
candidatePlanes[i] = cuboidMaxPoints[i];
startPointIsInsideCuboid = false;
}
else {
quadrants[i] = QUADRANT_BETWEEN;
}
}
if (startPointIsInsideCuboid) return StartPoint;
// Step 2: Find farthest dimension from cuboid
for (byte i = 0; i < NUM_DIMENSIONS; ++i) {
// ReSharper disable once CompareOfFloatsByEqualityOperator Exact check is desired here: Anything other than 0f is usable
if (quadrants[i] != QUADRANT_BETWEEN && Orientation[i] != 0f) {
float thisDimensionDist = (candidatePlanes[i] - StartPoint[i]) / Orientation[i];
if (thisDimensionDist > maxDistance) {
maxDistance = thisDimensionDist;
maxDistanceDimension = i;
}
}
}
if (maxDistance < 0f) return null;
if (maxDistance - Length > MathUtils.FlopsErrorMargin) return null;
float* intersectionPoint = stackalloc float[NUM_DIMENSIONS];
for (byte i = 0; i < NUM_DIMENSIONS; ++i) {
if (maxDistanceDimension == i) {
intersectionPoint[i] = StartPoint[i] + maxDistance * Orientation[i];
if (cuboidMinPoints[i] - intersectionPoint[i] > MathUtils.FlopsErrorMargin || intersectionPoint[i] - cuboidMaxPoints[i] > MathUtils.FlopsErrorMargin) return null;
}
else intersectionPoint[i] = candidatePlanes[i];
}
Vector result = new Vector(intersectionPoint[0], intersectionPoint[1], intersectionPoint[2]);
if (!IsInfiniteLength && Vector.DistanceSquared(StartPoint, result) > Length * Length) return null;
else return result;
}
However, although it sort of works, I'm getting incorrect results on the following part of a unit test:
Cuboid cuboid = new Cuboid(frontBottomLeft: new Vector(0f, 7.1f, 0f), width: 0f, height: 5f, depth: 0f);
Ray testRayC = new Ray(startPoint: new Vector(30f, 30f, 30f), orientation: new Vector(-1f, -1f, -1f));
Assert.AreEqual(
null,
testRayC.IntersectionWith(cuboid)
);
I am expecting null from the call to testRayC.IntersectionWith(cuboid), but instead it returns a Vector(0, 12.1, 0), which is not a point on the ray at all.
So is it just a case of adding a final check that the calculated point is on the ray? Or (and this is what I suspect), have I made an error in transcribing the code? I have double and triple checked but didn't see anything obvious...
The problem in your code is when you do if (maxDistanceDimension == i) {. The original code checks if (whichPlane != i) {. I don't have your data structures, but a fix should look like:
for (byte i = 0; i < NUM_DIMENSIONS; ++i)
{
if (maxDistanceDimension != i)
{
intersectionPoint[i] = StartPoint[i] + maxDistance * Orientation[i];
if (intersectionPoint[i] < cuboidMinPoints[i] - MathUtils.FlopsErrorMargin || intersectionPoint[i] > cuboidMaxPoints[i] + MathUtils.FlopsErrorMargin)
return null;
}
else
{
intersectionPoint[i] = candidatePlanes[i];
}
}
Next, the following isn't in the original code. What is this for?
if (maxDistance - Length > MathUtils.FlopsErrorMargin)
return null;
If you are trying to check if the hit is within the extent of the ray, this may be a bug. Given that your Orientation does not appear to be normalized, maxDistance is not necessarily in units of length. This may not matter in the original algorithm, but if you are going to check maxDistance against some other length you need to normalize Orientation (make it dimensionless) so that
thisDimensionDist = (candidatePlanes[i] - StartPoint[i]) / Orientation[i];
will have units of length.
Incidentally, in the original I think the following is wrong:
if(inside) {
coord = origin;
return (TRUE);
}
Assuming this code is c and not c++, this simply sets the the coord pointer to have the same reference as the origin pointer, which will have no effect on the caller. This issue doesn't apply to your version, however.
Also, in the course of looking at this, I made a more literal c# transcription of the algorithm here:
public static class RayXCuboid
{
enum HitQuadrant
{
Right = 0,
Left = 1,
Middle = 2,
}
const int Dimension = 3;
[Conditional("DEBUG")]
static void AssertValidArguments<TDoubleList>(params TDoubleList[] args) where TDoubleList : IList<double>
{
Debug.Assert(Dimension == 3);
foreach (var list in args)
Debug.Assert(list != null && list.Count == Dimension);
}
public static bool HitBoundingBox<TDoubleList>(TDoubleList minB, TDoubleList maxB, TDoubleList origin, TDoubleList dir, TDoubleList coord) where TDoubleList : IList<double>
{
AssertValidArguments(minB, maxB, origin, dir, coord);
HitQuadrant[] quadrant = new HitQuadrant[Dimension];
double[] maxT = new double[Dimension];
double[] candidatePlane = new double[Dimension];
/* Find candidate planes; this loop can be avoided if
rays cast all from the eye(assume perpsective view) */
bool inside = true;
for (int i = 0; i < Dimension; i++)
if (origin[i] < minB[i])
{
quadrant[i] = HitQuadrant.Left;
candidatePlane[i] = minB[i];
inside = false;
}
else if (origin[i] > maxB[i])
{
quadrant[i] = HitQuadrant.Right;
candidatePlane[i] = maxB[i];
inside = false;
}
else
{
quadrant[i] = HitQuadrant.Middle;
}
/* Ray origin inside bounding box */
if (inside)
{
CopyTo(origin, coord);
return true;
}
/* Calculate T distances to candidate planes */
for (int i = 0; i < Dimension; i++)
if (quadrant[i] != HitQuadrant.Middle && dir[i] != 0.0)
maxT[i] = (candidatePlane[i] - origin[i]) / dir[i];
else
maxT[i] = -1.0;
/* Get largest of the maxT's for final choice of intersection */
int whichPlane = 0;
for (int i = 1; i < Dimension; i++)
if (maxT[whichPlane] < maxT[i])
whichPlane = i;
/* Check final candidate actually inside box */
if (maxT[whichPlane] < 0.0)
{
FillWithDefault(coord);
return false;
}
for (int i = 0; i < Dimension; i++)
if (whichPlane != i)
{
coord[i] = origin[i] + maxT[whichPlane] * dir[i];
if (coord[i] < minB[i] || coord[i] > maxB[i])
{
FillWithDefault(coord);
return false;
}
}
else
{
coord[i] = candidatePlane[i];
}
return true; /* ray hits box */
}
static void FillWithDefault<T>(IList<T> list)
{
for (int i = 0; i < list.Count; i++)
list[i] = default(T);
}
static void CopyTo<T>(IList<T> from, IList<T> to)
{
int arrayIndex = 0;
foreach (var item in from)
to[arrayIndex++] = item;
}
}

A* path-finding algorithm not always finding shortest route C# XNA

I'm creating a simple XNA C# top-down game. In the game you will be followed by an enemy around a maze like map. ATM I have it set so that when the game starts the enemy finds the shortest path to you (which it does). A new path is found every second, if the player has moved.
The game always finds a route to you, but seems to have trouble finding the shortest route to a moving target, sometimes entering a loop whiles you're moving, and when you stop, the enemy travels half way round the map to find you, even if you're only a few steps away. It doesn't appear to be the heuristic, as I removed that (making it dijkstra) and it still appeared to have the same problem. I assume it's something simple that I overlooked, but am having trouble figuring out what.
Thanks in advance
class PathFinder
{
private Cell[,] map = Game1.cellArray;
public List<Cell> calculate(int currX, int currY, int destX, int destY)
{
resetMap();
Debug.WriteLine("starting over");
List<Cell> path = new List<Cell>();
List<Cell> openList = new List<Cell>();
List<Cell> closedList = new List<Cell>();
Cell startPos = map[currY, currX];
Cell endPos = map[destY, destX];
startPos.closed = true;
closedList.Add(startPos);
Cell curPos = startPos;
while (!closedList.Contains(endPos))
{
//add adjacent nodes to list, discover their scores
foreach (Cell v in getAdj(curPos))
{
if (v.walkable == true && v.closed == false)
{
if (!openList.Contains(v))
{
v.parent = curPos;
v.H = getH(v, endPos);
v.G = v.parent.G + 10;
v.F = v.G + v.H;
openList.Add(v);
}
//if square already on list would benefit from current tile being parent, make it so
else if (curPos.G + 10 < v.G)
{
v.parent = curPos;
v.G = v.parent.G + 10;
v.F = v.G + v.H;
}
}
}
if (openList.Count <= 1) {/*Console.WriteLine("Returned NULL");*/ return null; }
curPos = openList[1];
curPos.closed = true;
closedList.Add(curPos);
openList[1] = openList[openList.Count - 1];
openList.RemoveAt(openList.Count - 1);
openList.OrderBy(o => o.F);
openList.Reverse();
}
//backtrack to discover path
Cell curNode = endPos;
path.Add(endPos);
while (curNode != startPos)
{
curNode = curNode.parent;
path.Add(curNode);
Debug.WriteLine("Path// X: " + curNode.xLocation + " Y: " + curNode.yLocation);
}
path.Reverse();
return path;
}
//finds heuristic of current square by checking distance to destination ignoring walls
private int getH(Cell curPos, Cell endPos)
{
int diffX = curPos.xLocation - endPos.xLocation;
int diffY = curPos.yLocation - endPos.yLocation;
if (diffX < 0)
{
diffX *= -1;
}
if (diffY < 0)
{
diffY *= -1;
}
return ((diffX + diffY) * 10);
}
//get list of adjacent Cells
private List<Cell> getAdj(Cell curPos)
{
List<Cell> adjList = new List<Cell>();
if (curPos.xLocation - 1 >= 0){
adjList.Add(map[curPos.yLocation, curPos.xLocation - 1]);
}
if (curPos.xLocation < 19)
{
adjList.Add(map[curPos.yLocation, curPos.xLocation + 1]);
}
if (curPos.yLocation - 1 >= 0)
{
adjList.Add(map[curPos.yLocation - 1, curPos.xLocation]);
}
if (curPos.yLocation < 19)
{
adjList.Add(map[curPos.yLocation + 1, curPos.xLocation]);
}
return adjList;
}
private void resetMap()
{
//reset Cells to default values
for (int r = 0; r < map.GetLength(1); r++)
{
for (int c = 0; c < map.GetLength(0); c++)
{
map[r, c].reset();
}
}
}
}
Just worked out the problem. Wasn't sorting before changing the currently selected tile. I had assumed I was scoring them incorrectly lol.

"Infinite" world problems

I'm creating a minecraft like voxel engine thing in xna, and have started about implementing "infinite" world but have ran into a few problems. One such problem is that the following line always seems to apply the oppisite (eg if the player moved X+ REGION_SIZE_X it would assign Direction.X_DECREASING instead of the expected Direction.X_INCREASING).
Thread regionLoader;
public void CheckPlayer()
{
Direction direction = Direction.MAX;
float distancetraveledx = player.Origin.X - player.Position.X;
float distancetraveledy = player.Origin.Y - player.Position.Y;
float distancetraveledz = player.Origin.Z - player.Position.Z;
if (distancetraveledx > Variables.REGION_SIZE_X)
{
direction = Direction.XIncreasing;
player.Position = new Vector3(player.Origin.X, player.Position.Y, player.Position.Z);
}
else if (distancetraveledx < -Variables.REGION_SIZE_X)
{
direction = Direction.XDecreasing;
player.Position = new Vector3(player.Origin.X, player.Position.Y, player.Position.Z);
}
else if (distancetraveledz > Variables.REGION_SIZE_Z)
{
direction = Direction.ZIncreasing;
player.Position = new Vector3(player.Position.X, player.Position.Y, player.Origin.Z);
}
else if (distancetraveledz < -Variables.REGION_SIZE_Z)
{
direction = Direction.ZDecreasing;
player.Position = new Vector3(player.Position.X, player.Position.Y, player.Origin.Z);
}
if (direction != Direction.MAX)
{
regionManager.direction = direction;
regionLoader = new Thread(new ThreadStart(regionManager.ShiftRegions));
regionLoader.Start();
}
}
So what this is doing is checking if the player has moved REGION_SIZE from it's origin and if it has, reset the component of the position which has moved over that boundary.
This then calls the following function:
public void ShiftRegions()
{
// Future and current arrays are to avoid moving elements twice (or maybe I am thinking about it in the wrong way...)
future_regions = new Region[(int)Variables.RENDER_DISTANCE, (int)Variables.RENDER_DISTANCE, (int)Variables.RENDER_DISTANCE];
for (short j = 0; j < future_regions.GetLength(0); j++)
{
for (short m = 0; m < future_regions.GetLength(1); m++)
{
for (short i = 0; i < future_regions.GetLength(2); i++)
{
future_regions[j, m, i] = new Region(world, new Vector3i(new Vector3(j, m, i)));
switch (direction)
{
// This appears to work as XDecreasing
case Direction.XIncreasing:
// If it is not at the back
if (j != future_regions.GetLength(0) - 1)
{
// Make the regions look like they are scrolling backward
future_regions[j, m, i] = regions[j + 1, m, i];
future_regions[j, m, i].Index = new Vector3i((uint)j, (uint)m, (uint)i);
// In the next call the vertex buffer will be regenerated thus placing the "blocks" in the correct position
future_regions[j, m, i].Dirty = true;
}
// If it is the front most region to which the player is traveling ing
if (j == 0)
{
// New the region setting the Generated flags to false thus allowing it to be regenerated
future_regions[j, m, i] = new Region(world, new Vector3i(new Vector3(j, m, i)));
}
// If it is at the back of the regions
if (j == future_regions.GetLength(0) - 1)
{
//store region
}
break;
case Direction.XDecreasing:
break;
}
}
}
}
generator.build(ref future_regions);
direction = Direction.MAX;
this.regions = future_regions;
}
This actually moves the regions which causes the scrolling effect and the new regions which appear at the front. Which doesn't seem to work.
I was thinking istead of actually "moving" the regions i could just assign different offsets and move in the world matrix but when I do so i just get a blue screen...
Here is the rest of the code:
Generator class function:
public virtual void build(ref Region[,,] regions)
{
for (short j = 0; j < regions.GetLength(0); j++)
{
for (short m = 0; m < regions.GetLength(1); m++)
{
for (short i = 0; i < regions.GetLength(2); i++)
{
OutputBuffer.Add("Generating...");
if (regions[j, m, i].Generated == false)
{
regions[j, m, i].Dirty = true;
regions[j, m, i].Generated = true;
for (short x = 0; x < Variables.REGION_SIZE_X; x++)
{
for (short y = 0; y < Variables.REGION_SIZE_Y; y++)
{
for (short z = 0; z < Variables.REGION_SIZE_Z; z++)
{
regions[j, m, i].Blocks[x, y, z] = new Block();
Vector3i relativeBlock = new Vector3i(new Vector3(x + Variables.REGION_SIZE_X * j, y + Variables.REGION_SIZE_Y * m, z + Variables.REGION_SIZE_Z * i));
if (x == 0 || x == 16 || x == 32 || z == 0 || z == 16 || z == 32 || y == 0 || y == 16 || y == 32)
{
regions[j, m, i].Blocks[x, y, z].BlockType = BlockType.dirt;
}
else
{
regions[j, m, i].Blocks[x, y, z].BlockType = BlockType.grass;
}
}
}
}
}
}
}
}
}
and the code that checks if the region's buffers need to be rebuilt:
public void Update(World world)
{
this.world = world;
for (short j = 0; j < regions.GetLength(0); j++)
{
for (short m = 0; m < regions.GetLength(1); m++)
{
for (short i = 0; i < regions.GetLength(2); i++)
{
if (regions[j, m, i].Dirty &&
regions[j, m, i].Generated)
{
regions[j, m, i].BuildVertexBuffers();
}
}
}
}
}
By the way, if Dirty is set to true this means that the buffers need to be regenerated.
Any ideas why this is not creating new regions at the front and why it is not scrolling properly?
EDIT: I was just thinking logically and that my idea with the changing the regions position in the array will not change it's world position, and like I said above with transforming them to the correct positions instead of copying them - that seems like the most logical step. Well the thing is I may have to copy some to different places in the regions array because the array may become spaghetti in no time if you just transform them...
Thanks, Darestium
Just looking at this part
float distancetraveledx = player.Origin.X - player.Position.X;
You're subtracting position from origin, you should probably reverse the order and do this
float distancetraveledx = player.Position.X - player.Origin.X;
For example, if the player has moved from (0, 0, 0) to (10, 0, 0), the player has moved 10 units in the X direction, but your 'distancetraveledx' would give you 0 - 10, or -10.
Unrelated to your question:
Your variables will be easier to use if you follow CamelCase, with the first letter lower or upper-case:
float distanceTraveledX = player.Position.X - player.Origin.X;
In C# the convention is to have class names with the first letter capitalized, and variables with the first letter lower-case, like such:
MyClass myClass;

Categories