Sounds good. Hopefully it’s easy for you to spot. Here is the MoveAction:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MoveAction : BaseAction
{
private List<Vector3> positionList;
private int currentPositionIndex;
public event EventHandler OnStartMoving;
public event EventHandler OnStopMoving;
[SerializeField]
private int maxMoveDistance = 1;
private void Update()
{
//This setups up a bool so if this code is not active then it will return
if (!isActive)
{
return;
}
Vector3 targetPosition = positionList[currentPositionIndex];
//Make the unit move in a certain direction and speed
Vector3 moveDirection = (targetPosition - transform.position).normalized;
//Setting the transform of the unit as it moves to face the space clicked
float rotateSpeed = 15f;
transform.forward = Vector3.Lerp(transform.forward, moveDirection, Time.deltaTime * rotateSpeed);
//Learn the distance between the object and the unit moving and also making it stop and not shake
float stoppingDistance = .1f;
if (Vector3.Distance(transform.position, targetPosition) > stoppingDistance)
{
float moveSpeed = 4f;
transform.position += moveDirection * moveSpeed * Time.deltaTime;
}
else
{
currentPositionIndex++;
if (currentPositionIndex >= positionList.Count)
{
OnStopMoving?.Invoke(this, EventArgs.Empty);
//Setting isActive false after the move is complete
ActionComplete();
}
}
}
//This function is exposed so that we can move the unit without giving the rest of the code out
public override void TakeAction(GridPosition gridPosition, Action onActionComplete)
{
GridPosition unitGridPostion = unit.GetGridPostion();
List<GridPosition> pathGridPositionList = Pathfinding.Instance.FindPath(unitGridPostion, gridPosition, out int pathLength);
currentPositionIndex = 0;
positionList = new List<Vector3>();
foreach (GridPosition pathGridPosition in pathGridPositionList)
{
positionList.Add(LevelGrid.Instance.GetWorldPosition(pathGridPosition));
}
//setting up the animator from the animation script for walking
// Invoke the OnStartMoving event if it is not null
if (OnStartMoving != null)
{
OnStartMoving?.Invoke(this, EventArgs.Empty);
}
//setting up the delegate to call from the base class
ActionStart(onActionComplete);
}
//This list helps create the max move distance a unit can make from their current postion
//You get the grid position to make the logic for understanding where the unit is and the max distance from that location
public new bool IsValidActionGridPosition(GridPosition gridPosition)
{
List<GridPosition> validGridPositionList = GetValidActionGridPositionList();
return validGridPositionList.Contains(gridPosition);
}
public override List<GridPosition> GetValidActionGridPositionList()
{
List<GridPosition> validGridPositionList = new List<GridPosition>();
GridPosition unitGridPostion = unit.GetGridPostion();
//This needs to cycle through the grid
for (int x = -maxMoveDistance; x <= maxMoveDistance; x++)
{
for (int z = -maxMoveDistance; z <= maxMoveDistance; z++)
{
GridPosition offsetGridPostion = new GridPosition(x, z);
GridPosition testGridPosition = unitGridPostion + offsetGridPostion;
//This is the logic for moving the unit on the grid
//If position is valid we want to keep it. If not we want to discard
//that is what this boundary does. Calling from lvelgrid and gridsystem
if (!LevelGrid.Instance.IsValidGridPosition(testGridPosition))
{
//continue skips forward if the IF statement says that the iteration is not valid
//if it is valid then the code goes into the next step, currently debug
continue;
}
if (unitGridPostion == testGridPosition)
{
//Same gridPosition where the unit is already at
continue;
}
if (LevelGrid.Instance.HasAnyUnitOnGridPosition(testGridPosition))
{
//Making sure units don't stack on the same position
continue;
}
if (!Pathfinding.Instance.IsWalkAbleGridPosition(testGridPosition))
{
//If tile is not walkable then pathfinding will ignore the tile
continue;
}
if (!Pathfinding.Instance.HasPath(unitGridPostion, testGridPosition))
{
//If tile is not walkable then pathfinding will ignore the tile
continue;
}
//This prevents the path from reading tiles through walls as walkable for the player
int pathfindingDistanceMultiplier = 10;
if (Pathfinding.Instance.GetPathLength(unitGridPostion, testGridPosition) > maxMoveDistance * pathfindingDistanceMultiplier)
{
//Path length is too long
continue;
}
validGridPositionList.Add(testGridPosition);
}
}
return validGridPositionList;
}
//Overrides the name for the action button
public override string GetActionName()
{
return "Move";
}
public override int GetActionPointsCost()
{
return 1;
}
//This is setup for the enemy action override
public override EnemyAIAction GetEnemyAIAction(GridPosition gridPosition)
{
//This helps determine to shoot before moving and the importance of it
int targetCountAtGridPosition = unit.GetAction<ShootAction>().GetTargetCountAtPosition(gridPosition);
return new EnemyAIAction
{
gridPosition = gridPosition,
actionValue = targetCountAtGridPosition * 10,
};
}
}
and here is the Pathfinding
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Pathfinding : MonoBehaviour
{
public static Pathfinding Instance { get; private set; }
//Defining movement costs
private const int MOVE_STRAIGHT_COST = 10;
private const int MOVE_DIAGNOL_COST = 14;
[SerializeField]
private LayerMask obstaclesLayerMask;
[SerializeField]
private Transform gridDebugObjectPrefab;
private int width;
private int height;
private float cellSize;
private GridSystem<Pathnode> gridSystem;
private void Awake()
{
if(Instance != null)
{
Debug.LogError("There is more than one Pathfinding!" + transform + " " + Instance);
Destroy(gameObject);
return;
}
Instance = this;
}
public void Setup(int width, int height, float cellSize)
{
this.width = width;
this.height = height;
this.cellSize = cellSize;
//making the gridsystem
gridSystem = new GridSystem<Pathnode>(width, height, cellSize,
(GridSystem<Pathnode> g, GridPosition gridPosition) => new Pathnode(gridPosition));
//making the gridobject display all the text and color of the iswalkable. Commenting this out hides all that
// gridSystem.CreateDebugObjects(gridDebugObjectPrefab);
//cycle through the width and height
//This is how you call the raycast to hit the obstacles if there is one
//then you call get node to say that the obstacle is unwalkable
for(int x = 0; x < width; x ++)
{
for (int z = 0; z < height; z++)
{
GridPosition gridPosition = new GridPosition(x, z);
//Origin position
Vector3 worldPosition = LevelGrid.Instance.GetWorldPosition(gridPosition);
//The offset gets around the objects collider and prevents an error
float raycastOffsetDistance = 5f;
if (Physics.Raycast(
worldPosition +
Vector3.down *
raycastOffsetDistance,
Vector3.up,
raycastOffsetDistance * 2,
obstaclesLayerMask))
{
GetNode(x, z).SetIsWalkable(false);
}
}
}
}
public List<GridPosition> FindPath(GridPosition startGridPosition, GridPosition endGridPosition, out int pathLength)
{
//Setup the open and close list first
//openlist contains all the nodes that are for searching
List<Pathnode> openlist = new List<Pathnode>();
//the closed list are all the nodes that we already searched
List<Pathnode> closedList = new List<Pathnode>();
Pathnode startNode = gridSystem.GetGridObject(startGridPosition);
Pathnode endNode = gridSystem.GetGridObject(endGridPosition);
openlist.Add(startNode);
for (int x = 0; x < gridSystem.GetWidth(); x++)
{
for(int z = 0; z < gridSystem.GetHeight(); z++)
{
GridPosition gridPosition = new GridPosition(x, z);
Pathnode pathnode = gridSystem.GetGridObject(gridPosition);
pathnode.SetGCost(int.MaxValue);
pathnode.SetHCost(0);
pathnode.CalculateFCost();
//The reset node makes it so the previously calculated path doesn't show up when recalculating a new node
pathnode.ResetCameFromPathnode();
}
}
//This is the setup for the path
//The G cost for the begining node should always be 0 since its where the path begins
startNode.SetGCost(0);
startNode.SetHCost(CalculateDistance(startGridPosition, endGridPosition));
startNode.CalculateFCost();
while (openlist.Count > 0)
{
Pathnode currentNode = GetLowestFCostPathnode(openlist);
if(currentNode == endNode)
{
pathLength = endNode.GetFCost();
//we have reached the final node in the path
return CalculatePath(endNode);
}
openlist.Remove(currentNode);
closedList.Add(currentNode);
foreach(Pathnode neighborNode in GetNeighborList(currentNode))
{
if(closedList.Contains(neighborNode))
{
continue;
}
//create a is this walkable check
if(neighborNode.IsWalkable())
{
closedList.Add(neighborNode);
continue;
}
int tentativeGCost =
currentNode.GetGCost() + CalculateDistance(currentNode.GetGridPosition(), neighborNode.GetGridPosition());
if(tentativeGCost < neighborNode.GetGCost())
{
neighborNode.SetCameFromPathnode(currentNode);
neighborNode.SetGCost(tentativeGCost);
neighborNode.SetHCost(CalculateDistance(neighborNode.GetGridPosition(), endGridPosition));
neighborNode.CalculateFCost();
if(!openlist.Contains(neighborNode))
{
openlist.Add(neighborNode);
}
}
}
}
//No path found
pathLength = 0;
return null;
}
public int CalculateDistance(GridPosition gridPositionA, GridPosition gridPositionB)
{
GridPosition gridPositionDistance = gridPositionA - gridPositionB;
// use this if you don't want diagnol movement => int distance = Mathf.Abs(gridPositionDistance.x) + Mathf.Abs(gridPositionDistance.z);
//This sets up the diagnol movement cost delete all below but the last comment if you don't want diagnol
int xDistance = Mathf.Abs(gridPositionDistance.x);
int zDistance = Mathf.Abs(gridPositionDistance.z);
int remaining = Mathf.Abs(xDistance - zDistance);
return MOVE_DIAGNOL_COST * Mathf.Min(xDistance, zDistance) + MOVE_STRAIGHT_COST * remaining; ;
// delete the diagnol bits if you don't want diagnol and use this => return distance * MOVE_STRAIGHT_COST;
}
private Pathnode GetLowestFCostPathnode(List<Pathnode> pathnodeList)
{
Pathnode lowestFCostPathNode = pathnodeList[0];
for(int i = 0; i < pathnodeList.Count; i++)
{
if(pathnodeList[i].GetFCost() < lowestFCostPathNode.GetFCost())
{
lowestFCostPathNode = pathnodeList[i];
}
}
return lowestFCostPathNode;
}
private Pathnode GetNode(int x , int z)
{
return gridSystem.GetGridObject(new GridPosition(x, z));
}
//looking at neighbor nodes
private List<Pathnode> GetNeighborList(Pathnode currentNode)
{
List<Pathnode> neighborList = new List<Pathnode>();
GridPosition gridPosition = currentNode.GetGridPosition();
if (gridPosition.x - 1 >= 0)
{
//This is to get the neighbor on the left
neighborList.Add(GetNode(gridPosition.x - 1, gridPosition.z + 0));
if (gridPosition.z - 1 >= 0)
{
//This is to get the neighbor on the left down DELETE THIS IF YOU DON"T WANT DIAGNOL MOVEMENT
neighborList.Add(GetNode(gridPosition.x - 1, gridPosition.z - 1));
}
if(gridPosition.z + 1 <gridSystem.GetHeight())
{
//This is to get the neighbor on the left up DELETE THIS IF YOU DON"T WANT DIAGNOL MOVEMENT
neighborList.Add(GetNode(gridPosition.x - 1, gridPosition.z + 1));
}
}
if (gridPosition.x + 1 < gridSystem.GetWidth())
{
//Right
neighborList.Add(GetNode(gridPosition.x + 1, gridPosition.z + 0));
if (gridPosition.z - 1 >= 0)
{
//Right Down DELTET THIS IF YOU DON"T WANT DIAGNOL MOVEMENT
neighborList.Add(GetNode(gridPosition.x + 1, gridPosition.z - 1));
}
if (gridPosition.z + 1 < gridSystem.GetHeight())
{
//Right Up DELETE THIS IF YOU DON"T WANT DIAGNOL MOVEMENT
neighborList.Add(GetNode(gridPosition.x + 1, gridPosition.z + 1));
}
}
if (gridPosition.z - 1 >= 0)
{
//Down
neighborList.Add(GetNode(gridPosition.x + 0, gridPosition.z - 1));
}
if (gridPosition.z + 1 < gridSystem.GetHeight())
{
//UP
neighborList.Add(GetNode(gridPosition.x + 0, gridPosition.z + 1));
}
return neighborList;
}
private List<GridPosition>CalculatePath(Pathnode endNode)
{
List<Pathnode> pathNodeList = new List<Pathnode>();
pathNodeList.Add(endNode);
Pathnode currentNode = endNode;
while (currentNode.GetFromPathNode() != null)
{
pathNodeList.Add(currentNode.GetFromPathNode());
currentNode = currentNode.GetFromPathNode();
}
pathNodeList.Reverse();
List<GridPosition> gridPositionList = new List<GridPosition>();
foreach (Pathnode pathnode in pathNodeList)
{
gridPositionList.Add(pathnode.GetGridPosition());
}
return gridPositionList;
}
public bool IsWalkAbleGridPosition(GridPosition gridPosition)
{
return gridSystem.GetGridObject(gridPosition).IsWalkable();
}
public bool HasPath(GridPosition startGridPosition, GridPosition endGridPosition)
{
//This takes tiles off if they are unreachable and will not display them as walkable
return FindPath(startGridPosition, endGridPosition, out int pathlength) != null;
}
public int GetPathLength(GridPosition startGridPosition, GridPosition endGridPosition)
{
FindPath(startGridPosition, endGridPosition, out int pathLength);
return pathLength;
}
}