I implemented the algorithm A* Pathing to my enemies movement and it´s working fine. In order to step up the quality of my game I want to make my enemies also move diagonal so that their movement is more natural. I´ve made an attemp (will be possible to see in the code I will leave below) by adding to the crossable paths the tiles that are in the diagonal of the current tile with a cost that is half of the cost of a horizontal/vertical move. The problem is that when making diagonals the enemies can trespass my obstacles while with just top,down,left, right movement they avoid them. What would be a good aproach to find a solution?
public List<Vector2> GetPathPositions(Vector2 _start,Vector2 _end)
{
List<Vector2> positions = new List<Vector2>();
List<PathUnit> path = GetPath(_start, _end);
for(int i = 0; i < path.Count; i++)
{
int x = path[i].GetTileY();
int y = path[i].GetTileX();
int positionX = (tiles.GetTilesWidht() - 1) + tiles.GetTilesWidht() * (x - 1) + tiles.GetTilesWidht() / 2;
int positionY= (tiles.GetTilesHeight() - 1) + tiles.GetTilesHeight() * (y - 1) + tiles.GetTilesHeight() / 2;
positions.Add(new Vector2(positionX, positionY));
}
return positions;
}
public List<PathUnit> GetPath(Vector2 _start, Vector2 _end) //must convert the positions to the tile they correspond
{
List<PathUnit> path = new List<PathUnit>();
PathUnit start = new PathUnit( (int)(_start.Y/GetTiles().GetTilesHeight()), (int)(_start.X / GetTiles().GetTilesWidht()), null,0);
PathUnit finish = new PathUnit((int)(_end.Y / GetTiles().GetTilesHeight()), (int) (_end.X / GetTiles().GetTilesWidht()) , null, 0);
start.SetDistance(finish.GetTileX(), finish.GetTileY());
var activeTiles = new List<PathUnit>();
activeTiles.Add(start);
var visitedTiles = new List<PathUnit>();
while (activeTiles.Any())
{
var checkTile = activeTiles.OrderBy(x => x.GetCostDistance()).First();
if (checkTile.GetTileX() == finish.GetTileX() && checkTile.GetTileY() == finish.GetTileY()) //if it´s the destiny point
{
PathUnit tile = checkTile;
while (tile!=null)
{
path.Add(tile);
tile = tile.GetParent();
}
path.Reverse() ;
return path;
}
visitedTiles.Add(checkTile);
activeTiles.Remove(checkTile);
var walkableTiles = GetCrossableTiles( checkTile, finish);
foreach (var walkableTile in walkableTiles)
{
//We have already visited this tile so we don't need to do so again!
if (visitedTiles.Any(x => x.GetTileX() == walkableTile.GetTileX() && x.GetTileY() == walkableTile.GetTileY()))
continue;
//It's already in the active list, but that's OK, maybe this new tile has a better value (e.g. We might zigzag earlier but this is now straighter).
if (activeTiles.Any(x => x.GetTileX() == walkableTile.GetTileX() && x.GetTileY() == walkableTile.GetTileY()))
{
var existingTile = activeTiles.First(x => x.GetTileX() == walkableTile.GetTileX() && x.GetTileY() == walkableTile.GetTileY());
if (existingTile.GetCostDistance() > checkTile.GetCostDistance())
{
activeTiles.Remove(existingTile);
activeTiles.Add(walkableTile);
}
}
else
{
//We've never seen this tile before so add it to the list.
activeTiles.Add(walkableTile);
}
}
}
return path;
}
private List<PathUnit> GetCrossableTiles(PathUnit _currentPathUnit, PathUnit _targetPathUnit)
{
List<PathUnit> crossableTiles = new List<PathUnit>();
crossableTiles.Add(new PathUnit(_currentPathUnit.GetTileX(), _currentPathUnit.GetTileY() - 1, _currentPathUnit, _currentPathUnit.GetCost() + 1));
crossableTiles.Add(new PathUnit(_currentPathUnit.GetTileX(), _currentPathUnit.GetTileY() +1, _currentPathUnit, _currentPathUnit.GetCost() + 1));
crossableTiles.Add(new PathUnit(_currentPathUnit.GetTileX()-1, _currentPathUnit.GetTileY() , _currentPathUnit, _currentPathUnit.GetCost() + 1));
crossableTiles.Add(new PathUnit(_currentPathUnit.GetTileX()+1, _currentPathUnit.GetTileY() , _currentPathUnit, _currentPathUnit.GetCost() + 1));
for(int i = 0; i < crossableTiles.Count; i++)
{
crossableTiles[i].SetDistance(_targetPathUnit.GetTileX(), _targetPathUnit.GetTileY());
}
for (int i = 0; i < crossableTiles.Count; i++)
{
if (crossableTiles[i].GetTileX() < 0 || crossableTiles[i].GetTileX() > mapHeight - 1
|| crossableTiles[i].GetTileY() < 0 || crossableTiles[i].GetTileY() > mapWidth - 1
||!tiles.GetTile(map[crossableTiles[i].GetTileX(), crossableTiles[i].GetTileY()]).UnitCross() )
{
crossableTiles.RemoveAt(i);
}
}
return crossableTiles;
}
public class Enemy : Unit
{
protected int scoreAwarded;
protected Vector2 moveTo;
protected List<Vector2> path;
protected _Timer timer;
protected bool pathing;
public Enemy(string _path, Vector2 _position, Vector2 _dimensions, int _id) : base(_path, _position, _dimensions,_id)
{
moveTo = new Vector2(position.X,position.Y);
path = new List<Vector2>();
timer = new _Timer(3000);
pathing = false;
}
public int GetScoreAwarded()
{
return scoreAwarded;
}
public virtual void Update(Vector2 _offset, Character _character, List<Projectile> _projectiles,MapManager _map)
{
ArtificialIntelligence(_character, _projectiles,_map);
}
public void FindPath(MapManager _map,Vector2 _start,Vector2 _end,Character _character){
path.Clear();
path = _map.GetPathPositions(_start, _end);
List<Vector2> tempPath = new List<Vector2>();
tempPath.Add(position);
for (int i = 1; i < path.Count - 1; i++)
{
tempPath.Add(path[i]);
}
tempPath.Add(_character.GetPosition());
//path.RemoveAt(0);
//path.Insert(0, position);
// path.RemoveAt(path.Count - 1);
// path.Add(_character.GetPosition());
path = tempPath;
}
public void MoveUnit()
{
if(position.X != moveTo.X || position.Y != moveTo.Y)
{
rotation = RotateTowards(position, moveTo);
position += RadialMovement(moveTo, position, speed);
}
else if (path.Count > 0)
{
moveTo = path[0];
path.RemoveAt(0);
position += RadialMovement(moveTo, position, speed);
}
}
public virtual void ArtificialIntelligence(Character _character, List<Projectile> _projectiles,MapManager _map)
{
// SetPosition(GetPosition() + RadialMovement(_character.GetPosition(), this.GetPosition(), this.speed));
// SetRotation(RotateTowards(this.GetPosition(), _character.GetPosition()));
timer.UpdateTimer();
if (path == null || (path.Count == 0 && position.X == moveTo.X && position.Y == moveTo.Y) || timer.Test())
{
if (!pathing)
{
Task findPathTask = new Task(() =>
{
pathing = true;
FindPath(_map, position, _character.GetPosition(),_character);
moveTo = path[0];
path.RemoveAt(0);
timer.ResetToZero();
pathing = false;
});
findPathTask.Start();
}
}
else
{
MoveUnit();
if (Vector2.Distance(this.GetPosition(), _character.GetPosition()) < this.GetAttackArea())
{
if (_character.HasShield())
{
_character.SetShieldStatus(false);
Debug.WriteLine("Perdeu o escudo");
isDead = true;
}
else
{
_character.GetHit(GetDamage());
isDead = true; //replace by character resistant to damage for x seconds
}
}
}
}
public Vector2 RadialMovement(Vector2 focus, Vector2 position, float speed) //focus is the destination point
{
float distance = Vector2.Distance(position, focus);
if (distance <= speed)
{
return focus - position;
}
else
{
return (focus - position) * speed / distance;
}
}
public override void Draw(Vector2 _offset)
{
base.Draw(_offset);
}
}