I'm trying to implement an A star searching method for a college work, so i kinda need to make it from scratch, but I'm having some trouble making it work the correct way.
Here's a picture of my problem:
As you can see, it does find a path, but not the easiest one.
Here is my implement:
public List<node> updateAstar(){
//clear all the lists
openedNodes.Clear();
closedNodes.Clear();
nodesToLook.Clear();
//check if starpos and endpos are okay
if(startPos!=null && endPos!=null){
float F;
node currentNote=Grid.getNodeAtPos(startPos);
openedNodes.Add(currentNote);
int i = 0;
int size = 100;
while(currentNote.type!=tilesType.END){
if(i<=size){ //debugging purpose. prevent infinite loop
nodesToLook.Clear();
foreach(node nearNode in currentNote.getNearestTiles()){
if(closedNodes.Find(r => ((r.pos.x==nearNode.pos.x)&&(r.pos.y==nearNode.pos.y)))==null){
nodesToLook.Add(nearNode);
}
}
float bestValue=float.PositiveInfinity;
node bestNode=new node();
foreach(node lookingNode in nodesToLook){
//check if current node is not on the closed list
if((closedNodes.Find(r => ((r.pos.x==lookingNode.pos.x)&&(r.pos.y==lookingNode.pos.y)))==null)
&&(openedNodes.Find(r => ((r.pos.x==lookingNode.pos.x)&&(r.pos.y==lookingNode.pos.y)))==null)
&& lookingNode.type!=tilesType.BLOCK){
//calculate F=G+H
//assume path number is 0 for the question purpose
F=lookingNode.G[pathNumber]+lookingNode.H[pathNumber];
if(F<bestValue){
bestValue=F;
bestNode=lookingNode;
}else
closedNodes.Add(lookingNode);
}
}
openedNodes.Add(bestNode);
currentNote=bestNode;
i++;
}else{
Debug.Log("Error getting better path");
break;
}
}
}else Debug.Log("Current path does not have an startpos nor endpos");
return openedNodes;
}
Here is how I instantiate each node (I save it on a matrix):
coordinate posAux=new coordinate();
this.myNodes=new node[columnNumber,lineNumber];
this.lineNumber=lineNumber;
this.columnNumber=columnNumber;
for(int y=0;y<lineNumber;y++){ // Y Desce = linhas
for(int x=0; x<columnNumber; x++){ // X vai pro lado = colunas
//create a node based on matrix position
posAux.Set(x, y);
tilesType type;
node current=new node(posAux);
//update up and left nodes
//"nodeDireita" means rightNode and "nodeEsquerda" means left node
if(x-1>=0){
current.nodeEsquerda=myNodes[x-1, y];
myNodes[x-1, y].nodeDireita=current;
}
if(y-1>=0){
current.nodeAcima=myNodes[x, y-1];
current.nodeAcima.nodeAbaixo=current;
}
//UNity stuff to set type of node visually based on what object is in it
Collider[] colliders;
if((colliders = Physics.OverlapSphere(coordinate.gridToUnity(posAux), 3f)).Length >0){
foreach(Collider collider in colliders){
objScript obj = collider.gameObject.GetComponent<objScript>();
current.type=obj.type;
if(current.type==tilesType.START){
path Path = new path (obj.pos, obj.posEnd, this);
addPath (Path);
Path.numeroPath=paths.IndexOf(Path);
}
}
}
myNodes[x,y]=current;
}
}
//adicionar vetor[] para H e G com numero de paths nos nodes
//create a vector for multiple paths in each node
int numeroPaths = paths.Count;
for (int y = 0; y < lineNumber; y++) {
for (int x = 0; x < columnNumber; x++) {
myNodes [x, y].H=new float[numeroPaths];
myNodes [x, y].G=new float[numeroPaths];
}
}
//adicionar Heuristica e G para cada node em cada path
//calculate heuristic and G for each node in each path
foreach (path Path in paths) {
coordinate start=Path.startPos, end=Path.endPos;
int numeroPath=paths.IndexOf(Path);
for (int y = 0; y < lineNumber; y++) {
for (int x = 0; x < columnNumber; x++) {
coordinate pos = myNodes [x, y].pos;
//G e H as manhattan distance
/*Mathf.Sqrt(Mathf.Pow((start.x - pos.x), 2) + Mathf.Pow((start.y - pos.y), 2)); euclidian-does not apply x.x */
myNodes [x, y].H[numeroPath]=Mathf.Abs(pos.x-end.x) + Mathf.Abs(pos.y-end.y);
myNodes [x, y].G[numeroPath]=Mathf.Abs(start.x-pos.x) + Mathf.Abs(start.y-pos.y);
}
}
}
Code refs:
--node is a custom class that holds "G" and "H" that I use the Manhattan formula to define, "x", "y", "BLOCK" or "NORMAL"(availability of the position)
--openedNodes is a List that I put the correct nodes for the path
--closedNodes are the nodes I checked, but have bigger "F" values;
--nodesToLook are the neighbor nodes for checking.
I appreciate any help.
Thanks.
Since you haven't posted your whole code, i have not the slightest clue what you are doing with your nodes, but what i can see:
You are not updating G. G is not a heuristic, it is the actual cost of reaching that node. Aka: nextTile.G = currentTile.G + distanceBetween(currentTile,nextTile)
You are only adding the best option to the open list. So instead of checking all 4, you only check 1
i can go on, but your whole algorithm does not work like A*, at all. Fixing your code means rewriting it completely.
The algorithm is realy easy. The pseudocode on wikipedia can be copied and implemented directly. It just seems you missed a couple of steps there and implemented a lot of things incorrectly.
Related
I am trying to spawn the neighbours of my path finder but when doing so the original path (path) blue also becomes covered up. I have tried switching of the order of spawn but this does not seem to help the problem. Quite new to Unity and would appreciate any insight into how I might go about spawning the path the neighbours without losing the original blue path.
Obtaining path neighbours; spawning the path + neighbours
for (int i = 1; i < path.Count; i++)
{
var pathCellPosition = path[i];
pathCellPosition.Position = new Vector3(pathCellPosition.Position.x, pathPrefab.transform.position.y, pathCellPosition.Position.z);
List<Node> neighbours = GetNeighbours(pathCellPosition);
var p = Instantiate(pathPrefab, pathCellPosition.Position, Quaternion.identity, PathCells);
foreach (Node n in neighbours)
{
neighbourPrefab.GetComponentInChildren<Text>().text = n.Cost.ToString();
Instantiate(neighbourPrefab, n.Position, Quaternion.identity, p.transform);
}
}
Original path
Path getting covered by neighbours
Implemented suggestions fixing part of the problem
The tiles around now seem to appear but when applying the same solution to implement neighbours of neighbours it seems to cause an overlap with the original neighbouring nodes.
Code changes:
HashSet<Node> visitedNodes = new HashSet<Node>();
for (int i = 1; i < path.Count; i++)
{
var pathCellPosition = path[i];
visitedNodes.Add(path[i]);
pathCellPosition.Position = new Vector3(pathCellPosition.Position.x, pathPrefab.transform.position.y, pathCellPosition.Position.z);
List<Node> neighbours = GetNeighbours(path[i]);
int fCost = DistanceFromStart(pathCellPosition) + HCostDistanceFromEndNode(pathCellPosition) + pathCellPosition.Cost;
pathPrefab.GetComponentInChildren<Text>().text = fCost.ToString();
var p = Instantiate(pathPrefab, pathCellPosition.Position, Quaternion.identity, PathCells);
for(int x = 0; x < neighbours.Count; x++)
{
var node = neighbours[x];
if (visitedNodes.Contains(node))
{
continue;
}
visitedNodes.Add(node);
fCost = DistanceFromStart(node) + HCostDistanceFromEndNode(node) + node.Cost;
neighbourPrefab.GetComponentInChildren<Text>().text = fCost.ToString();
Instantiate(neighbourPrefab, node.Position, Quaternion.identity, p.transform);
List<Node> NeighBourOfNeighbourNodes = GetNeighbours(node);
if(NeighBourOfNeighbourNodes.Count > 0)
{
for (int y = 0; y < NeighBourOfNeighbourNodes.Count; y++)
{
var neighbourNode = NeighBourOfNeighbourNodes[y];
if (visitedNodes.Contains(neighbourNode))
{
continue;
}
visitedNodes.Add(neighbourNode);
fCost = DistanceFromStart(neighbourNode) + HCostDistanceFromEndNode(neighbourNode) + neighbourNode.Cost;
nofn.GetComponentInChildren<Text>().text = fCost.ToString();
Instantiate(nofn, neighbourNode.Position, Quaternion.identity, p.transform);
}
}
}
If I don't draw neighbour of neighbours, this looks right. But if I do neighbour of neighbours the purple ones don't appear as much as they should.
Before:
After:
A typical simple solution is to check if the node is part of the path before adding. A HashSet can be good for this, but you will need something that can be reliably compared.
Perhaps something like this:
var visitedNodes = new HashSet<Node>(path);
for (int i = 1; i < path.Count; i++)
{
var pathCellPosition = path[i];
pathCellPosition.Position = new Vector3(pathCellPosition.Position.x, pathPrefab.transform.position.y, pathCellPosition.Position.z);
List<Node> neighbours = GetNeighbours(pathCellPosition);
var p = Instantiate(pathPrefab, pathCellPosition.Position, Quaternion.identity, PathCells);
foreach (Node n in neighbours)
{
if(visitedNodes.Contains(n))
continue;
visitedNodes.Add(n);
neighbourPrefab.GetComponentInChildren<Text>().text = n.Cost.ToString();
Instantiate(neighbourPrefab, n.Position, Quaternion.identity, p.transform);
}
}
You can also use the same approach to ensure a neighbour is not added twice.
If you want multiple levels from the node you might need to do something like a breath first search, ideally keeping track of the distance from the original path. The following generic code should traverse the graph in a breadth first manner, just use the path as the first parameter, and the GetNeighbours method for the second.
public static IEnumerable<(T Node, int Distance)> GetNeighborsWithDistance<T>(IEnumerable<T> self, Func<T, IEnumerable<T>> selector)
{
var stack = new Queue<(T Node, int Distance)>();
var visited = new HashSet<T>();
foreach (var node in self)
{
stack.Enqueue((node, 0));
}
while (stack.Count > 0)
{
var current = stack.Dequeue();
if(visited.Contains(current.Node))
continue;
yield return current;
visited.Add(current.Node);
foreach (var child in selector(current.Node))
{
stack.Enqueue((child, current.Distance+ 1));
}
}
}
This should be guaranteed to return nodes in order of distance from the original path, so just do .TakeWhile(p => p.Distance < DesiredDistanceFromOriginalPath) to get as many nodes as you want.
I am trying to work out a method by which I can shift items in an array of objects, but keep some items in the array exactly where they are, having all items above dropping bellow the fixed items. Think in terms of a Gem Matching game, where gems can be matched. Here are the rules:
Board can be of any X,Y size
Board is filled with Gems (that can break and therefore be destroyed) and Rocks that cannot break (and therefore cannot be destroyed)
Rocks are stationary in their position and cannot move.
When user matches gems, they get destroyed and all the remaining gems drop down to the next available space.
If gems that are matched are below a Rock, then gems above the rock should skip the rock position and fill the bottom most open position.
Rocks can be stacked n deep, vertically or horizontally.
My code works, but only if there is one rock. Please note, this is written in C# for Unity, but the solution is not Unity specific (or even c#). If there are two Rocks stacked on top each other, like this:
[Gem_1]
[Rock_1]
[Rock_2]
[Gem_2]
[Gem_3]
[Gem_4]
And Gems 2,3 and 4 are destroyed, then the gems should all fall and look like this:
[Gem_7]
[Rock_1]
[Rock_2]
[Gem_6]
[Gem_5]
[Gem_1]
But, with my code, when they fall, they look like this:
[Gem_7]
[Rock_1]
[Rock_2]
[Gem_1]
[Gem_6]
[Gem_5]
Here is my Updated code:
private gem_script NextGem(Vector2Int currentGem)
{
for (int y = 0; y < board_height; y++ )
{
if(allGems[currentGem.x, y] != null && isGem(layoutStore[currentGem.x, y]) && y > 0){
return allGems[currentGem.x, y].GetComponent<gem_script>();
}
}
return null;
}
private IEnumerator ScrollNewGems()
{
for (int x = 0; x < board_width; x++)
{
for (int y = 0; y < board_height; y++)
{
if (layoutStore[x, y] != Gem_Type.empty)
{
if (allGems[x, y] == null)
{
Vector2Int current_gem_pos = new Vector2Int(x, y);
gem_script next_gem = NextGem(current_gem_pos);
if(next_gem != null)
{
Vector2Int next_gem_pos = next_gem.GetGemPos();
next_gem.SetGemXYPos(new Vector2Int(current_gem_pos.x, current_gem_pos.y));
allGems[x, y] = allGems[next_gem_pos.x, next_gem_pos.y];
allGems[next_gem_pos.x, next_gem_pos.y] = null;
}
}
}
}
}
}
Edit
I updated my code, but this only works if there is one Gem to drop down. If there are more than one gems, then they stay on the top
There was a typo in my edited script. The nextGem function should not start a y=0 but instead at currentGem.y. Otherwise, it starts from the beginning. Complete working code is:
private gem_script NextGem(Vector2Int currentGem)
{
for (int y = currentGem.y; y < board_height; y++ )
{
if(allGems[currentGem.x, y] != null && isGem(layoutStore[currentGem.x, y])){
return allGems[currentGem.x, y].GetComponent<gem_script>();
}
}
return null;
}
private IEnumerator ScrollNewGems()
{
gameState = GameState.Wait;
yield return new WaitForSeconds(.2f);
for (int x = 0; x < board_width; x++)
{
for (int y = 0; y < board_height; y++)
{
if (layoutStore[x, y] != Gem_Type.empty)
{
if (allGems[x, y] == null)
{
Vector2Int current_gem_pos = new Vector2Int(x, y);
gem_script next_gem = NextGem(current_gem_pos);
if(next_gem != null)
{
Vector2Int next_gem_pos = next_gem.GetGemPos();
next_gem.SetGemXYPos(new Vector2Int(current_gem_pos.x, current_gem_pos.y));
allGems[x, y] = allGems[next_gem_pos.x, next_gem_pos.y];
allGems[next_gem_pos.x, next_gem_pos.y] = null;
}
}
}
}
}
}
I am making a precipitation map for an experiment with Perlin noise, where I also create biomes.
I am using temperature and precipitation to determine a biome of a certain pixel, I have the program for the precipitation, but the multiple For loops in the program is making Unity3d become unresponsive for a long period of time.
Does anyone know how to make this faster? I have looked around on the internet, but I couldn't find an answer
Here is my code:
public float[,] PrecipMap (float[,] noise,int mapWidth,int mapHeight)
{
float[,] precipMap = new float[mapWidth, mapHeight];//array that it to be used for precipitation
float[,] waterTiles = WaterTiles(mapHeight, mapWidth, noise);//array with all values that are water
for (int y = 0; y < mapHeight; y++)
{
for(int x = 0; x < mapWidth; x++)
{
float[] distance = new float[count];//distance between pixel and water tile
for(int wy = 0; wy < mapHeight; wy++)
{
for(int wx = 0; wx < mapWidth; wx++)
{
if (waterTiles[x, y] == 1) { // if the selected tile in water tiles has water
for(int i = 0; i < count; i++)// distance makes an array of all possible distances.
{
distance[i] = Mathf.Sqrt(((x + -wx) * (x + -wx)) + ((y +-wy) * (y +-wy)));// finds distance between pixel and water tile
}
}
}
Array.Sort(distance); /// sorts distance from least to greatest
precipMap[x, y] = distance[count-1];//enters in distance
}
}
}
return precipMap;
}
If anyone could help, I would be thankful. I am very grateful for any help/criticism.
As was already commented your loops run quite often and by default everything in unity runs on the main thread. So before Unity can render the next frame, you method has to finish.
Additionally you run Array.Sort which is quite expensive and allocate new arrays for each iteration which also keeps your GC busy!
Then I don't see what your distance array is good for. The variable i is Neve used in yoir calculation so you just fill all 1500 entries with the same value, sort it and read out the last one ... They will all be equal anyway so this is completely redundant!
Also you check redundantly
if (waterTiles[x, y] == 1)
which would be enough to check once as soon as you have x and y and is unnecessarily done further below within the nested loops - loops that you could have skipped completely if the condition is false anyway.
You could actually move your entire method to a thread and wait for the result using async await (also see Using async-await in Unity3D). Unfortunately I'm no async-await expert but this should do it
public async void CalculatePrecipMap(float[,] noise, int mapWidth, int mapHeight, Action<float[,]> callback)
{
var result = await PrecipMap(noise, mapWidth, mapHeight);
callback?.Invoke(result);
}
private async Task<float[,]> PrecipMap (float[,] noise,int mapWidth,int mapHeight)
{
//array that it to be used for precipitation
float[,] precipMap = new float[mapWidth, mapHeight];
//array with all values that are water
float[,] waterTiles = WaterTiles(mapHeight, mapWidth, noise);
for (var y = 0; y < mapHeight; y++)
{
for(var x = 0; x < mapWidth; x++)
{
// Skip as soon as possible
// Completely unnecessary to run your loop at all if this is already false
if (waterTiles[x, y] != 1) continue;
// if the selected tile in water tiles has water
for(var wy = 0; wy < mapHeight; wy++)
{
for(var wx = 0; wx < mapWidth; wx++)
{
// i is nowhere used in below calculation!
// I don't see any reason to use an distance array here at all !!!
precipMap[x, y] = Mathf.Sqrt(((x + -wx) * (x + -wx)) + ((y +-wy) * (y +-wy)));
}
}
}
}
return precipMap;
}
You would then use it using a lambda expression like
CalculatePrecipMap(someNoise, someWidth, someHeight, result =>
{
// Do something with the result
}
or using a method like
CalculatePrecipMap(someNoise, someWidth, someHeight, OnResultReady);
private void OnResultReady (float[,] result)
{
// Do something with the result
}
Note: Typed on smartphone but I hope the idea gets clear
Okay, so this is the code I have but I am trying to figure out how to change this foreach loop to an if statement. I've been trying to figure it out by myself but it doesn't seem to be working the way I want it to. So if anyone could help that would be much appreciated. Just as something extra, I am still a noob at C#. :)
// Used to make sure that the script can read the text file
using (StreamReader sr = new StreamReader ("Maze.txt"))
{
lineArray = File.ReadAllLines("Maze.txt");
// Access one line at a time
foreach (string line in lineArray)
{
// Reset X axis for each line of the file
int x = 0;
// Access each character in the file
foreach (char c in line)
{
string currentPosition = c.ToString();
newFilePosition = Convert.ToInt32(currentPosition);
if (newFilePosition == 1)
{
// Create a cube on the X axis
NewCubePosition = new Vector3 (x, y, 0);
Instantiate (g_mazeCubes, NewCubePosition, Quaternion.identity);
}
// Loop until X axis is done
x++;
}
// Loop until Y axis is done
y++;
}
}
If you are refering to transform a foreach to a for that test an condition. Making this transformation in code means you don't need to set x=0 and y=0 before and increment them inside foreach loop.
Using for you will do initialization, the condition, and the afterthought of x and y and iterate through array at once.
As many say you don't neet StreamReader. File.ReadAllLines opens and close the file for you.
lineArray = File.ReadAllLines("Maze.txt");
// Access one line at a time
for (y = 0; y < lineArray.Count(); y++)
{
string line = lineArray[y];
// Access each character in the file
for (int x = 0 ; x < line.Count(); x++)
{
char c = line[x];
string currentPosition = c.ToString();
newFilePosition = Convert.ToInt32(currentPosition);
if (newFilePosition == 1)
{
// Create a cube on the X axis
NewCubePosition = new Vector3 (x, y, 0);
Instantiate (g_mazeCubes, NewCubePosition, Quaternion.identity);
}
}
}
You can reduce the code with a bit of LINQ, but I don't think there's anything you can do to get rid of the inner loop.
var lineArray = File.ReadAllLines("Maze.txt");
for (var y = 0; y < lineArray.Length; y++)
{
foreach (var c in lineArray[y].Select((value, i) => new { Character = value, Index = i })
.Where(x => Convert.ToInt32(x.Character) == 1))
{
Instantiate(g_mazeCubes, new Vector3(c.Index, y, 0), Quaternion.identity);
}
}
(You also don't need the StreamReader)
In my opinion if you try to reduce the the code further than this you'll obfuscate the intent with no benefit.
I am working on a website in which I have the User's Location Lat and Long saved in my Location table. Now I want to use a filter SearchByDistance which have values: 5km, 10km, 15km through which the user can get the results according to the specified Range.
Now what I actually wants to do is to get the input from the user say 5km and get the results from my table which falls with in the range of user's saved LatLong in the Location table and 5km to that LatLong. I google on this and found some links like:
How do I find the lat/long that is x km north of a given lat/long
http://msdn.microsoft.com/en-us/library/system.device.location.geocoordinate.getdistanceto%28v=vs.110%29.aspx
But I am unable to get my requirement from the above. Please help. Thanks
I think your approach can be, first create a circle (your center will be user lat and long) with the given radius say 5KM or 10Km and then find the rows from the Polygon Rings. I wrote it for esri maps. Hope it will solve your problem
Polygon areaOfInterset;
void CreateCircle(double radius)
{
var point = new MapPoint(Your Lat and Long);// This will be user lat and long on which you want to draw a circle
var graphic = new Graphic();
var circle = new ESRI.ArcGIS.Client.Geometry.PointCollection();
int i = 0;
while (i <= 360)
{
double degree = (i * (Math.PI / 180));
double x = point.X + Math.Cos(degree) * radius;
double y = point.Y + Math.Sin(degree) * radius;
circle.Add(new MapPoint(x, y));
i++;
}
var rings = new ObservableCollection<ESRI.ArcGIS.Client.Geometry.PointCollection> { circle };
areaOfInterset = new Polygon { Rings = rings};
}
Now next task is to find the rows. For that you can use below code inside the loop.
foreach(MapPoint selectedRow in DatabaseRowsToBeSearched)
{
var isExist = IsInsideCircle(selectedRow)
}
bool IsInsideCircle(MapPoint location)
{
var isInside = false;
foreach (var shape in areaOfInterset.Rings)
{
for (int i = 0, j = shape.Count - 1; i < shape.Count; j = i++)
{
if ((((shape[i].Y <= location.Y) && (location.Y < shape[j].Y)) ||
((shape[j].Y <= location.Y) && (location.Y < shape[i].Y))) &&
(location.X < (shape[j].X - shape[i].X) * (location.Y - shape[i].Y) / (shape[j].Y - shape[i].Y) + shape[i].X))
{
isInside = !isInside;
}
}
}
return isInside;
}