Files
BABA_YAGA/Assets/Opsive/UltimateCharacterController/Scripts/FirstPersonController/Camera/FirstPerson.cs
2026-06-14 23:57:44 +07:00

1085 lines
59 KiB
C#

/// ---------------------------------------------
/// Ultimate Character Controller
/// Copyright (c) Opsive. All Rights Reserved.
/// https://www.opsive.com
/// ---------------------------------------------
namespace Opsive.UltimateCharacterController.FirstPersonController.Camera.ViewTypes
{
using Opsive.Shared.Events;
using Opsive.Shared.Game;
using Opsive.Shared.Utility;
using Opsive.UltimateCharacterController.Camera;
using Opsive.UltimateCharacterController.Camera.ViewTypes;
using Opsive.UltimateCharacterController.Game;
using Opsive.UltimateCharacterController.Motion;
using Opsive.UltimateCharacterController.StateSystem;
using Opsive.UltimateCharacterController.Utility;
using UnityEngine;
/// <summary>
/// The FirstPerson ViewType allows the camera to be placed in a first person perspective.
/// </summary>
public abstract class FirstPerson : ViewType
{
#if ULTIMATE_CHARACTER_CONTROLLER_LWRP || ULTIMATE_CHARACTER_CONTROLLER_UNIVERSALRP
/// <summary>
/// Specifies how the overlay objects are rendered.
/// </summary>
public enum ObjectOverlayRenderType
{
SecondCamera, // Use a second stacked camera to ensure the overlay objects do no clip with any other objects.
RenderPipeline, // Use the LWRP/URP render pipeline to ensure the overlay objects do no clip with any other objects.
None // No special rendering for the overlay objects.
}
#endif
[Tooltip("The distance that the character should look ahead.")]
[SerializeField] protected float m_LookDirectionDistance = 100;
[Tooltip("The offset between the anchor and the camera.")]
[SerializeField] protected Vector3 m_LookOffset = new Vector3(0, .1f, 0.27f);
[Tooltip("Amount to adjust the camera position by when the character is looking down.")]
[SerializeField] protected Vector3 m_LookDownOffset = new Vector3(0, 0, 0.28f);
[Tooltip("The culling mask of the camera.")]
[SerializeField] protected LayerMask m_CullingMask = ~(1 << LayerManager.Overlay);
[Tooltip("The field of view of the main camera.")]
[SerializeField] protected float m_FieldOfView = 70f;
[Tooltip("The damping time of the field of view angle when changed.")]
[SerializeField] protected float m_FieldOfViewDamping = 0.2f;
[Tooltip("Specifies the position offset from the camera that the first person objects should render.")]
[SerializeField] protected Vector3 m_FirstPersonPositionOffset;
[Tooltip("Specifies the rotation offset from the camera that the first person objects should render.")]
[SerializeField] protected Vector3 m_FirstPersonRotationOffset;
#if ULTIMATE_CHARACTER_CONTROLLER_LWRP || ULTIMATE_CHARACTER_CONTROLLER_UNIVERSALRP
[Tooltip("Specifies how the overlay objects are rendered.")]
[SerializeField] protected ObjectOverlayRenderType m_OverlayRenderType = ObjectOverlayRenderType.SecondCamera;
#else
[Tooltip("Should the first person camera be used?")]
[SerializeField] protected bool m_UseFirstPersonCamera = true;
#endif
[Tooltip("A reference to the first person camera.")]
[SerializeField] protected UnityEngine.Camera m_FirstPersonCamera;
[Tooltip("The culling mask of the first person objects.")]
[SerializeField] protected LayerMask m_FirstPersonCullingMask = 1 << LayerManager.Overlay;
[Tooltip("Should the first person camera's field of view be synchronized with the main camera?")]
[SerializeField] protected bool m_SynchronizeFieldOfView = true;
[Tooltip("Specifies the field of view for the first person camera.")]
[SerializeField] protected float m_FirstPersonFieldOfView = 30f;
[Tooltip("The damping time of the field of view angle when changed.")]
[SerializeField] protected float m_FirstPersonFieldOfViewDamping = 0.2f;
[Tooltip("A vertical limit intended to prevent the camera from intersecting with the character.")]
[SerializeField] protected float m_PositionLowerVerticalLimit = 0.25f;
[Tooltip("Determines how much the camera will be pushed down when the player falls onto a surface.")]
[SerializeField] protected float m_PositionFallImpact = 2f;
[Tooltip("The number of frames that the fall impact force should be applied.")]
[SerializeField] protected int m_PositionFallImpactSoftness = 4;
[Tooltip("Rotates the camera depending on the sideways local velocity of the character, resulting in the camera leaning into or away from its sideways movement direction.")]
[SerializeField] protected float m_RotationStrafeRoll = 0.01f;
[Tooltip("Determines how much the camera will roll when the player falls onto a surface.")]
[SerializeField] protected float m_RotationFallImpact = 0.1f;
[Tooltip("The number of frames that the fall impact force should be applied.")]
[SerializeField] protected int m_RotationFallImpactSoftness = 1;
[Tooltip("The positional spring used for regular movement.")]
[SerializeField] protected Spring m_PositionSpring = new Spring();
[Tooltip("The rotational spring used for regular movement.")]
[SerializeField] protected Spring m_RotationSpring = new Spring();
[Tooltip("The positional spring which returns to equilibrium after a small amount of time (for recoil).")]
[SerializeField] protected Spring m_SecondaryPositionSpring = new Spring();
[Tooltip("The rotational spring which returns to equilibrium after a small amount of time (for recoil).")]
[SerializeField] protected Spring m_SecondaryRotationSpring = new Spring();
[Tooltip("The minimum pitch angle (in degrees).")]
[SerializeField] protected float m_MinPitchLimit = -72;
[Tooltip("The maximum pitch angle (in degrees).")]
[SerializeField] protected float m_MaxPitchLimit = 72;
[Tooltip("The rate that the camera changes its position while the character is moving.")]
[SerializeField] protected Vector3 m_BobPositionalRate = new Vector3(0.0f, 1.4f, 0.0f);
[Tooltip("The strength of the positional camera bob. Determines how far the camera swings in each respective direction.")]
[SerializeField] protected Vector3 m_BobPositionalAmplitude = new Vector3(0.0f, 0.35f, 0.0f);
[Tooltip("The rate that the camera changes its roll rotation value while the character is moving.")]
[SerializeField] protected float m_BobRollRate = 0.9f;
[Tooltip("The strength of the roll within the camera bob. Determines how far the camera tilts from left to right.")]
[SerializeField] protected float m_BobRollAmplitude = 1.7f;
[Tooltip("This tweaking feature is useful if the bob motion gets out of hand after changing character velocity.")]
[SerializeField] protected float m_BobInputVelocityScale = 1;
[Tooltip("A cap on the velocity value from the bob function, preventing the camera from flipping out when the character travels at excessive speeds.")]
[SerializeField] protected float m_BobMaxInputVelocity = 1000;
[Tooltip("A trough should only occur when the bob vertical offset is less then the specified value.")]
[SerializeField] protected float m_BobMinTroughVerticalOffset = -0.01f;
[Tooltip("The amount of force to add when the bob has reached its lowest point. This can be used to add a shaking effect to the camera to mimick a giant walking.")]
[SerializeField] protected Vector3 m_BobTroughForce = new Vector3(0.0f, 0.0f, 0.0f);
[Tooltip("Determines whether the bob should stay in effect only when the character is on the ground.")]
[SerializeField] protected bool m_BobRequireGroundContact = true;
[Tooltip("The speed that the camera should shake.")]
[SerializeField] protected float m_ShakeSpeed = 0.1f;
[Tooltip("The strength of the shake. Determines how much the camera will tilt.")]
[SerializeField] protected Vector3 m_ShakeAmplitude = new Vector3(10, 10);
[Tooltip("Number of head offset values to average together. The head offset value specifies how reactive the camera is to head movements.")]
[SerializeField] protected int m_SmoothHeadOffsetSteps = 2;
[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("Should the camera rotate as the character's head rotates?")]
[SerializeField] protected bool m_RotateWithHead;
public Vector3 LookOffset { get { return m_LookOffset; }
set
{
m_LookOffset = value;
if (m_PositionSpring != null && m_CameraController != null) {
InitializePositionSpringValue();
}
}
}
public Vector3 LookDownOffset { get { return m_LookDownOffset; } set { m_LookDownOffset = value; } }
public LayerMask CullingMask { get { return m_CullingMask; } set {
if (m_CullingMask != value) {
m_CullingMask = value;
UpdateFirstPersonCamera(m_CharacterLocomotion.FirstPersonPerspective);
}
} }
public float FieldOfView { get { return m_FieldOfView; } set { m_FieldOfView = value; } }
public float FieldOfViewDamping { get { return m_FieldOfViewDamping; } set { m_FieldOfViewDamping = value; } }
public Vector3 FirstPersonPositionOffset { get { return m_FirstPersonPositionOffset; }
set
{
m_FirstPersonPositionOffset = value;
if (m_FirstPersonCameraTransform != null) {
m_FirstPersonCameraTransform.localPosition = m_FirstPersonPositionOffset;
}
}
}
public Vector3 FirstPersonRotationOffset { get { return m_FirstPersonRotationOffset; }
set
{
m_FirstPersonRotationOffset = value;
if (m_FirstPersonCameraTransform != null) {
m_FirstPersonCameraTransform.localRotation = Quaternion.Euler(m_FirstPersonRotationOffset);
}
}
}
public override float LookDirectionDistance { get { return m_LookDirectionDistance; } }
#if ULTIMATE_CHARACTER_CONTROLLER_LWRP || ULTIMATE_CHARACTER_CONTROLLER_UNIVERSALRP
[NonSerialized] public ObjectOverlayRenderType OverlayRenderType { get { return m_OverlayRenderType; } set { m_OverlayRenderType = value; } }
#else
public bool UseFirstPersonCamera { get { return m_UseFirstPersonCamera; }
set
{
m_UseFirstPersonCamera = value;
if (m_FirstPersonCamera == null || m_CharacterLocomotion == null) {
return;
}
UpdateFirstPersonCamera(m_CharacterLocomotion.FirstPersonPerspective);
}
}
#endif
[NonSerialized] public UnityEngine.Camera FirstPersonCamera { get { return m_FirstPersonCamera; } set { m_FirstPersonCamera = value; } }
public LayerMask FirstPersonCullingMask { get { return m_FirstPersonCullingMask; }
set {
if (m_FirstPersonCamera != null && m_FirstPersonCullingMask != value) {
m_FirstPersonCullingMask = value;
m_FirstPersonCamera.cullingMask = m_FirstPersonCullingMask;
}
}
}
public bool SynchronizeFieldOfView { get { return m_SynchronizeFieldOfView; } set { m_SynchronizeFieldOfView = value; } }
public float FirstPersonFieldOfView { get { return m_FirstPersonFieldOfView; } set { m_FirstPersonFieldOfView = value; } }
public float FirstPersonFieldOfViewDamping { get { return m_FirstPersonFieldOfViewDamping; } set { m_FirstPersonFieldOfViewDamping = value; } }
public float PositionLowerVerticalLimit { get { return m_PositionLowerVerticalLimit; }
set {
m_PositionLowerVerticalLimit = value;
if (m_PositionSpring != null && m_CameraController != null) {
InitializePositionSpringValue();
}
} }
public float PositionFallImpact { get { return m_PositionFallImpact; } set { m_PositionFallImpact = value; } }
public int PositionFallImpactSoftness { get { return m_PositionFallImpactSoftness; } set { m_PositionFallImpactSoftness = value; } }
public float RotationStrafeRoll { get { return m_RotationStrafeRoll; } set { m_RotationStrafeRoll = value; } }
public float RotationFallImpact { get { return m_RotationFallImpact; } set { m_RotationFallImpact = value; } }
public int RotationFallImpactSoftness { get { return m_RotationFallImpactSoftness; } set { m_RotationFallImpactSoftness = value; } }
public Spring PositionSpring { get { return m_PositionSpring; }
set {
m_PositionSpring = value;
if (m_CameraController != null) { m_PositionSpring.Initialize(false, true); InitializePositionSpringValue(); }
} }
public Spring RotationSpring { get { return m_RotationSpring; }
set {
m_RotationSpring = value;
if (m_RotationSpring != null) { m_RotationSpring.Initialize(true, true); }
} }
public Spring SecondaryPositionSpring { get { return m_SecondaryPositionSpring; }
set {
m_SecondaryPositionSpring = value;
if (m_SecondaryPositionSpring != null) { m_SecondaryPositionSpring.Initialize(false, true); }
} }
public Spring SecondaryRotationSpring { get { return m_SecondaryRotationSpring; }
set {
m_SecondaryRotationSpring = value;
if (m_SecondaryRotationSpring != null) { m_SecondaryRotationSpring.Initialize(true, true); }
} }
public float MinPitchLimit { get { return m_MinPitchLimit; } set { m_MinPitchLimit = value; } }
public float MaxPitchLimit { get { return m_MaxPitchLimit; } set { m_MaxPitchLimit = value; } }
public Vector3 BobPositionalRate { get { return m_BobPositionalRate; } set { m_BobPositionalRate = value; } }
public Vector3 BobPositionalAmplitude { get { return m_BobPositionalAmplitude; } set { m_BobPositionalAmplitude = value; } }
public float BobRollRate { get { return m_BobRollRate; } set { m_BobRollRate = value; } }
public float BobRollAmplitude { get { return m_BobRollAmplitude; } set { m_BobRollAmplitude = value; } }
public float BobInputVelocityScale { get { return m_BobInputVelocityScale; } set { m_BobInputVelocityScale = value; } }
public float BobMaxInputVelocity { get { return m_BobMaxInputVelocity; } set { m_BobMaxInputVelocity = value; } }
public float BobMinTroughVerticalOffset { get { return m_BobMinTroughVerticalOffset; } set { m_BobMinTroughVerticalOffset = value; } }
public Vector3 BobTroughForce { get { return m_BobTroughForce; } set { m_BobTroughForce = value; } }
public bool BobRequireGroundContact { get { return m_BobRequireGroundContact; } set { m_BobRequireGroundContact = value; } }
public float ShakeSpeed { get { return m_ShakeSpeed; } set { m_ShakeSpeed = value; } }
public Vector3 ShakeAmplitude { get { return m_ShakeAmplitude; } set { m_ShakeAmplitude = value; } }
public int SmoothHeadOffsetSteps { get { return m_SmoothHeadOffsetSteps; } set {
m_SmoothHeadOffsetSteps = value;
if (value == 0) {
m_SmoothHeadBufferCount = 0;
}
} }
public bool RotateWithHead { get { return m_RotateWithHead; } set { m_RotateWithHead = value; } }
private UnityEngine.Camera m_Camera;
private Transform m_CrosshairsTransform;
private AimAssist m_AimAssist;
private Transform m_FirstPersonCameraTransform;
protected float m_Pitch;
protected float m_Yaw;
private float m_PrevBobSpeed;
protected Vector3 m_Shake;
protected Quaternion m_CharacterRotation;
private Quaternion m_CharacterPlatformRotationOffset = Quaternion.identity;
private Quaternion m_PlatformRotation = Quaternion.identity;
private bool m_AppendingZoomState;
private Vector3 m_CrosshairsLocalPosition;
private Quaternion m_CrosshairsDeltaRotation;
private Transform m_CharacterAnchor;
private Vector3 m_CharacterAnchorOffset;
private Quaternion m_CharacterAnchorRotation;
private Vector3[] m_SmoothHeadOffsetBuffer;
private int m_SmoothHeadBufferIndex;
private int m_SmoothHeadBufferCount;
private ScheduledEventBase m_SmoothHeadBufferEvent;
private RaycastHit m_RaycastHit;
private float m_FieldOfViewChangeTime;
private float m_FirstPersonFieldOfViewChangeTime;
private float m_BobVerticalOffset = float.MaxValue;
private bool m_BobVerticalOffsetDecreasing = false;
private float m_VerticalOffsetAdjustment;
private Vector3 m_PrevPositionSpringValue;
private Vector3 m_PrevPositionSpringVelocity;
private Vector3 m_PrevRotationSpringValue;
private Vector3 m_PrevRotationSpringVelocity;
private Vector3 m_PrevSecondaryPositionSpringValue;
private Vector3 m_PrevSecondaryPositionSpringVelocity;
private Vector3 m_PrevSecondaryRotationSpringValue;
private Vector3 m_PrevSecondaryRotationSpringVelocity;
private float m_PrevFieldOfViewDamping;
private float m_PrevFirstPersonFieldOfViewDamping;
private int m_StateChangeFrame = -1;
public override float Pitch { get { return m_Pitch; } }
public override float Yaw { get { return m_Yaw; } }
public override Quaternion CharacterRotation { get { return m_CharacterRotation; } }
public override bool FirstPersonPerspective { get { return true; } }
public Vector3 Shake { get { return m_Shake; } }
/// <summary>
/// Initializes the view type to the specified camera controller.
/// </summary>
/// <param name="cameraController">The camera controller to initialize the view type to.</param>
public override void Initialize(CameraController cameraController)
{
base.Initialize(cameraController);
m_Camera = cameraController.gameObject.GetCachedComponent<UnityEngine.Camera>();
m_AimAssist = m_GameObject.GetCachedComponent<AimAssist>();
m_Camera.depth = 0;
#if ULTIMATE_CHARACTER_CONTROLLER_LWRP || ULTIMATE_CHARACTER_CONTROLLER_UNIVERSALRP
if (m_OverlayRenderType != ObjectOverlayRenderType.None) {
#else
if (m_UseFirstPersonCamera) {
#endif
m_Camera.cullingMask &= m_CullingMask;
}
// Setup the overlay camera.
if (m_FirstPersonCamera != null) {
if (!UnityEngine.XR.XRSettings.enabled) {
m_Camera.fieldOfView = m_FieldOfView;
m_FirstPersonCamera.fieldOfView = m_SynchronizeFieldOfView ? m_FieldOfView : m_FirstPersonFieldOfView;
}
m_FirstPersonCamera.clearFlags = CameraClearFlags.Depth;
m_FirstPersonCamera.cullingMask = m_FirstPersonCullingMask;
m_FirstPersonCamera.depth = m_Camera.depth + 1;
m_FirstPersonCamera.rect = m_Camera.rect;
m_FirstPersonCameraTransform = m_FirstPersonCamera.transform;
m_FirstPersonCameraTransform.parent = m_Transform;
m_FirstPersonCameraTransform.localPosition = m_FirstPersonPositionOffset;
m_FirstPersonCameraTransform.localRotation = Quaternion.Euler(m_FirstPersonRotationOffset);
}
// Using the buffer is optional.
if (m_SmoothHeadOffsetSteps > 0) {
m_SmoothHeadOffsetBuffer = new Vector3[m_SmoothHeadOffsetSteps];
}
// Initialize the springs.
m_PositionSpring.Initialize(false, false);
m_RotationSpring.Initialize(true, true);
m_SecondaryPositionSpring.Initialize(false, false);
m_SecondaryRotationSpring.Initialize(true, true);
InitializePositionSpringValue();
m_PositionSpring.Reset();
// Register for any interested events.
EventHandler.RegisterEvent(m_GameObject, "OnAnchorOffsetUpdated", InitializePositionSpringValue);
EventHandler.RegisterEvent<ViewType, bool>(m_GameObject, "OnCameraChangeViewTypes", OnChangeViewType);
}
/// <summary>
/// Initializes the position spring value.
/// </summary>
private void InitializePositionSpringValue()
{
m_PositionSpring.RestValue = m_CameraController.AnchorOffset + m_LookOffset;
var value = m_PositionSpring.MinValue;
value.y = m_PositionSpring.RestValue.y - m_PositionLowerVerticalLimit;
m_PositionSpring.MinValue = value;
}
/// <summary>
/// Attaches the camera to the specified character.
/// </summary>
/// <param name="character">The character to attach the camera to.</param>
public override void AttachCharacter(GameObject character)
{
// Unregister from any events on the previous character.
if (m_Character != null) {
EventHandler.UnregisterEvent<Transform>(m_Character, "OnCharacterChangeMovingPlatforms", OnCharacterChangeMovingPlatforms);
EventHandler.UnregisterEvent<bool>(m_Character, "OnCameraChangePerspectives", UpdateFirstPersonCamera);
EventHandler.UnregisterEvent<float>(m_Character, "OnCharacterLand", OnCharacterLand);
EventHandler.UnregisterEvent<float, float, float>(m_Character, "OnCharacterLean", OnCharacterLean);
EventHandler.UnregisterEvent<float>(m_Character, "OnHeightChangeAdjustHeight", AdjustVerticalOffset);
if (m_SmoothHeadBufferEvent != null) {
Scheduler.Cancel(m_SmoothHeadBufferEvent);
m_SmoothHeadBufferEvent = null;
}
m_CharacterAnchor = null;
}
base.AttachCharacter(character);
// Initialize the camera with the new character.
if (m_Character != null) {
var characterAnimator = m_Character.GetCachedComponent<Animator>();
if (characterAnimator != null && m_SmoothHeadOffsetBuffer != null) {
m_CharacterAnchor = characterAnimator.GetBoneTransform(HumanBodyBones.Neck);
// If the neck doesn't exist then try to get the head.
if (m_CharacterAnchor == null) {
m_CharacterAnchor = characterAnimator.GetBoneTransform(HumanBodyBones.Head);
}
if (m_CharacterAnchor != null) {
if (m_SmoothHeadBufferEvent != null) {
Scheduler.Cancel(m_SmoothHeadBufferEvent);
m_SmoothHeadBufferEvent = null;
}
InitializeSmoothHeadBuffer();
}
} else {
EventHandler.RegisterEvent<float>(m_Character, "OnHeightChangeAdjustHeight", AdjustVerticalOffset);
}
m_SmoothHeadBufferIndex = -1;
m_SmoothHeadBufferCount = 0;
EventHandler.RegisterEvent<Transform>(m_Character, "OnCharacterChangeMovingPlatforms", OnCharacterChangeMovingPlatforms);
EventHandler.RegisterEvent<bool>(m_Character, "OnCameraChangePerspectives", UpdateFirstPersonCamera);
EventHandler.RegisterEvent<float>(m_Character, "OnCharacterLand", OnCharacterLand);
EventHandler.RegisterEvent<float, float, float>(m_Character, "OnCharacterLean", OnCharacterLean);
}
}
/// <summary>
/// Initializes the SmoothHeadBuffer after the character is grounded. This will allow the animator to be the correct height.
/// </summary>
private void InitializeSmoothHeadBuffer()
{
if (!m_CharacterLocomotion.Grounded || m_CharacterLocomotion.GroundRaycastHit.distance > m_CharacterLocomotion.ColliderSpacing + 0.011f) {
m_SmoothHeadBufferEvent = Scheduler.Schedule(Time.fixedDeltaTime, InitializeSmoothHeadBuffer);
return;
}
m_CharacterAnchorOffset = m_CharacterTransform.InverseTransformPoint(m_CharacterAnchor.position);
m_SmoothHeadBufferEvent = null;
}
/// <summary>
/// The view type has changed.
/// </summary>
/// <param name="activate">Should the current view type be activated?</param>
/// <param name="pitch">The pitch of the camera (in degrees).</param>
/// <param name="yaw">The yaw of the camera (in degrees).</param>
/// <param name="characterRotation">The rotation of the character.</param>
public override void ChangeViewType(bool activate, float pitch, float yaw, Quaternion characterRotation)
{
if (activate) {
m_Pitch = pitch;
m_Yaw = yaw;
m_CharacterRotation = characterRotation;
if (m_CharacterLocomotion.Platform != null) {
UpdatePlatformRotationOffset(m_CharacterLocomotion.Platform);
}
UpdateFirstPersonCamera(m_CharacterLocomotion.FirstPersonPerspective);
}
}
/// <summary>
/// Reset the ViewType's variables.
/// </summary>
/// <param name="characterRotation">The rotation of the character.</param>
public override void Reset(Quaternion characterRotation)
{
m_Pitch = 0;
m_Yaw = 0;
m_Shake = Vector3.zero;
m_CharacterRotation = characterRotation;
m_BobVerticalOffset = float.MaxValue;
m_BobVerticalOffsetDecreasing = false;
m_PrevBobSpeed = 0;
if (m_CharacterLocomotion.Platform != null) {
UpdatePlatformRotationOffset(m_CharacterLocomotion.Platform);
}
if (m_CharacterAnchor != null) {
m_CharacterAnchorRotation = MathUtility.InverseTransformQuaternion(m_CharacterRotation, m_CharacterAnchor.rotation);
}
InitializePositionSpringValue();
m_PositionSpring.Reset();
m_RotationSpring.Reset();
m_SecondaryPositionSpring.Reset();
m_SecondaryRotationSpring.Reset();
// The head position should be reset to the current values.
m_SmoothHeadBufferIndex = -1;
m_SmoothHeadBufferCount = 0;
UpdateHeadOffset();
}
/// <summary>
/// Sets the crosshairs to the specified transform.
/// </summary>
/// <param name="crosshairs">The transform of the crosshairs.</param>
public override void SetCrosshairs(Transform crosshairs)
{
m_CrosshairsTransform = crosshairs;
if (m_CrosshairsTransform != null) {
var screenPoint = RectTransformUtility.WorldToScreenPoint(null, m_CrosshairsTransform.position);
m_CrosshairsDeltaRotation = Quaternion.LookRotation(m_Camera.ScreenPointToRay(screenPoint).direction, m_Transform.up) * Quaternion.Inverse(m_Transform.rotation);
m_CrosshairsLocalPosition = m_CrosshairsTransform.localPosition;
}
}
/// <summary>
/// Returns the delta rotation caused by the crosshairs.
/// </summary>
/// <returns>The delta rotation caused by the crosshairs.</returns>
public override Quaternion GetCrosshairsDeltaRotation()
{
if (m_CrosshairsTransform == null) {
return Quaternion.identity;
}
// The crosshairs direction should only be updated when it changes.
if (m_CrosshairsLocalPosition != m_CrosshairsTransform.localPosition) {
var screenPoint = RectTransformUtility.WorldToScreenPoint(null, m_CrosshairsTransform.position);
m_CrosshairsDeltaRotation = Quaternion.LookRotation(m_Camera.ScreenPointToRay(screenPoint).direction, m_Transform.up) * Quaternion.Inverse(m_Transform.rotation);
m_CrosshairsLocalPosition = m_CrosshairsTransform.localPosition;
}
return m_CrosshairsDeltaRotation;
}
/// <summary>
/// The moving platform object has changed.
/// </summary>
/// <param name="movingPlatform">The moving platform to set. Can be null.</param>
private void OnCharacterChangeMovingPlatforms(Transform movingPlatform)
{
if (movingPlatform != null) {
UpdatePlatformRotationOffset(movingPlatform);
} else {
m_PlatformRotation = Quaternion.identity;
}
}
/// <summary>
/// Updates the Character Platform Rotation Offset variable.
/// </summary>
/// <param name="platform">The platform that the character is on top of.</param>
private void UpdatePlatformRotationOffset(Transform platform)
{
m_CharacterPlatformRotationOffset = m_CharacterRotation * Quaternion.Inverse(platform.rotation);
if (!m_CharacterLocomotion.AlignToGravity) {
// Only the local y rotation should affect the character's rotation.
var localPlatformRotationOffset = MathUtility.InverseTransformQuaternion(m_CharacterRotation, m_CharacterPlatformRotationOffset).eulerAngles;
localPlatformRotationOffset.x = localPlatformRotationOffset.z = 0;
m_CharacterPlatformRotationOffset = MathUtility.TransformQuaternion(m_CharacterRotation, Quaternion.Euler(localPlatformRotationOffset));
}
}
/// <summary>
/// Updates the first person camera and culling mask depending on if the first person camera is in use.
/// </summary>
/// <param name="firstPersonPerspective">Is the character in a first person perspective?</param>
private void UpdateFirstPersonCamera(bool firstPersonPerspective)
{
#if ULTIMATE_CHARACTER_CONTROLLER_LWRP || ULTIMATE_CHARACTER_CONTROLLER_UNIVERSALRP
if (m_OverlayRenderType == ObjectOverlayRenderType.None) {
#else
if (!m_UseFirstPersonCamera) {
#endif
return;
}
if (firstPersonPerspective) {
if (m_FirstPersonCamera != null) {
m_FirstPersonCamera.gameObject.SetActive(true);
}
#if ULTIMATE_CHARACTER_CONTROLLER_LWRP || ULTIMATE_CHARACTER_CONTROLLER_UNIVERSALRP
if (m_OverlayRenderType == ObjectOverlayRenderType.RenderPipeline) {
m_Camera.cullingMask |= m_FirstPersonCullingMask;
}
#endif
} else {
if (m_FirstPersonCamera != null) {
m_FirstPersonCamera.gameObject.SetActive(false);
}
#if ULTIMATE_CHARACTER_CONTROLLER_LWRP || ULTIMATE_CHARACTER_CONTROLLER_UNIVERSALRP
if (m_OverlayRenderType == ObjectOverlayRenderType.RenderPipeline) {
m_Camera.cullingMask &= ~m_FirstPersonCullingMask;
}
#endif
}
}
/// <summary>
/// The view type has changed.
/// </summary>
/// <param name="viewType">The ViewType that was activated or deactivated.</param>
/// <param name="activate">Should the current view type be activated?</param>
private void OnChangeViewType(ViewType viewType, bool activate)
{
if (activate || viewType == this) {
return;
}
// Apply the spring/shake values so when switching between first person view types it is a continuous motion.
if (viewType is FirstPerson) {
var firstPersonViewType = (viewType as FirstPerson);
m_PositionSpring.Value = firstPersonViewType.PositionSpring.Value;
m_PositionSpring.Velocity = firstPersonViewType.PositionSpring.Velocity;
m_RotationSpring.Value = firstPersonViewType.RotationSpring.Value;
m_RotationSpring.Velocity = firstPersonViewType.RotationSpring.Velocity;
m_Shake = firstPersonViewType.Shake;
}
}
/// <summary>
/// Updates the camera field of view.
/// </summary>
/// <param name="immediateUpdate">Should the camera be updated immediately?</param>
public override void UpdateFieldOfView(bool immediateUpdate)
{
if (m_Camera.fieldOfView != m_FieldOfView) {
var zoom = (immediateUpdate || m_FieldOfViewDamping == 0) ? 1 : ((Time.time - m_FieldOfViewChangeTime) / (m_FieldOfViewDamping / m_CharacterLocomotion.TimeScale));
m_Camera.fieldOfView = Mathf.SmoothStep(m_Camera.fieldOfView, m_FieldOfView, zoom);
}
if (m_FirstPersonCamera != null) {
if (m_SynchronizeFieldOfView) {
m_FirstPersonCamera.fieldOfView = m_Camera.fieldOfView;
} else if (m_FirstPersonCamera.fieldOfView != m_FirstPersonFieldOfView) {
var zoom = immediateUpdate ? 1 : ((Time.time - m_FirstPersonFieldOfViewChangeTime) / (m_FirstPersonFieldOfViewDamping / m_CharacterLocomotion.TimeScale));
m_FirstPersonCamera.fieldOfView = Mathf.SmoothStep(m_FirstPersonCamera.fieldOfView, m_FirstPersonFieldOfView, zoom);
}
}
}
/// <summary>
/// Rotates the camera according to the horizontal and vertical movement values.
/// </summary>
/// <param name="horizontalMovement">-1 to 1 value specifying the amount of horizontal movement.</param>
/// <param name="verticalMovement">-1 to 1 value specifying the amount of vertical movement.</param>
/// <param name="immediateUpdate">Should the camera be updated immediately?</param>
/// <returns>The updated rotation.</returns>
public override Quaternion Rotate(float horizontalMovement, float verticalMovement, bool immediateUpdate)
{
UpdateShakes();
// Rotate with the moving platform.
if (m_CharacterLocomotion.Platform != null) {
m_PlatformRotation = MathUtility.InverseTransformQuaternion(m_CharacterLocomotion.Platform.rotation, m_CharacterPlatformRotationOffset) *
Quaternion.Inverse(MathUtility.InverseTransformQuaternion(m_CharacterLocomotion.Platform.rotation, m_CharacterRotation *
Quaternion.Inverse(m_CharacterLocomotion.Platform.rotation)));
if (!m_CharacterLocomotion.AlignToGravity) {
// Only the local y rotation should affect the character's rotation.
var localPlatformTorque = MathUtility.InverseTransformQuaternion(m_CharacterTransform.rotation, m_PlatformRotation).eulerAngles;
localPlatformTorque.x = localPlatformTorque.z = 0;
m_PlatformRotation = MathUtility.TransformQuaternion(m_CharacterTransform.rotation, Quaternion.Euler(localPlatformTorque));
}
m_CharacterRotation *= m_PlatformRotation;
}
// The camera should always stay aligned to the character's up direction.
if (m_CharacterLocomotion.AlignToGravity) {
var localRotation = MathUtility.InverseTransformQuaternion(m_CharacterTransform.rotation, m_CharacterRotation).eulerAngles;
localRotation.x = localRotation.z = 0;
m_CharacterRotation = MathUtility.TransformQuaternion(m_CharacterTransform.rotation, Quaternion.Euler(localRotation));
}
// Remember the offset so the delta can be compared the next update.
if (m_CharacterLocomotion.Platform != null) {
UpdatePlatformRotationOffset(m_CharacterLocomotion.Platform);
}
// Update the rotation. The pitch may have a limit.
if (Mathf.Abs(m_MinPitchLimit - m_MaxPitchLimit) < 180) {
m_Pitch = MathUtility.ClampAngle(m_Pitch, -verticalMovement, m_MinPitchLimit, m_MaxPitchLimit);
} else {
m_Pitch -= verticalMovement;
}
// Prevent the values from getting too large.
m_Pitch = MathUtility.ClampInnerAngle(m_Pitch);
m_Yaw = MathUtility.ClampInnerAngle(m_Yaw);
// If aim assist has a target then the camera should look in the specified direction.
if (m_AimAssist != null) {
m_AimAssist.UpdateBreakForce(Mathf.Abs(horizontalMovement) + Mathf.Abs(verticalMovement));
if (m_AimAssist.HasTarget()) {
var rotation = MathUtility.TransformQuaternion(m_CharacterRotation, Quaternion.Euler(m_Pitch, m_Yaw, 0));
var assistRotation = rotation * MathUtility.InverseTransformQuaternion(rotation, m_AimAssist.TargetRotation(rotation));
// Set the pitch and yaw so when the target is lost the view type won't snap back to the previous rotation value.
var localAssistRotation = MathUtility.InverseTransformQuaternion(m_CharacterRotation, assistRotation).eulerAngles;
m_Pitch = MathUtility.ClampInnerAngle(localAssistRotation.x);
m_Yaw = MathUtility.ClampInnerAngle(localAssistRotation.y);
}
}
var headRotation = Quaternion.identity;
if (m_RotateWithHead) {
headRotation = MathUtility.InverseTransformQuaternion(m_CharacterRotation, m_CharacterAnchor.rotation) * Quaternion.Inverse(m_CharacterAnchorRotation);
// The camera should only follow the pitch of the head rotation.
var eulerHeadRotation = headRotation.eulerAngles;
eulerHeadRotation.y = eulerHeadRotation.z = 0;
headRotation = Quaternion.Euler(eulerHeadRotation);
}
// Return the rotation.
return MathUtility.TransformQuaternion(m_CharacterRotation, Quaternion.Euler(m_Pitch, m_Yaw, 0)) *
Quaternion.Euler(m_RotationSpring.Value) *
Quaternion.Euler(m_SecondaryRotationSpring.Value) * headRotation;
}
/// <summary>
/// Updates the procedular shaking of the camera.
/// </summary>
private void UpdateShakes()
{
if (m_ShakeSpeed == 0) {
return;
}
// Subtract shake from the last frame or the camera will drift.
m_Yaw -= m_Shake.x;
m_Pitch -= m_Shake.y;
// Apply the new shake.
m_Shake = Vector3.Scale(SmoothRandom.GetVector3Centered(m_ShakeSpeed), m_ShakeAmplitude);
m_Yaw += m_Shake.x;
m_Pitch += m_Shake.y;
m_RotationSpring.AddForce(Vector3.forward * m_Shake.z * m_CharacterLocomotion.TimeScale * Time.timeScale);
}
/// <summary>
/// Moves the camera according to the current pitch and yaw values.
/// </summary>
/// <param name="immediateUpdate">Should the camera be updated immediately?</param>
/// <returns>The updated position.</returns>
public override Vector3 Move(bool immediateUpdate)
{
UpdateSway();
UpdateBob();
UpdateHeadOffset();
var targetPosition = GetTargetPosition();
// Adjust the camera position to prevent the body from clipping with the camera's spring-based motions.
if (m_MaxPitchLimit != 0) {
var lookDown = Mathf.Max(0, m_Pitch / m_MaxPitchLimit);
lookDown = Mathf.SmoothStep(0, 1, lookDown);
targetPosition += m_Transform.TransformDirection(m_LookDownOffset * lookDown);
}
// 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 offset = m_PositionSpring.Value + GetHeadOffset();
offset.x = offset.z = 0;
var startPosition = GetAnchorTransformPoint(offset);
var direction = targetPosition - startPosition;
if (Physics.SphereCast(startPosition, 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 -= direction.normalized * ((direction.magnitude + m_Camera.nearClipPlane) - m_RaycastHit.distance);
}
m_CharacterLocomotion.EnableColliderCollisionLayer(collisionLayerEnabled);
return targetPosition;
}
/// <summary>
/// Returns the target position accounting for the springs and head offset.
/// </summary>
/// <returns>The target position accounting for the springs and head offset.</returns>
public Vector3 GetTargetPosition()
{
var targetPosition = GetAnchorTransformPoint(m_PositionSpring.Value + m_SecondaryPositionSpring.Value + Vector3.up * m_VerticalOffsetAdjustment);
// The camera should move with the head offset of the character.
targetPosition += GetHeadOffset();
return targetPosition;
}
/// <summary>
/// Updates the head offset to match the current animator head position.
/// </summary>
private void UpdateHeadOffset()
{
// Allow the camera to move with the character's neck/head so the body doesn't clip the camera.
if (m_SmoothHeadBufferEvent == null && m_CharacterAnchor != null && m_SmoothHeadOffsetBuffer != null && m_SmoothHeadOffsetSteps > 0) {
var offset = m_CharacterTransform.InverseTransformPoint(m_CharacterAnchor.position) - m_CharacterAnchorOffset;
// Allow the offset to settle before storing the difference.
if (offset.sqrMagnitude > 0) {
// Add the current offset to the buffer.
m_SmoothHeadBufferIndex = (m_SmoothHeadBufferIndex + 1) % m_SmoothHeadOffsetBuffer.Length;
m_SmoothHeadOffsetBuffer[m_SmoothHeadBufferIndex] = offset;
if (m_SmoothHeadBufferCount <= m_SmoothHeadBufferIndex) {
m_SmoothHeadBufferCount++;
}
}
}
}
/// <summary>
/// Returns the smoothed head offset.
/// </summary>
/// <returns>The smoothed head offset</returns>
private Vector3 GetHeadOffset()
{
if (m_SmoothHeadBufferCount > 0) {
// Find the average.
var total = Vector3.zero;
for (int i = 0; i < m_SmoothHeadBufferCount; ++i) {
var index = m_SmoothHeadBufferIndex - i;
if (index < 0) { index = m_SmoothHeadBufferCount + m_SmoothHeadBufferIndex - i; }
total += m_SmoothHeadOffsetBuffer[index];
}
return m_CharacterTransform.TransformDirection(total / m_SmoothHeadBufferCount);
}
return Vector3.zero;
}
/// <summary>
/// Applies a sway force on the camera in response to character controller motion.
/// </summary>
private void UpdateSway()
{
var localVelocity = m_Transform.InverseTransformDirection(m_CharacterLocomotion.LocomotionVelocity * 0.016f) * m_CharacterLocomotion.TimeScale * Time.timeScale;
m_RotationSpring.AddForce(Vector3.forward * localVelocity.x * m_RotationStrafeRoll);
}
/// <summary>
/// Updates the first person bob.
/// </summary>
private void UpdateBob()
{
if ((m_BobPositionalRate == Vector3.zero || m_BobPositionalAmplitude == Vector3.zero) && (m_BobRollRate == 0 || m_BobRollAmplitude == 0)) {
return;
}
var bobSpeed = ((m_BobRequireGroundContact && !m_CharacterLocomotion.Grounded) ? 0 : m_CharacterLocomotion.LocomotionVelocity.sqrMagnitude);
// Scale and limit the input velocity.
bobSpeed = Mathf.Min(bobSpeed * m_BobInputVelocityScale, m_BobMaxInputVelocity);
// Reduce the number of decimals to avoid floating point imprecision issues.
bobSpeed = Mathf.Round(bobSpeed * 1000) / 1000;
// If the bob speed is zero then fade out the last speed value. It is important to clamp the speed to the
// last bob speed value because a preset may have changed since the last last bob.
if (bobSpeed == 0) {
bobSpeed = Mathf.Min((m_PrevBobSpeed * 0.93f), m_BobMaxInputVelocity);
}
// Update the positional and roll bob value.
var currentPositionalBobAmplitude = (bobSpeed * (m_BobPositionalAmplitude * -0.0001f));
var currentRollBobAmplitude = (bobSpeed * (m_BobRollAmplitude * -0.0001f));
Vector3 currentBobOffsetValue;
currentBobOffsetValue.x = Mathf.Cos(m_BobPositionalRate.x * m_CharacterLocomotion.TimeScale * Time.time * 10) * currentPositionalBobAmplitude.x;
currentBobOffsetValue.y = Mathf.Cos(m_BobPositionalRate.y * m_CharacterLocomotion.TimeScale * Time.time * 10) * currentPositionalBobAmplitude.y;
currentBobOffsetValue.z = Mathf.Cos(m_BobPositionalRate.z * m_CharacterLocomotion.TimeScale * Time.time * 10) * currentPositionalBobAmplitude.z;
var currentBobRollValue = Mathf.Cos(m_BobRollRate * m_CharacterLocomotion.TimeScale * Time.time * 10) * currentRollBobAmplitude;
// Add the bob value to the positional and rotational spring.
m_PositionSpring.AddForce(currentBobOffsetValue);
m_RotationSpring.AddForce(Vector3.forward * currentBobRollValue);
m_PrevBobSpeed = bobSpeed;
// Detect if the bob was previously decreasing and is now moving back up. This will indicate that the bob was at the lowest position.
if (currentBobOffsetValue.y < m_BobMinTroughVerticalOffset && m_BobVerticalOffset < currentBobOffsetValue.y && m_BobVerticalOffsetDecreasing) {
m_SecondaryPositionSpring.AddForce(m_BobTroughForce);
}
m_BobVerticalOffsetDecreasing = currentBobOffsetValue.y < m_BobVerticalOffset;
m_BobVerticalOffset = currentBobOffsetValue.y;
}
/// <summary>
/// Returns the direction that the character is looking.
/// </summary>
/// <param name="characterLookDirection">Is the character look direction being retrieved?</param>
/// <returns>The direction that the character is looking.</returns>
public override Vector3 LookDirection(bool characterLookDirection)
{
var crosshairsDeltaRotation = characterLookDirection ? Quaternion.identity : GetCrosshairsDeltaRotation();
var shake = characterLookDirection ? Quaternion.Inverse(Quaternion.Euler(m_Shake.y, m_Shake.x, 0)) : Quaternion.identity;
var platformRotation = characterLookDirection ? Quaternion.Inverse(m_PlatformRotation) : Quaternion.identity;
return (m_Transform.rotation * crosshairsDeltaRotation * shake * platformRotation) * Vector3.forward;
}
/// <summary>
/// Returns the direction that the character is looking.
/// </summary>
/// <param name="lookPosition">The position that the character is looking from.</param>
/// <param name="characterLookDirection">Is the character look direction being retrieved?</param>
/// <param name="layerMask">The LayerMask value of the objects that the look direction can hit.</param>
/// <param name="useRecoil">Should recoil be included in the look direction?</param>
/// <returns>The direction that the character is looking.</returns>
public override Vector3 LookDirection(Vector3 lookPosition, bool characterLookDirection, int layerMask, bool useRecoil)
{
var collisionLayerEnabled = m_CharacterLocomotion.CollisionLayerEnabled;
m_CharacterLocomotion.EnableColliderCollisionLayer(false);
// If a crosshairs is specified then the character should look at the crosshairs. Do not use the crosshairs delta for character look directions to prevent
// the character's rotation from being affected by the crosshairs.
var crosshairsDeltaRotation = characterLookDirection ? Quaternion.identity : GetCrosshairsDeltaRotation();
var shake = characterLookDirection ? Quaternion.Inverse(Quaternion.Euler(m_Shake.y, m_Shake.x, 0)) : Quaternion.identity;
var platformRotation = characterLookDirection ? Quaternion.Inverse(m_PlatformRotation) : Quaternion.identity;
// Cast a ray from the camera point in the forward direction. The look direction is then the vector from the look position to the hit point.
RaycastHit hit;
Vector3 hitPoint;
var rotation = (useRecoil ? m_Transform.rotation : MathUtility.TransformQuaternion(m_CharacterRotation, Quaternion.Euler(m_Pitch, m_Yaw, 0))) *
crosshairsDeltaRotation * shake * platformRotation;
if (Physics.Raycast(m_Transform.position, rotation * Vector3.forward, out hit, m_LookDirectionDistance, layerMask, QueryTriggerInteraction.Ignore)) {
hitPoint = hit.point;
} else {
Vector3 position;
if (useRecoil) {
position = GetAnchorTransformPoint(m_PositionSpring.Value + m_SecondaryPositionSpring.Value);
} else {
position = lookPosition;
}
var lookDirection = Vector3.zero;
lookDirection.Set(0, 0, m_LookDirectionDistance);
hitPoint = MathUtility.TransformPoint(position, rotation, lookDirection);
}
m_CharacterLocomotion.EnableColliderCollisionLayer(collisionLayerEnabled);
return (hitPoint - lookPosition).normalized;
}
/// <summary>
/// Adds a positional force to the ViewType.
/// </summary>
/// <param name="force">The force to add.</param>
public override void AddPositionalForce(Vector3 force)
{
m_PositionSpring.AddForce(force);
}
/// <summary>
/// Adds a rotational force to the ViewType. The secondary position spring is more stiff compared to the primary positional spring.
/// </summary>
/// <param name="force">The force to add.</param>
public override void AddRotationalForce(Vector3 force)
{
m_RotationSpring.AddForce(force);
}
/// <summary>
/// Adds a secondary positional force to the ViewType.
/// </summary>
/// <param name="force">The force to add.</param>
/// <param name="restAccumulation">The percent of the force to accumulate to the rest value.</param>
public override void AddSecondaryPositionalForce(Vector3 force, float restAccumulation)
{
if (restAccumulation > 0 && (m_AimAssist == null || !m_AimAssist.HasTarget())) {
m_SecondaryPositionSpring.RestValue += force * restAccumulation;
}
m_SecondaryPositionSpring.AddForce(force);
}
/// <summary>
/// Adds a delayed rotational force to the ViewType.
/// </summary>
/// <param name="force">The force to add.</param>
/// <param name="restAccumulation">The percent of the force to accumulate to the rest value.</param>
public override void AddSecondaryRotationalForce(Vector3 force, float restAccumulation)
{
if (restAccumulation > 0 && (m_AimAssist == null || !m_AimAssist.HasTarget())) {
m_Pitch += force.x * restAccumulation;
m_Yaw += force.y * restAccumulation;
var springRest = m_SecondaryRotationSpring.RestValue;
springRest.z += force.z * restAccumulation;
m_SecondaryRotationSpring.RestValue = springRest;
}
m_SecondaryRotationSpring.AddForce(force);
}
/// <summary>
/// The character has landed on the ground.
/// </summary>
/// <param name="height">The height of the fall.</param>
private void OnCharacterLand(float height)
{
var positionImpact = height * m_PositionFallImpact;
var rotationImpact = height * m_RotationFallImpact;
// Apply impact to camera position spring.
m_PositionSpring.AddForce(positionImpact * -Vector3.up, m_PositionFallImpactSoftness);
// Apply impact to camera rotation spring. Randomize the rotation upon landing.
var roll = Random.value > 0.5f ? (rotationImpact * 2) : -(rotationImpact * 2);
m_RotationSpring.AddForce(Vector3.forward * roll, m_RotationFallImpactSoftness);
}
/// <summary>
/// The character has started to lean.
/// </summary>
/// <param name="distance">The distance that the character is leaning.</param>
/// <param name="tilt">The amount of tilt to apply to the lean.</param>
/// <param name="itemTiltMultiplier">The multiplier to apply to the tilt of an item.</param>
private void OnCharacterLean(float distance, float tilt, float itemTiltMultiplier)
{
m_PositionSpring.RestValue = m_CameraController.AnchorOffset + m_LookOffset + distance * Vector3.right;
m_RotationSpring.RestValue = tilt * Vector3.forward;
}
/// <summary>
/// Adjusts the vertical offset by the given amount.
/// </summary>
/// <param name="amount">The amount to adjust the vertical offset height by.</param>
private void AdjustVerticalOffset(float amount)
{
m_VerticalOffsetAdjustment += amount;
}
/// <summary>
/// Callback when the StateManager will change the active state on the current object.
/// </summary>
public override void StateWillChange()
{
// Remember the interal spring values so they can be restored if a new spring is applied during the state change.
m_PrevPositionSpringValue = m_PositionSpring.Value;
m_PrevPositionSpringVelocity = m_PositionSpring.Velocity;
m_PrevRotationSpringValue = m_RotationSpring.Value;
m_PrevRotationSpringVelocity = m_RotationSpring.Velocity;
m_PrevSecondaryPositionSpringValue = m_SecondaryPositionSpring.Value;
m_PrevSecondaryPositionSpringVelocity = m_SecondaryPositionSpring.Velocity;
m_PrevSecondaryRotationSpringValue = m_SecondaryRotationSpring.Value;
m_PrevSecondaryRotationSpringVelocity = m_SecondaryRotationSpring.Velocity;
// Multiple state changes can occur within the same frame. Only remember the first damping value.
if (m_StateChangeFrame != Time.frameCount) {
m_PrevFieldOfViewDamping = m_FieldOfViewDamping;
m_PrevFirstPersonFieldOfViewDamping = m_FirstPersonFieldOfViewDamping;
}
m_StateChangeFrame = Time.frameCount;
}
/// <summary>
/// Callback when the StateManager has changed the active state on the current object.
/// </summary>
public override void StateChange()
{
// Append the zoom state name so the combination of state names will be called, such as "CrouchZoom".
if (!string.IsNullOrEmpty(m_CameraController.ZoomState) && !m_AppendingZoomState) {
m_AppendingZoomState = true;
for (int i = 0; i < m_States.Length; ++i) {
StateManager.SetState(m_GameObject, m_States[i].Name + m_CameraController.ZoomState, m_States[i].Active && m_CameraController.ZoomInput);
}
m_AppendingZoomState = false;
}
if (m_Camera.fieldOfView != m_FieldOfView) {
m_FieldOfViewChangeTime = Time.time;
// The field of view should get a head start if the damping was previously 0. This will allow the field of view to move back to the original value even
// when the state is no longer active.
if (m_CameraController.ActiveViewType == this && m_PrevFieldOfViewDamping == 0) {
m_Camera.fieldOfView = (m_Camera.fieldOfView + m_FieldOfView) * 0.5f;
if (m_FirstPersonCamera != null && m_SynchronizeFieldOfView) {
m_FirstPersonCamera.fieldOfView = m_Camera.fieldOfView;
}
}
}
if (m_FirstPersonCamera != null && m_FirstPersonCamera.fieldOfView != m_FirstPersonFieldOfView) {
m_FirstPersonFieldOfViewChangeTime = Time.time;
// The field of view should get a head start if the damping was previously 0. This will allow the field of view to move back to the original value even
// when the state is no longer active.
if (m_CameraController.ActiveViewType == this && m_PrevFirstPersonFieldOfViewDamping == 0) {
m_FirstPersonCamera.fieldOfView = (m_FirstPersonCamera.fieldOfView + m_FirstPersonFieldOfView) * 0.5f;
}
}
m_PositionSpring.Value = m_PrevPositionSpringValue;
m_PositionSpring.Velocity = m_PrevPositionSpringVelocity;
m_RotationSpring.Value = m_PrevRotationSpringValue;
m_RotationSpring.Velocity = m_PrevRotationSpringVelocity;
m_SecondaryPositionSpring.Value = m_PrevSecondaryPositionSpringValue;
m_SecondaryPositionSpring.Velocity = m_PrevSecondaryPositionSpringVelocity;
m_SecondaryRotationSpring.Value = m_PrevSecondaryRotationSpringValue;
m_SecondaryRotationSpring.Velocity = m_PrevSecondaryRotationSpringVelocity;
}
/// <summary>
/// The camera has been destroyed.
/// </summary>
public override void OnDestroy()
{
base.OnDestroy();
m_PositionSpring.Destroy();
m_RotationSpring.Destroy();
m_SecondaryPositionSpring.Destroy();
m_SecondaryRotationSpring.Destroy();
EventHandler.UnregisterEvent(m_GameObject, "OnAnchorOffsetUpdated", InitializePositionSpringValue);
EventHandler.UnregisterEvent<ViewType, bool>(m_GameObject, "OnCameraChangeViewTypes", OnChangeViewType);
}
}
}