目录
一.网格式寻路
1.创建一个A*寻路脚本,命名为"AStarPathfinding.cs"。
2.创建一个人物控制的脚本,命名为"CharacterController2D.cs"。
3.创建一个游戏管理脚本,命名为"GameManager.cs"。
二.UGUI下的自动寻路
1.路径点脚本
示意图:
代码:
2.A*寻路脚本
3.人物控制脚本
4.管理脚本
using UnityEngine;
using System.Collections.Generic;
public class AStarPathfinding : MonoBehaviour
{
// 定义一个节点类来表示地图中的每个格子
private class Node
{
public int x;
public int y;
public bool walkable;
public int gCost;
public int hCost;
public Node parent;
public Node(int _x, int _y, bool _walkable)
{
x = _x;
y = _y;
walkable = _walkable;
}
public int fCost
{
get { return gCost + hCost; }
}
}
public LayerMask obstacleMask; // 障碍物层
private Node[,] grid;
private Vector2Int gridSize;
public void InitializeGrid(Vector2Int size)
{
gridSize = size;
CreateGrid();
}
private void CreateGrid()
{
grid = new Node[gridSize.x, gridSize.y];
for (int x = 0; x < gridSize.x; x++)
{
for (int y = 0; y < gridSize.y; y++)
{
Vector3 worldPoint = new Vector3(x, y, 0);
bool walkable = !Physics2D.OverlapCircle(worldPoint, 0.1f, obstacleMask); // 检测当前格子是否可行走
grid[x, y] = new Node(x, y, walkable);
}
}
}
public List FindPath(Vector3 startPos, Vector3 targetPos)
{
Node startNode = GetNodeFromWorldPoint(startPos);
Node targetNode = GetNodeFromWorldPoint(targetPos);
List openSet = new List();
HashSet closedSet = new HashSet();
openSet.Add(startNode);
while (openSet.Count > 0)
{
Node currentNode = openSet[0];
for (int i = 1; i < openSet.Count; i++)
{
if (openSet[i].fCost < currentNode.fCost || openSet[i].fCost == currentNode.fCost && openSet[i].hCost < currentNode.hCost)
{
currentNode = openSet[i];
}
}
openSet.Remove(currentNode);
closedSet.Add(currentNode);
if (currentNode == targetNode)
{
return RetracePath(startNode, targetNode);
}
foreach (Node neighbor in GetNeighbors(currentNode))
{
if (!neighbor.walkable || closedSet.Contains(neighbor))
{
continue;
}
int newCostToNeighbor = currentNode.gCost + GetDistance(currentNode, neighbor);
if (newCostToNeighbor < neighbor.gCost || !openSet.Contains(neighbor))
{
neighbor.gCost = newCostToNeighbor;
neighbor.hCost = GetDistance(neighbor, targetNode);
neighbor.parent = currentNode;
if (!openSet.Contains(neighbor))
{
openSet.Add(neighbor);
}
}
}
}
return null; // 如果找不到路径,返回空
}
private List RetracePath(Node startNode, Node endNode)
{
List path = new List();
Node currentNode = endNode;
while (currentNode != startNode)
{
path.Add(new Vector2Int(currentNode.x, currentNode.y));
currentNode = currentNode.parent;
}
path.Reverse();
return path;
}
private Node GetNodeFromWorldPoint(Vector3 worldPos)
{
int x = Mathf.RoundToInt(worldPos.x);
int y = Mathf.RoundToInt(worldPos.y);
return grid[x, y];
}
private List GetNeighbors(Node node)
{
List neighbors = new List();
for (int x = -1; x <= 1; x++)
{
for (int y = -1; y <= 1; y++)
{
if (x == 0 && y == 0)
continue;
int checkX = node.x + x;
int checkY = node.y + y;
if (checkX >= 0 && checkX < gridSize.x && checkY >= 0 && checkY < gridSize.y)
{
neighbors.Add(grid[checkX, checkY]);
}
}
}
return neighbors;
}
private int GetDistance(Node nodeA, Node nodeB)
{
int distX = Mathf.Abs(nodeA.x - nodeB.x);
int distY = Mathf.Abs(nodeA.y - nodeB.y);
return distX + distY;
}
}
using UnityEngine;
public class CharacterController2D : MonoBehaviour
{
public float speed = 5f;
public AStarPathfinding pathfinding;
private Vector3 targetPosition;
private bool isMoving = false;
private int currentPathIndex = 0;
private float pathfindingUpdateInterval = 0.5f;
private float lastPathfindingUpdateTime;
private void Start()
{
lastPathfindingUpdateTime = Time.time;
targetPosition = transform.position;
}
private void Update()
{
HandleInput();
if (isMoving)
{
Move();
}
}
private void HandleInput()
{
if (Input.GetMouseButtonDown(0))
{
Vector3 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
targetPosition = new Vector3(Mathf.Round(mousePosition.x), Mathf.Round(mousePosition.y), 0f);
// 调用A*寻路
if (Time.time - lastPathfindingUpdateTime > pathfindingUpdateInterval)
{
lastPathfindingUpdateTime = Time.time;
var path = pathfinding.FindPath(transform.position, targetPosition);
if (path != null && path.Count > 0)
{
currentPathIndex = 0;
targetPosition = new Vector3(path[0].x, path[0].y, 0f);
isMoving = true;
}
}
}
}
private void Move()
{
Vector3 direction = (targetPosition - transform.position).normalized;
transform.position += direction * speed * Time.deltaTime;
if (Vector3.Distance(transform.position, targetPosition) < 0.05f)
{
// 到达当前路径点,更新目标路径点
currentPathIndex++;
if (currentPathIndex >= path.Count)
{
isMoving = false;
}
else
{
targetPosition = new Vector3(path[currentPathIndex].x, path[currentPathIndex].y, 0f);
}
}
}
}
using UnityEngine;
public class GameManager : MonoBehaviour
{
public AStarPathfinding pathfinding;
public CharacterController2D character;
private void Start()
{
pathfinding.InitializeGrid(new Vector2Int(10, 10)); // 设置地图大小
// 这里可以根据需求设置障碍物等
}
}
在场景中创建一个空物体并将"GameManager.cs"脚本和其他脚本(A*寻路脚本、人物控制脚本)挂载到这个空物体上。然后创建一个2D人物(例如一个精灵或一个SpriteRenderer)并挂载"CharacterController2D.cs"脚本。配置好障碍物层和人物的移动速度等参数。
现在,当你运行游戏并点击鼠标左键在地图上选择目标点,人物就会使用A算法进行寻路,并沿着最短路径移动到目标点。
using UnityEngine;
public class PathPoint : MonoBehaviour
{
[HideInInspector]
public RectTransform rect;
//当前路径点 如:[0,0]
public Vector2Int point;
//层级
public int sort = -9;
private void Awake()
{
rect = GetComponent();
}
[ContextMenu("SetPoint")]
public void SetPoint()
{
point = new Vector2Int(int.Parse(gameObject.name.Split('-')[0]), int.Parse(gameObject.name.Split('-')[1]));
}
}
using System;
using System.Collections.Generic;
using UnityEngine;
public class AStar
{
private List list_open = new List();//开启列表
private List list_close = new List();//关闭列表
public Func IsBarAction;
//定义一个路径数组
private List way = new List();
private int startX, startY, endX, endY;
public void Init(int _startX, int _startY, int _endX, int _endY)
{
startX = _startX;
startY = _startY;
endX = _endX;
endY = _endY;
}
//从开启列表中找到那个F值最小的格子
private Point FindMinFInOpenList()
{
Point minPoint = null;
foreach (var v in list_open)
{
if (minPoint == null || minPoint.GetF > v.GetF)
minPoint = v;
}
return minPoint;
}
//从开启列表中找到格子
private Point FindInOpenList(int x, int y)
{
foreach (var v in list_open)
{
if (v.x == x && v.y == y)
return v;
}
return null;
}
//判断某点是否在开启列表中
private bool IsInOpenList(int x, int y)
{
foreach (var v in list_open)
{
if (v.x == x && v.y == y)
return true;
}
return false;
}
//判断某点是否在关闭列表中
private bool IsInCloseList(int x, int y)
{
foreach (var v in list_close)
{
if (v.x == x && v.y == y)
return true;
}
return false;
}
///
/// a星寻路
///
/// 寻到的路径
/// 起点
/// 终点
public List AStarFindWay(Vector2Int starPoint, Vector2Int targetPoint)
{
//Debug.LogError("寻路:" + starPoint.ToString() + "..." + targetPoint.ToString());
//清空容器
way.Clear();
list_open.Clear();
list_close.Clear();
//初始化起点格子
Point starMapPoint = new Point(starPoint.x, starPoint.y);
//初始化终点格子
Point targetMapPoint = new Point(targetPoint.x, targetPoint.y);
//将起点格子添加到开启列表中
list_open.Add(starMapPoint);
//寻找最佳路径
//当目标点不在打开路径中时或者打开列表为空时循环执行
while (!IsInOpenList(targetMapPoint.x, targetMapPoint.y) || list_open.Count == 0)
{
//从开启列表中找到那个F值最小的格子
Point minPoint = FindMinFInOpenList();
if (minPoint == null)
return null;
//将该点从开启列表中删除,同时添加到关闭列表中
list_open.Remove(minPoint);
list_close.Add(minPoint);
//检查该点周边的格子
CheckPerPointWithMapFour(minPoint, targetMapPoint);
}
//在开启列表中找到终点
Point endPoint = FindInOpenList(targetMapPoint.x, targetMapPoint.y);
Vector2Int everyWay = new Vector2Int(endPoint.x, endPoint.y);//保存单个路径点
way.Add(everyWay);//添加到路径数组中
//遍历终点,找到每一个父节点:即寻到的路
while (endPoint.fatherPoint != null)
{
everyWay.x = endPoint.fatherPoint.x;
everyWay.y = endPoint.fatherPoint.y;
way.Add(everyWay);
endPoint = endPoint.fatherPoint;
}
//将路径从倒序变成正序并返回
List ways = new List();
for (int i = way.Count - 1; i >= 0; --i)
{
ways.Add(way[i]);
}
//清空容器
way.Clear();
list_open.Clear();
list_close.Clear();
//返回正序的路径数组
return ways;
}
//判断地图上某个坐标点是不是障碍点
private bool IsBar(int x, int y)
{
return IsBarAction(x, y);
}
//计算某方块的G值
public int GetG(Point p)
{
if (p.fatherPoint == null)
return 0;
if (p.x == p.fatherPoint.x || p.y == p.fatherPoint.y)
return p.fatherPoint.G + 10;
else
return p.fatherPoint.G + 14;
}
//计算某方块的H值
public int GetH(Point p, Point targetPoint)
{
return (Mathf.Abs(targetPoint.x - p.x) + Mathf.Abs(targetPoint.y - p.y)) * 10;
}
//检查某点周边的格子 周围8个
private void CheckPerPointWithMapEight(Point _point, Point targetPoint)
{
for (int i = _point.x - 1; i <= _point.x + 1; ++i)
{
for (int j = _point.y - 1; j <= _point.y + 1; ++j)
{
//剔除超过地图的点
if (i < startX || i >= endX || j < startY || j >= endY)
continue;
//剔除该点是障碍点:即周围有墙的点
if (IsBar(i, j))
continue;
//剔除已经存在关闭列表或者本身点
if (IsInCloseList(i, j) || (i == _point.x && j == _point.y))
continue;
//剩下的就是没有判断过的点了
if (IsInOpenList(i, j))
{
//如果该点在开启列表中
//找到该点
Point point = FindInOpenList(i, j);
int G = 0;
//计算出该点新的移动代价
if (point.x == _point.x || point.y == _point.y)
G = point.G + 10;
else
G = point.G + 14;
//如果该点的新G值比前一次小
if (G < point.G)
{
//更新新的G点
point.G = G;
point.fatherPoint = _point;
}
}
else
{
//如果该点不在开启列表内
//初始化该点,并将该点添加到开启列表中
Point newPoint = new Point();
newPoint.x = i;
newPoint.y = j;
newPoint.fatherPoint = _point;
//计算该点的G值和H值并赋值
newPoint.G = GetG(newPoint);
newPoint.H = GetH(newPoint, targetPoint);
//将初始化完毕的格子添加到开启列表中
list_open.Add(newPoint);
}
}
}
}
//检查某点周边的格子 上下左右4个
private void CheckPerPointWithMapFour(Point _point, Point targetPoint)
{
for (int i = _point.x - 1; i <= _point.x + 1; ++i)
{
for (int j = _point.y - 1; j <= _point.y + 1; ++j)
{
//剔除超过地图的点
if (i < startX || i > endX || j < startY || j > endY)
continue;
//去除斜线方向上的点
if (Mathf.Abs(i - _point.x) + Mathf.Abs(j - _point.y) != 1)
continue;
//去除该点是障碍点:即周围有墙的点
if (IsBar(i, j))
continue;
//去除已经存在关闭列表或者本身点
if (IsInCloseList(i, j) || (i == _point.x && j == _point.y))
continue;
//剩下的就是没有判断过的点了
if (IsInOpenList(i, j))
{
//如果该点在开启列表中
//找到该点
Point point = FindInOpenList(i, j);
int G = 0;
//计算出该点新的移动代价
if (point.x == _point.x || point.y == _point.y)
G = point.G + 10;
//else
// G = point.G + 14;
//如果该点的新G值比前一次小
if (G < point.G)
{
//更新新的G点
point.G = G;
point.fatherPoint = _point;
}
}
else
{
//如果该点不在开启列表内
//初始化该点,并将该点添加到开启列表中
Point newPoint = new Point();
newPoint.x = i;
newPoint.y = j;
newPoint.fatherPoint = _point;
//计算该点的G值和H值并赋值
newPoint.G = GetG(newPoint);
newPoint.H = GetH(newPoint, targetPoint);
//将初始化完毕的格子添加到开启列表中
list_open.Add(newPoint);
}
}
}
}
}
public class Point
{
//F = G + H
//G 从起点A移动到指定方格的移动代价,父格子到本格子代价:直线为10,斜线为14
//H 使用 Manhattan 计算方法, 计算(当前方格到目标方格的横线上+竖线上所经过的方格数)* 10
public int x;//格子的x坐标
public int y;//格子的y坐标
public int G;//G : 从开始到当前位置
public int H;//H : 从当前位置到目标
public int GetF
{
get
{
return G + H;
}
}
public Point fatherPoint;//父格子
public Point() { }
public Point(int _x, int _y)
{
x = _x;
y = _y;
}
public Point(int _x, int _y, int _G, int _H, Point _fatherPoint)
{
this.x = _x;
this.y = _y;
this.G = _G;
this.H = _H;
this.fatherPoint = _fatherPoint;
}
}
using System;
using System.Collections.Generic;
using UnityEngine;
///
/// 移动
///
public class Mover : MonoBehaviour
{
private List movePath;
private int moveIndex;
private Vector3 targetPos;
private bool isMove = false;
private Table target;
private System.Action moveEnd;
public System.Action moveAnim;
private RectTransform rect;
private Canvas canvas;
public float speed = 200;
public bool isOut = false;
private void Awake()
{
rect = GetComponent();
canvas = GetComponent
using System.Collections.Generic;
using UnityEngine;
public class PathHouseMgr : MonoBehaviour
{
public static PathHouseMgr Inst { get; private set; }
public Vector2Int max;
public Vector2Int min = new Vector2Int(-1, 0);
public PathPoint[] pathPoint;
private Dictionary points;
public PathPoint goOutPoint;//离开点
public PathPoint takePoint;//取餐点
private AStar aStar;
private void Awake()
{
Inst = this;
}
private void Start()
{
points = new Dictionary();
foreach (var v in pathPoint) points.Add(v.point, v);
aStar = new AStar();
aStar.Init(min.x, min.y, max.x, max.y);
aStar.IsBarAction += IsNotMove;
}
public List GetPath(Vector2Int cur, Vector2Int target)
{
List path = new List();
var aStar = this.aStar.AStarFindWay(cur, target);
foreach (var v in aStar)
{
path.Add(points[v]);
}
return path;
}
public PathPoint GetClosestPoint(Vector3 pos)
{
float min = float.MaxValue;
float dis = 0;
PathPoint p = null;
foreach (var v in pathPoint)
{
dis = Vector3.Distance(pos, v.transform.position);
if (dis <= min)
{
min = dis;
p = v;
}
}
return p;
}
//是否是不能移动的点
public bool IsNotMove(int x, int y)
{
return !points.ContainsKey(new Vector2Int(x, y));
}
}
5.移动目标脚本
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Table : MonoBehaviour
{
public RectTransform serviceTra; //服务点
public PathPoint clothPathPoint;//距离最近的移动点
public string food = "can1";
[SerializeField] private GameObject[] stateObjs;
public State state = State.Empty; //当前桌子状态
private Ornament ornament; //装饰物
[HideInInspector] public BaseTable table;
[SerializeField] private Image foodIcon;
[SerializeField] private GameObject eatObj;
private GuestGroup guest;
private float timer;
[HideInInspector] public GuestOrder order;
private float eatBaseTime = 5;
public bool FaceLeft { get => serviceTra.position.x > transform.position.x; }
private void Awake()
{
ornament = GetComponent();
table = GetComponent();
serviceTra.SetParent(transform.parent);
}
///
/// empty空桌、waitGuest等待客人、order等待点餐、cooking正在烹饪、
/// MealFodd送餐、Eating正在用餐、Accoint等待结账、Clean等待清扫、Waiter服务员正在服务中
///
public enum State
{
Empty = 0, //空桌
WaitGuest, //等待客人
Order, //等待点餐
Cooking, //正在烹饪
MealFood, //送餐
Eating, //正在吃饭
Account, //等待结账
Clean, //等待清扫
Waiter, //服务员正在服务中
}
public bool IsUnlock { get => ornament.partData.IsUnlock; }
public string TableName { get => ornament.partData.data.part; }
public int PeopleNum { get => guest.guests.Count; }
public float EatRate
{
get
{
return DataManager.GetModle().GetDecoration(ornament.partData.Decoration).data.funcValue;
}
}
public void GuestSit(GuestGroup guest, Guest guest1)
{
this.guest = guest;
table.Sit(guest1, guest1.index);
//SetState(State.Order);
}
//设置当前状态
public void SetState(State state)
{
this.state = state;
int s = -1;
switch (state)
{
case State.Order:
s = 0;
break;
case State.MealFood:
s = 1;
break;
case State.Account:
s = 2;
break;
case State.Clean:
s = 3;
break;
}
for (int i = 0; i < stateObjs.Length; i++)
{
stateObjs[i].SetActive(i == s);
}
}
private void Update()
{
if (state == State.Cooking)
{
timer -= Time.deltaTime;
if (timer <= 0)
{
//等待送餐
SetState(State.MealFood);
}
}
else if (state == State.Eating)
{
timer -= Time.deltaTime;
if (timer <= 0)
{
//结账
foodIcon.gameObject.SetActive(false);
eatObj.SetActive(true);
guest.guests.ForEach((s) => s.SetState(Guest.State.Sit));
SetState(State.Account);
if (!DataManager.GetModle().IsPass("Level") && GuideMgr.CurIndex == 16)
{
GuideMgr.OnGuide("Level", 17);
}
if (DataManager.GetModle().IsPass("Rubbish") && Random.value <= DataManager.GetModle().curLevel.rubishRate)
{
RubbishMgr.Inst.SpawnRubbish();
}
}
}
}
//点餐
public void Order()
{
order = new GuestOrder(guest.GetDishNum());
//烹饪时间
timer = order.CookTime * (1 - OrderMgr.Inst.GetFuncData(DecorationFunc.DownCookTime));
SetState(State.Cooking);
}
//送餐
public void MealFood()
{
foodIcon.sprite = GUtil.GetSprite($"{food}_{Random.Range(1, 5)}");
foodIcon.gameObject.SetActive(true);
guest.guests.ForEach((s) => s.SetState(Guest.State.Eating));
timer = eatBaseTime * (1 - EatRate);
SetState(State.Eating);
}
//结账
public void Btn_Account()
{
Account(true);
UIManager.OpenUIWindow().ShowBill(this, order);
}
public void Account(bool isClick = false)
{
//客人离场
SetState(State.Clean);
guest.GoOut();
var bill = order.Account();
float rate = DataManager.GetModle().curLevel.account - RubbishMgr.Inst.RubbishCount() * 0.05f;
float earn = (bill[0] - bill[1]) * (1 + rate);
PlayerInfo.Inst.CoinDeal((int)earn, Currency.Coin);
DataManager.GetModle().CompleteTask(D_Task.TaskType.Service);
FlurryScript.Instance.SendEvent(isClick ? Custom_Event.Table_Acount_Click.ToString() : Custom_Event.Table_Acount.ToString(),
new Dictionary()
{
{"收益", ((int)((bill[0] - bill[1]) / (float)bill[1] * 100f)).ToString("d2")}
});
}
public void Btn_Clean()
{
SetState(State.Empty);
OrderMgr.Inst.OnEmpty();
eatObj.SetActive(false);
StaffMgr.Inst.OnWash();
}
}