zoukankan      html  css  js  c++  java
  • 【转】unity下的Line of Sight(LOS)的绘制

    http://www.cnblogs.com/yangrouchuan/p/6366629.html

    先说说什么是Linf of Sight。在很多RTS游戏中,单位与单位之间的视野关系经常会受到障碍物遮挡。Line of Sight指的就是两个物体之间是否没有障碍物遮挡。

    比如在dota中,玩家的视野会被树挡住。此时树后面的区域对该玩家是不可见的。

    如何确定两个单位是否有Line of sight是一个非常大的话题。在这方面有非常多的论文研究。但是事实上LOS的检测和绘制的关系并不是很大。我们今天主要讲一讲绘制。

    关于绘制的方法,我这两天的资料查阅下来,主要归纳如下:

    1.tile-based map中,以格子为单位绘制。可见的格子就完全可见,不可见的格子完全不可见。这种方法的优点在于效率极高,可以把每个格子和周围格子的可见性一次计算后保存起来,查询的速度到了O(1),当然缺点在于必须是tile-based的游戏。

    2.raycast的方法。用大量raycast覆盖想要检查的区域。把相邻的raycast的命中点和原点连成三角形,最后组成一个模型即为结果。优点在于视觉效果好、缺点当然是慢,不管是大量raycast还是实时组装mesh都是低效率的操作。

    目前大部分的LOS绘制都是以给游戏带来一定限制的情况下,结合两种方法来提高效率。游戏《Monaco》的制作人员在fb上发过一篇文章叙述它们的LOS绘制的方法(https://m.facebook.com/notes/monaco/line-of-sight-in-a-tile-based-world/411301481995?_rdr)其中提到了它们的tile-based系统是这个LOS绘制效率足够高的必不可少的条件之一

    在utb上的一篇视频中https://www.youtube.com/watch?v=rQG9aUWarwE,作者则用的是大量的raycast覆盖可视区域的做法。这是考虑到作者没有对游戏本身做任何假设,要求实现的方法能套用在任何游戏中而做的一种实现。
    发射大量raycast。依据交点建立模型。(图来自视频)

    这种方法中一个性能消耗较大的步骤是,当遇到边界时,需要去在这一块儿区域密集的raycast进行迭代,找到足够精确的交点。下图是不迭代时的效果。

    这是作者完成的工程的github https://github.com/SebLague/Field-of-View

    上面两种方法中,相同点在于计算得到一些遮挡的关键点,然后生成一个模型。区别在于,monaco得益于tile based map,需要的raycast次数非常少。

    本篇文章中,我们做一定的假设,要求游戏的视野遮挡物必须是box collider,算是上面两种方法的一个综合。

    用box collider的好处是,相比第二种方法中用迭代去找到一个边界点,box collider中的边界点是已经知道的。(box上的四个点)

    我们首先定义一个点集points。代表待会儿raycast的目标。

    首先我们做一次circlecastall,把所有的box collider捕捉到。然后把box collider上的四个点加入points。

    需要注意的是,对于单个box collider上的点,我们需要加入两个点到points里,一个是稍微远离box collider的点,一个是稍微靠近box collider的点。
    之后的raycast中,远离的点会打到边界上,靠近box collider的点会打到box collider上,这样才能获得正确的模型。

    //_find.FindRadius是视野的最大距离
    //BlockMask指会阻挡视野的物体的layermask。
            var blocks = Physics2D.CircleCastAll(this.transform.position, _find.FindRadius, Vector2.zero, 0, BlockMask);
    
            //获取阻挡物上的点
            var points = blocks
                .Select(rh => rh.collider as BoxCollider2D)
                .Where(bx => bx != null)
                .SelectMany(bx => {
                    Vector2[] pts = new Vector2[8];
                    var temp = bx.size;
                    temp.y = -temp.y;
                    //转换到世界坐标
                    pts[0] = bx.transform.TransformPoint(bx.offset + bx.size / 2.001f);
                    pts[1] = bx.transform.TransformPoint(bx.offset - bx.size / 2.001f);
                    pts[2] = bx.transform.TransformPoint(bx.offset + temp / 2.001f);
                    pts[3] = bx.transform.TransformPoint(bx.offset - temp / 2.001f);
                    pts[4] = bx.transform.TransformPoint(bx.offset + bx.size / 1.999f);
                    pts[5] = bx.transform.TransformPoint(bx.offset - bx.size / 1.999f);
                    pts[6] = bx.transform.TransformPoint(bx.offset + temp / 1.999f);
                    pts[7] = bx.transform.TransformPoint(bx.offset - temp / 1.999f);
                    return pts;
                });

    第二步是生成周围一圈的边界点。这样我们在边界处能获得圆滑的阴影。

            //填充边界点,BorderDentisy表示边界一共有多少点。值越大,阴影越圆滑。
            Vector2[] borderPoints = new Vector2[BorderDentisy];
            float deltaAngle = 360.0f / BorderDentisy;
            for (int i = 0; i < BorderDentisy; i++) {
                borderPoints[i] = transform.position + Quaternion.Euler(0, 0, i*deltaAngle) * Vector2.up * _find.FindRadius;
            }

    第三步即向所有点进行raycast。
    注意当raycast没有碰到东西时,返回对应的边界位置处的点

            //向所有点投影。留下最近的交点。
            points = points.Concat(borderPoints).Select(
                    pt => {
                        var t = Physics2D.Raycast(transform.position, pt - (Vector2)transform.position,_find.FindRadius, BlockMask);
                        if (t.collider == null)
                            return (Vector2)transform.position + (pt - (Vector2)transform.position).normalized * _find.FindRadius;
                        else
                            return t.point;
                    }
                );

    这样我们就获得了所有的关键点了,接下来我们把它们按角度排序,再写入模型数据即可。

    //转回local space并且按角度排序
            var orderedpts = points
                .Select(pt => transform.InverseTransformPoint(pt))
                .OrderByDescending(
                    pt => {
                        var sign = Mathf.Sign(Vector2.up.x * pt.y - Vector2.up.y * pt.x);
                        return Vector2.Angle(Vector2.up, pt) * sign;        //Angle只会返回正值,要角度排序必须区分正负
                    }
                );
    
            var zeropt = new Vector3[1];            //记得加入原点
            zeropt[0] = Vector3.zero;
            var verticesArray = zeropt.Concat(orderedpts).ToArray();
            int[] triangles = new int[(verticesArray.Length - 1 ) * 3];
    
            for(int i=0;i<verticesArray.Length - 1; i++) {        //相邻两点和原点构成一个三角形。
                triangles[i * 3] = 0;
                triangles[i * 3 + 1] = i + 1;
                triangles[i * 3 + 2] = (i + 1) % (verticesArray.Length-1) + 1;    //让最后一个三角形的最后一个顶点为1。
            }
    
            mesh.Clear();
            mesh.vertices = verticesArray;
            mesh.triangles = triangles;
            mesh.RecalculateNormals();

    因为完全的代码和其他部分有关联,就不放出了。把该脚本挂在gameobject下,添加一个mesh filter、mesh renderer,把layer设置成上一篇中说到的遮罩模型层(FowMask),应该就能看到结果了。

    最终效果:

     
    分类: Unity
  • 相关阅读:
    向现有的磁盘组加入/删除ASM磁盘
    词语相似度计算
    C++ STL algorithm 列表
    ORACLE内核参数
    一款好的UI草图设计软件
    (转)svn检出的时候报 Unable to connect to a repository at URL错误
    HTTP 错误 500.19 – Internal Server Error web.config 文件的 system.webServer/httpErrors 节中不允许绝对物理路径“C:\inetpub\custerr”。
    使用SVN后系统变慢的解决方法
    oracle 11g ORA12541: TNS: 无监听程序 (DBD ERROR: OCIServerAttach)
    [转载]无法删除oci.dll文件的解决办法
  • 原文地址:https://www.cnblogs.com/mimime/p/6856930.html
Copyright © 2011-2022 走看看