0

I am trying to make an A* pathfinder for my game in C# XNA 4.0.

If the pathfinder executes once, then the monster moves correctly. But while the monster is moving the pathfinder executes again, and uses the current position of the enemy. The pathfider takes about a half to one second to finish. So when the pathfinder is done and returns the new path, the monster have moved away from the position used meanwhile. So now the monster moves the whole way back to the position used, just to start the new path.

How do i solve this problem?

I dont know what you need. If it's the pathfinder code you'll need to see then i'll post it but here is the zombie class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using System.IO;

namespace *Private name*
{
    class Zombie : Obj
    {
        ContentManager Content;
        private Vector2 dest;
        private bool[,] map;
        private List<Note> path = new List<Note>();
        private int PathIndex = 0;
        private bool queued = false;
        private int queTimer = 0;
        private int queTime = 10;
        private bool finding = false;
        private bool walkable = false;
        private Thread t;

        public Zombie(Vector2 pos, string sprName, int MaxHP, int HP, int damage) : base(pos, sprName, MaxHP, HP)
        {
            position = pos;
            spriteName = sprName;

            maxHealth = MaxHP;

            if (HP > maxHealth)
                Health = maxHealth;
            else if (HP < 0)
                Health = 1;
            else
                Health = HP;

            maxHealth = 10;
            Health = 10;

            solid = true;
            speed = 1.0f;
            dest = position;
            this.damage = damage;
        }

        //Loads the content and sets some values
        public override void LoadContent(ContentManager content)
        {
            spriteTexture = content.Load<Texture2D>(spriteName);
            spriteRectangle = new Rectangle((int)position.X, (int)position.Y, spriteTexture.Width, spriteTexture.Height);
            centerPosRec.X = (int)position.X + (spriteRectangle.Height / 2);
            centerPosRec.Y = (int)position.Y + (spriteRectangle.Width / 2);
            Content = content;
        }

        public override void Update()
        {
            if (!alive) return;

            if (Health <= 0)
                alive = false;

            if (attackCoolDown > 0)
                attackCoolDown--;

            if (queTimer > queTime)
            {
                queTimer = 0;
                NewSetPath();
            }
            else
            { queTimer++; }

            MoveToDestination();

            spriteRectangle.X = (int)position.X;
            spriteRectangle.Y = (int)position.Y;

            centerPosRec.X = (int)position.X + (spriteRectangle.Height / 2);
            centerPosRec.Y = (int)position.Y + (spriteRectangle.Width / 2);
        }

        //Here the program finds the path
        private void NewSetPath()
        {
            if (t != null)
                if (t.IsAlive == true)
                    return;

            dest = Player.player1.centerPosRec;

            if (!finding)
            {
                finding = true;
                t = new Thread(NewFindPath);
                t.Start();
            }

            if (!t.IsAlive && finding)
            {
                t.Abort();
                finding = false;
                queued = false;
                PathIndex = 0;
            }
        }
        //Here it also finds the path
        private void NewFindPath()
        {
            map = MyPathFinder.writeMap();
            MyPathFinder finder;

            finder = new MyPathFinder(map);
            path = finder.findPath(this.centerPosRec, this.dest);
        }
        //Moves to the next point/note in the path list
        private void MoveToDestination()
        {  
            if (path == null)
            {
                return;
            }

            if (PathIndex < path.Count)
            {
                if (stepToPoint(path[PathIndex]))
                {
                    PathIndex++;
                }
                else
                {
                    PushTo(speed, rotation);
                }
            }
            else if (path.Count >= 0)
            {
                path = null;
                PathIndex = 0;
                queued = false;
                dest = Player.player1.position;
                NewSetPath();
            }
        }

        //Checkes for collition and distance to the point
        private bool stepToPoint(Note note)
        {
            if (PointDist(centerPosRec.X, centerPosRec.Y, note.posRectangle.Y + (pathFinder.gridSize / 2), note.posRectangle.X + (pathFinder.gridSize / 2)) < pathFinder.gridSize / 2)
            {
                speed = 0;
                return true;
            }
            rotation = Point_Direction(centerPosRec.X, centerPosRec.Y, note.posRectangle.Y + (pathFinder.gridSize / 2), note.posRectangle.X + (pathFinder.gridSize / 2));
            speed = 2f;

            return false;
        }

        public override void Draw(SpriteBatch spriteBatch)
        {
            try
            {
                Vector2 center = new Vector2(spriteTexture.Width / 2, spriteTexture.Height / 2);

                foreach (Note n in path)
                {
                    spriteBatch.Draw(Content.Load<Texture2D>("OfficeWall"), new Vector2(n.position.Y * 32, n.position.X * 32), null, Color.White, 0, center, scale, SpriteEffects.None, 0);
                }
            }
            catch
            { }

            base.Draw(spriteBatch);

            try
            {
                spriteBatch.DrawString(Content.Load<SpriteFont>("HUDFont"), PathIndex + "/" + path.Count, new Vector2(500, 500), Color.White);
            }
            catch
            { }
        }
        //Gets distance between points
        public static float PointDist(float x1, float y1, float x2, float y2)
        { 
            float xRect = (x1 -x2) * (x1 - x2);
            float yRect = (y1 - y2) * (y1 - y2);

            double hRect = xRect + yRect;

            float dist = (float)Math.Sqrt(hRect);
            return dist;
        }
        //Movement
        public override void PushTo(float pix, float dir)
        {
            float newX = (float)Math.Cos(MathHelper.ToRadians(dir));
            float newY = (float)Math.Sin(MathHelper.ToRadians(dir));
            newX *= pix;
            newY *= pix;

            if (!Collision(new Vector2(newX, newY)))
            {
                base.PushTo(pix, dir);
            }
            else if (!Collision(new Vector2(0, newY)))
            {
                this.position += new Vector2(0, newY);
            }
            else if (!Collision(new Vector2(newX, 0)))
            {
                this.position += new Vector2(newX, 0);
            }
        }

        public override bool Collision(Vector2 pos)
        {
            Rectangle area = new Rectangle(spriteRectangle.X, spriteRectangle.Y, spriteRectangle.Width, spriteRectangle.Height);
            area.X += (int)pos.X;
            area.Y += (int)pos.Y;

            foreach (Obj o in Items.objList)
            {
                if (o.solid && o.alive && o != this && o.GetType() != typeof(Zombie))
                {
                    if (area.Intersects(Player.player1.spriteRectangle))
                    {
                        if (Player.player1.alive == true && attackCoolDown == 0)
                        {
                            this.attackCoolDown = 55;

                            Player.player1.Health -= damage;
                            Player.player1.regenAfterDamageTimer = 0;
                            Player.player1.PushTo(5f, rotation);
                            return true;
                        }
                    }

                    if (o.spriteRectangle.Intersects(area))
                    {
                        return true;
                    }
                }
            }

            return false;
        }
    }
}

Ok, Here's the pathfinder, it is not completely done, and I know I have to move the if statement that checks if the end position is being checked. I made it faster by making it update each tick and it is working better now. Now it takes one milisecond But here it is :D

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using System.IO;

namespace *Private name*
{
    class MyPathFinder
    {
        List<Note> path = new List<Note>();
        List<Note> openList = new List<Note>();
        List<Note> closedList = new List<Note>();
        Note start;
        Note endPos;

        private Note[,] map;
        public static int GridSize = 32;
        bool NorthRight = false, NorthLeft = false, SouthRight = false, SouthLeft = false, EastRight = false, EastLeft = false, WestRight = false, WestLeft = false;
        //string values = "";

        public MyPathFinder(bool[,] cMap)
        {
            map = new Note[cMap.GetLength(0), cMap.GetLength(1)];
            for (int x = 0; x < cMap.GetLength(0); x++)
            {
                for (int y = 0; y < cMap.GetLength(1); y++)
                {
                    Rectangle rec = new Rectangle();
                    rec.Width = rec.Height = pathFinder.gridSize;
                    rec.X = x * pathFinder.gridSize;
                    rec.Y = y * pathFinder.gridSize;

                    map[x, y] = new Note();
                    map[x, y].walkable = cMap[x, y];
                    map[x, y].posRectangle = rec;
                    map[x, y].position = new Point(x, y);
                }
            }


        }

        public static bool[,] writeMap(params Obj[] objEx)
        {

            bool[,] cMap = new bool[Convert.ToInt16(Game1.room.Width / pathFinder.gridSize), Convert.ToInt16(Game1.room.Height / pathFinder.gridSize)];
            //string output2 = "";

            //loop through rows
            for (int x = 0; x < cMap.GetLength(0); x++)
            {
                for (int y = 0; y < cMap.GetLength(1); y++)
                {
                    Rectangle rec = new Rectangle();
                    rec.Width = rec.Height = pathFinder.gridSize;
                    rec.X = y * pathFinder.gridSize; 
                    rec.Y = x * pathFinder.gridSize;

                    //output2 += Convert.ToString("{" + y + "," + x);

                    //If collision with grid then grid is unwalkable
                    foreach (Obj o in Items.objList)
                    {
                        if (o.spriteRectangle.Intersects(rec) && o.alive && o.solid && !objEx.Contains<Obj>(o) && o.GetType() != typeof(Zombie) && o.GetType() != typeof(Player)) 
                        {
                            cMap[x, y] = false;
                            break;
                        }
                        else
                        {
                            cMap[x, y] = true;

                        }
                    }
                    //output2 += " = " + cMap[x, y] + "} -- ";
                }
                //output2 += "\r\n";
            }
            //File.WriteAllText("DEBUG-Walkable.txt", output2);

            return cMap;
        }

        public List<Note> findPath(Vector2 pos, Vector2 dest)
        {
            string wtf = "";
            int num = 0;
            path.Clear();
            openList.Clear();
            closedList.Clear();

            Point startPos = new Point(Convert.ToInt16(Math.Floor((double)(pos.Y / GridSize))), Convert.ToInt16(Math.Floor((double)(pos.X / GridSize))));
            Point end = new Point(Convert.ToInt16(Math.Floor((double)(dest.Y / GridSize))), Convert.ToInt16(Math.Floor((double)(dest.X / GridSize))));

            start = map[startPos.X, startPos.Y];
            endPos = (map[end.X, end.Y]);

            openList.Add(map[startPos.X, startPos.Y]);

            while (!endPos.closed)
            {
                num++;
                openList = openList.OrderBy(p => p.F).ToList();
                Note temp = null;

                foreach (Note n in openList)
                {
                    if (!closedList.Contains(n))
                    {
                        wtf += "number " + num + " = " + n.position.X + ", " + n.position.Y + "\r\n";
                        parenting(n.position, end);
                        n.closed = true;
                        temp = n;
                        break;
                    }
                }

                if (temp != null)
                {
                    closedList.Add(temp);
                    openList.Remove(temp);
                }

                //File.WriteAllText("checking.txt", wtf);
                //File.WriteAllText("listContent.txt", outp);
                //File.WriteAllText("listCount.txt", openList.Count.ToString());
            }

            return path;
        }

        private void parenting(Point pos, Point dest)
        {
            checkNorth(pos, dest);
            checkSouth(pos, dest);
            checkEast(pos, dest);
            checkWest(pos, dest);

            //File.WriteAllText("DEBUGpos[" + pos.Y + ", " + pos.X + "].txt", values);

            //if (NorthLeft && NorthRight && SouthLeft && SouthRight && EastLeft && EastRight && WestLeft && WestRight)
            //   Settings.exit = true;

        }

        private int GetH(Point v, Point endPos)
        {
            Point diff = new Point(v.X - endPos.X, v.Y - endPos.Y);
            if (diff.X < 0) { diff.X *= 1; }
            if (diff.Y < 0) { diff.Y *= 1; }

            return Convert.ToInt16(diff.X + diff.Y);
        }

        private void checkNorth(Point pos, Point dest)
        {
            Point p = new Point(pos.X - 1, pos.Y);
            if (map.XInRange(p.X))
            {
                if (map[p.X, p.Y].walkable && !map[p.X, p.Y].closed)
                {
                    //values += p.X.ToString() + "," + p.Y.ToString() + " - " + map[p.X, p.Y].walkable + "\r\n";
                    if (map[p.X, p.Y] == endPos)
                    {
                        //File.WriteAllText("EndFound.txt", p.X + " - " + p.Y);
                        endPos.parent = pos;
                        endPos.closed = true;
                        closedList.Add(endPos);
                        path.Add(map[p.X, p.Y]);

                        while (!path.Contains(start))
                        {
                            path.Add(map[path[path.Count - 1].parent.X, path[path.Count - 1].parent.Y]);
                        }

                        path.Reverse();
                    }
                    else
                    {
                        if (map[p.X, p.Y].open == true)
                        {
                            if (map[pos.X, pos.Y].G + 10 < map[p.X, p.Y].G)
                            {
                                Settings.exit = true;
                            }
                        }
                        else
                        {
                            map[p.X, p.Y].open = true;
                            map[p.X, p.Y].parent = pos;
                            map[p.X, p.Y].H = GetH(p, dest);
                            map[p.X, p.Y].G = map[pos.X, pos.Y].G + 10;
                            map[p.X, p.Y].F = map[p.X, p.Y].G + map[p.X, p.Y].H;

                            NorthLeft = (map[p.X, p.Y - 1].walkable == true) ? true : false;
                            NorthRight = (map[p.X, p.Y + 1].walkable == true) ? true : false;

                            openList.Add(map[p.X, p.Y]);

                        }
                    }

                }
                else
                {
                    NorthLeft = false;
                    NorthRight = false;
                }
            }
            else
            {
                SouthLeft = false;
                SouthRight = false;

            }
            //values += NorthLeft.ToString() + " " + NorthRight.ToString() + "\r\n";
        }

        private void checkSouth(Point pos, Point dest)
        {
            Point p = new Point(pos.X + 1, pos.Y);

            if (p.X < 0)
            {
                SouthLeft = false;
                SouthRight = false;
                return;
            }

            //values += p.X.ToString() + "," + p.Y.ToString() + " - " + map[p.X, p.Y].walkable + "\r\n";
            if (Enumerable.Range(0, map.GetLength(0)).Contains(p.X))
            {
                if (map[p.X, p.Y].walkable && !map[p.X, p.Y].closed)
                {
                    if (map[p.X, p.Y] == endPos)
                    {
                        //File.WriteAllText("EndFound.txt", p.X + " - " + p.Y);
                        endPos.parent = pos;
                        endPos.closed = true;
                        closedList.Add(endPos);
                        path.Add(map[p.X, p.Y]);

                        while (!path.Contains(start))
                        {
                            path.Add(map[path[path.Count - 1].parent.X, path[path.Count - 1].parent.Y]);
                        }

                        path.Reverse();
                    }
                    else
                    {
                        if (map[p.X, p.Y].open == true)
                        {
                            if (map[pos.X, pos.Y].G + 10 < map[p.X, p.Y].G)
                            {

                            }
                        }
                        else
                        {
                            map[p.X, p.Y].open = true;
                            map[p.X, p.Y].parent = pos;
                            map[p.X, p.Y].H = GetH(p, dest);
                            map[p.X, p.Y].G = map[pos.X, pos.Y].G + 10;
                            map[p.X, p.Y].F = map[p.X, p.Y].G + map[p.X, p.Y].H;

                            if (Enumerable.Range(0, map.GetLength(1)).Contains(p.Y + 1))
                                SouthLeft = (map[p.X, p.Y + 1].walkable == true) ? true : false;
                            if (Enumerable.Range(0, map.GetLength(1)).Contains(p.Y - 1))
                                SouthRight = (map[p.X, p.Y - 1].walkable == true) ? true : false;

                            openList.Add(map[p.X, p.Y]);
                        }
                    }
                }
                else
                {
                    SouthLeft = false;
                    SouthRight = false;

                }
            }
            else
            {
                SouthLeft = false;
                SouthRight = false;

            }
            //values += SouthLeft.ToString() + " " + SouthRight.ToString() + "\r\n";

        }

        private void checkEast(Point pos, Point dest)
        {
            Point p = new Point(pos.X, pos.Y + 1);
            //File.WriteAllText("testEastpos.txt", p.X + " - " + p.Y + " -- " + map[p.X, p.Y].walkable);
            //values += p.X.ToString() + "," + p.Y.ToString() + " - " + map[p.X, p.Y].walkable + "\r\n";
            if (Enumerable.Range(0, map.GetLength(1)).Contains(p.Y))
            {
                if (map[p.X, p.Y].walkable && !map[p.X, p.Y].closed)
                {
                    if (map[p.X, p.Y] == endPos)
                    {
                        //File.WriteAllText("EndFound.txt", p.X + " - " + p.Y);
                        endPos.parent = pos;
                        endPos.closed = true;
                        closedList.Add(endPos);
                        path.Add(map[p.X, p.Y]);

                        while (!path.Contains(start))
                        {
                            path.Add(map[path[path.Count - 1].parent.X, path[path.Count - 1].parent.Y]);
                        }

                        path.Reverse();
                    }
                    else
                    {
                        if (map[p.X, p.Y].open == true)
                        {
                            if (map[pos.X, pos.Y].G + 10 < map[p.X, p.Y].G)
                            {

                            }
                        }
                        else
                        {
                            map[p.X, p.Y].open = true;
                            map[p.X, p.Y].parent = pos;
                            map[p.X, p.Y].H = GetH(p, dest);
                            map[p.X, p.Y].G = map[pos.X, pos.Y].G + 10;
                            map[p.X, p.Y].F = map[p.X, p.Y].G + map[p.X, p.Y].H;

                            if (p.X - 1 >= 0)
                                EastLeft = (map[p.X - 1, p.Y].walkable == true) ? true : false;
                            else
                                EastLeft = false;

                            if (Enumerable.Range(0, map.GetLength(0)).Contains(p.Y))
                            {
                                EastRight = (map[p.X + 1, p.Y].walkable == true) ? true : false;
                            }
                            else
                            { EastRight = false; }

                            openList.Add(map[p.X, p.Y]);
                        }
                    }
                }
                else
                {
                    SouthLeft = false;
                    SouthRight = false;

                }
            }
            else
            {

                EastLeft = false;
                EastRight = false;
            }

            try
            {
                //File.WriteAllText("testEastSidepos.txt", (p.X - 1) + " - " + p.Y + " -- " + map[p.X - 1, p.Y].walkable + "\r\n" +
                //                                        (p.X + 1) + " - " + p.Y + " -- " + map[p.X + 1, p.Y].walkable);
            }
            catch
            { }

            //values += EastLeft.ToString() + " " + EastRight.ToString() + "\r\n";
        }

        private void checkWest(Point pos, Point dest)
        {
            Point p = new Point(pos.X, pos.Y - 1);

            if (Enumerable.Range(0, map.GetLength(1)).Contains(p.Y))
            {
                //values += p.X.ToString() + "," + p.Y.ToString() + " - " + map[p.X, p.Y].walkable + "\r\n";

                if (map[p.X, p.Y].walkable && !map[p.X, p.Y].closed)
                {

                    if (map[p.X, p.Y] == endPos)
                    {
                        //File.WriteAllText("EndFound.txt", p.X + " - " + p.Y);
                        endPos.parent = pos;
                        endPos.closed = true;
                        closedList.Add(endPos);
                        path.Add(map[p.X, p.Y]);

                        while (!path.Contains(start))
                        {
                            path.Add(map[path[path.Count - 1].parent.X, path[path.Count - 1].parent.Y]);
                        }

                        path.Reverse();
                    }
                    else
                    {
                        if (map[p.X, p.Y].open == true)
                        {
                            if (map[pos.X, pos.Y].G + 10 < map[p.X, p.Y].G)
                            {

                            }
                        }
                        else
                        {
                            map[p.X, p.Y].open = true;
                            map[p.X, p.Y].parent = pos;
                            map[p.X, p.Y].H = GetH(p, dest);
                            map[p.X, p.Y].G = map[pos.X, pos.Y].G + 10;
                            map[p.X, p.Y].F = map[p.X, p.Y].G + map[p.X, p.Y].H;

                            WestLeft = (map[p.X + 1, p.Y].walkable == true) ? true : false;

                            if (p.X - 1 >= 0)
                                WestRight = (map[p.X - 1, p.Y].walkable == true) ? true : false;
                            else
                                WestRight = false;

                            openList.Add(map[p.X, p.Y]);
                        }
                    }
                }
                else
                {

                    WestLeft = false;
                    WestRight = false;
                }

                //values += WestLeft.ToString() + " " + WestRight.ToString() + "\r\n";
            }
        }
    }
}
MasterXD
  • 804
  • 9
  • 18
  • I'd say your best bet is just have the zombie stop while it's calculating a new path. – Kevin DiTraglia Dec 17 '13 at 15:56
  • Wouldnt that look stupid?... Could it be a better idea to check what point the enemy is closest to and make a fast way to there and then start from there? – MasterXD Dec 17 '13 at 16:01
  • This is *really really* slow. I'd optimize the path finder and throw out the thread. Even Dijkstra's algorithm should be faster than that. – CodesInChaos Dec 17 '13 at 16:10
  • Why would i throw out the thread? then the WHOLE game will stop to calculate a path and then run again when its done. The result: extreme lag. – MasterXD Dec 17 '13 at 16:12
  • @MasterXD How large is the map? (in grid units) – CodesInChaos Dec 17 '13 at 16:23
  • The rooms Width: 40 grids and Height: 40 grids. The zombie moves in the grids. – MasterXD Dec 17 '13 at 16:25
  • 1
    In that case, Dijkstra's algorithm should run in less than a millisecond when implemented properly. For example you should use a `HashSet` for the `closedList`. You should also use a priority queue. – CodesInChaos Dec 17 '13 at 16:31

1 Answers1

0

There is no need for the pathfinder to run while the monster is moving. It found a path, use that path.

A path must be recalculated, if the calculated path is blocked by an unpassable obstacle.

Then your zobie has to stop. The path is invalid anyway, it cannot move.

A path can be recalculated, if the environment changed in a way that suggests there will be a cheaper path available.

Only use the new path, if the full new path is cheaper then the remainder of the current path.

Edit:

Optimizing the pathfinder:

openList = openList.OrderBy(p => p.F).ToList();

This line is sorting the list every single time. You need a sorted list where it is sorted after inserting. As a quick fix, call openList.Sort() after inserting something. That may not be the best option though because it sorts the whole list while you already know that only a single element is unsorted.

The very same problem of finding a priority queue class in .NET is handled here

Your four north/east/south/west functions are code duplication. Check what they have in common and extract that into one common function. Your code will be just 25% of what it is now. That's not faster, but with less code, it's easier to see stuff.

Community
  • 1
  • 1
nvoigt
  • 61,531
  • 23
  • 73
  • 116
  • But I am probably moving all the time, and the pathfinder avoids obstacles. – MasterXD Dec 17 '13 at 16:06
  • Does the target you are moving to is moving, or why are you recalculating? – nvoigt Dec 17 '13 at 16:08
  • i recalculate as long as the zombie is away from the player. And the zombie will try to find a way to me. – MasterXD Dec 17 '13 at 16:10
  • Then you have two options: optimize your pathfinder or optimize the zombie. You could start your pathfinder from the point your zombie *will be* in a second... or you can check your pathfinding algorithm, because half a second seems slow to me. – nvoigt Dec 17 '13 at 16:13
  • If I posted the code, could you then tell me how to optimize it? – MasterXD Dec 17 '13 at 16:14
  • I can try. The good thing is, there are a lot of people here smarter than me, if you post it, we can all give it a try. – nvoigt Dec 17 '13 at 16:15
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/43352/discussion-between-masterxd-and-nvoigt) – MasterXD Dec 17 '13 at 16:31