• 游戏开发37课 狙击枪 视野问题


     

    首先说一下视野的思路:我们可视化的视野全部都是以扇形显示的,同时为了后期的方便调整我们的视野和距离都必须是动态的。那么我们是不是可以使用度数来控制视野范围,那么我们就需要画出一个扇形。那么我们可以先画出来一个圆 然后在这个圆上面来截取扇形,在画圆之前我们需要定义圆的半径(因为我们的物体在圆的中心,所以圆的半径就代表了视野的纵向长度,代表了物体可以看多远),我们还需要定义一个视野的角度(视野 的角度代表了扇形的大小,表示视野的横向大小)。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    public class FieldOfView : MonoBehaviour
    {
        ///


        /// 视野的半径
        ///

        public float viewRadius;
        ///
        /// 视野的角度
        ///

        [Range(0, 360)]//Unity特性(限制最大最小值)并以滑动条的形式显示出来
        public float viewAngle;
        ///
        /// 玩家的层级
        ///

        public LayerMask targetMask;
        ///
        /// 障碍物的层级
        ///

        public LayerMask obstacleMask;

        ///


        /// 保存射线打到敌人的位置的列表
        ///

        [HideInInspector]//特性(使其不会在检视面板显示)
        public List visibleTargets = new List();

        ///


        /// 每一度发射多少射线
        ///

        public float meshResolution;
        public int edgeResolverations;
        public float edgeDstThreshold;

        ///


        /// 布尔值控制是否可以进行变色
        ///

        bool isColor = false;

        ///


        /// 网格
        ///

        public MeshFilter viewMeshFilter;

        Mesh viewMesh;

        private void Start()
        {
            //viewMesh = new Mesh();
            //viewMesh.name = "View Mesh";
            //viewMeshFilter.mesh = viewMesh;
            //viewMesh.name = "111";
            viewMesh = new Mesh();
            viewMeshFilter.mesh = viewMesh;
            //开启协程
            StartCoroutine("FindTargetsWithDelay",0.2f);
        }
        IEnumerator FindTargetsWithDelay(float delay)
        {
            while (true)
            {
                yield return new WaitForSeconds(delay);
                FindVisibleTarget();
            }
        }

        void LateUpdate()
        {
            DrawFieldOfView();
        }

        ///


        /// 把打到的玩家物体保存在数组中
        ///

        void FindVisibleTarget()
        {
            //列表清空
            visibleTargets.Clear();

            //创建一个数组保存所有与重叠球接触的碰撞器 。 方法的参数数据为(球的中心,球的半径,选择投射的层级)
            Collider[] targetsInViewRadius = Physics.OverlapSphere(transform.position, viewRadius, targetMask);
            //遍历数组中所有的碰撞器
            for (int i = 0; i < targetsInViewRadius.Length; i++)
            {
                //遍历接收所有碰撞器的位置
                Transform target = targetsInViewRadius[i].transform;
                //获取接收到的碰撞器与物体之间的vector3向量(方向)
                Vector3 dirToTarget = (target.position - transform.position).normalized;
                //如果物体与碰撞器的角度小于视野角度的二分之一
                if (Vector3.Angle(transform.forward, dirToTarget) < viewAngle / 2)
                {
                    //计算物体与碰撞器的距离
                    float dstToTarget = Vector3.Distance(transform.position, target.position);
                    //如果射线没有打到障碍物就把打到的物体保存在数组中{如果视野范围内有玩家,判断他们之间是否有障碍物}(起点,方向,射线的最大长度,射线可以照到的层级)
                    if (!Physics.Raycast(transform.position, dirToTarget, dstToTarget, obstacleMask))
                    {
                        //把打到的层为targetMask的物体保存在数组中
                        visibleTargets.Add(target);
                        //print("打到了" + target.tag);
                        isColor = true;
                    }
                    else
                    {
                        isColor = false;
                    }
                }
            }
        }
        //控制视野的变色
        float a = 0f;
        private void Update()
        {
            if (isColor)
            {
                a += 0.001f;
                print(a);
                if (a >= 1)
                {
                    a = 1;
                }
                MeshRenderer ma = GameObject.Find("View Visualisation").GetComponent();
                ma.material.color = Color.Lerp(new Color(1, 1, 1, .5f), new Color(1, 0, 0, 0.5f),/*Mathf.PingPong(Time.time,1)*/a);
                print(ma.material.color);
            }
            else
            {
                a = 0;
                MeshRenderer ma = GameObject.Find("View Visualisation").GetComponent();
                ma.material.color = new Color(1, 1, 1, .5f);
            }
            print(isColor);
        }

        ///


        /// 绘制视图
        ///

        void DrawFieldOfView()
        {
            //判断发射多少条射线
            int stepCount = Mathf.RoundToInt(viewAngle * meshResolution);
            print(stepCount);
            //把视野分为以0.1度为单位
            float stepAngleSize = viewAngle / stepCount;
            //创建一个Vector3的列表
            List viewPoints = new List();

            //以一度为单位遍历所有的视野度数
            for (int i = 0; i <= stepCount; i++)
            {
                //物体的旋转跟随移动
                float angle = transform.eulerAngles.y - viewAngle / 2 + stepAngleSize * i;
                //print("欧拉角:" + angle);
                //Debug.DrawLine(transform.position, transform.position + DirFromAngle(angle, true) * viewRadius, Color.red);
                //把角度传进去判断是否达到了障碍物层级(如果达到了障碍物层级就把障碍物的位置,距离,还有传进去的角度全部返回结构体)
                ViewCastInfo newViewCast = ViewCast(angle);
                //if (i > 0)
                //{
                //    //如果i大于0返回视野打到障碍物的距离是否大于0.5
                //    bool edgeDsrThresholdExceeded = Mathf.Abs(oldViewCast.dst - newViewCast.dst) > edgeDstThreshold;
                //    print(edgeDsrThresholdExceeded);
                //    print("old" + oldViewCast.point);
                //    print(edgeDstThreshold);
                //    //如果old的布尔值不等与new的布尔值或者old为true new为true,edge 为true(判断打到了障碍物)
                //    if (oldViewCast.hit != newViewCast.hit || (oldViewCast.hit && newViewCast.hit && edgeDsrThresholdExceeded))
                //    {
                //        EdgeInfo edge = FindEdge(oldViewCast, newViewCast);
                //        if (edge.pointA != Vector3.zero)
                //        {
                //            viewPoints.Add(edge.pointA);
                //        }
                //        if (edge.pointB != Vector3.zero)
                //        {
                //            viewPoints.Add(edge.pointB);
                //        }
                //    }
                //}
                //保存所有的Vector3
                viewPoints.Add(newViewCast.point);
                //oldViewCast = newViewCast;
            }
            //顶点数
            int vertexCount = viewPoints.Count + 1;
            //保存顶点的数组
            Vector3[] vertices = new Vector3[vertexCount];
            //保存构建所有三角形的所有顶点数
            int[] triangles = new int[(vertexCount - 2) * 3];
            //第一个顶点的位置为0
            vertices[0] = Vector3.zero;
            //因为设置了第一个顶点,所以需要减1
            for (int i = 0; i < vertexCount - 1; i++)
            {
                //给保存顶点的数组赋值(世界坐标转换成局部坐标)
                vertices[i + 1] = transform.InverseTransformPoint(viewPoints[i]);

                if (i < vertexCount - 2)
                {
                    //给构建所有三角形的所有数组赋值
                    triangles[i * 3] = 0;
                    triangles[i * 3 + 1] = i + 1;
                    triangles[i * 3 + 2] = i + 2;
                }

            }
            viewMesh.Clear();
            //网格的顶点等于顶点数组
            viewMesh.vertices = vertices;
            //给构建三角形的所有顶点赋值
            viewMesh.triangles = triangles;
            //每次赋值结束重新计算法线
            viewMesh.RecalculateNormals();

        }

        //EdgeInfo FindEdge(ViewCastInfo minViewCast, ViewCastInfo maxViewVast)
        //{
        //    //最小的角度
        //    float minAngle = minViewCast.angle;
        //    //最大的角度
        //    float maxAngle = maxViewVast.angle;
        //    //最小向量
        //    Vector3 minPoint = Vector3.zero;
        //    //最大向量
        //    Vector3 maxPoint = Vector3.zero;

        //    //遍历4
        //    for (int i = 0; i < edgeResolverations; i++)
        //    {
        //        float angle = (minAngle + maxAngle) / 2;

        //        ViewCastInfo newViewCast = ViewCast(angle);

        //        bool edgeDsrThresholdExceeded = Mathf.Abs(minViewCast.dst - newViewCast.dst) > edgeDstThreshold;
        //        if (newViewCast.hit == minViewCast.hit && !edgeDsrThresholdExceeded)
        //        {
        //            minAngle = angle;
        //            minPoint = newViewCast.point;
        //        }
        //        else
        //        {
        //            maxAngle = angle;
        //            maxPoint = newViewCast.point;
        //        }
        //    }

        //    return new EdgeInfo(minPoint, maxPoint);

        //}

        ///


        /// 把传进来的角度转为向量,然后发射射线判断是否达到了障碍物层级(如果打到了障碍物返回碰撞信息以及障碍物的位置以及障碍物的距离)
        ///

        ///
        ///
        ViewCastInfo ViewCast(float globalAngle)
        {
            //把传进来的角度转为向量
            Vector3 dir = DirFromAngle(globalAngle, true);
            RaycastHit hit;
            //如果投射了射线(射线的参数为:起点的位置,射线的方向,射线碰撞信息的返回值,射线的最大长度,射线可以投射的层级为障碍物层级)
            if (Physics.Raycast(transform.position, dir, out hit, viewRadius, obstacleMask))
            {
                //返回值为bool值,射线碰撞信息的位置,射线发生碰撞的距离,传进来的角度
                return new ViewCastInfo(true, hit.point, hit.distance, globalAngle);
            }
            //如果没有打到的不是障碍物层级
            else
            {
                //返回值为bool值,玩家的位置+传进来的位置转为的Vector3,视野的半径,传进来的角度
                return new ViewCastInfo(false, transform.position + dir * viewRadius, viewRadius, globalAngle);
            }   
        }

        ///


        /// 把传进来的度数转为Vector3(给出角的方向)
        ///

        /// 传进来的角度
        /// bool值判断角度是否变化
        ///
        public Vector3 DirFromAngle(float angleInDegrees, bool angleIsGlobal)
        {
            //如果使false
            if (!angleIsGlobal)
            {
                //角度加上自身的欧拉角的y(旋转角度的Y轴)
                angleInDegrees += transform.eulerAngles.y;
            }
            //返回(把传进来的度数转为弧度再转为正弦,0,把传进来的角度转为弧度再转为余弦)
            return new Vector3(Mathf.Sin(angleInDegrees * Mathf.Deg2Rad), 0, Mathf.Cos(angleInDegrees * Mathf.Deg2Rad));
        }

        ///


        /// 结构体用来接收数据射线的信息
        ///

        public struct ViewCastInfo
        {
            ///
            /// 射线是否打中物体
            ///

            public bool hit;
            ///
            /// 位置点(终点)
            ///

            public Vector3 point;
            ///
            /// 距离(射线的长度)
            ///

            public float dst;
            ///
            /// 角度
            ///

            public float angle;

            public ViewCastInfo(bool _hit, Vector3 _point, float _dst, float _angle)
            {
                hit = _hit;
                point = _point;
                dst = _dst;
                angle = _angle;
            }
        }

        public struct EdgeInfo
        {
            public Vector3 pointA;
            public Vector3 pointB;

            public EdgeInfo(Vector3 _pointA, Vector3 _pointB)
            {
                pointA = _pointA;
                pointB = _pointB;
            }
        }

    }
    ————————————————
    版权声明:本文为CSDN博主「你好棒梆」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/Plutogd/article/details/117636942

  • 相关阅读:
    前端——重要 发送表单数据 post
    虚拟机扩容
    从底层理解MySQL-字符类型
    C语言 | Leetcode C语言题解之第129题求根节点到叶节点数字之和
    pcb线路板制作过程分析
    Docker Compose 容器编排
    微服务开发面试题,java服务端面试题
    【VIM TMUX】开发工具 Vim 在 bash 中的显示与 tmux 中的显示不同
    一文搞懂并查集
    `Executor` 接口
  • 原文地址:https://blog.csdn.net/s178435865/article/details/128212053