前言
在Unity中,顶视角(TOPDOWN)游戏,摄像机以一定角度俯瞰场景,比如斜45°。在此情况下,有功能需求(比如世界地图)需要点击场景,让摄像机移动到点击位置。
对于资深程序要实现此功能并不存在问题,但对于低代码水平的业余开发者来说,实现此功能颇为费劲(下文会论述)。日前在尝试制作一个世界地图DEMO时,发现对于代码新手来说,Unity还有一个更简略的方法来实现。
功能需求
摄像机以一定角度倾斜看向场景,当点击摄像机可视范围内,场景中的某一个点时,摄像机移动至该位置。
基本思路
使用射线检测的方式,将鼠标点击位置存储下来,并将摄像机的坐标设为鼠标点击位置。
射线检测需要对象具有碰撞体Box Collider,并设置对象的Tag标签为Ground。
基本代码逻辑如下:
//MouseManager部分
RaycastHit hitInf;
Ray ray;
public Action<Vector3> OnMouseClicked;//Action事件,通过委托调用摄像机移动方法。
void Update()
{
ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hitInf)) { MouseControl(); }
}
void MouseControl()//鼠标控制方法
{
if (Input.GetMouseButtonDown(0) && hitInf.collider != null)//判断鼠标左键点击并且射线碰撞到的对象其碰撞体不为空(避免点到地图之外的地方)
{
//判断射线碰撞到的对象标签是否地面,并执行委托方法,传入鼠标点击位置信息hitInf
if (hitInf.collider.gameObject.CompareTag("Ground"))
OnMouseClicked?.Invoke(hitInf.point);
}
}
//Camera部分
private Transform cam;
private void OnEnable()
{
cam = Camera.main.transform;
}
void Start()
{
//MouseManager是一个泛型单例,也可以直接用public MouseManager mouseManager来获取
MouseManager.Instance.OnMouseClicked += MoveToTarget;
}
private void MoveToTarget(Vector3 Target)
{
cam.position = new Vector3(Target.x, cam.position.y, Target.z);
}
通过一个Action事件让鼠标部分的代码委托执行摄像机移动的方法
遇到的问题
由于摄像机的投影坐标位于视口范围之外,只能点视口范围内的场景地面,而该部分区域均位于摄像机坐标上方,因此每次点击摄像机位置只能朝着场景上方移动,没法移动到场景下方,因为点不到摄像机坐标下方的场景地图。
解决方案一
点击地面某点,移动摄像机坐标,应变更为移动的是摄像机屏幕中心点,是使屏幕中心点移动到鼠标点击位置。
此方案需要将屏幕中心点位置转换为世界坐标,并计算其和点击位置的向量偏移值。由于本人是代码新手,尚未掌握向量计算的方法,因此折腾1天发现此路不同,网上也没有搜索到可参考代码,颇为沮丧。
解决方案二
随后思考时,突然想到Cinemachine虚拟相机是可以锁定和跟随一个对象,并且gameobject可以是一个空物体。因此转换思路,鼠标点击的时候设置摄像机位置改为设置该空物体的位置,相机就可以通过lookat和follow属性自动执行位置变化了。
使相机的LookAt和Follow对象均是空对象Target
using UnityEngine;
using Cinemachine;
public class CameraController : MonoBehaviour
{
public CinemachineVirtualCamera vcam;
private Transform vcamTarget;
private void OnEnable()
{
//通过虚拟相机的lookat属性获取对象的transform
vcamTarget = vcam.LookAt.transform;
}
void Start()
{
MouseManager.Instance.OnMouseClicked += MoveToTarget;
}
private void MoveToTarget(Vector3 Target)
{
vcamTarget.position = new Vector3(Target.x, vcamTarget.position.y, Target.z);
}
}
MouseManager部分的代码不变。这样调整后,就间接实现了点击场景中某个位置,移动摄像机到该位置的功能。并且因为Cinemachine自带移动过渡曲线,因此摄像机在移动时会自带阻尼缓动效果。