一、创建脚本
using UnityEngine; using System.Collections; /// <summary> /// 把此类挂到要跟随物体的相机上 /// </summary> public class FollowPlayer : MonoBehaviour { //跟随目标对象 public Transform target; //相机与目标的相对位置偏移 public Vector3 offset = Vector3.zero; private Transform cacheTran; void Awake() { cacheTran = GetComponent<Transform>(); } void Update () { if (null == target) return; cacheTran.position = target.position + offset; } }
二、调整参数
运行效果
using UnityEngine; using UnityEngine.AI; /// <summary> /// 把此类挂到要跟随物体的相机上 /// </summary> public class CameraFollowPlayer : MonoBehaviour { public Transform player; public bool fixCamera; public bool mouseRightRotate; public float sensitivity = 1.0f; public float nearRate = 0.9f; public float farRate = 1.1f; private Transform mTransform; private Vector3 originPosition; private Vector3 offset; private float distance; private bool rightMouseDown; private Vector3 mouseLastPosition; private Vector3 playerHeadPosition; private float agentHeight; private bool m_Initialized; void Awake() { if (player != null) Initialize(); } void Initialize() { m_Initialized = true; NavMeshAgent agent = player.GetComponent<NavMeshAgent>(); agentHeight = agent.height; mTransform = transform; originPosition = mTransform.position; //offset相对于头顶坐标 playerHeadPosition = player.position; playerHeadPosition.y += agentHeight; offset = originPosition - playerHeadPosition; distance = Vector3.Distance(originPosition, playerHeadPosition); } void Update() { if (player == null) return; if (player != null && !m_Initialized) Initialize(); if (fixCamera) { //自动跟随 playerHeadPosition = player.position; playerHeadPosition.y += agentHeight; mTransform.position = playerHeadPosition + offset; //支持鼠标右键旋转镜头 if (mouseRightRotate) { if (Input.GetMouseButtonDown(1)) { rightMouseDown = true; mouseLastPosition = Input.mousePosition; } if (Input.GetMouseButtonUp(1)) rightMouseDown = false; if (rightMouseDown) { Vector3 mousePosition = Input.mousePosition; float deltaX = mousePosition.x - mouseLastPosition.x; mTransform.RotateAround(player.position, Vector3.up, deltaX * sensitivity); mouseLastPosition = mousePosition; offset = mTransform.position - playerHeadPosition; } } //支持鼠标滚轮推拉镜头 Vector2 mouseScrollDelta = Input.mouseScrollDelta; if (mouseScrollDelta.y > 0) //镜头拉近 { if (Vector3.Magnitude(offset) > distance * nearRate) offset *= 0.9f; } else if (mouseScrollDelta.y < 0) //镜头拉远 { if (Vector3.Magnitude(offset) < distance * farRate) offset *= 1.1f; } } else { //自动跟随&旋转镜头 Vector3 back = -player.forward; Vector3 camPos = player.position + back * distance; camPos.y = originPosition.y; mTransform.position = camPos; mTransform.LookAt(player); } } }
第三版:适合自由控制镜头的RPG游戏
建议在 FixedUpdate() 中控制 NavMeshAgent 或 Rigidbody 移动。在 LateUpdate() 中控制镜头跟随。
using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; using UnityEngine.EventSystems; /// <summary> /// 把此类挂到要跟随玩家的相机上 /// 适合:自由控制镜头的RPG游戏 /// 功能:自动跟随玩家、鼠标右键/手指旋转镜头 /// </summary> public class CameraFollowPlayer : MonoBehaviour { public Transform player; //是否允许旋转镜头 public bool allowRotate = true; //像机相对玩家中部的垂直偏移 public float offsetHeight = 4f; //像机距离玩家后方的距离 public float offsetDistance = 5f; //像机绕玩家中心旋转的最小半径 public float minRadius = 1; //像机绕玩家中心旋转的最大半径 public float maxRadius = 5; private Transform mTransform; private Vector3 originPosition; private Vector3 offset; private Vector3 playerHeadPosition; private float agentHeight; private bool m_Initialized; private Vector3 lastPlayerPosition; //滑动屏幕旋转像机 [Tooltip("旋转速度")] [SerializeField] private float m_RotationSpeed = 0.1f; //玩家到像机向量与Y轴的最小夹角,取值范围(0, 90) private readonly float m_MinLookupAngle = 20; //玩家到像机向量与Y轴的最大夹角,取值范围(0, 90) private readonly float m_MaxLookupAngle = 80; private Vector3 lastPosition; private bool m_IsPointerOverUI; private Touch m_RotTouch; //控制旋转的手指 private Dictionary<int, bool> m_FingerOverUIDic = new Dictionary<int, bool>(); private float radiusOffset = 0f; //End #region 上帝视角 public bool godPerspective; public float godHeight = 40; #endregion //摄像机最小距离 [SerializeField] private float m_MinCameraDistance = 20f; //摄像机最大距离 [SerializeField] private float m_MaxCameraDistance = 600f; //鼠标滚轮灵敏度 [SerializeField] private float m_ScrollWheelSensitivity = 40f; //是否开启鼠标滚轮 [SerializeField] private bool m_ScrollWheelEnabled = true; void Awake() { mTransform = transform; radiusOffset = maxRadius - minRadius; if (player != null) Initialize(); } void Initialize() { m_Initialized = true; NavMeshAgent agent = player.GetComponent<NavMeshAgent>(); if (agent) agentHeight = agent.height; ResetCamera(); } public void ResetCamera() { lastPlayerPosition = player.position; playerHeadPosition = player.position; playerHeadPosition.y += agentHeight; //将像机设置到Player后上方 originPosition = player.position - player.forward * offsetDistance; originPosition.y += offsetHeight; offset = originPosition - PlayerCenterPosition; offset = offset.normalized * OffsetRadius; mTransform.position = PlayerCenterPosition + offset; mTransform.LookAt(PlayerCenterPosition); } // 计算离环绕点的半径 public float OffsetRadius { get { float cameraY = mTransform.position.y; float playerY = PlayerCenterPosition.y; float playerTopY = playerY + maxRadius; float playerBottomY = playerY - minRadius; float t = (cameraY - playerBottomY) / (playerTopY - playerBottomY); float radius = Mathf.Lerp(minRadius, maxRadius, t); return radius; } } // 玩家中心点坐标 public Vector3 PlayerCenterPosition { get { Vector3 p = player.position; p.y += agentHeight / 2; return p; } } // 计算环绕半径 void CalculateRoundRadius() { offset = mTransform.position - PlayerCenterPosition; offset = offset.normalized * OffsetRadius; mTransform.position = PlayerCenterPosition + offset; mTransform.LookAt(PlayerCenterPosition); } void LateUpdate() { if (player == null) return; if (player != null && !m_Initialized) Initialize(); //如果是上帝视角 if (godPerspective) { var pos = player.position; pos.y += godHeight; mTransform.position = pos; mTransform.LookAt(player); return; } //自动跟随 FollowPlayer(); //支持鼠标右键/手指旋转镜头 if (allowRotate) { MouseRotation(); TouchRotation(); CalculateRoundRadius(); } MouseScrollWheel(); } // 跟随玩家 void FollowPlayer() { //相对于上一帧玩家偏移 Vector3 moveOffset = player.position - lastPlayerPosition; lastPlayerPosition = player.position; mTransform.position += moveOffset; } // 鼠标控制旋转 void MouseRotation() { if (!Input.mousePresent) return; if (!Input.GetMouseButton(1)) return; if (Input.GetMouseButtonDown(1)) { lastPosition = Input.mousePosition; m_IsPointerOverUI = IsPointerOverUI(); return; } if (m_IsPointerOverUI) return; Vector3 mousePosition = Input.mousePosition; Vector3 offset = mousePosition - lastPosition; CameraRotation(offset); lastPosition = mousePosition; } // 触摸控制旋转 void TouchRotation() { if (!Input.touchSupported) return; if (Input.touchCount <= 0) { m_FingerOverUIDic.Clear(); return; } int touchCount = Input.touchCount; for (int i = 0; i < touchCount; i++) { int fingerId = Input.touches[i].fingerId; if (Input.touches[i].phase == TouchPhase.Began) { //记录下在Began阶段,哪些手指处于UI上 bool overUI = IsPointerOverUI(i); if (m_FingerOverUIDic.ContainsKey(fingerId)) m_FingerOverUIDic[fingerId] = overUI; else m_FingerOverUIDic.Add(fingerId, overUI); if (overUI) continue; //找到Began阶段,未处于UI上的手指 m_RotTouch = Input.touches[i]; lastPosition = m_RotTouch.position; return; } else if (Input.touches[i].phase == TouchPhase.Moved) { //与Began阶段选中的手指作比较 if (m_RotTouch.fingerId == Input.touches[i].fingerId) m_RotTouch = Input.touches[i]; } else if (Input.touches[i].phase == TouchPhase.Ended) { //手指离开屏幕后移除记录 if (m_FingerOverUIDic.ContainsKey(fingerId)) m_FingerOverUIDic.Remove(fingerId); } } if (!m_FingerOverUIDic.ContainsKey(m_RotTouch.fingerId)) return; if (m_FingerOverUIDic[m_RotTouch.fingerId]) return; Vector3 firstTouch = m_RotTouch.position; Vector3 offset = firstTouch - lastPosition; CameraRotation(offset); lastPosition = firstTouch; } // 像机旋转 private void CameraRotation(Vector3 offset) { float t = offset.x; offset.x = offset.y; offset.y = t; if (player) { Vector3 camV = mTransform.position - PlayerCenterPosition; float angle = Vector3.Angle(camV, Vector3.up); //已达到最大府角 if (angle < m_MinLookupAngle && offset.x <= 0) offset.x = 0; //已达到最大仰角 if (angle > m_MaxLookupAngle && offset.x >= 0) offset.x = 0; //府视/仰视 旋转轴 Vector3 rotAxis = Vector3.Cross(PlayerCenterPosition - mTransform.position, Vector3.up); mTransform.RotateAround(PlayerCenterPosition, rotAxis, offset.x * m_RotationSpeed); mTransform.RotateAround(PlayerCenterPosition, Vector3.up, offset.y * m_RotationSpeed); //重置Z轴 Vector3 localEulerAngles = mTransform.localEulerAngles; localEulerAngles.z = 0; mTransform.localEulerAngles = localEulerAngles; } else { mTransform.Rotate(offset.x * m_RotationSpeed, 0, 0, Space.Self); mTransform.Rotate(0, offset.y * m_RotationSpeed, 0, Space.World); } } // 鼠标滚轮 private void MouseScrollWheel() { if (!m_ScrollWheelEnabled) return; float scrollWheel = Input.GetAxis("Mouse ScrollWheel"); if (scrollWheel == 0f) return; float distanceOffset = scrollWheel* m_ScrollWheelSensitivity; if (minRadius - distanceOffset <= m_MinCameraDistance) { minRadius = m_MinCameraDistance; maxRadius = minRadius + radiusOffset; return; } if (maxRadius - distanceOffset >= m_MaxCameraDistance) { maxRadius = m_MaxCameraDistance; minRadius = maxRadius - radiusOffset; return; } minRadius -= distanceOffset; maxRadius -= distanceOffset; } // 判断点击事件是否发生在UI上 public static bool IsPointerOverUI(int touchIndex = 0) { bool over = false; if (Input.GetMouseButtonDown(1) || Input.touchCount > 0) { if (EventSystem.current == null) return over; #if UNITY_EDITOR || UNITY_STANDALONE_WIN if (EventSystem.current.IsPointerOverGameObject()) #elif UNITY_IPHONE || UNITY_ANDROID if (EventSystem.current.IsPointerOverGameObject(Input.GetTouch(touchIndex).fingerId)) #else if (EventSystem.current.IsPointerOverGameObject()) #endif over = true; else over = false; } return over; } }
/// <summary> /// 中介 /// </summary> public class CameraFollowPlayerMediator : GameEventBehaviour { public CameraFollowPlayer cameraFollowPlayer; protected override void OnAwake() { this.AddListener(GameEventType.INSTANTIATE_PLAYER_COMPLETE, OnInstantiatePlayerComplete); this.AddListener(GameEventType.PLAYER_DESTROY, OnPlayerDestroy); this.AddListener(GameEventType.INVOKE_CAMERA_FOLLOW_SETTING, OnInvokeCameraFollowSetting); } private void OnInstantiatePlayerComplete(GameEventType type, object data) { Transform player = data as Transform; cameraFollowPlayer.player = player; } private void OnPlayerDestroy(GameEventType type, object data) { cameraFollowPlayer.player = null; } private void OnInvokeCameraFollowSetting(GameEventType type, object data) { CameraFollowParam p = data as CameraFollowParam; cameraFollowPlayer.offsetDistance = p.offsetDistance; cameraFollowPlayer.offsetHeight = p.offsetHeight; cameraFollowPlayer.allowRotate = p.allowRotate; cameraFollowPlayer.ResetCamera(); //近镜头 //var p = new CameraFollowParam(); //p.offsetDistance = 0.5f; //p.offsetHeight = 0.2f; //FireEvent(GameEventType.INVOKE_CAMERA_FOLLOW_SETTING, p); //远镜头 //var p = new CameraFollowParam(); //p.offsetDistance = 3; //p.offsetHeight = 1; //FireEvent(GameEventType.INVOKE_CAMERA_FOLLOW_SETTING, p); } } public class CameraFollowParam { public float offsetDistance = 3; public float offsetHeight = 2; public bool allowRotate = true; }
问题:解决NavMeshAgent旋转太慢
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; /// <summary> /// NavMeshAgent的旋转太慢 /// 可用此脚本替换NavMeshAgent的旋转功能 /// </summary> public class NavMeshAgentRotation : MonoBehaviour { [SerializeField] private NavMeshAgent navMeshAgent; public float angularSpeed = 10f; private Transform mTransform; private void Awake() { mTransform = transform; if (navMeshAgent == null) navMeshAgent = this.GetComponent<NavMeshAgent>(); } void Update() { if (navMeshAgent == null) return; //代理是否绑定到导航网格 if (!navMeshAgent.isOnNavMesh) return; if (navMeshAgent.isStopped || navMeshAgent.pathPending || !navMeshAgent.hasPath) return; navMeshAgent.updateRotation = navMeshAgent.remainingDistance < 0.1f; /* 由NavMeshAgent控制旋转 */ if (navMeshAgent.updateRotation) return; /* 手动控制旋转 */ //当前朝向 Quaternion forward = Quaternion.identity; forward.SetLookRotation(mTransform.forward); //目标朝向 Vector3 targetForward = navMeshAgent.steeringTarget - mTransform.position; Quaternion steering = Quaternion.identity; steering.SetLookRotation(targetForward); //旋转插值 Quaternion rotation = Quaternion.Lerp(forward, steering, Time.deltaTime * angularSpeed); mTransform.rotation = rotation; } }
using UnityEngine;
/// <summary>
/// 镜头跟随,实用于赛车游戏
/// 支持 第一人称视角 & 第三人称视角
/// 用法:将脚本挂到要跟随汽车的Camera上
/// </summary>
[DisallowMultipleComponent]
public class CameraFollowCar : MonoBehaviour
{
[SerializeField]
private PersonPerspective perspective;
//第三人称视角要跟随的目标对象
public Transform thirdTarget;
//第一人称视角所在位置
public Transform firstTarget;
#region 第三人称视角参数
//镜头相对于汽车后方的距离偏移(跟车距离)
[SerializeField]
private float backOffset = 5f;
//镜头相对于汽车的高度偏移
[SerializeField]
private float heightOffset = 2f;
//镜头旋转速度
[SerializeField]
private float rotationSpeed = 4f;
//镜头移动速度
[SerializeField]
private float motionSpeed = 0.002f;
#endregion
#region 上帝视角参数
public float godHeight = 10;
#endregion
private Transform cacheTransform;
//视角
public enum PersonPerspective
{
First, //第一人称视角
Third, //第三人称视角
God //上帝视角
}
void Awake()
{
cacheTransform = transform;
if (thirdTarget != null)
{
cacheTransform.position = CalcCameraPosition();
cacheTransform.LookAt(thirdTarget);
}
}
// 切换到第一人称视角
public void ChangeFirstPerspective()
{
this.perspective = PersonPerspective.First;
}
// 切换到第三人称视角
public void ChangeThirdPerspective()
{
this.perspective = PersonPerspective.Third;
cacheTransform.position = CalcCameraPosition();
cacheTransform.LookAt(thirdTarget);
}
// 切换到上帝视角
public void ChangeGodPerspective()
{
this.perspective = PersonPerspective.God;
}
// 计算摄像机坐标
private Vector3 CalcCameraPosition()
{
Vector3 back = -thirdTarget.forward * backOffset;
Vector3 camPos = thirdTarget.position + back;
camPos.y += heightOffset;
#region 绘制辅助线
//后方距离
Debug.DrawRay(thirdTarget.position, back, Color.red);
//back方向矢量转位置矢量
Vector3 backPos = thirdTarget.position + back;
Debug.DrawRay(backPos, thirdTarget.up * heightOffset, Color.red);
//摄像机正方向
Vector3 camForward = thirdTarget.position - camPos;
Debug.DrawRay(camPos, camForward, Color.yellow);
#endregion
return camPos;
}
private void LateUpdate()
{
if (perspective == PersonPerspective.First)
{
cacheTransform.position = firstTarget.position;
cacheTransform.rotation = firstTarget.rotation;
}
else if (perspective == PersonPerspective.God)
{
var pos = thirdTarget.position;
pos.y += godHeight;
cacheTransform.position = pos;
cacheTransform.LookAt(thirdTarget);
}
}
void FixedUpdate()
{
if (perspective == PersonPerspective.Third)
{
var new_position = CalcCameraPosition();
//------Camera Tween Motion--------
var cur_position = cacheTransform.position;
cur_position.y = thirdTarget.position.y;
//摄像机相对于汽车所在方位
var cur_direction = cur_position - thirdTarget.position;
cur_direction.Normalize();
//摄像机与汽车保持恒定跟车距离
var dis_direction = cur_direction * backOffset;
//摄像机坐标
cur_position = thirdTarget.position + dis_direction;
//摄像机与汽车保持恒定高度
cur_position.y = thirdTarget.position.y + heightOffset;
//通过插值运算缓慢运动到最终位置
cacheTransform.position = Vector3.Lerp(cur_position, new_position, motionSpeed * Time.time);
//------End------------------------
//------Camera Tween Rotation-------
//之前的摄像机方向矢量
Vector3 preCamForward = cacheTransform.forward;
//之后的摄像机方向矢量
Vector3 curCamForward = thirdTarget.position - new_position;
curCamForward.Normalize();
//镜头旋转缓动插值
Quaternion preCamForwardQua = Quaternion.LookRotation(preCamForward);
Quaternion curCamForwardQua = Quaternion.LookRotation(curCamForward);
Quaternion lerpQua = Quaternion.Lerp(preCamForwardQua, curCamForwardQua, rotationSpeed * Time.fixedDeltaTime);
cacheTransform.rotation = lerpQua;
//-------End-------------------------
}
}
}