10.寻路优化

10.寻路优化


10.1 知识点

目前存在的问题:当大角度翻转移动时,士兵会进行穿插。希望的效果是士兵后阵变前阵,尽量不要穿插。


解决思路:判断当前方向向量和士兵朝向的夹角,如果夹角大于60度,就要对士兵列表重新排序。排序规则不仅要依据兵种,还要计算各个士兵和目标值的距离,距离近的放在前面。


修改控制士兵移动函数。判断新朝向和老朝向夹角是否大于60度。是的话对士兵列表重新排序,优先计算兵种,再计算士兵和目标点的距离。

/// <summary>
/// 控制士兵移动
/// </summary>
private void ControlSoldiersMove()
{
    if(Input.GetMouseButtonDown(1))
    {
        //...

        //获取目标点 通过射线检测
        if( Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hitInfo, 1000, 1 << LayerMask.NameToLayer("Ground")) )
        {
            //判断队伍新朝向和队伍老朝向之间的夹角
            //新朝向:(hitInfo.point - soldierObjs[0].transform.position).normalized
            //老朝向:soldierObjs[0].transform.forward 我们把之前的第一个士兵的面朝向作为了 阵型老朝向
            //两个朝向之间的夹角大于60度 我们就对士兵列表重新进行排序
            if( Vector3.Angle((hitInfo.point - soldierObjs[0].transform.position).normalized, soldierObjs[0].transform.forward) > 60 )
            {
                //满足条件后 重新进行士兵的排序
                //优先 兵种排序
                //其次 才是 距离排序
                soldierObjs.Sort((a, b) => {
                    //优先兵种排序
                    if (a.type < b.type)
                        return -1;
                    else if (a.type == b.type)
                    {
                        //只有兵种相同时 才会以离目标点距离进行排序
                        if (Vector3.Distance(a.transform.position, hitInfo.point) <= 
                            Vector3.Distance(b.transform.position, hitInfo.point))
                            return -1;
                        else
                            return 1;
                    }
                    else
                        return 1;
                });
            }

            //...
        }
    }
}

10.2 知识点代码

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

public class Controller : MonoBehaviour
{
    //鼠标左键是否按下
    private bool isMouseDown = false;
    
    //获取LineRenderer组件 用于绘制线段
    private LineRenderer line;
    
    //屏幕上的四个点 用于绘制矩形
    //鼠标按下时 记录的当前鼠标位置
    private Vector3 leftUpPoint;
    private Vector3 rightUpPoint;
    private Vector3 leftDownPoint;
    private Vector3 rightDownPoint;

    //射线检测获取到的信息
    private RaycastHit hitInfo;
    private Vector3 beginWorldPos;
    //选择到的士兵对象 存储在该容器
    private List<SoldierObj> soldierObjs = new List<SoldierObj>();

    //上一次鼠标点击的位置
    private Vector3 frontPos = Vector3.zero;

    //士兵之间的间隔距离
    private float soldierOffset = 3;
    
    void Start()
    {
        line = this.GetComponent<LineRenderer>();
    }
    
    void Update()
    {
        //选择士兵逻辑处理
        SelSoldiers();

        //控制士兵移动
        ControlSoldiersMove();
    }

    /// <summary>
    /// 选择士兵方法
    /// </summary>
    private void SelSoldiers()
    {
        if (Input.GetMouseButtonDown(0))
        {
            //记录鼠标当前位置
            leftUpPoint = Input.mousePosition;
            isMouseDown = true;
            
            //通过射线检测 得到地面上的点 之后 用于 范围检测
            if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hitInfo, 1000, 1 << LayerMask.NameToLayer("Ground")))
                beginWorldPos = hitInfo.point;
        }
        else if(Input.GetMouseButtonUp(0))
        {
            isMouseDown = false;
            //将线段的点设置为0个 就不会去绘制
            line.positionCount = 0;

            //每一次重新选择士兵时  我们都把记录的上一个位置清空
            frontPos = Vector3.zero;

            //清空选择
            for (int i = 0; i < soldierObjs.Count; i++)
                soldierObjs[i].SetSelSelf(false);
            soldierObjs.Clear();

            //选择当前的对象
            //通过射线检测 得到地面上的另一个点 用于 范围检测
            if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hitInfo, 1000, 1 << LayerMask.NameToLayer("Ground")))
            {
                //范围检测用的中心点 (由于 摄像机 朝向 是世界坐标系正方向 没有旋转 所以我们可以这样做)
                Vector3 center = new Vector3((hitInfo.point.x + beginWorldPos.x) / 2, 1, (hitInfo.point.z + beginWorldPos.z) / 2);
                
                //范围检测 盒装的 长宽高的一半
                Vector3 halfExtents = new Vector3(Mathf.Abs(hitInfo.point.x - beginWorldPos.x) / 2, 1, Mathf.Abs(hitInfo.point.z - beginWorldPos.z) / 2);
                
                //得到 这个盒装范围内的 所有碰撞器
                Collider[] colliders = Physics.OverlapBox(center, halfExtents);
                //soldierObjs.Count < 12 是 设置的 选择上限 最多只能选择12个士兵
                for (int i = 0; i < colliders.Length && soldierObjs.Count < 12; i++)
                {
                    SoldierObj obj = colliders[i].GetComponent<SoldierObj>();
                    if (obj != null)
                    {
                        obj.SetSelSelf(true);
                        soldierObjs.Add(obj);
                    }
                }

                //我们根据兵种的类型进行排序
                //对应类型值晓得 放到前面 否则 放到后面
                soldierObjs.Sort((a, b)=> {
                    if (a.type < b.type)
                        return -1;
                    else if (a.type == b.type)
                        return 0;
                    else
                        return 1;
                });
            }
        }

        //当鼠标左键处于按下状态时
        //就在这里面去处理 线段绘制的逻辑
        if( isMouseDown)
        {
            //注意:目前我们获取的位置 是屏幕坐标系的位置
            //设置屏幕上的4个点
            leftUpPoint.z = 5;
            
            rightDownPoint = Input.mousePosition;
            rightDownPoint.z = 5;

            rightUpPoint.x = rightDownPoint.x;
            rightUpPoint.y = leftUpPoint.y;
            rightUpPoint.z = 5;

            leftDownPoint.x = leftUpPoint.x;
            leftDownPoint.y = rightDownPoint.y;
            leftDownPoint.z = 5;

            //设置线段画线的世界坐标系的点
            line.positionCount = 4;
            line.SetPosition(0, Camera.main.ScreenToWorldPoint(leftUpPoint));
            line.SetPosition(1, Camera.main.ScreenToWorldPoint(rightUpPoint));
            line.SetPosition(2, Camera.main.ScreenToWorldPoint(rightDownPoint));
            line.SetPosition(3, Camera.main.ScreenToWorldPoint(leftDownPoint));
        }
    }

    /// <summary>
    /// 控制士兵移动
    /// </summary>
    private void ControlSoldiersMove()
    {
        if(Input.GetMouseButtonDown(1))
        {
            //没有士兵不移动
            if (soldierObjs.Count == 0)
                return;

            //获取目标点 通过射线检测
            if( Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hitInfo, 1000, 1 << LayerMask.NameToLayer("Ground")) )
            {
                //判断队伍新朝向和队伍老朝向之间的夹角
                //新朝向:(hitInfo.point - soldierObjs[0].transform.position).normalized
                //老朝向:soldierObjs[0].transform.forward 我们把之前的第一个士兵的面朝向作为了 阵型老朝向
                //两个朝向之间的夹角大于60度 我们就对士兵列表重新进行排序
                if( Vector3.Angle((hitInfo.point - soldierObjs[0].transform.position).normalized, soldierObjs[0].transform.forward) > 60 )
                {
                    //满足条件后 重新进行士兵的排序
                    //优先 兵种排序
                    //其次 才是 距离排序
                    soldierObjs.Sort((a, b) => {
                        //优先兵种排序
                        if (a.type < b.type)
                            return -1;
                        else if (a.type == b.type)
                        {
                            //只有兵种相同时 才会以离目标点距离进行排序
                            if (Vector3.Distance(a.transform.position, hitInfo.point) <= 
                                Vector3.Distance(b.transform.position, hitInfo.point))
                                return -1;
                            else
                                return 1;
                        }
                        else
                            return 1;
                    });
                }

                //通过目标点 计算出 真正的 阵型目标点
                //计算目标点
                List<Vector3> targetsPos = GetTargetPos(hitInfo.point);
                //命令士兵朝向各自的目标点 移动
                for (int i = 0; i < soldierObjs.Count; i++)
                    soldierObjs[i].Move(targetsPos[i]);
            }
        }
    }

    /// <summary>
    /// 根据鼠标点击的目标点 计算出 阵型的其它点位
    /// </summary>
    /// <param name="targetPos"></param>
    /// <returns></returns>
    private List<Vector3> GetTargetPos(Vector3 targetPos)
    {
        //需要计算目标点 的 面朝向和 右朝向
        Vector3 nowForward = Vector3.zero;
        Vector3 nowRigth = Vector3.zero;

        //是一批士兵 上一次已经移动过一次了 有上一次的位置
        if(frontPos != Vector3.zero)
            nowForward = (targetPos - frontPos).normalized;//有上一次的点 就直接计算
        else//没有上一次的点 就用第一个士兵的位置 作为上一次的点来计算
            nowForward = (targetPos - soldierObjs[0].transform.position).normalized;
        //根据面朝向 得到右朝向 旋转y轴 90度
        nowRigth = Quaternion.Euler(0, 90, 0) * nowForward;

        List<Vector3> targetsPos = new List<Vector3>();

        switch (soldierObjs.Count)
        {
            case 1:
                targetsPos.Add(targetPos);
                break;
            case 2:
                targetsPos.Add(targetPos + nowRigth * soldierOffset / 2);
                targetsPos.Add(targetPos - nowRigth * soldierOffset / 2);
                break;
            case 3:
                targetsPos.Add(targetPos);
                targetsPos.Add(targetPos + nowRigth * soldierOffset);
                targetsPos.Add(targetPos - nowRigth * soldierOffset);
                break;
            case 4:
                targetsPos.Add(targetPos + nowForward * soldierOffset / 2 - nowRigth * soldierOffset / 2);
                targetsPos.Add(targetPos + nowForward * soldierOffset / 2 + nowRigth * soldierOffset / 2);
                targetsPos.Add(targetPos - nowForward * soldierOffset / 2 - nowRigth * soldierOffset / 2);
                targetsPos.Add(targetPos - nowForward * soldierOffset / 2 + nowRigth * soldierOffset / 2);
                break;
            case 5:
                targetsPos.Add(targetPos + nowForward * soldierOffset / 2);
                targetsPos.Add(targetPos + nowForward * soldierOffset / 2 - nowRigth * soldierOffset);
                targetsPos.Add(targetPos + nowForward * soldierOffset / 2 + nowRigth * soldierOffset);
                targetsPos.Add(targetPos - nowForward * soldierOffset / 2 - nowRigth * soldierOffset);
                targetsPos.Add(targetPos - nowForward * soldierOffset / 2 + nowRigth * soldierOffset);
                break;
            case 6:
                targetsPos.Add(targetPos + nowForward * soldierOffset / 2);
                targetsPos.Add(targetPos + nowForward * soldierOffset / 2 - nowRigth * soldierOffset);
                targetsPos.Add(targetPos + nowForward * soldierOffset / 2 + nowRigth * soldierOffset);
                targetsPos.Add(targetPos - nowForward * soldierOffset / 2 - nowRigth * soldierOffset);
                targetsPos.Add(targetPos - nowForward * soldierOffset / 2 + nowRigth * soldierOffset);
                targetsPos.Add(targetPos - nowForward * soldierOffset / 2);
                break;
            case 7:
                targetsPos.Add(targetPos + nowForward * soldierOffset);
                targetsPos.Add(targetPos + nowForward * soldierOffset - nowRigth * soldierOffset);
                targetsPos.Add(targetPos + nowForward * soldierOffset + nowRigth * soldierOffset);
                targetsPos.Add(targetPos - nowRigth * soldierOffset);
                targetsPos.Add(targetPos + nowRigth * soldierOffset);
                targetsPos.Add(targetPos);
                targetsPos.Add(targetPos - nowForward * soldierOffset);
                break;
            case 8:
                targetsPos.Add(targetPos + nowForward * soldierOffset);
                targetsPos.Add(targetPos + nowForward * soldierOffset - nowRigth * soldierOffset);
                targetsPos.Add(targetPos + nowForward * soldierOffset + nowRigth * soldierOffset);
                targetsPos.Add(targetPos - nowRigth * soldierOffset);
                targetsPos.Add(targetPos + nowRigth * soldierOffset);
                targetsPos.Add(targetPos);
                targetsPos.Add(targetPos - nowForward * soldierOffset - nowRigth * soldierOffset);
                targetsPos.Add(targetPos - nowForward * soldierOffset + nowRigth * soldierOffset);
                break;
            case 9:
                targetsPos.Add(targetPos + nowForward * soldierOffset);
                targetsPos.Add(targetPos + nowForward * soldierOffset - nowRigth * soldierOffset);
                targetsPos.Add(targetPos + nowForward * soldierOffset + nowRigth * soldierOffset);
                targetsPos.Add(targetPos - nowRigth * soldierOffset);
                targetsPos.Add(targetPos + nowRigth * soldierOffset);
                targetsPos.Add(targetPos);
                targetsPos.Add(targetPos - nowForward * soldierOffset - nowRigth * soldierOffset);
                targetsPos.Add(targetPos - nowForward * soldierOffset + nowRigth * soldierOffset);
                targetsPos.Add(targetPos - nowForward * soldierOffset);
                break;
            case 10:
                targetsPos.Add(targetPos + nowForward * soldierOffset - nowRigth * soldierOffset / 2);
                targetsPos.Add(targetPos + nowForward * soldierOffset + nowRigth * soldierOffset / 2);
                targetsPos.Add(targetPos + nowForward * soldierOffset - nowRigth * soldierOffset * 1.5f);
                targetsPos.Add(targetPos + nowForward * soldierOffset + nowRigth * soldierOffset * 1.5f);
                targetsPos.Add(targetPos - nowRigth * soldierOffset * 1.5f);
                targetsPos.Add(targetPos + nowRigth * soldierOffset * 1.5f);
                targetsPos.Add(targetPos - nowRigth * soldierOffset / 2);
                targetsPos.Add(targetPos + nowRigth * soldierOffset / 2);
                targetsPos.Add(targetPos - nowForward * soldierOffset - nowRigth * soldierOffset * 1.5f);
                targetsPos.Add(targetPos - nowForward * soldierOffset + nowRigth * soldierOffset * 1.5f);
                break;
            case 11:
                targetsPos.Add(targetPos + nowForward * soldierOffset - nowRigth * soldierOffset / 2);
                targetsPos.Add(targetPos + nowForward * soldierOffset + nowRigth * soldierOffset / 2);
                targetsPos.Add(targetPos + nowForward * soldierOffset - nowRigth * soldierOffset * 1.5f);
                targetsPos.Add(targetPos + nowForward * soldierOffset + nowRigth * soldierOffset * 1.5f);
                targetsPos.Add(targetPos - nowRigth * soldierOffset * 1.5f);
                targetsPos.Add(targetPos + nowRigth * soldierOffset * 1.5f);
                targetsPos.Add(targetPos - nowRigth * soldierOffset / 2);
                targetsPos.Add(targetPos + nowRigth * soldierOffset / 2);
                targetsPos.Add(targetPos - nowForward * soldierOffset - nowRigth * soldierOffset * 1.5f);
                targetsPos.Add(targetPos - nowForward * soldierOffset + nowRigth * soldierOffset * 1.5f);
                targetsPos.Add(targetPos - nowForward * soldierOffset);
                break;
            case 12:
                targetsPos.Add(targetPos + nowForward * soldierOffset - nowRigth * soldierOffset / 2);
                targetsPos.Add(targetPos + nowForward * soldierOffset + nowRigth * soldierOffset / 2);
                targetsPos.Add(targetPos + nowForward * soldierOffset - nowRigth * soldierOffset * 1.5f);
                targetsPos.Add(targetPos + nowForward * soldierOffset + nowRigth * soldierOffset * 1.5f);
                targetsPos.Add(targetPos - nowRigth * soldierOffset * 1.5f);
                targetsPos.Add(targetPos + nowRigth * soldierOffset * 1.5f);
                targetsPos.Add(targetPos - nowRigth * soldierOffset / 2);
                targetsPos.Add(targetPos + nowRigth * soldierOffset / 2);
                targetsPos.Add(targetPos - nowForward * soldierOffset - nowRigth * soldierOffset * 1.5f);
                targetsPos.Add(targetPos - nowForward * soldierOffset + nowRigth * soldierOffset * 1.5f);
                targetsPos.Add(targetPos - nowForward * soldierOffset - nowRigth * soldierOffset / 2);
                targetsPos.Add(targetPos - nowForward * soldierOffset + nowRigth * soldierOffset / 2);
                break;
        }

        //计算完毕后  记录当前次的位置 
        frontPos = targetPos;

        return targetsPos;
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏