using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using System.Linq; namespace Com.LuisPedroFonseca.ProCamera2D { /// /// Core class of the plugin. Everything starts and happens through here. /// All extensions and triggers have a reference to an instance of this class. /// #if UNITY_5_3_OR_NEWER [HelpURLAttribute("http://www.procamera2d.com/user-guide/core/")] #endif [RequireComponent(typeof(Camera))] public class ProCamera2D : MonoBehaviour, ISerializationCallbackReceiver { public const string Title = "Pro Camera 2D"; public static readonly Version Version = new Version("2.8.1"); #region Inspector Variables public List CameraTargets = new List(); 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 /// Get ProCamera2D's static instance public static ProCamera2D Instance { get { if (Equals(_instance, null)) { _instance = FindObjectOfType(typeof(ProCamera2D)) as ProCamera2D; if (Equals(_instance, null)) throw new UnityException("ProCamera2D does not exist."); } return _instance; } } static ProCamera2D _instance; /// Property to know if there's a ProCamera2D present public static bool Exists { get { return _instance != null; } } /// Is the camera moving? public bool IsMoving { get { return Vector3H(_transform.localPosition) != Vector3H(_previousCameraPosition) || Vector3V(_transform.localPosition) != Vector3V(_previousCameraPosition); } } /// Update ProCamera2D's camera rect public Rect Rect { get { return GameCamera.rect; } set { GameCamera.rect = value; ProCamera2DParallax parallax = GetComponentInChildren(); 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 PreMoveUpdate; public Action PostMoveUpdate; public Action OnCameraResize; public Action OnUpdateScreenSizeFinished; public Action 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 Vector3H; Func Vector3V; Func Vector3D; Func VectorHV; Func VectorHVD; Coroutine _updateScreenSizeCoroutine; Coroutine _dollyZoomRoutine; List _influences = new List(); float _originalCameraDepthSign; float _previousCameraTargetHorizontalPositionSmoothed; float _previousCameraTargetVerticalPositionSmoothed; int _previousScreenWidth; int _previousScreenHeight; Vector3 _previousCameraPosition; WaitForFixedUpdate _waitForFixedUpdate = new WaitForFixedUpdate(); Transform _transform; List _preMovers = new List(); List _positionDeltaChangers = new List(); List _positionOverriders = new List(); List _sizeDeltaChangers = new List(); List _sizeOverriders = new List(); List _postMovers = new List(); #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(); 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 /// Returns the current global offset on the horizontal axis public float GetOffsetX() { return IsRelativeOffset ? OffsetX * ScreenSizeInWorldCoordinates.x * .5f : OffsetX; } /// Returns the current global offset on the vertical axis public float GetOffsetY() { return IsRelativeOffset ? OffsetY * ScreenSizeInWorldCoordinates.y * .5f : OffsetY; } /// Apply the given influence to the camera during this frame. /// The vector representing the influence to be applied public void ApplyInfluence(Vector2 influence) { if (Time.deltaTime < .0001f || float.IsNaN(influence.x) || float.IsNaN(influence.y)) return; _influences.Add(VectorHV(influence.x, influence.y)); } /// Apply the given influences to the camera during the corresponding durations. /// An array of the vectors representing the influences to be applied /// An array with the durations of the influences to be applied public Coroutine ApplyInfluencesTimed(Vector2[] influences, float[] durations) { return StartCoroutine(ApplyInfluencesTimedRoutine(influences, durations)); } /// Add a target for the camera to follow. /// The Transform of the target /// The influence this target horizontal position should have when calculating the average position of all the targets /// The influence this target vertical position should have when calculating the average position of all the targets /// The time it takes for this target to reach it's influence. Use for a more progressive transition. /// A vector that offsets the target position that the camera will follow 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; } /// Add multiple targets for the camera to follow. /// An array or list with the new targets /// The influence the targets horizontal position should have when calculating the average position of all the targets /// The influence the targets vertical position should have when calculating the average position of all the targets /// The time it takes for the targets to reach their influence. Use for a more progressive transition. /// A vector that offsets the target position that the camera will follow public void AddCameraTargets(IList 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); } } /// Add multiple targets for the camera to follow. /// An array or list with the new targets public void AddCameraTargets(IList cameraTargets) { CameraTargets.AddRange(cameraTargets); } /// Gets the corresponding CameraTarget from an object's transform. /// The Transform of the target 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; } /// Remove a target from the camera. /// The Transform of the target /// The time it takes for this target to reach a zero influence. Use for a more progressive transition. 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]); } } } /// Removes all targets from the camera. /// The time it takes for all targets to reach a zero influence. Use for a more progressive transition. 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)); } } } /// Adjusts a target influence /// The CameraTarget of the target /// The influence this target horizontal position should have when calculating the average position of all the targets /// The influence this target vertical position should have when calculating the average position of all the targets /// The time it takes for this target to reach it's influence. Don't use a duration if calling every frame. 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; } } /// Adjusts a target influence, finding it first by its transform. /// The Transform of the target /// The influence this target horizontal position should have when calculating the average position of all the targets /// The influence this target vertical position should have when calculating the average position of all the targets /// The time it takes for this target to reach it's influence. Don't use a duration if calling every frame. 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); } /// Translates the camera by the specified amount while maintaining internal values /// The camera will be offset by this amount 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; } /// Moves the camera instantly to the supplied position /// The final position of the camera public void MoveCameraInstantlyToPosition(Vector2 cameraPos) { _transform.localPosition = VectorHVD(cameraPos.x, cameraPos.y, Vector3D(_transform.localPosition)); ResetMovement(); } /// 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 /// If true, the camera will move to the "final" targets position /// If true, resets the camera size to the start value /// If true, resets all active extensions to their start values public void Reset(bool centerOnTargets = true, bool resetSize = true, bool resetExtensions = true) { if (centerOnTargets) CenterOnTargets(); else ResetMovement(); if(resetSize) ResetSize(); if (resetExtensions) ResetExtensions(); } /// /// Cancels any existing camera movement easing. /// Also check CenterOnTargets and MoveCameraInstantlyToPosition. /// public void ResetMovement() { CameraTargetPosition = _transform.localPosition; _cameraTargetHorizontalPositionSmoothed = Vector3H(CameraTargetPosition); _cameraTargetVerticalPositionSmoothed = Vector3V(CameraTargetPosition); _previousCameraTargetHorizontalPositionSmoothed = _cameraTargetHorizontalPositionSmoothed; _previousCameraTargetVerticalPositionSmoothed = _cameraTargetVerticalPositionSmoothed; TargetsMidPoint = CameraTargetPosition; PreviousTargetsMidPoint = TargetsMidPoint; } /// /// Resets the camera size to the start value. /// public void ResetSize() { SetScreenSize(StartScreenSizeInWorldCoordinates.y / 2); } /// /// Resets the start camera size that is used for some calculations. /// public void ResetStartSize(Vector2 newSize = default(Vector2)) { if(newSize != default(Vector2)) StartScreenSizeInWorldCoordinates = newSize; else StartScreenSizeInWorldCoordinates = Utils.GetScreenSizeInWorldCoords(GameCamera, Mathf.Abs(Vector3D(_transform.localPosition))); } /// /// Resets all active extensions to their start values. /// Notice you can manually reset each extension using the "OnReset" method. /// public void ResetExtensions() { if (OnReset != null) OnReset(); } /// Instantly moves the camera to the targets' position. public void CenterOnTargets() { var targetsMidPoint = GetTargetsWeightedMidPoint(ref CameraTargets); var finalPos = new Vector2(Vector3H(targetsMidPoint), Vector3V(targetsMidPoint)); finalPos += new Vector2(GetOffsetX(), GetOffsetY()); MoveCameraInstantlyToPosition(finalPos); } /// Resize the camera to the supplied size /// Half of the wanted size in world units /// How long it should take to reach the provided size. Use 0 if instant or calling repeatedly /// The easing method to apply. Only used when the duration is bigger than 0 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); } /// Recalculates the camera size in world coordinates. Call only if you change the camera size manually outside of ProCamera2D. public void CalculateScreenSize() { GameCamera.ResetAspect(); ScreenSizeInWorldCoordinates = Utils.GetScreenSizeInWorldCoords(GameCamera, Mathf.Abs(Vector3D(_transform.localPosition))); _previousScreenWidth = Screen.width; _previousScreenHeight = Screen.height; } /// Zoom in or out the camera by the supplied amount /// The amount to zoom in world units /// How long it should take to reach the new zoom. Use 0 if instant or calling repeatedly /// The easing method to apply. Only used when the duration is bigger than 0 public void Zoom(float zoomAmount, float duration = 0f, EaseType easeType = EaseType.EaseInOut) { UpdateScreenSize(ScreenSizeInWorldCoordinates.y * .5f + zoomAmount, duration, easeType); } /// Creates a dolly zoom effect, also known as the Hitchcock effect /// The final field of view /// The duration of the effect /// The ease type of the transition 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)); } } /// /// 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. /// /// The time in seconds it took to complete the last frame 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); } /// For internal use public 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 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 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(); // 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 } }