Unity——防视觉死角的镜头

这两天在搞Unity方面的小项目中镜头的脚本,做出了几种规避镜头因为死角看不到玩家的问题的方案,写下来以作记录。

首先说一下我人物镜头设置的方案,不同于网上常见的把镜头斜放在人物上方,线性拉伸与缩短的方法,我的镜头是按照半圆为路径进行变换的。之所以要这么做,是因为本来我想的方案是可以在真·第三人称(镜头在人物后脑勺)和上帝视角切换的,如果要这么做,要么就需要定两个点在需要的时候切换坐标,要么就放两个镜头一直跟着。然而后来我突然想到,如果有一种方案能够兼顾以上两种情况,那可以节约一些资源,不多说,上图:

这个圆周运动怎么完成呢?要用到一个很简单的数学公式:

sin²Θ+cos²Θ=1

Unity世界坐标系为左手坐标系(具体可以看我写的这篇文章),所以前是Z,上是Y,所以我们可以通过这种方法来算出坐标:

posY = radius * Mathf.Sin(angled * Mathf.Deg2Rad);
posZ = radius * Mathf.Cos(angled * Mathf.Deg2Rad);

稍微解释一下这个Deg2Rad是什么:Degree to Radian。角度制转换成弧度制的意思,角度制大家很清楚不多说,弧度制简单说一下。长度为半径长的弧,所对的圆心角是 1 弧度(Radian),用符号rad表示。三角函数中的参数制式为弧度制,这与平时印象有点不同,要记住。

这里求出的posY和posZ是以物体为圆心算出的结果,要做圆心偏移很简单,设置一个movepoint变量,在求nextposition时往Z方向加上即可:

Vector3 nextposition = CirclePoint.forward * -1 * (movepoint + posZ) + CirclePoint.up * posY + CirclePoint.position;

这里的-1是因为相机始终在人物之后,有一点要注意,如果不以这种方法求出nextposition(另一种方法是直接给nextposition赋值),那么物体转向的时候,摄像机是不会进行跟随的。这一步做完,简单的变换基本就完成了,最后不要忘了transform.LookAt()物体调整一下相机角度看向物体。

0,基础知识

在我的所有方案中,镜头都与“线”有关,因为要知道相机能不能看到物体,只需要在物体和摄像机之间连一条线,如果这条线碰到物体了,那么可以认为,看不到。我这里举例两种,那么看函数:

Linecast(Vector3 origin, Vector3 direction, out hit);//线段参数
Raycast(Vector3 origin, Vector3 direction, float maxDistance, out hit);//射线参数

一般而言,我们需要传入的就这几个参数,其他不做讨论。两个函数返回值都为bool类型,如果有碰撞,则为true,反之为false。

origin是起点坐标,direction是终点坐标或者矢量方向,maxDistance是射线的最长长度,hit将包含碰撞物体的内容,可以通过hit.collider.name或者hit.collider.tag等方法来获取碰撞物体的信息。

这里获取的信息是第一个碰撞到物体的信息。这里要注意,这个线,会把你的物体也算在里面,也就是说,就算物体和摄像机间没有物体,按照我的做法也会返回true。这个触发返回值的物体是端点物体本身。

要避免这个情况,可以通过把maxDistance设置为Camera.position() – Object.position() – 1,也可以设置一个tag,如果hit返回的是本物体的tag,则为无碰撞。反之,则作出相对动作。

1,飞檐走壁

第一种方法是模拟人的行为来进行缩放,即如果你看不到物体了,会自动进行缩放

如图所示,如果摄像机和物体连线间有别的物体的话,那么就可以算成看不见。

如果是人的话,遇到一个物体,肯定会想着从上面把镜头调过去,我们只需要通过自动变换把物体晃过去就可以。

做法很简单,模拟触发滚轮动作和直接进行一次调整都可以,不多赘述。

那么说一下问题:

如图所示的两种情况是我还没能解决的,靠墙的时候,因为防穿地的机制会导致镜头在地面上上下弹跳,造成不良的游玩体验。这里留下个解决思路,可以将镜头临时调至物体正上方。

上面这种情况是人和机器观察误差,尽管连线是直接连在摄像机和物体之间,但是实际上人不一定看到物体。我没去解决这个问题的原因是数学上不太熟练,留下个思路:通过相机的fieldofview获取角度,然后根据这个角度把相机调的更加高一点。

2,直接透视

直接透视是一种比较偷懒但是观感还不错的解决方法,不过在寻找使障碍物透明方法的路上我碰了不少壁。一开始我想的方案是障碍物完全透明,用线条再勾勒框架以示物体所在位置,这种方法需要使用到Shader,所以才有了我上一篇文章,感兴趣可以去看看。第二种方法我直接把障碍物透明了,但是问题是这样一来,阴影就不见了,显得特别突兀,特别假,留下代码以供参考:

Object.GetComponent<Renderer>().enabled = false;
Object.GetComponent<Renderer>().enabled = true;

最后一种方法是半透明,为了实现这个,我在网上找了不少资料,稍微列下来以示不满足我条件的原因:

  • Unity界面调整材质(我需要动态变化,这种不合适)。
  • Shader(暂时我没有掌握动态触发渲染的方法,以及不具备编写Shader的能力)。
  • 相机挂脚本(为了减少相机数量我才想出的圆形镜头变换方式,不能为此放弃)。
  • 还有一种什么renderer的,但是那个是只有二维才能用的,不知道谁出的馊主意还是百度知道的回答。

最后我从材质这个方面找到思路,通过Renderer的Color属性中alpha值改变透明度,上代码:

Object.GetComponent<Renderer>().material.color = new Color(Object.GetComponent<Renderer>().material.color.r, Object.GetComponent<Renderer>().material.color.g, Object.GetComponent<Renderer>().material.color.b, 0.4f);

Object.GetComponent<Renderer>().material.color = new Color(Object.GetComponent<Renderer>().material.color.r, Object.GetComponent<Renderer>().material.color.g, Object.GetComponent<Renderer>().material.color.b, 1.0f);

注意一点,不能直接通过改变color中r,g,b,a值来进行变化,目前我发现只能通过赋予新的实例来改变。下面这种是错误方法:

Object.GetComponent<Renderer>().material.color.a = 0.4f;
Object.GetComponent<Renderer>().material.color.a = 1.0f;

好了,大概就是这些,完整项目会在我完善之后上传到我的github,感谢阅读,如有问题和错误请提出和指正(如果有人看的话)。

发表评论

电子邮件地址不会被公开。 必填项已用*标注