Files
ihob/Assets/ProCamera2D/Runtime/ProCamera2D.cs
2026-02-21 17:04:05 -08:00

1168 lines
46 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
namespace Com.LuisPedroFonseca.ProCamera2D
{
/// <summary>
/// Core class of the plugin. Everything starts and happens through here.
/// All extensions and triggers have a reference to an instance of this class.
/// </summary>
[HelpURLAttribute("http://www.procamera2d.com/user-guide/")]
[RequireComponent(typeof(Camera))]
public class ProCamera2D : MonoBehaviour, ISerializationCallbackReceiver
{
public const string Title = "Pro Camera 2D";
public static readonly Version Version = new Version("2.9.4");
#region Inspector Variables
public List<CameraTarget> CameraTargets = new List<CameraTarget>();
public bool CenterTargetOnStart;
public MovementAxis Axis;
public UpdateType UpdateType;
public bool FollowHorizontal = true;
public float HorizontalFollowSmoothness = 0.15f;
public bool FollowVertical = true;
public float VerticalFollowSmoothness = 0.15f;
[Range(-1f, 1f)]
public float OffsetX;
[Range(-1f, 1f)]
public float OffsetY;
public bool IsRelativeOffset = true;
public bool ZoomWithFOV;
public bool IgnoreTimeScale;
#endregion
#region Properties
/// <summary>Get ProCamera2D's static instance</summary>
public static ProCamera2D Instance
{
get
{
if (Equals(_instance, null))
{
_instance = FindObjectOfType<ProCamera2D>();
if (Equals(_instance, null))
throw new UnityException("ProCamera2D does not exist.");
}
return _instance;
}
}
static ProCamera2D _instance;
/// <summary>Property to know if there's a ProCamera2D present</summary>
public static bool Exists { get { return _instance != null; } }
/// <summary>Is the camera moving?</summary>
public bool IsMoving
{
get
{
return
Vector3H(_transform.localPosition) != Vector3H(_previousCameraPosition) ||
Vector3V(_transform.localPosition) != Vector3V(_previousCameraPosition);
}
}
/// <summary>Update ProCamera2D's camera rect</summary>
public Rect Rect
{
get
{
return GameCamera.rect;
}
set
{
GameCamera.rect = value;
ProCamera2DParallax parallax = GetComponentInChildren<ProCamera2DParallax>();
if (parallax != null)
{
for (int i = 0; i < parallax.ParallaxLayers.Count; i++)
{
parallax.ParallaxLayers[i].ParallaxCamera.rect = value;
}
}
}
}
public Vector2 CameraTargetPositionSmoothed
{
get
{
return new Vector2(_cameraTargetHorizontalPositionSmoothed, _cameraTargetVerticalPositionSmoothed);
}
set
{
_cameraTargetHorizontalPositionSmoothed = value.x;
_cameraTargetVerticalPositionSmoothed = value.y;
}
}
float _cameraTargetHorizontalPositionSmoothed;
float _cameraTargetVerticalPositionSmoothed;
public Vector3 LocalPosition { get { return _transform.localPosition; } set { _transform.localPosition = value; } }
public Vector2 StartScreenSizeInWorldCoordinates { get; private set; }
public Vector2 ScreenSizeInWorldCoordinates { get; private set; }
public Vector3 PreviousTargetsMidPoint { get; private set; }
public Vector3 TargetsMidPoint { get; private set; }
public Vector3 CameraTargetPosition { get; private set; }
public float DeltaTime { get; private set; }
public Vector3 ParentPosition { get; private set; }
public Vector3 InfluencesSum { get { return _influencesSum; } }
Vector3 _influencesSum = Vector3.zero;
#endregion
#region Public Variables
public Action<float> PreMoveUpdate;
public Action<float> PostMoveUpdate;
public Action<Vector2> OnCameraResize;
public Action<float> OnUpdateScreenSizeFinished;
public Action<float> OnDollyZoomFinished;
public Action OnReset;
public Vector3? ExclusiveTargetPosition;
public int CurrentZoomTriggerID;
public bool IsCameraPositionLeftBounded;
public bool IsCameraPositionRightBounded;
public bool IsCameraPositionTopBounded;
public bool IsCameraPositionBottomBounded;
public Camera GameCamera;
#endregion
#region Private Variables
Func<Vector3, float> Vector3H;
Func<Vector3, float> Vector3V;
Func<Vector3, float> Vector3D;
Func<float, float, Vector3> VectorHV;
Func<float, float, float, Vector3> VectorHVD;
Coroutine _updateScreenSizeCoroutine;
Coroutine _dollyZoomRoutine;
List<Vector3> _influences = new List<Vector3>();
float _originalCameraDepthSign;
float _previousCameraTargetHorizontalPositionSmoothed;
float _previousCameraTargetVerticalPositionSmoothed;
int _previousScreenWidth;
int _previousScreenHeight;
Vector3 _previousCameraPosition;
WaitForFixedUpdate _waitForFixedUpdate = new WaitForFixedUpdate();
Transform _transform;
List<IPreMover> _preMovers = new List<IPreMover>();
List<IPositionDeltaChanger> _positionDeltaChangers = new List<IPositionDeltaChanger>();
List<IPositionOverrider> _positionOverriders = new List<IPositionOverrider>();
List<ISizeDeltaChanger> _sizeDeltaChangers = new List<ISizeDeltaChanger>();
List<ISizeOverrider> _sizeOverriders = new List<ISizeOverrider>();
List<IPostMover> _postMovers = new List<IPostMover>();
#endregion
#region MonoBehaviour
void Awake()
{
_instance = this;
_transform = transform;
// Get parent position
if (_transform.parent != null)
ParentPosition = _transform.parent.position;
if (GameCamera == null)
GameCamera = GetComponent<Camera>();
if (GameCamera == null)
Debug.LogError("Unity Camera not set and not found on the GameObject: " + gameObject.name);
// Reset the axis functions
ResetAxisFunctions();
// Remove empty targets
for (int i = 0; i < CameraTargets.Count; i++)
{
if (CameraTargets[i].TargetTransform == null)
{
CameraTargets.RemoveAt(i);
}
}
// Calculates current screen size
CalculateScreenSize();
ResetStartSize();
// We save this so we know the direction of the camera when moving it on the depth axis
_originalCameraDepthSign = Mathf.Sign(Vector3D(_transform.localPosition));
}
void Start()
{
SortPreMovers();
SortPositionDeltaChangers();
SortPositionOverriders();
SortSizeDeltaChangers();
SortSizeOverriders();
SortPostMovers();
// Set some values ahead of the update loop so that other extensions can use them on Awake/Start
TargetsMidPoint = GetTargetsWeightedMidPoint(ref CameraTargets);
_cameraTargetHorizontalPositionSmoothed = Vector3H(TargetsMidPoint);
_cameraTargetVerticalPositionSmoothed = Vector3V(TargetsMidPoint);
DeltaTime = IgnoreTimeScale ? Time.unscaledDeltaTime : Time.deltaTime;
// Center on target
if (CenterTargetOnStart && CameraTargets.Count > 0)
{
var targetsMidPoint = GetTargetsWeightedMidPoint(ref CameraTargets);
var cameraTargetPositionX = FollowHorizontal ? Vector3H(targetsMidPoint) : Vector3H(_transform.localPosition);
var cameraTargetPositionY = FollowVertical ? Vector3V(targetsMidPoint) : Vector3V(_transform.localPosition);
var finalPos = new Vector2(cameraTargetPositionX, cameraTargetPositionY);
finalPos += new Vector2(GetOffsetX() - Vector3H(ParentPosition), GetOffsetY() - Vector3V(ParentPosition));
MoveCameraInstantlyToPosition(finalPos);
}
else
{
CameraTargetPosition = _transform.position - ParentPosition;
_cameraTargetHorizontalPositionSmoothed = Vector3H(CameraTargetPosition);
_previousCameraTargetHorizontalPositionSmoothed = _cameraTargetHorizontalPositionSmoothed;
_cameraTargetVerticalPositionSmoothed = Vector3V(CameraTargetPosition);
_previousCameraTargetVerticalPositionSmoothed = _cameraTargetVerticalPositionSmoothed;
}
}
void LateUpdate()
{
if (UpdateType == UpdateType.LateUpdate)
Move(IgnoreTimeScale ? Time.unscaledDeltaTime : Time.deltaTime);
}
void FixedUpdate()
{
if (UpdateType == UpdateType.FixedUpdate)
Move(IgnoreTimeScale ? Time.fixedUnscaledDeltaTime : Time.fixedDeltaTime);
}
void OnApplicationQuit()
{
_instance = null;
}
#endregion
#region Public Methods
/// <summary>Returns the current global offset on the horizontal axis</summary>
public float GetOffsetX()
{
return IsRelativeOffset ? OffsetX * ScreenSizeInWorldCoordinates.x * .5f : OffsetX;
}
/// <summary>Returns the current global offset on the vertical axis</summary>
public float GetOffsetY()
{
return IsRelativeOffset ? OffsetY * ScreenSizeInWorldCoordinates.y * .5f : OffsetY;
}
/// <summary>Apply the given influence to the camera during this frame.</summary>
/// <param name="influence">The vector representing the influence to be applied</param>
public void ApplyInfluence(Vector2 influence)
{
if (!isActiveAndEnabled || Time.deltaTime < .0001f || float.IsNaN(influence.x) || float.IsNaN(influence.y))
return;
_influences.Add(VectorHV(influence.x, influence.y));
}
/// <summary>Apply the given influences to the camera during the corresponding durations.</summary>
/// <param name="influences">An array of the vectors representing the influences to be applied</param>
/// <param name="durations">An array with the durations of the influences to be applied</param>
public Coroutine ApplyInfluencesTimed(Vector2[] influences, float[] durations)
{
return StartCoroutine(ApplyInfluencesTimedRoutine(influences, durations));
}
/// <summary>Add a target for the camera to follow.</summary>
/// <param name="targetTransform">The Transform of the target</param>
/// <param name="targetInfluenceH">The influence this target horizontal position should have when calculating the average position of all the targets</param>
/// <param name="targetInfluenceV">The influence this target vertical position should have when calculating the average position of all the targets</param>
/// <param name="duration">The time it takes for this target to reach it's influence. Use for a more progressive transition.</param>
/// <param name="targetOffset">A vector that offsets the target position that the camera will follow</param>
public CameraTarget AddCameraTarget(Transform targetTransform, float targetInfluenceH = 1f, float targetInfluenceV = 1f, float duration = 0f, Vector2 targetOffset = default(Vector2))
{
var newCameraTarget = new CameraTarget
{
TargetTransform = targetTransform,
TargetInfluenceH = targetInfluenceH,
TargetInfluenceV = targetInfluenceV,
TargetOffset = targetOffset
};
CameraTargets.Add(newCameraTarget);
if (duration > 0f)
{
newCameraTarget.TargetInfluence = 0f;
StartCoroutine(AdjustTargetInfluenceRoutine(newCameraTarget, targetInfluenceH, targetInfluenceV, duration));
}
return newCameraTarget;
}
/// <summary>Add multiple targets for the camera to follow.</summary>
/// <param name="targetsTransforms">An array or list with the new targets</param>
/// <param name="targetsInfluenceH">The influence the targets horizontal position should have when calculating the average position of all the targets</param>
/// <param name="targetsInfluenceV">The influence the targets vertical position should have when calculating the average position of all the targets</param>
/// <param name="duration">The time it takes for the targets to reach their influence. Use for a more progressive transition.</param>
/// <param name="targetOffset">A vector that offsets the target position that the camera will follow</param>
public void AddCameraTargets(IList<Transform> targetsTransforms, float targetsInfluenceH = 1f, float targetsInfluenceV = 1f, float duration = 0f, Vector2 targetOffset = default(Vector2))
{
for (int i = 0; i < targetsTransforms.Count; i++)
{
AddCameraTarget(targetsTransforms[i], targetsInfluenceH, targetsInfluenceV, duration, targetOffset);
}
}
/// <summary>Add multiple targets for the camera to follow.</summary>
/// <param name="cameraTargets">An array or list with the new targets</param>
public void AddCameraTargets(IList<CameraTarget> cameraTargets)
{
CameraTargets.AddRange(cameraTargets);
}
/// <summary>Gets the corresponding CameraTarget from an object's transform.</summary>
/// <param name="targetTransform">The Transform of the target</param>
public CameraTarget GetCameraTarget(Transform targetTransform)
{
for (int i = 0; i < CameraTargets.Count; i++)
{
if (CameraTargets[i].TargetTransform.GetInstanceID() == targetTransform.GetInstanceID())
{
return CameraTargets[i];
}
}
return null;
}
/// <summary>Remove a target from the camera.</summary>
/// <param name="targetTransform">The Transform of the target</param>
/// <param name="duration">The time it takes for this target to reach a zero influence. Use for a more progressive transition.</param>
public void RemoveCameraTarget(Transform targetTransform, float duration = 0f)
{
for (int i = 0; i < CameraTargets.Count; i++)
{
if (CameraTargets[i].TargetTransform.GetInstanceID() == targetTransform.GetInstanceID())
{
if (duration > 0)
{
StartCoroutine(AdjustTargetInfluenceRoutine(CameraTargets[i], 0, 0, duration, true));
}
else
CameraTargets.Remove(CameraTargets[i]);
}
}
}
/// <summary>Removes all targets from the camera.</summary>
/// <param name="duration">The time it takes for all targets to reach a zero influence. Use for a more progressive transition.</param>
public void RemoveAllCameraTargets(float duration = 0f)
{
if (duration == 0)
{
CameraTargets.Clear();
}
else
{
for (int i = 0; i < CameraTargets.Count; i++)
{
StartCoroutine(AdjustTargetInfluenceRoutine(CameraTargets[i], 0, 0, duration, true));
}
}
}
/// <summary>Adjusts a target influence</summary>
/// <param name="cameraTarget">The CameraTarget of the target</param>
/// <param name="targetInfluenceH">The influence this target horizontal position should have when calculating the average position of all the targets</param>
/// <param name="targetInfluenceV">The influence this target vertical position should have when calculating the average position of all the targets</param>
/// <param name="duration">The time it takes for this target to reach it's influence. Don't use a duration if calling every frame.</param>
public Coroutine AdjustCameraTargetInfluence(CameraTarget cameraTarget, float targetInfluenceH, float targetInfluenceV, float duration = 0)
{
if (duration > 0)
return StartCoroutine(AdjustTargetInfluenceRoutine(cameraTarget, targetInfluenceH, targetInfluenceV, duration));
else
{
cameraTarget.TargetInfluenceH = targetInfluenceH;
cameraTarget.TargetInfluenceV = targetInfluenceV;
return null;
}
}
/// <summary>Adjusts a target influence, finding it first by its transform.</summary>
/// <param name="cameraTargetTransf">The Transform of the target</param>
/// <param name="targetInfluenceH">The influence this target horizontal position should have when calculating the average position of all the targets</param>
/// <param name="targetInfluenceV">The influence this target vertical position should have when calculating the average position of all the targets</param>
/// <param name="duration">The time it takes for this target to reach it's influence. Don't use a duration if calling every frame.</param>
public Coroutine AdjustCameraTargetInfluence(Transform cameraTargetTransf, float targetInfluenceH, float targetInfluenceV, float duration = 0)
{
var cameraTarget = GetCameraTarget(cameraTargetTransf);
if (cameraTarget == null)
return null;
return AdjustCameraTargetInfluence(cameraTarget, targetInfluenceH, targetInfluenceV, duration);
}
/// <summary>Translates the camera by the specified amount while maintaining internal values</summary>
/// <param name="translateAmount">The camera will be offset by this amount</param>
public void TranslateCamera(Vector2 translateAmount)
{
var currentPos = _transform.localPosition;
var newPos = currentPos + VectorHVD(translateAmount.x, translateAmount.y, Vector3D(currentPos));
_transform.localPosition = newPos;
CameraTargetPosition = newPos;
TargetsMidPoint = newPos;
PreviousTargetsMidPoint = newPos;
_cameraTargetHorizontalPositionSmoothed += translateAmount.x;
_cameraTargetVerticalPositionSmoothed += translateAmount.y;
_previousCameraTargetHorizontalPositionSmoothed += translateAmount.x;
_previousCameraTargetVerticalPositionSmoothed += translateAmount.y;
}
/// <summary>Moves the camera instantly to the supplied position</summary>
/// <param name="cameraPos">The final position of the camera</param>
public void MoveCameraInstantlyToPosition(Vector2 cameraPos)
{
_transform.localPosition = VectorHVD(cameraPos.x, cameraPos.y, Vector3D(_transform.localPosition));
ResetMovement();
}
/// <summary>Resets the camera movement and size and also all of its extensions to their start values.
/// This could be useful if, for example, your player dies and respawns somewhere else on the level</summary>
/// <param name="centerOnTargets">If true, the camera will move to the "final" targets position</param>
/// <param name="resetSize">If true, resets the camera size to the start value</param>
/// <param name="resetExtensions">If true, resets all active extensions to their start values</param>
public void Reset(bool centerOnTargets = true, bool resetSize = true, bool resetExtensions = true)
{
if (centerOnTargets)
CenterOnTargets();
else
ResetMovement();
if(resetSize)
ResetSize();
if (resetExtensions)
ResetExtensions();
}
/// <summary>
/// Cancels any existing camera movement easing.
/// Also check CenterOnTargets and MoveCameraInstantlyToPosition.
/// </summary>
public void ResetMovement()
{
CameraTargetPosition = _transform.localPosition;
_cameraTargetHorizontalPositionSmoothed = Vector3H(CameraTargetPosition);
_cameraTargetVerticalPositionSmoothed = Vector3V(CameraTargetPosition);
_previousCameraTargetHorizontalPositionSmoothed = _cameraTargetHorizontalPositionSmoothed;
_previousCameraTargetVerticalPositionSmoothed = _cameraTargetVerticalPositionSmoothed;
TargetsMidPoint = CameraTargetPosition;
PreviousTargetsMidPoint = TargetsMidPoint;
}
/// <summary>
/// Resets the camera size to the start value.
/// </summary>
public void ResetSize()
{
SetScreenSize(StartScreenSizeInWorldCoordinates.y / 2);
}
/// <summary>
/// Resets the start camera size that is used for some calculations.
/// </summary>
public void ResetStartSize(Vector2 newSize = default(Vector2))
{
if(newSize != default(Vector2))
StartScreenSizeInWorldCoordinates = newSize;
else
StartScreenSizeInWorldCoordinates = Utils.GetScreenSizeInWorldCoords(GameCamera, Mathf.Abs(Vector3D(_transform.localPosition)));
}
/// <summary>
/// Resets all active extensions to their start values.
/// Notice you can manually reset each extension using the "OnReset" method.
/// </summary>
public void ResetExtensions()
{
if (OnReset != null)
OnReset();
}
/// <summary>Instantly moves the camera to the targets' position.</summary>
public void CenterOnTargets()
{
var targetsMidPoint = GetTargetsWeightedMidPoint(ref CameraTargets);
var finalPos = new Vector2(Vector3H(targetsMidPoint), Vector3V(targetsMidPoint));
finalPos += new Vector2(GetOffsetX(), GetOffsetY());
MoveCameraInstantlyToPosition(finalPos);
}
/// <summary>Resize the camera to the supplied size</summary>
/// <param name="newSize">Half of the wanted size in world units</param>
/// <param name="duration">How long it should take to reach the provided size. Use 0 if instant or calling repeatedly</param>
/// <param name="easeType">The easing method to apply. Only used when the duration is bigger than 0</param>
public void UpdateScreenSize(float newSize, float duration = 0f, EaseType easeType = EaseType.EaseInOut)
{
if (!enabled)
return;
if (_updateScreenSizeCoroutine != null)
StopCoroutine(_updateScreenSizeCoroutine);
if (duration > 0)
_updateScreenSizeCoroutine = StartCoroutine(UpdateScreenSizeRoutine(newSize, duration, easeType));
else
SetScreenSize(newSize);
}
/// <summary>Recalculates the camera size in world coordinates. Call only if you change the camera size manually outside of ProCamera2D.</summary>
public void CalculateScreenSize()
{
GameCamera.ResetAspect();
ScreenSizeInWorldCoordinates = Utils.GetScreenSizeInWorldCoords(GameCamera, Mathf.Abs(Vector3D(_transform.localPosition)));
_previousScreenWidth = Screen.width;
_previousScreenHeight = Screen.height;
}
/// <summary>Zoom in or out the camera by the supplied amount</summary>
/// <param name="zoomAmount">The amount to zoom in world units</param>
/// <param name="duration">How long it should take to reach the new zoom. Use 0 if instant or calling repeatedly</param>
/// <param name="easeType">The easing method to apply. Only used when the duration is bigger than 0</param>
public void Zoom(float zoomAmount, float duration = 0f, EaseType easeType = EaseType.EaseInOut)
{
UpdateScreenSize(ScreenSizeInWorldCoordinates.y * .5f + zoomAmount, duration, easeType);
}
/// <summary>Creates a dolly zoom effect, also known as the Hitchcock effect</summary>
/// <param name="targetFOV">The final field of view</param>
/// <param name="duration">The duration of the effect</param>
/// <param name="easeType">The ease type of the transition</param>
public void DollyZoom(float targetFOV, float duration = 1f, EaseType easeType = EaseType.EaseInOut)
{
if (!enabled)
return;
if (GameCamera.orthographic)
{
Debug.LogWarning("Dolly zooming is only supported on perspective cameras");
return;
}
if (_dollyZoomRoutine != null)
StopCoroutine(_dollyZoomRoutine);
targetFOV = Mathf.Clamp(targetFOV, 0.1f, 179.9f);
if (duration <= 0)
{
GameCamera.fieldOfView = targetFOV;
_transform.localPosition = VectorHVD(
Vector3H(_transform.localPosition),
Vector3V(_transform.localPosition),
GetCameraDistanceForFOV(GameCamera.fieldOfView, ScreenSizeInWorldCoordinates.y) * _originalCameraDepthSign);
}
else
{
StartCoroutine(DollyZoomRoutine(targetFOV, duration, easeType));
}
}
/// <summary>
/// Move the camera to the average position of all the targets.
/// This method is automatically called when using LateUpdate or FixedUpdate.
/// If using ManualUpdate, you have to call it yourself.
/// </summary>
/// <param name="deltaTime">The time in seconds it took to complete the last frame</param>
public void Move(float deltaTime)
{
// Save previous camera position
_previousCameraPosition = _transform.localPosition;
//Detect resolution changes
if (Screen.width != _previousScreenWidth || Screen.height != _previousScreenHeight)
CalculateScreenSize();
// Delta time
DeltaTime = deltaTime;
if (DeltaTime < .0001f)
return;
// Pre-Move update
if (PreMoveUpdate != null)
PreMoveUpdate(DeltaTime);
// Cycle through the pre movers
for (int i = 0; i < _preMovers.Count; i++)
{
_preMovers[i].PreMove(deltaTime);
}
// Calculate targets mid point
PreviousTargetsMidPoint = TargetsMidPoint;
TargetsMidPoint = GetTargetsWeightedMidPoint(ref CameraTargets);
CameraTargetPosition = TargetsMidPoint;
// Calculate influences
_influencesSum = Utils.GetVectorsSum(_influences);
CameraTargetPosition += _influencesSum;
_influences.Clear();
// Follow only on selected axis
var cameraTargetPositionX = FollowHorizontal ? Vector3H(CameraTargetPosition) : Vector3H(_transform.localPosition);
var cameraTargetPositionY = FollowVertical ? Vector3V(CameraTargetPosition) : Vector3V(_transform.localPosition);
CameraTargetPosition = VectorHV(cameraTargetPositionX - Vector3H(ParentPosition), cameraTargetPositionY - Vector3V(ParentPosition));
// Ignore targets and influences if exclusive position is set
if (ExclusiveTargetPosition.HasValue)
{
CameraTargetPosition = VectorHV(Vector3H(ExclusiveTargetPosition.Value) - Vector3H(ParentPosition), Vector3V(ExclusiveTargetPosition.Value) - Vector3V(ParentPosition));
ExclusiveTargetPosition = null;
}
// Add offset
CameraTargetPosition += VectorHV(FollowHorizontal ? GetOffsetX() : 0, FollowVertical ? GetOffsetY() : 0);
// Tween camera final position
_cameraTargetHorizontalPositionSmoothed = Utils.SmoothApproach(_cameraTargetHorizontalPositionSmoothed, _previousCameraTargetHorizontalPositionSmoothed, Vector3H(CameraTargetPosition), 1f / HorizontalFollowSmoothness, DeltaTime);
_previousCameraTargetHorizontalPositionSmoothed = _cameraTargetHorizontalPositionSmoothed;
_cameraTargetVerticalPositionSmoothed = Utils.SmoothApproach(_cameraTargetVerticalPositionSmoothed, _previousCameraTargetVerticalPositionSmoothed, Vector3V(CameraTargetPosition), 1f / VerticalFollowSmoothness, DeltaTime);
_previousCameraTargetVerticalPositionSmoothed = _cameraTargetVerticalPositionSmoothed;
// Movement this step
var horizontalDeltaMovement = _cameraTargetHorizontalPositionSmoothed - Vector3H(_transform.localPosition);
var verticalDeltaMovement = _cameraTargetVerticalPositionSmoothed - Vector3V(_transform.localPosition);
// Calculate the base delta movement
var deltaMovement = VectorHV(horizontalDeltaMovement, verticalDeltaMovement);
// Cycle through the size delta changers
var deltaSize = 0f;
for (int i = 0; i < _sizeDeltaChangers.Count; i++)
{
deltaSize = _sizeDeltaChangers[i].AdjustSize(deltaTime, deltaSize);
}
// Calculate the new size
var newSize = ScreenSizeInWorldCoordinates.y * .5f + deltaSize;
// Cycle through the size overriders
for (int i = 0; i < _sizeOverriders.Count; i++)
{
newSize = _sizeOverriders[i].OverrideSize(deltaTime, newSize);
}
// Apply the new size
if (newSize != ScreenSizeInWorldCoordinates.y * .5f)
SetScreenSize(newSize);
// Cycle through the position delta changers
for (int i = 0; i < _positionDeltaChangers.Count; i++)
{
deltaMovement = _positionDeltaChangers[i].AdjustDelta(deltaTime, deltaMovement);
}
// Calculate the new position
var newPos = LocalPosition + deltaMovement;
// Cycle through the position overriders
for (int i = 0; i < _positionOverriders.Count; i++)
{
newPos = _positionOverriders[i].OverridePosition(deltaTime, newPos);
}
// Apply the new position
_transform.localPosition = VectorHVD(Vector3H(newPos), Vector3V(newPos), Vector3D(_transform.localPosition));
// Cycle through the post movers
for (int i = 0; i < _postMovers.Count; i++)
{
_postMovers[i].PostMove(deltaTime);
}
// Post-Move update
if (PostMoveUpdate != null)
PostMoveUpdate(DeltaTime);
}
/// <summary>For internal use</summary>
internal YieldInstruction GetYield()
{
switch (UpdateType)
{
case UpdateType.FixedUpdate:
return IgnoreTimeScale ? null : _waitForFixedUpdate;
default:
return null;
}
}
#endregion
#region Private Methods
void ResetAxisFunctions()
{
switch (Axis)
{
case MovementAxis.XY:
Vector3H = vector => vector.x;
Vector3V = vector => vector.y;
Vector3D = vector => vector.z;
VectorHV = (h, v) => new Vector3(h, v, 0);
VectorHVD = (h, v, d) => new Vector3(h, v, d);
break;
case MovementAxis.XZ:
Vector3H = vector => vector.x;
Vector3V = vector => vector.z;
Vector3D = vector => vector.y;
VectorHV = (h, v) => new Vector3(h, 0, v);
VectorHVD = (h, v, d) => new Vector3(h, d, v);
break;
case MovementAxis.YZ:
Vector3H = vector => vector.z;
Vector3V = vector => vector.y;
Vector3D = vector => vector.x;
VectorHV = (h, v) => new Vector3(0, v, h);
VectorHVD = (h, v, d) => new Vector3(d, v, h);
break;
}
}
Vector3 GetTargetsWeightedMidPoint(ref List<CameraTarget> targets)
{
var midPointH = 0f;
var midPointV = 0f;
if (targets.Count == 0)
return transform.localPosition;
var totalInfluencesH = 0f;
var totalInfluencesV = 0f;
var totalAccountableTargetsH = 0;
var totalAccountableTargetsV = 0;
for (int i = 0; i < targets.Count; i++)
{
if (targets[i] == null || targets[i].TargetTransform == null)
{
targets.RemoveAt(i);
continue;
}
midPointH += (Vector3H(targets[i].TargetPosition) + targets[i].TargetOffset.x) * targets[i].TargetInfluenceH;
midPointV += (Vector3V(targets[i].TargetPosition) + targets[i].TargetOffset.y) * targets[i].TargetInfluenceV;
totalInfluencesH += targets[i].TargetInfluenceH;
totalInfluencesV += targets[i].TargetInfluenceV;
if (targets[i].TargetInfluenceH > 0)
totalAccountableTargetsH++;
if (targets[i].TargetInfluenceV > 0)
totalAccountableTargetsV++;
}
if (totalInfluencesH < 1 && totalAccountableTargetsH == 1)
totalInfluencesH += (1 - totalInfluencesH);
if (totalInfluencesV < 1 && totalAccountableTargetsV == 1)
totalInfluencesV += (1 - totalInfluencesV);
if (totalInfluencesH > .0001f)
midPointH /= totalInfluencesH;
if (totalInfluencesV > .0001f)
midPointV /= totalInfluencesV;
return VectorHV(midPointH, midPointV);
}
IEnumerator ApplyInfluencesTimedRoutine(IList<Vector2> influences, float[] durations)
{
var count = -1;
while (count < durations.Length - 1)
{
count++;
var duration = durations[count];
yield return StartCoroutine(ApplyInfluenceTimedRoutine(influences[count], duration));
}
}
IEnumerator ApplyInfluenceTimedRoutine(Vector2 influence, float duration)
{
while (duration > 0)
{
duration -= DeltaTime;
ApplyInfluence(influence);
yield return GetYield();
}
}
IEnumerator AdjustTargetInfluenceRoutine(CameraTarget cameraTarget, float influenceH, float influenceV, float duration, bool removeIfZeroInfluence = false)
{
var startInfluenceH = cameraTarget.TargetInfluenceH;
var startInfluenceV = cameraTarget.TargetInfluenceV;
var t = 0f;
while (t <= 1.0f)
{
t += DeltaTime / duration;
cameraTarget.TargetInfluenceH = Utils.EaseFromTo(startInfluenceH, influenceH, t, EaseType.Linear);
cameraTarget.TargetInfluenceV = Utils.EaseFromTo(startInfluenceV, influenceV, t, EaseType.Linear);
yield return GetYield();
}
if (removeIfZeroInfluence && cameraTarget.TargetInfluenceH <= 0 && cameraTarget.TargetInfluenceV <= 0)
CameraTargets.Remove(cameraTarget);
}
IEnumerator UpdateScreenSizeRoutine(float finalSize, float duration, EaseType easeType)
{
var startSize = ScreenSizeInWorldCoordinates.y * .5f;
var newSize = startSize;
var t = 0f;
while (t <= 1.0f)
{
t += DeltaTime / duration;
newSize = Utils.EaseFromTo(startSize, finalSize, t, easeType);
SetScreenSize(newSize);
yield return GetYield();
}
_updateScreenSizeCoroutine = null;
if (OnUpdateScreenSizeFinished != null)
OnUpdateScreenSizeFinished(newSize);
}
IEnumerator DollyZoomRoutine(float finalFOV, float duration, EaseType easeType)
{
var startFOV = GameCamera.fieldOfView;
var newFOV = startFOV;
var t = 0f;
while (t <= 1.0f)
{
t += DeltaTime / duration;
newFOV = Utils.EaseFromTo(startFOV, finalFOV, t, easeType);
GameCamera.fieldOfView = newFOV;
_transform.localPosition = VectorHVD(
Vector3H(_transform.localPosition),
Vector3V(_transform.localPosition),
GetCameraDistanceForFOV(newFOV, ScreenSizeInWorldCoordinates.y) * _originalCameraDepthSign);
yield return GetYield();
}
_dollyZoomRoutine = null;
if (OnDollyZoomFinished != null)
OnDollyZoomFinished(newFOV);
if (OnUpdateScreenSizeFinished != null)
OnUpdateScreenSizeFinished(ScreenSizeInWorldCoordinates.y * .5f);
}
void SetScreenSize(float newSize)
{
#if UNITY_EDITOR
if (_transform == null)
_transform = transform;
#endif
if (GameCamera.orthographic)
{
newSize = Mathf.Max(newSize, .1f);
GameCamera.orthographicSize = newSize;
}
else
{
if (ZoomWithFOV)
{
var newFieldOfView = 2f * Mathf.Atan(newSize / Mathf.Abs(Vector3D(_transform.localPosition))) * Mathf.Rad2Deg;
GameCamera.fieldOfView = Mathf.Clamp(newFieldOfView, .1f, 179.9f);
}
else
{
_transform.localPosition = VectorHVD(
Vector3H(_transform.localPosition),
Vector3V(_transform.localPosition),
(newSize / Mathf.Tan(GameCamera.fieldOfView * 0.5f * Mathf.Deg2Rad)) * _originalCameraDepthSign);
}
}
ScreenSizeInWorldCoordinates = new Vector2(newSize * 2f * GameCamera.aspect, newSize * 2f);
OnCameraResize?.Invoke(ScreenSizeInWorldCoordinates);
}
float GetCameraDistanceForFOV(float fov, float cameraHeight)
{
return cameraHeight / (2f * Mathf.Tan(0.5f * fov * Mathf.Deg2Rad));
}
#endregion
#region Extension Interfaces
public void AddPreMover(IPreMover mover)
{
_preMovers.Add(mover);
}
public void RemovePreMover(IPreMover mover)
{
_preMovers.Remove(mover);
}
public void SortPreMovers()
{
_preMovers = _preMovers.OrderBy(a => a.PrMOrder).ToList();
}
public void AddPositionDeltaChanger(IPositionDeltaChanger changer)
{
_positionDeltaChangers.Add(changer);
}
public void RemovePositionDeltaChanger(IPositionDeltaChanger changer)
{
_positionDeltaChangers.Remove(changer);
}
public void SortPositionDeltaChangers()
{
_positionDeltaChangers = _positionDeltaChangers.OrderBy(a => a.PDCOrder).ToList();
}
public void AddPositionOverrider(IPositionOverrider overrider)
{
_positionOverriders.Add(overrider);
}
public void RemovePositionOverrider(IPositionOverrider overrider)
{
_positionOverriders.Remove(overrider);
}
public void SortPositionOverriders()
{
_positionOverriders = _positionOverriders.OrderBy(a => a.POOrder).ToList();
}
public void AddSizeDeltaChanger(ISizeDeltaChanger changer)
{
_sizeDeltaChangers.Add(changer);
}
public void RemoveSizeDeltaChanger(ISizeDeltaChanger changer)
{
_sizeDeltaChangers.Remove(changer);
}
public void SortSizeDeltaChangers()
{
_sizeDeltaChangers = _sizeDeltaChangers.OrderBy(a => a.SDCOrder).ToList();
}
public void AddSizeOverrider(ISizeOverrider overrider)
{
_sizeOverriders.Add(overrider);
}
public void RemoveSizeOverrider(ISizeOverrider overrider)
{
_sizeOverriders.Remove(overrider);
}
public void SortSizeOverriders()
{
_sizeOverriders = _sizeOverriders.OrderBy(a => a.SOOrder).ToList();
}
public void AddPostMover(IPostMover mover)
{
_postMovers.Add(mover);
}
public void RemovePostMover(IPostMover mover)
{
_postMovers.Remove(mover);
}
public void SortPostMovers()
{
_postMovers = _postMovers.OrderBy(a => a.PMOrder).ToList();
}
#endregion
#region ISerializationCallbackReceiver implementation
public void OnBeforeSerialize()
{
}
public void OnAfterDeserialize()
{
ResetAxisFunctions();
}
#endregion
#if UNITY_EDITOR
int _drawGizmosCounter;
void OnDrawGizmos()
{
// HACK to prevent Unity bug on startup: http://forum.unity3d.com/threads/screen-position-out-of-view-frustum.9918/
_drawGizmosCounter++;
if (_drawGizmosCounter < 5 && UnityEditor.EditorApplication.timeSinceStartup < 60f)
return;
if (Vector3H == null)
ResetAxisFunctions();
var gameCamera = GetComponent<Camera>();
// Don't draw gizmos on other cameras
if (Camera.current != gameCamera &&
((UnityEditor.SceneView.lastActiveSceneView != null && Camera.current != UnityEditor.SceneView.lastActiveSceneView.camera) ||
(UnityEditor.SceneView.lastActiveSceneView == null)))
return;
var cameraDimensions = Utils.GetScreenSizeInWorldCoords(gameCamera, Mathf.Abs(Vector3D(transform.position)));
float cameraDepthOffset = Vector3D(transform.position) + Mathf.Abs(Vector3D(transform.position)) * Vector3D(transform.forward);
// Targets mid point
Gizmos.color = EditorPrefsX.GetColor(PrefsData.TargetsMidPointColorKey, PrefsData.TargetsMidPointColorValue);
var targetsMidPoint = GetTargetsWeightedMidPoint(ref CameraTargets);
targetsMidPoint = VectorHVD(Vector3H(targetsMidPoint), Vector3V(targetsMidPoint), cameraDepthOffset);
Gizmos.DrawWireSphere(targetsMidPoint, .01f * cameraDimensions.y);
// Influences sum
Gizmos.color = EditorPrefsX.GetColor(PrefsData.InfluencesColorKey, PrefsData.InfluencesColorValue);
if (_influencesSum != Vector3.zero)
Utils.DrawArrowForGizmo(targetsMidPoint, _influencesSum, .04f * cameraDimensions.y);
// Overall offset line
Gizmos.color = EditorPrefsX.GetColor(PrefsData.OverallOffsetColorKey, PrefsData.OverallOffsetColorValue);
if (OffsetX != 0 || OffsetY != 0)
{
if(IsRelativeOffset)
Utils.DrawArrowForGizmo(targetsMidPoint, VectorHV((OffsetX * cameraDimensions.x * .5f), (OffsetY * cameraDimensions.y * .5f)), .04f * cameraDimensions.y);
else
Utils.DrawArrowForGizmo(targetsMidPoint, VectorHV(OffsetX, OffsetY), .04f * cameraDimensions.y);
}
// Camera target position
Gizmos.color = EditorPrefsX.GetColor(PrefsData.CamTargetPositionColorKey, PrefsData.CamTargetPositionColorValue);
var cameraTargetPosition = targetsMidPoint + _influencesSum + VectorHV((OffsetX * cameraDimensions.x * .5f), (OffsetY * cameraDimensions.y * .5f));
if(!IsRelativeOffset)
cameraTargetPosition = targetsMidPoint + _influencesSum + VectorHV(OffsetX, OffsetY);
var cameraTargetPos = VectorHVD(Vector3H(cameraTargetPosition), Vector3V(cameraTargetPosition), cameraDepthOffset);
Gizmos.DrawWireSphere(cameraTargetPos, .015f * cameraDimensions.y);
// Camera target position smoothed
if (Application.isPlaying)
{
Gizmos.color = EditorPrefsX.GetColor(PrefsData.CamTargetPositionSmoothedColorKey, PrefsData.CamTargetPositionSmoothedColorValue);
var cameraTargetPosSmoothed = VectorHVD(_cameraTargetHorizontalPositionSmoothed + Vector3H(ParentPosition), _cameraTargetVerticalPositionSmoothed + Vector3V(ParentPosition), cameraDepthOffset);
Gizmos.DrawWireSphere(cameraTargetPosSmoothed, .02f * cameraDimensions.y);
Gizmos.DrawLine(cameraTargetPos, cameraTargetPosSmoothed);
}
// Current camera position
Gizmos.color = EditorPrefsX.GetColor(PrefsData.CurrentCameraPositionColorKey, PrefsData.CurrentCameraPositionColorValue);
var currentCameraPos = VectorHVD(Vector3H(transform.position), Vector3V(transform.position), cameraDepthOffset);
Gizmos.DrawWireSphere(currentCameraPos, .025f * cameraDimensions.y);
}
#endif
}
}