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角度。以下均为弧度计算:

     //计算的角度均为弧度值,传入纵向的(高)Fov的一半得到横向的(宽)Fov的一半
public float GetHorizontalFovHalf(float vhfov, float aspect)
{
return Mathf.Atan(Mathf.Tan(vhfov) * aspect);
}

上面已经讲过原理了这里就不在进行过多叙述了,简单来说就是利用摄像机的深度值进行了一次转换,因为无论是纵向还是横向的Fov,它们的深度值都是相同的,读者可以自行画图或脑补一下。

通过上面的方法我们就可以求得∠DPA的大小了,它正好就是横向Fov的一半,那个∠α的大小就可以轻易求出,现在问题的关键就是要求出边AP的长度,AP的长度得出的话,就可以利用∠α余弦求得AD,DP等。

利用正弦定理可以非常快速的解决上面的问题,当然你也可以设未知数利用勾股定律解一元二次方程,但当你写程序的时候你可能会有想吐的冲动:

     //计算轴向偏移值
private float GetSizeOffse(float fbangel, float distance, float wh, float followy)
{
//直角弧度值
var rightangel = * Mathf.Deg2Rad;
//∠PAC
var disangel = fbangel + rightangel;
//求出正弦定理的比值
var sin = distance / Mathf.Sin(disangel);
//求∠APC的正弦值
var angelo = (wh - followy) / sin;
//三角形内角和求∠ACP
var angel = rightangel * - Mathf.Asin(angelo) - disangel;
//计算AP利用α余弦返回AD
return sin * angel * Mathf.Cos(fbangel);
}

fbangel即为上图中的∠α,distance即为上图中的CP,wh即为上图中的AB,followy即为上图中的CB。

X轴向的偏移计算完毕后,Z轴的偏移也是类似的,只不过需要考虑旋转值而已,接下来就是摄像机的高度(注意摄像机的高度是一个变量),这个很容易计算。下面给出生成摄像机运动区域的参考:

     //计算并生成透视摄像机的运动区域
public void GenZone()
{
Camera = Camera.main; //计算从地图中心到边缘的向量
var toedge = WidthHeight * UnitLength * .5f;
//左后
var lb = CenterPoint - toedge;
//右前
var rf = CenterPoint + toedge;
//墙高
var wh = WallHeight; zone = new GameObject("CameraZone"); var box = zone.AddComponent<BoxCollider>();
var cvc = GetComponent<CinemachineVirtualCamera>();
var cft = cvc.GetCinemachineComponent<CinemachineFramingTransposer>(); var cvcs = cvc.m_Lens;
//摄像机跟踪目标的高度
var followy = cvc.m_Follow.position.y;
//跟踪距离
var distance = cft.m_CameraDistance;
//屏幕高对应的Fov的一半(真实Fov)
var hfov = cvcs.FieldOfView * .5f * Mathf.Deg2Rad;
//摄像机视口宽高比
var aspect = Camera.aspect;
//摄像机轴向旋转值
var rotation = Camera.transform.eulerAngles.x * Mathf.Deg2Rad;
var rightangel = * Mathf.Deg2Rad;
//屏幕宽对应的Fov的一半(转化后的Fov)
var whfov = GetHorizontalFovHalf(hfov, aspect); //摄像机当前高度
var height = Mathf.Sin(rotation) * distance + followy; //计算左右偏移(对称)
var lrangel = rightangel - whfov;
var widthh = GetSizeOffse(lrangel, distance, wh, followy);
var left = lb.x + widthh;
var right = rf.x - widthh;
var sizex = Mathf.Abs(left - right); //计算前后偏移(带旋转值,非对称)
var fangel = rotation - hfov;
var front = rf.y - GetSizeOffse(fangel, distance, wh, followy); var bangel = rotation + hfov;
var back = lb.y - GetSizeOffse(bangel, distance, wh, followy); var sizez = Mathf.Abs(front - back); //设置摄像机运动范围的大小,因为在XZ平面上,盒子的高度可以为一个常量
box.size = new Vector3(sizex, , sizez);
zone.transform.position = new Vector3((left + right) * .5f, height, (front + back) * .5f); CC.m_BoundingVolume = zone.GetComponent<BoxCollider>();
}

生成该盒子后,只需要将它赋值给CinemachineConfiner的BoundingVolume属性即可:

为了更方便的进行测试和调试,可以写一个Editor脚本在编辑器模式下生成:

 using UnityEditor;
using UnityEngine; [CustomEditor(typeof(CameraZoneCtrl))]
public class CameraZoneEditor : Editor
{
public override void OnInspectorGUI()
{
DrawDefaultInspector();
CameraZoneCtrl ctrl = (CameraZoneCtrl)target;
if (GUILayout.Button("创建摄像机范围"))
{
ctrl.GenZone();
}
}
}

Unity 基于Cinemachine计算透视摄像机在地图中的移动范围的更多相关文章

  1. Unity 通过NGUI 完成单摄像机 制作地图

    本次思想主要是通过 Ngui的Scroll View 主要是UIPanel的Clipping属性的Alipha Clip 调节窗口大小,遮蔽地图试地图实现在屏幕的部分显示.此方法的好处是不用担心sha ...

  2. unity 基于scrollRect实现翻页显示

    unity 基于scrollRect实现翻页显示,并定为到某一页,而不是某一页的中间方法(第二个脚本采用实际位置计算,并在update里实现平滑过渡): 组场景时,经常需要获取鼠标(或者点击)开始结束 ...

  3. Unity 3d 实现物体跟随摄像机视野运动

    https://blog.csdn.net/qq_31411825/article/details/61623857 Unity 3d 实现物体跟随摄像机视野运动Created by miccall ...

  4. 基于ArcEngine与C#的鹰眼地图实现

    鹰眼图是对全局地图的一种概略表达,具有与全局地图的空间参考和空间范围.为了更好起到空间提示和导航作用,有些还具备全局地图中重要地理要素,如主要河流.道路等的概略表达.通过两个axMapControl控 ...

  5. Spark 介绍(基于内存计算的大数据并行计算框架)

    Spark 介绍(基于内存计算的大数据并行计算框架)  Hadoop与Spark 行业广泛使用Hadoop来分析他们的数据集.原因是Hadoop框架基于一个简单的编程模型(MapReduce),它支持 ...

  6. 跨平台移动开发_PhoneGap 使用Geolocation基于所在地理位置坐标调用百度地图API

    使用Geolocation基于所在地理位置坐标调用百度地图API 效果图 示例代码 <!DOCTYPE html> <html> <head> <title& ...

  7. 10-THREE.JS perspective透视摄像机和orthographic正交摄像机区别

    <!DOCTYPE html> <html> <head> <title></title> <script src="htt ...

  8. 一元建站-基于函数计算 + wordpress 构建 serverless 网站

    前言 本文旨在通过 快速部署一个 wordpress 网站到阿里云函数计算平台 这个示例来展示 serverless web 新的开发模式, 包括 FUN 工具一键初始化 NAS, 同步网站到 NAS ...

  9. 基于函数计算 + TensorFlow 的 Serverless AI 推理

    前言概述 本文介绍了使用函数计算部署深度学习 AI 推理的最佳实践, 其中包括使用 FUN 工具一键部署安装第三方依赖.一键部署.本地调试以及压测评估, 全方位展现函数计算的开发敏捷特性.自动弹性伸缩 ...

随机推荐

  1. CentOS7.0下安装FTP服务的方法

    http://www.jb51.net/article/106604.htm   本篇文章主要介绍了CentOS7.0下安装FTP服务的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟 ...

  2. linux scull 函数open 方法

    open 方法提供给驱动来做任何的初始化来准备后续的操作. 在大部分驱动中, open 应当 进行下面的工作: 检查设备特定的错误(例如设备没准备好, 或者类似的硬件错误 如果它第一次打开, 初始化设 ...

  3. 「THUPC 2019」不等式 / inequality

    https://loj.ac/problem/6620 高中数学好题.. |kx+b|的函数图像很直观,直接考虑函数图像: 一定只有一段极小值点! 这个点就是最小值了 特点:斜率为0! 而且发现,如果 ...

  4. java Io流的应用

                                                                         标准输入输出流 1.1标准输入流 源数据源是标准输入设备(键盘 ...

  5. HDU 1540 Tunnel Warfare (线段树)

    Tunnel Warfare Problem Description During the War of Resistance Against Japan, tunnel warfare was ca ...

  6. .map() .filter() .reduce() .includes() .some() .every()的用法

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  7. linux 每-CPU 的变量

    每-CPU 变量是一个有趣的 2.6 内核的特性. 当你创建一个每-CPU 变量, 系统中每个处理 器获得它自己的这个变量拷贝. 这个可能象一个想做的奇怪的事情, 但是它有自己的优点. 存取每-CPU ...

  8. ASP.NET MVC4.0+EF+LINQ+bui+bootstrap+网站+角色权限管理系统(3)

    接下来完成用户.角色的增删查改,以及用户角色.权限的设置 对用户表.角色表做了一些扩展如下[可以更加自己需要增减字段] 相应的M_UserProfile.cs.M_Roles.cs进行扩展 using ...

  9. 备战省赛组队训练赛第六场(UPC)

    传送门 外来博客题解1:戳这里 外来博客题解2:戳这里 CRWG全方位题解:戳这里

  10. 使用 Git和Github

    本地库初始化 git init 效果: 注意: .git 目录中存放的是本地库相关的子目录和文件,不要删除,也不要胡 乱修改. 设置签名 其实就是为了区分不同开发人员的身份需要设置一下自己的信息,包括 ...