Unity 基于Cinemachine计算透视摄像机在地图中的移动范围
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了Unity 基于Cinemachine计算透视摄像机在地图中的移动范围,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含4376字,纯文字阅读大概需要7分钟。
内容图文
![Unity 基于Cinemachine计算透视摄像机在地图中的移动范围](/upload/InfoBanner/zyjiaocheng/1328/bca00c2113f140f3ace2c3edfa0afbd7.jpg)
Unity中Cinemachine的基础功能介绍可详见之前写的博客:
https://www.cnblogs.com/koshio0219/p/11820654.html
本篇的重点是讨论,在给定规则地图的长宽和中心点坐标的情况下,如何动态生成一个透视摄像机的碰撞盒子以限定摄像机的视野永远不会超出地图的边界。
例如,下面这种规则地图:(或者其他用程序生成的单位块地图)
在输入一些参数后:
可以自动创建形如:
这样的摄像机运动范围,且输出的范围能够适配到屏幕的分辨率,考虑到相机绕某一轴向的旋转等问题。
其实基本都是纯粹的数学运算,开始之前,必须先弄清楚透视摄像机的一些基本原理,它的视窗大小和屏幕分辨率之间到底是什么关系:
1.FOV:这是透视摄像机区别于正交摄像机最重要的一个特性——视口大小,它表示的是当前摄像机视野范围的开口角度,也因该角度大小的不同,使得透视摄像机的近裁剪平面和远裁剪平面大小不一,从而产生三维空间中近大远小的特点。
2.Aspect:当前摄像机的宽高比。为什么要设置这样一个东西呢?理由就是屏幕有不同的分辨率,而相机映照出来的画面最终是要在屏幕当中显示的,当我们的屏幕分辨率发生变化时,相机的视口面积也会对应的发生变化,这时,仅仅只有一个FOV没办法满足不同类型的屏幕分辨率,于是就需要额外设置相机的宽高比来对最终呈现的摄像机视口大小进行辅助调整。
在Unity中,是以视口的高为基准进行计算的,也就是说,Unity中的透视摄像机的Fov角度其实是按照屏幕分辩率的高度进行对应的,而宽度对应的Fov则随着Aspect的变化而变化,不是面板设置的Fov大小。
试比较下面两张图,分别是摄像机的宽和高的Fov:
设置的Fov为40度,当前的屏幕分辨率为2960*1440:
很显然,只有高度对应的Fov为面板中显示的值,而宽度对应的Fov明显大于40度。实际宽的的Fov应该是82度左右(40*2960/1440)。
知道了上面这些后我们才能更愉快的进行接下来的计算,不然只会计算出许多错误也搞不清是什么原因。
在Cinemachine中,一般会设置一个跟随目标,且跟踪该目标的距离是一个常量,可以从面板中取得:
我们先分析摄像机的左右运动范围是如何计算的:(本例中的摄像机只在X轴向上存在旋转值,一般斜向的摄像机也只需要旋转一个轴即可,左右看上去一般追求对称性)
观察上图,假设现在摄像机位于空中的P点,已知AB为地图的边缘围墙高度,BC为角色的高度,CP为跟踪的摄像机到角色的距离,现在我们需要求出摄像机所在的X轴向的坐标,关键就是要求出AD的距离。
我们还知道一个数据就是摄像机的Fov,但是由于该Fov并非高度对应的值,所以我们先要进行一次转换,以得到摄像机宽度视口的Fov角度。以下均为弧度计算:
1 // 计算的角度均为弧度值,传入纵向的(高)Fov的一半得到横向的(宽)Fov的一半 2 public float GetHorizontalFovHalf(float vhfov, float aspect) 3 { 4return Mathf.Atan(Mathf.Tan(vhfov) * aspect); 5 }
上面已经讲过原理了这里就不在进行过多叙述了,简单来说就是利用摄像机的深度值进行了一次转换,因为无论是纵向还是横向的Fov,它们的深度值都是相同的,读者可以自行画图或脑补一下。
通过上面的方法我们就可以求得∠DPA的大小了,它正好就是横向Fov的一半,那个∠α的大小就可以轻易求出,现在问题的关键就是要求出边AP的长度,AP的长度得出的话,就可以利用∠α余弦求得AD,DP等。
利用正弦定理可以非常快速的解决上面的问题,当然你也可以设未知数利用勾股定律解一元二次方程,但当你写程序的时候你可能会有想吐的冲动:
1 // 计算轴向偏移值 2 private float GetSizeOffse(float fbangel, float distance, float wh, float followy) 3 { 4//直角弧度值 5var rightangel = 90 * Mathf.Deg2Rad; 6//∠PAC 7var disangel = fbangel + rightangel; 8//求出正弦定理的比值 9var sin = distance / Mathf.Sin(disangel); 10//求∠APC的正弦值11var angelo = (wh - followy) / sin; 12//三角形内角和求∠ACP13var angel = rightangel * 2 - Mathf.Asin(angelo) - disangel; 14//计算AP利用α余弦返回AD15return sin * angel * Mathf.Cos(fbangel); 16 }
fbangel即为上图中的∠α,distance即为上图中的CP,wh即为上图中的AB,followy即为上图中的CB。
X轴向的偏移计算完毕后,Z轴的偏移也是类似的,只不过需要考虑旋转值而已,接下来就是摄像机的高度(注意摄像机的高度是一个变量),这个很容易计算。下面给出生成摄像机运动区域的参考:
1 // 计算并生成透视摄像机的运动区域 2 public void GenZone() 3 { 4 Camera = Camera.main; 5 6//计算从地图中心到边缘的向量 7var toedge = WidthHeight * UnitLength * .5f; 8//左后 9var lb = CenterPoint - toedge; 10//右前11var rf = CenterPoint + toedge; 12//墙高13var wh = WallHeight; 1415 zone = new GameObject("CameraZone"); 1617var box = zone.AddComponent<BoxCollider>(); 18var cvc = GetComponent<CinemachineVirtualCamera>(); 19var cft = cvc.GetCinemachineComponent<CinemachineFramingTransposer>(); 2021var cvcs = cvc.m_Lens; 22//摄像机跟踪目标的高度23var followy = cvc.m_Follow.position.y; 24//跟踪距离25var distance = cft.m_CameraDistance; 26//屏幕高对应的Fov(真实Fov)27var hfov = cvcs.FieldOfView * .5f * Mathf.Deg2Rad; 28//摄像机视口宽高比29var aspect = Camera.aspect; 30//摄像机轴向旋转值31var rotation = Camera.transform.eulerAngles.x * Mathf.Deg2Rad; 32var rightangel = 90 * Mathf.Deg2Rad; 33//屏幕宽对应的Fov(转化后的Fov)34var whfov = GetHorizontalFovHalf(hfov, aspect); 3536//摄像机当前高度37var height = Mathf.Sin(rotation) * distance + followy; 3839//计算左右偏移(对称)40var lrangel = rightangel - whfov; 41var widthh = GetSizeOffse(lrangel, distance, wh, followy); 42var left = lb.x + widthh; 43var right = rf.x - widthh; 44var sizex = Mathf.Abs(left - right); 4546//计算前后偏移(带旋转值,非对称)47var fangel = rotation - hfov; 48var front = rf.y - GetSizeOffse(fangel, distance, wh, followy); 4950var bangel = rotation + hfov; 51var back = lb.y - GetSizeOffse(bangel, distance, wh, followy); 5253var sizez = Mathf.Abs(front - back); 5455//设置摄像机运动范围的大小,因为在XZ平面上,盒子的高度可以为一个常量56 box.size = new Vector3(sizex, 5, sizez); 57 zone.transform.position = new Vector3((left + right) * .5f, height, (front + back) * .5f); 5859 CC.m_BoundingVolume = zone.GetComponent<BoxCollider>(); 60 }
生成该盒子后,只需要将它赋值给CinemachineConfiner的BoundingVolume属性即可:
为了更方便的进行测试和调试,可以写一个Editor脚本在编辑器模式下生成:
1 using UnityEditor; 2 using UnityEngine; 3 4 [CustomEditor(typeof(CameraZoneCtrl))] 5publicclass CameraZoneEditor : Editor 6{ 7publicoverridevoid OnInspectorGUI() 8 { 9 DrawDefaultInspector(); 10 CameraZoneCtrl ctrl = (CameraZoneCtrl)target; 11if (GUILayout.Button("创建摄像机范围")) 12 { 13 ctrl.GenZone(); 14 } 15 } 16 }
原文:https://www.cnblogs.com/koshio0219/p/12145525.html
内容总结
以上是互联网集市为您收集整理的Unity 基于Cinemachine计算透视摄像机在地图中的移动范围全部内容,希望文章能够帮你解决Unity 基于Cinemachine计算透视摄像机在地图中的移动范围所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。