先说说什么是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上,这样才能获得正确的模型。

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

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

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

第三步即向所有点进行raycast。

注意当raycast没有碰到东西时,返回对应的边界位置处的点

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

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

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

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

最终效果:

unity下的Line of Sight(LOS)的绘制的更多相关文章

  1. 【转】unity下的Line of Sight(LOS)的绘制

    http://www.cnblogs.com/yangrouchuan/p/6366629.html 先说说什么是Linf of Sight.在很多RTS游戏中,单位与单位之间的视野关系经常会受到障碍 ...

  2. 【转】Using Raycasts and Dynamically Generated Geometry to Create a Line of Sight on Unity3D

    http://www.linkedin.com/pulse/using-raycasts-dynamically-generated-geometry-create-line-thomas José ...

  3. Unity下的开发框架--适应web和微端游戏异步资源请求的框架

    一.   内容简介: 1.   框架对Web与微端游戏特性的支持: Web和微端游戏最重要的特性是,资源是持续从服务器上即时下载下来的.而保证体验流畅的关键就是保证资源下载分散到持续的体验过程中,并保 ...

  4. Unity下XLua方案的各值类型GC优化深度剖析

    转自:http://gad.qq.com/article/detail/25645 前言 Unity下的C#GC Alloc(下面简称gc)是个大问题,而嵌入一个动态类型的Lua后,它们之间的交互很容 ...

  5. Poj 2074 Line of Sight

    地址:http://poj.org/problem?id=2074 题目: Line of Sight Time Limit: 1000MS   Memory Limit: 30000K Total ...

  6. 将一个Head下的Line复制到另一个Head下(ef+linq)

    今天工作中有一个需求,要求将一个Item下的Line复制到另外一个Item下面 这个需求在工作中很多,按照以往的经验肯定是先delete from,然后再一条条遍历后insert into 这两天正好 ...

  7. [原] Unity下的ElectroServer的连接

    ES的版本是5.4.1,示例目录下code_examples\ConnectAndLoginManually是Unity的连接和登录代码. 除了host和port需要指定,在连接时需要指定连接方式,如 ...

  8. 解决ubuntu unity下gvim菜单消失的问题

    #问题描述:在终端下用gvim 指令打开 gvim就不显示菜单.在不启用unity的桌面环境下用终端打开gvim是有菜单的.从程序菜单中打开gvim是显示菜单的.用sudo打开gvim也可以显示菜单, ...

  9. unity下贴图混合(Texture Blending)

    在unity制作自定义时,经常会遇到自定义妆容等问题,美术会提供大量的眉毛/胡子/腮红等贴图,来供用户选择. 美术给出的眉毛的小贴图如下: 在用户选用不同的胡子眉毛,可以将选定的小贴图和皮肤base贴 ...

随机推荐

  1. python语句和语法

    python语句和语法 python程序结构: 1.程序由模块构成. 2.模块包含语句. 3.语句包含表达式. 4.表达式建立并处理对象. python的语法实质上是有语句和表达式组成的.表达式处理对 ...

  2. runtime总结 iOS

    Runtime的特性主要是消息(方法)传递,如果消息(方法)在对象中找不到,就进行转发,具体怎么实现的呢.我们从下面几个方面探寻Runtime的实现机制. Runtime介绍 Runtime消息传递 ...

  3. 使用PSSH批量操作Linux服务器

    简介 服务器多了,有一个问题就是如何批量快速操作多台服务器,在网上搜到了PSSH工具,试用了一下发现挺好用,推荐给大家. pssh是一个python编写的可以在多台服务器上执行命令的轻量级管理工具,同 ...

  4. 安装一个apk文件源代码

     /**   * 安装一个apk文件   *   * @param file   * 要安装的完整文件名   */  protected void installApk(File file) {   ...

  5. OpenCV入门:(五:更改图片对比度和亮度)

    1. 理论 图片的转换就是将图片中的每个像素点经过一定的变换,得到新像素点,新像素点组合成一张新的图片. 改变图片对比度和亮度的变换如下: 其中α和β被称作增益参数(gain parameter)和偏 ...

  6. 用Python 的一些用法与 JS 进行类比,看有什么相似?

    Python 是一门运用很广泛的语言,自动化脚本.爬虫,甚至在深度学习领域也都有 Python 的身影.作为一名前端开发者,也了解 ES6 中的很多特性借鉴自 Python (比如默认参数.解构赋值. ...

  7. PHP Fatal error: Undefined class constant 'MYSQL_ATTR_INIT_COMMAND'

    最近用ThinkPHP,给公司布置线上的网站的时候,遇到的一个问题,记录一下. 打开IE浏览器的设置,Internet选项里的高级,将”显示友好的HTTP错误消息“前都勾去掉! 再次刷新,看到的错误是 ...

  8. maven环境变的配置(复制自己看)

    Maven项目对象模型(POM),可以通过一小段描述信息来管理项目的构建,报告和文档的软件项目管理工具. Maven 除了以程序构建能力为特色之外,还提供高级项目管理工具.由于 Maven 的缺省构建 ...

  9. const在c/c++中的区别

    #include <iostream> using namespace std; int main() { ; ; }; ; i < sizeof array / sizeof *a ...

  10. lintcode-108-分割回文串 II

    108-分割回文串 II 给定一个字符串s,将s分割成一些子串,使每个子串都是回文. 返回s符合要求的的最少分割次数. 样例 比如,给出字符串s = "aab", 返回 1, 因为 ...