/// ---------------------------------------------
/// Ultimate Character Controller
/// Copyright (c) Opsive. All Rights Reserved.
/// https://www.opsive.com
/// ---------------------------------------------
namespace Opsive.UltimateCharacterController.FirstPersonController.Camera.ViewTypes
{
using Opsive.Shared.Game;
using Opsive.UltimateCharacterController.Camera;
using Opsive.UltimateCharacterController.Camera.ViewTypes;
using Opsive.UltimateCharacterController.Utility;
using UnityEngine;
///
/// The TransformLook ViewType is a first person view type that will have the camera look in the forward direction relative to the target transform.
///
public class TransformLook : ViewType
{
[Tooltip("The object that determines the position of the camera.")]
[SerializeField] protected Transform m_MoveTarget;
[Tooltip("The object that determines the rotation of the camera.")]
[SerializeField] protected Transform m_RotationTarget;
[Tooltip("The offset relative to the move target.")]
[SerializeField] protected Vector3 m_Offset = new Vector3(0, 0.2f, 0.2f);
[Tooltip("The radius of the camera's collision sphere to prevent it from clipping with other objects.")]
[SerializeField] protected float m_CollisionRadius = 0.05f;
[Tooltip("The speed at which the camera should move.")]
[SerializeField] protected float m_MoveSpeed = 10;
[Tooltip("The speed at which the view type should rotate towards the target rotation.")]
[Range(0, 1)] [SerializeField] protected float m_RotationalLerpSpeed = 0.9f;
[Tooltip("Should the pitch be restricted? If false the pitch can be changed by the player.")]
[SerializeField] protected bool m_RestrictPitch = true;
[Tooltip("The minimum pitch angle (in degrees), used if the pitch is not restricted.")]
[HideInInspector] [SerializeField] protected float m_MinPitchLimit = -72;
[Tooltip("The maximum pitch angle (in degrees), used if the pitch is not restricted.")]
[HideInInspector] [SerializeField] protected float m_MaxPitchLimit = 72;
[Tooltip("Should the yaw be restricted? If false the yaw can be changed by the player.")]
[SerializeField] protected bool m_RestrictYaw = true;
public Transform MoveTarget { get { return m_MoveTarget; } set { m_MoveTarget = value; } }
public Transform RotationTarget { get { return m_RotationTarget; } set { m_RotationTarget = value; } }
public Vector3 Offset { get { return m_Offset; } set { m_Offset = value; } }
public float CollisionRadius { get { return m_CollisionRadius; } set { m_CollisionRadius = value; } }
public float MoveSpeed { get { return m_MoveSpeed; } set { m_MoveSpeed = value; } }
public float RotationalLerpSpeed { get { return m_RotationalLerpSpeed; } set { m_RotationalLerpSpeed = value; } }
public bool RestrictPitch { get { return m_RestrictPitch; } set { m_RestrictPitch = value; } }
public float MinPitchLimit { get { return m_MinPitchLimit; } set { m_MinPitchLimit = value; } }
public float MaxPitchLimit { get { return m_MaxPitchLimit; } set { m_MaxPitchLimit = value; } }
public bool RestrictYaw { get { return m_RestrictYaw; } set { m_RestrictYaw = value; } }
public override Quaternion CharacterRotation { get { return m_CharacterTransform.rotation; } }
public override bool FirstPersonPerspective { get { return true; } }
public override float LookDirectionDistance { get { return m_Offset.magnitude; } }
public override float Pitch { get { return m_Pitch; } }
public override float Yaw { get { return m_Yaw; } }
private UnityEngine.Camera m_Camera;
private RaycastHit m_RaycastHit;
private float m_Pitch;
private float m_Yaw;
///
/// Initializes the view type to the specified camera controller.
///
/// The camera controller to initialize the view type to.
public override void Initialize(CameraController cameraController)
{
base.Initialize(cameraController);
m_Camera = cameraController.gameObject.GetCachedComponent();
}
///
/// Attaches the view type to the specified character.
///
/// The character to attach the camera to.
public override void AttachCharacter(GameObject character)
{
base.AttachCharacter(character);
if (m_MoveTarget == null || m_RotationTarget == null) {
if (m_Character == null) {
return;
}
Transform moveTarget = m_CharacterTransform, rotationTarget = m_CharacterTransform;
var characterAnimator = m_Character.GetCachedComponent();
if (characterAnimator != null) {
moveTarget = characterAnimator.GetBoneTransform(HumanBodyBones.Head);
rotationTarget = characterAnimator.GetBoneTransform(HumanBodyBones.Hips);
}
m_MoveTarget = moveTarget;
m_RotationTarget = rotationTarget;
}
}
///
/// The view type has changed.
///
/// Should the current view type be activated?
/// The pitch of the camera (in degrees).
/// The yaw of the camera (in degrees).
/// The rotation of the character.
public override void ChangeViewType(bool activate, float pitch, float yaw, Quaternion characterRotation)
{
if (activate) {
m_Pitch = m_RestrictPitch ? 0 : pitch;
m_Yaw = m_RestrictYaw ? 0 : yaw;
}
}
///
/// Reset the ViewType's variables.
///
/// The rotation of the character.
public override void Reset(Quaternion characterRotation)
{
m_Pitch = 0;
m_Yaw = 0;
}
///
/// Rotates the camera to face in the same direction as the target.
///
/// -1 to 1 value specifying the amount of horizontal movement.
/// -1 to 1 value specifying the amount of vertical movement.
/// Should the camera be updated immediately?
/// The updated rotation.
public override Quaternion Rotate(float horizontalMovement, float verticalMovement, bool immediateUpdate)
{
var rotation = m_RotationTarget.rotation;
if (!immediateUpdate) {
rotation = Quaternion.Slerp(m_Transform.rotation, rotation, m_RotationalLerpSpeed);
}
// Update the pitch and yaw if they are not restricted.
if (!m_RestrictPitch) {
if (Mathf.Abs(m_MinPitchLimit - m_MaxPitchLimit) < 180) {
m_Pitch = MathUtility.ClampAngle(m_Pitch, -verticalMovement, m_MinPitchLimit, m_MaxPitchLimit);
} else {
m_Pitch -= verticalMovement;
}
}
if (!m_RestrictYaw) {
if (m_Pitch > 90 || m_Pitch < -90) {
horizontalMovement *= -1;
}
m_Yaw = MathUtility.ClampInnerAngle(m_Yaw + horizontalMovement);
}
return rotation * Quaternion.Euler(m_Pitch, m_Yaw, 0);
}
///
/// Moves the camera to be in the target position.
///
/// Should the camera be updated immediately?
/// The updated position.
public override Vector3 Move(bool immediateUpdate)
{
// Ensure there aren't any objects obstructing the distance between the anchor offset and the target position.
var collisionLayerEnabled = m_CharacterLocomotion.CollisionLayerEnabled;
m_CharacterLocomotion.EnableColliderCollisionLayer(false);
var targetPosition = m_MoveTarget.TransformPoint(m_Offset);
var direction = targetPosition - m_MoveTarget.position;
if (Physics.SphereCast(m_MoveTarget.position, m_CollisionRadius, direction.normalized, out m_RaycastHit, direction.magnitude + m_Camera.nearClipPlane,
m_CharacterLayerManager.IgnoreInvisibleCharacterWaterLayers, QueryTriggerInteraction.Ignore)) {
// Move the camera in if an object obstructed the view.
targetPosition = m_RaycastHit.point + m_RaycastHit.normal * (m_Camera.nearClipPlane + m_CharacterLocomotion.ColliderSpacing);
}
m_CharacterLocomotion.EnableColliderCollisionLayer(collisionLayerEnabled);
return Vector3.MoveTowards(m_Transform.position, targetPosition, immediateUpdate ? float.MaxValue : Time.deltaTime * m_MoveSpeed);
}
///
/// Returns the direction that the character is looking.
///
/// Is the character look direction being retrieved?
/// The direction that the character is looking.
public override Vector3 LookDirection(bool characterLookDirection)
{
return m_CharacterTransform.forward;
}
///
/// Returns the direction that the character is looking.
///
/// The position that the character is looking from.
/// Is the character look direction being retrieved?
/// The LayerMask value of the objects that the look direction can hit.
/// Should recoil be included in the look direction?
/// The direction that the character is looking.
public override Vector3 LookDirection(Vector3 lookPosition, bool characterLookDirection, int layerMask, bool useRecoil)
{
return m_CharacterTransform.forward;
}
}
}