最近在做一个有关桥的web端项目,其中有一个要求是点击左边的构件(合起来有上千个)列表后,镜头平滑移动到指定的构件上面,并可对镜头旋转,缩放来观察构件。一般来说这个要求很常见,但是这里涉及到上千个物体,每个物体大小不一样,因此观察距离不一样,有的在桥左部分,有的在桥又部分,因此观察角度又有区分,因此要如何简单的控制摄像机呢?
一般说到相机系统,大家首先想到的就是Cinemachine插件,功能强大,基本上能满足大多数游戏里对相机使用的要求。Cinemachine里要想实现两个相机之间的切换,普遍的方式就是新建多个虚拟相机,通过priority值实现切换,如果需要观察的点较多,比如一个大场景,需要通过菜单列表逐个看多个子模型,这种情况下一般建两个虚拟相机,再通过自己的包装能也能实现切换,而不是需要每个观察点都建一个相机。但是相机切换后就存在一个角度不合适的问题,比如我们的模型一栋房子,我们需要点击菜单后视角移动到窗户,天花板,前门,后门,等各个位置观察,如果每个点都建一个相机,那随着需要观察点的数量剧增就不是很方便,因此只能采用上面说的建两个相机切换,相机的位置随需要观察的点变换,这种方式会方便很多。但问题在于每个物体有他自己的最佳观察点,比如窗户和天花板,合适的观察距离就不一样,而前门和后面观察的方向也不一样,这种方式镜头移动到指定物体后,很难对镜头的方向和与物体的距离进行设置,并不能很好的满足我们的具体需求。
因此,我自己写了个简单的脚本,满足镜头的移动切换,并且切换后能根据需要设置角度,还可以针对模型的大小自动设置镜头与模型的距离,嫩对观测点进行旋转,缩放观察。
摄像机绕物体旋转缩放的代码我就不细说了,网上很多,主要说一下摄像机移动切换的代码:
///
/// 自适应视角
///
/// 需要观看的点
/// 视角三维方向
public void SetFocues(Transform v3,Vector3 visualAngle)
{
float dis = BestSize(v3);
ThisTarget = v3;
CamMoveTarget.position = v3.position + new Vector3(dis * camDisMultiple * visualAngle.x, dis * camDisMultiple*0.6f * visualAngle.y, dis * camDisMultiple * visualAngle.z);
CamMoveTarget.LookAt(v3.position);
this.transform.DORotate(CamMoveTarget.eulerAngles, 1);
this.transform.DOMove(CamMoveTarget.position, 1);
}
传入两个参数,第一个是被观察的物体transform,这个好理解,另一个是摄像机相对这个物体的坐标方向,也就是说以被观察的物体为三维坐标原点,摄像机的位置,如果传入(1,0,0)就表示摄像机在物体的右边,(0,1,0)就表示在物体的正上方,(0,0,1)就表示在物体的正前方,依次来确定摄像机的方向。
然后是实现过程,float dis = BestSize(v3);是根据传入的物体tansform获取物体的轮廓长宽,从而得到合适的距离参数,BestSize()方法后面会贴出来,可以根据自己的需要做修改比例值,然后我虚拟出一个叫目标点CamMoveTarget(Transform类型)的东西,它就是摄像机最后要去的坐标位置点以及转向。
通过下面这行代码确定坐标位置:
CamMoveTarget.position = v3.position + new Vector3(dis * camDisMultiple * visualAngle.x, dis * camDisMultiple*0.6f * visualAngle.y, dis * camDisMultiple * visualAngle.z);
摄像机前往的位置 = 目标点本身的位置+偏移量,偏移量由前面说的dis,摄像机和物体距离的默认倍数已经摄像机相对于物体的方位共同构成。
摄像机前往的位置确定后,需要lookat物体,确保一开始物体在摄像机的正方向,也就是决定了摄像机的转向。
当摄像机需要去的三维坐标位置和摄像机的转向都确定后,只需要调用Dotween动画,让它在一定时间内移动并旋转到目标点就好了,当然你也可以不用动画,做插值计算(vector3.lerp)移动也能达到效果:
this.transform.DORotate(CamMoveTarget.eulerAngles, 1);
this.transform.DOMove(CamMoveTarget.position, 1);
下面贴出源码,只需要把他挂到摄像机上,然后调用里面的SetFocues(Transform v3,Vector3 visualAngle)方法指定观察点和观察方向,就可以实现摄像机的移动切换,和旋转缩放功能。里面有的内置参数我是针对自己的项目所做的,代码并不复杂,所以修改增加功能也非常方便。
public class RotateCam : MonoBehaviour
{
private Transform CamMoveTarget;
private Transform ThisTarget;
private float wheelSpeed=2;
public float camDisMultiple = 2;//摄像机默认距离倍数
public Vector2 distanceLimit;
//private float disOffset;
// Start is called before the first frame update
private void Awake()
{
GameObject go = new GameObject("CamMoveTarget");
CamMoveTarget = go.transform;
}
// Update is called once per frame
void Update()
{
if (ThisTarget != null)
{
camerarotate();
camerazoom();
}
}
///
/// 自适应视角
///
/// 需要观看的点
/// 视角三维方向
public void SetFocues(Transform v3,Vector3 visualAngle)
{
float dis = BestSize(v3);
ThisTarget = v3;
CamMoveTarget.position = v3.position + new Vector3(dis * camDisMultiple * visualAngle.x, dis * camDisMultiple*0.6f * visualAngle.y, dis * camDisMultiple * visualAngle.z);
CamMoveTarget.LookAt(v3.position);
this.transform.DORotate(CamMoveTarget.eulerAngles, 1);
this.transform.DOMove(CamMoveTarget.position, 1);
}
//摄像机围绕目标旋转操作
private void camerarotate()
{
var mouse_x = Input.GetAxis("Mouse X");//获取鼠标X轴移动
var mouse_y = -Input.GetAxis("Mouse Y");//获取鼠标Y轴移动
if (Input.GetKey(KeyCode.Mouse1))
{
transform.Translate(Vector3.left * (mouse_x * 15f) * Time.deltaTime);
transform.Translate(Vector3.up * (mouse_y * 15f) * Time.deltaTime);
}
if (Input.GetKey(KeyCode.Mouse0))
{
transform.RotateAround(ThisTarget.transform.position, Vector3.up, mouse_x * 5);
transform.RotateAround(ThisTarget.transform.position, transform.right, mouse_y * 5);
}
}
//摄像机滚轮缩放
private void camerazoom()
{
if (Input.GetAxis("Mouse ScrollWheel") > 0)
{
//if (disOffset >= 0.2f) return;
//disOffset += Time.deltaTime;
transform.Translate(Vector3.forward * wheelSpeed); ;
}
if (Input.GetAxis("Mouse ScrollWheel") < 0)
{
//if (disOffset <= -0.2f) return;
//disOffset -= Time.deltaTime;
transform.Translate(Vector3.forward * wheelSpeed * -1f);
}
}
///
/// 根据传入物体的模型size计算出与摄像机的合适距离
///
///
///
public float BestSize(Transform tran)
{
float Sx = tran.GetComponent
float Sy = tran.GetComponent
float Sz = tran.GetComponent
float Bb = Sx;
if (Sy > Bb) { Bb = Sy; }
if (Sz > Bb) { Bb = Sz; }
wheelSpeed = 2 + Bb * 0.12f;
return Bb;
}
}