开发游戏的时候,有时候点击NPC,摄像机会移动到角色面前正视NPC脸部这个功能。今天实现了下,确实需要点底子。注释里解释应该已经很详细了 。 一串白色的小球主要是用来参考相机运动轨迹的,可以 IsShowTip=false 来隐藏掉。支持相机任意角度,任意位置,就是这么任性。speed 可以调节运动快慢。Distance可以调节相机到人脸的距离 ,有瑕疵的地方可能就相机旋转不够自然,不过问题不大,足以应付。没到吹毛求疵的地步没啥大碍。先来两幅GIF瞧瞧
2021-7-8 升级组件,更灵活的功能,需要DoTween的插件支持,dotween不用说,好用之极
using System;
using DG.Tweening;
using UnityEngine;
public class MoveToFront: MonoBehaviour
{
/// <summary>
/// 运动时间
/// </summary>
public float duration = 1f;
public float TargetDistance = 6.5f;
private float _distance = 6.5f;
public bool IsShowTip = true;
public Transform Head;
/// <summary>
/// 需要旋转的角度
/// </summary>
private float _angle;
/// <summary>
/// 相机到头部的向量
/// </summary>
private Vector3 _camToheadDir;
/// <summary>
/// 相机到头部的相对高度
/// </summary>
private float _height;
private Tween _tween;
private float _target;
// Use this for initialization
void Start()
{
if (Head == null) throw new UnityException("头部物体为空,请先确定头部物体");
}
// Update is called once per frame
void Update()
{
if (_tween != null)
{
Transform cam = Camera.main.transform;
//不好理解可以先吧_timeTemp=1的时候理解
//角度插值系数
float lerpValue = Mathf.Lerp(0f, _angle, _target);
//Debug.Log(lerpValue);
//高度插值系数
float tempHeight = Mathf.Lerp(0f, _height, _target);
//距离插值系数
float dis = Mathf.Lerp(_distance, TargetDistance, _target);
//相机到头部的向量 + 头部到相机的高度的向量 当_timeTemp=1时,dir的y轴必为0,这样就可以水平注视头部
Vector3 dir = _camToheadDir + new Vector3(0f, tempHeight, 0f);
dir = dir.normalized;
//r为旋转量,当_timeTemp=1时,就是相机应该旋转多少度才能跟头部相对的 量
//-lerpValue可能为正,也可能为负数,这就是-号存在的意义 正数顺时针,负数逆时针
Quaternion r = Quaternion.Euler(new Vector3(0f, -lerpValue, 0f));
Vector3 newDir = r * -dir;//用四元数旋转向量,很牛逼,必须记住//-号是因为我们需要的是反向的向量
//新位置= 位置 +向量 这个公式不用说了吧
// newDir * dis单位向量乘以长度得到长度为dis的向量
cam.position = Head.position + newDir * dis;//
//旋转相对容易,难得是位置
cam.transform.forward = Vector3.Lerp(cam.transform.forward, (-newDir).normalized, _target);//使相机慢慢朝向脸 timeTemp可另外使用一个变量参数
if (IsShowTip)
{
GameObject go = GameObject.CreatePrimitive(PrimitiveType.Sphere);
go.transform.position = cam.position;
}
}
}
public void MoveToHead(Action completedEvent)
{
Transform cam = Camera.main.transform;
_camToheadDir = Head.position - cam.position;
_camToheadDir = new Vector3(_camToheadDir.x, 0f, _camToheadDir.z);
Vector3 headDir = new Vector3(Head.forward.x, 0f, Head.forward.z);
_angle = Vector3.Angle(_camToheadDir, headDir);
Vector3 dir = Vector3.Cross(_camToheadDir, headDir);//叉乘主要判断在脸部的左边还是右边
if (dir.y > 0)
{
_angle *= -1;
}
Debug.Log(_angle);
//算出角度后,再重新获取完整的向量
_camToheadDir = Head.position - cam.position;
_distance = _camToheadDir.magnitude;
_height = cam.position.y - Head.position.y;
_target = 0f;
_tween = DOTween.To(() => _target, x => _target = x, 1, duration).SetEase(Ease.InOutQuad).OnComplete((() =>
{
_tween = null;
if (completedEvent != null)
completedEvent();
}));
if (IsShowTip)
{
GameObject go = GameObject.CreatePrimitive(PrimitiveType.Sphere);
go.transform.position = cam.position;
}
}
#if UNITY_EDITOR_WIN
private void OnGUI()
{
if (GUI.Button(new Rect(0f, 0f, 100f, 100f), "Test"))
{
// MyScreenToWorldPoint(new Vector3(300, 400, 200));
MoveToHead(null);
// StartCoroutine( PrintPoints());
}
}
#endif
}