unity--2d( A*寻路)

news2024/12/23 12:52:03

目录

一.网格式寻路

1.创建一个A*寻路脚本,命名为"AStarPathfinding.cs"。

2.创建一个人物控制的脚本,命名为"CharacterController2D.cs"。

3.创建一个游戏管理脚本,命名为"GameManager.cs"。

二.UGUI下的自动寻路

1.路径点脚本

示意图:

 代码:

2.A*寻路脚本

3.人物控制脚本

4.管理脚本


一.网格式寻路

1.创建一个A*寻路脚本,命名为"AStarPathfinding.cs"。
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<Vector2Int> FindPath(Vector3 startPos, Vector3 targetPos)
    {
        Node startNode = GetNodeFromWorldPoint(startPos);
        Node targetNode = GetNodeFromWorldPoint(targetPos);

        List<Node> openSet = new List<Node>();
        HashSet<Node> closedSet = new HashSet<Node>();
        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<Vector2Int> RetracePath(Node startNode, Node endNode)
    {
        List<Vector2Int> path = new List<Vector2Int>();
        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<Node> GetNeighbors(Node node)
    {
        List<Node> neighbors = new List<Node>();
        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;
    }
}
2.创建一个人物控制的脚本,命名为"CharacterController2D.cs"。
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);
            }
        }
    }
}
3.创建一个游戏管理脚本,命名为"GameManager.cs"。
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算法进行寻路,并沿着最短路径移动到目标点。

二.UGUI下的自动寻路

1.路径点脚本

示意图:

 代码:
using UnityEngine;

public class PathPoint : MonoBehaviour
{
    [HideInInspector]
    public RectTransform rect;

    //当前路径点 如:[0,0]
    public Vector2Int point;
    //层级
    public int sort = -9;

    private void Awake()
    {
        rect = GetComponent<RectTransform>();
    }


    [ContextMenu("SetPoint")]
    public void SetPoint()
    {
        point = new Vector2Int(int.Parse(gameObject.name.Split('-')[0]), int.Parse(gameObject.name.Split('-')[1]));
    }
}

2.A*寻路脚本

using System;
using System.Collections.Generic;
using UnityEngine;

public class AStar
{
    private List<Point> list_open = new List<Point>();//开启列表
    private List<Point> list_close = new List<Point>();//关闭列表
    public Func<int, int, bool> IsBarAction;

    //定义一个路径数组
    private List<Vector2Int> way = new List<Vector2Int>();

    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;
    }

    /// <summary>
    /// a星寻路
    /// </summary>
    /// <returns>寻到的路径</returns>
    /// <param name="starPoint">起点</param>
    /// <param name="targetPoint">终点</param>
    public List<Vector2Int> 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<Vector2Int> ways = new List<Vector2Int>();
        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;
    }
}

3.人物控制脚本

 

using System;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 移动
/// </summary>
public class Mover : MonoBehaviour
{
    private List<PathPoint> movePath;
    private int moveIndex;
    private Vector3 targetPos;
    private bool isMove = false;
    private Table target;
    private System.Action moveEnd;
    public System.Action<bool> moveAnim;
    private RectTransform rect;
    private Canvas canvas;
    public float speed = 200;
    public bool isOut = false;

    private void Awake()
    {
        rect = GetComponent<RectTransform>();
        canvas = GetComponent<Canvas>();
    }

    public bool IsMove
    {
        get => isMove; set
        {
            isMove = value;
            moveAnim?.Invoke(isMove);
        }
    }

    /// <summary>
    /// 移动到某一个桌子
    /// </summary>
    /// <param name="point"></param>
    public void MoveToServicePoint(Table table, System.Action action)
    {
        MoveToPoint(table.clothPathPoint, action);
        target = table;
    }

    //移动到某一路径点
    public void MoveToPoint(PathPoint point, System.Action action)
    {
        PathPoint cur = null;
        if (isOut)
        {
            cur = PathOutMgr.Inst.GetClosestPoint(transform.position);
            movePath = PathOutMgr.Inst.GetPath(cur.point, point.point);
        }
        else
        {
            try
            {
                cur = PathHouseMgr.Inst.GetClosestPoint(transform.position);
                movePath = PathHouseMgr.Inst.GetPath(cur.point, point.point);
            }
            catch (Exception e)
            {
                Debug.LogError("出错路径:" + cur.point.ToString() + "..." + point.point.ToString());
            }
        }
        //PathPoint cur = isOut ? PathOutMgr.Inst.GetClosestPoint(transform.position) : PathHouseMgr.Inst.GetClosestPoint(transform.position);
        //movePath = isOut ? PathOutMgr.Inst.GetPath(cur.point, point.point) : PathHouseMgr.Inst.GetPath(cur.point, point.point);
        if (movePath.Count > 1)
        {
            float angle = Vector2.Angle(movePath[0].rect.anchoredPosition - rect.anchoredPosition,
                movePath[0].rect.anchoredPosition - movePath[1].rect.anchoredPosition);
            if (angle < 90) movePath.RemoveAt(0);
            //Debug.Log(angle);
        }
        moveEnd = action;
        target = null;

        IsMove = true;
        MoveTo(0);
    }

    private void MoveTo(int index)
    {
        targetPos = index < movePath.Count ? movePath[index].rect.anchoredPosition : target.serviceTra.anchoredPosition;
        SetFace(targetPos.x <= rect.anchoredPosition.x);
        moveIndex = index;
        if (targetPos.x >= rect.anchoredPosition.x)
        {
            if (moveIndex - 1 >= 0)
                canvas.sortingOrder = movePath[moveIndex - 1].sort;
        }
        else
        {
            if (moveIndex < movePath.Count)
                canvas.sortingOrder = movePath[moveIndex].sort;
        }
    }

    private void FixedUpdate()
    {
        if (!IsMove) return;
        rect.anchoredPosition = Vector2.MoveTowards(rect.anchoredPosition, targetPos, speed * Time.fixedDeltaTime);
        if (Vector3.Distance(rect.anchoredPosition, targetPos) <= 1)
        {
            if (moveIndex + 1 <= (target != null ? movePath.Count : movePath.Count - 1))
            {

                MoveTo(moveIndex + 1);
            }
            else
            {
                MoveEnd();
            }
        }
    }

    //移动结束
    private void MoveEnd()
    {
        IsMove = false;
        //SetFace(target.FaceLeft);
        moveEnd?.Invoke();
    }

    //设置朝向
    public void SetFace(bool isLeft)
    {
        transform.localScale = new Vector3(isLeft ? 1 : -1, 1, 1);
    }
}

4.管理脚本

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<Vector2Int, PathPoint> points;
    public PathPoint goOutPoint;//离开点
    public PathPoint takePoint;//取餐点
    private AStar aStar;

    private void Awake()
    {
        Inst = this;
    }

    private void Start()
    {
        points = new Dictionary<Vector2Int, PathPoint>();
        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<PathPoint> GetPath(Vector2Int cur, Vector2Int target)
    {
        List<PathPoint> path = new List<PathPoint>();
        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<Ornament>();
        table = GetComponent<BaseTable>();
        serviceTra.SetParent(transform.parent);
    }

    /// <summary>
    /// empty空桌、waitGuest等待客人、order等待点餐、cooking正在烹饪、
    /// MealFodd送餐、Eating正在用餐、Accoint等待结账、Clean等待清扫、Waiter服务员正在服务中
    /// </summary>
    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<Decoration>().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<Guide>().IsPass("Level") && GuideMgr.CurIndex == 16)
                {
                    GuideMgr.OnGuide("Level", 17);
                }

                if (DataManager.GetModle<Guide>().IsPass("Rubbish") && Random.value <= DataManager.GetModle<Restaurant>().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<PanelBill>().ShowBill(this, order);
    }

    public void Account(bool isClick = false)
    {
        //客人离场
        SetState(State.Clean);
        guest.GoOut();

        var bill = order.Account();
        float rate = DataManager.GetModle<Restaurant>().curLevel.account - RubbishMgr.Inst.RubbishCount() * 0.05f;
        float earn = (bill[0] - bill[1]) * (1 + rate);
        PlayerInfo.Inst.CoinDeal((int)earn, Currency.Coin);
        DataManager.GetModle<D_Task>().CompleteTask(D_Task.TaskType.Service);

        FlurryScript.Instance.SendEvent(isClick ? Custom_Event.Table_Acount_Click.ToString() : Custom_Event.Table_Acount.ToString(),
            new Dictionary<string, string>()
            {
                {"收益",  ((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();
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/779432.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《零基础入门学习Python》第063讲:论一只爬虫的自我修养11:Scrapy框架之初窥门径

上一节课我们好不容易装好了 Scrapy&#xff0c;今天我们就来学习如何用好它&#xff0c;有些同学可能会有些疑惑&#xff0c;既然我们懂得了Python编写爬虫的技巧&#xff0c;那要这个所谓的爬虫框架又有什么用呢&#xff1f;其实啊&#xff0c;你懂得Python写爬虫的代码&…

基于半监督算法的工业图像缺陷检测方法:MemSeg

来源&#xff1a;投稿 作者&#xff1a;橡皮 编辑&#xff1a;学姐 论文&#xff1a;https://arxiv.org/ftp/arxiv/papers/2205/2205.00908.pdf 代码&#xff1a;https://github.com/TooTouch/MemSeg 主要贡献 提出了一个精心设计的异常模拟策略&#xff0c;用于模型的自监督…

缓存雪崩问题及解决思路

缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机&#xff0c;导致大量请求到达数据库&#xff0c;带来巨大压力。为了解决这个问题&#xff0c;我们可以采取以下几种方案。 1. 给不同的Key的TTL添加随机值 在设置缓存的过期时间&#xff08;TTL&#xff09;时…

PhpStudy靶场首页管理

PhpStudy靶场首页管理 一、源码一二、源码二三、源码三四、源码四 一、源码一 index.html <!DOCTYPE html> <html><head><meta charset"UTF-8"><title>靶场访问首页</title><style>body {background-color: #f2f2f2;colo…

一个月学通Python(二十三):RESTful架构和DRF入门

专栏介绍 结合自身经验和内部资料总结的Python教程&#xff0c;每天3-5章&#xff0c;最短1个月就能全方位的完成Python的学习并进行实战开发&#xff0c;学完了定能成为大佬&#xff01;加油吧&#xff01;卷起来&#xff01; 全部文章请访问专栏&#xff1a;《Python全栈教…

【字符流】案例:集合到文件(改进版)

案例&#xff1a;集合到文件&#xff08;改进版&#xff09; 1.需求&#xff1a; 把ArrayList集合中的学生数据写入到文本文件。要求&#xff1a;每一个学生对象的数据作为文件中的一行数据 ​ 格式&#xff1a;学号&#xff0c;姓名&#xff0c;年龄&#xff0c;居住地 2.思…

python与深度学习(五):CNN和手写数字识别

目录 1. 说明2. 卷积运算3. 填充4. 池化5. 卷积神经网络实战-手写数字识别的CNN模型5.1 导入相关库5.2 加载数据5.3 数据预处理5.4 数据处理5.5 构建网络模型5.6 模型编译5.7 模型训练、保存和评价5.8 模型测试5.9 模型训练结果的可视化 6. 手写数字识别的CNN模型可视化结果图7…

HideSeeker论文阅读

文章目录 3.1 Overview of Our System HideSeeker3.2 Visual Information Extraction3.3 Relation Graph Learning3.4 Hidden Object Inference 4 EVALUATIONS4.7 Summary 6 DISCUSSIONS AND CONCLUSION 3.1 Overview of Our System HideSeeker 我们设计了一种名为“HideSeeke…

【Selenium+Pytest+allure报告生成自动化测试框架】附带项目源码和项目部署文档

目录 前言 【文章末尾给大家留下了大量的福利】 测试框架简介 首先管理时间 添加配置文件 conf.py config.ini 读取配置文件 记录操作日志 简单理解POM模型 简单学习元素定位 管理页面元素 封装Selenium基类 创建页面对象 简单了解Pytest pytest.ini 编写测试…

保护数字世界的壁垒

随着科技的不断发展和互联网的普及&#xff0c;我们的生活日益依赖于数字化的世界。然而&#xff0c;随之而来的是网络安全威胁的不断增加。网络攻击、数据泄露和身份盗窃等问题已经成为我们所面临的现实。因此&#xff0c;网络安全变得尤为重要&#xff0c;我们需要采取措施来…

MySQL常见的几种约束

系列文章目录 后续补充 文章目录 系列文章目录前言一、主键约束二、非空约束三、唯一约束四、检查约束五、默认值约束六、字段值自动增加约束七、外键约束总结 前言 为防止不符合规范的数据存入数据库&#xff0c;在用户对数据进行插入、修改、删除等操作时&#xff0c;MySQL提…

新架构网易云音乐UI风格大变身,更像Apple Music?

继QQ的NT版本出来后&#xff0c;掀起了一番热潮&#xff0c;不少科技资讯的UP开始评测采用全新架构的QQ的性能以及内存占用情况&#xff0c; 文末中&#xff0c;苏音也提到了&#xff0c;是否在QQ新版本的发布下&#xff0c;会有越来越多的产品向Electron架构靠近&#xff1f;…

优雅的使用CLion开发STM32 2023最新版本~

1.下载资料 一共需要的资料如下 ✈代表需要魔法 没有标注可直接访问 Clion下载链接 cubemx下载链接 mingw 下载连接 ✈安装完直接解压到文件夹 并且把bin文件的路径存入path环境变量 gcc下载链接✈安装完直接解压到文件夹 并且把bin文件的路径存入path环境变量 openocd下…

详细解析黑马微信小程序视频--【思维导图知识范围】

其实总目录集链接在此&#xff1a; 如何0元学微信小程序–【浅入深出系列000】 先列前几辑&#xff0c;后面的更新在 系列000里 专辑及链接难度&#xff08;五星制&#xff09;详细解析黑马微信小程序视频–【浅入深出系列-001】难度★✫✰✰✰让别人的小程序长成自己的样子…

仿写SpringMVC中的注解和方法映射功能

本项目已开源&#xff0c;欢迎各位大佬访问并指正&#xff1a;仿写SpringMVC中的注解和方法映射功能 文章目录 一、仿写流程1、初始化2、测试 二、代码实现1、自定义注解Controller和RequestMapping2、扫描本项目下Controller下所有的java文件3、识别注解&#xff0c;完成映射4…

【算组合数】CF1833 F

少见地秒了这道1700&#xff0c;要是以后都这样就好了.... Problem - F - Codeforces 题意&#xff1a; 给定一个数列&#xff0c;让你在这个数列里找一个大小为M的子集&#xff0c;使得极差不超过M 思路&#xff1a; 子集&#xff0c;不是子序列&#xff0c;说明和顺序无…

【算法与数据结构】101、LeetCode对称二叉树

文章目录 一、题目二、递归法三、迭代法三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、递归法 思路分析&#xff1a;这道题目标就是要对比左右两半的树是否对称&#xff0c;因此对比不是左右节点是否相等&…

ThreadPoolExecutor自定义线程池|拒绝策略|线程工厂|统一捕获异常

线程池的7大参数含义介绍 corePoolSize&#xff1a;池中一直保持的线程的数量。 maximumPoolSize&#xff1a;池中允许的最大的线程数。 keepAliveTime&#xff1a;当线程数大于核心线程数的时候&#xff0c;线程在最大多长时间没有接到新任务就会终止释放&#xff0c; 最终…

点击加号添加新的输入框

实现如上图的效果 html部分&#xff1a; <el-form-item class"forminput" v-for"(item,index) in formdata.description" :key"index" :label"描述(index1)" prop"description"><el-input v-model"formdata…

STM32入门之创建工程模板

1.STM32固件库的结构图如下。从图中可以看出&#xff0c;我们在配置STM32的固件库时需要配置用户层、CMSIS层的文件。配置库文件即正确的配置这些函数的文件。CMSIS(Cortex Microcontroller Software Interface Standard)是ARM公司提供的微控制器软件接口标准&#xff0c;所有使…