#if CINEMACHINE_EXPERIMENTAL_VCAM using UnityEngine; using System; using System.Linq; namespace Cinemachine { /// /// /// NOTE: THIS CLASS IS EXPERIMENTAL, AND NOT FOR PUBLIC USE /// /// Lighter-weight version of the CinemachineVirtualCamera. /// /// [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)] [DisallowMultipleComponent] [ExecuteAlways] [AddComponentMenu("Cinemachine/CinemachineNewVirtualCamera")] public class CinemachineNewVirtualCamera : CinemachineVirtualCameraBase { /// Object for the camera children to look at (the aim target) [Tooltip("Object for the camera children to look at (the aim target).")] [NoSaveDuringPlay] [VcamTargetProperty] public Transform m_LookAt = null; /// Object for the camera children wants to move with (the body target) [Tooltip("Object for the camera children wants to move with (the body target).")] [NoSaveDuringPlay] [VcamTargetProperty] public Transform m_Follow = null; /// Specifies the LensSettings of this Virtual Camera. /// These settings will be transferred to the Unity camera when the vcam is live. [Tooltip("Specifies the lens properties of this Virtual Camera. This generally mirrors the " + "Unity Camera's lens settings, and will be used to drive the Unity camera when the vcam is active.")] public LensSettings m_Lens = LensSettings.Default; /// Collection of parameters that influence how this virtual camera transitions from /// other virtual cameras public TransitionParams m_Transitions; /// API for the editor, to make the dragging of position handles behave better. public bool UserIsDragging; /// Updates the child rig cache protected override void OnEnable() { base.OnEnable(); InvalidateComponentCache(); } void Reset() { DestroyComponents(); } /// Validates the settings avter inspector edit protected override void OnValidate() { base.OnValidate(); m_Lens.Validate(); } /// The camera state, which will be a blend of the child rig states override public CameraState State { get { return m_State; } } /// The camera state, which will be a blend of the child rig states protected CameraState m_State = CameraState.Default; /// Get the current LookAt target. Returns parent's LookAt if parent /// is non-null and no specific LookAt defined for this camera override public Transform LookAt { get { return ResolveLookAt(m_LookAt); } set { m_LookAt = value; } } /// Get the current Follow target. Returns parent's Follow if parent /// is non-null and no specific Follow defined for this camera override public Transform Follow { get { return ResolveFollow(m_Follow); } set { m_Follow = value; } } /// This is called to notify the vcam that a target got warped, /// so that the vcam can update its internal state to make the camera /// also warp seamlessy. /// The object that was warped /// The amount the target's position changed public override void OnTargetObjectWarped(Transform target, Vector3 positionDelta) { if (target == Follow) { transform.position += positionDelta; m_State.RawPosition += positionDelta; } UpdateComponentCache(); for (int i = 0; i < m_Components.Length; ++i) { if (m_Components[i] != null) m_Components[i].OnTargetObjectWarped(target, positionDelta); } base.OnTargetObjectWarped(target, positionDelta); } /// /// Force the virtual camera to assume a given position and orientation /// /// Worldspace pposition to take /// Worldspace orientation to take public override void ForceCameraPosition(Vector3 pos, Quaternion rot) { PreviousStateIsValid = false; transform.position = pos; transform.rotation = rot; m_State.RawPosition = pos; m_State.RawOrientation = rot; UpdateComponentCache(); for (int i = 0; i < m_Components.Length; ++i) if (m_Components[i] != null) m_Components[i].ForceCameraPosition(pos, rot); base.ForceCameraPosition(pos, rot); } /// /// Query components and extensions for the maximum damping time. /// /// Highest damping setting in this vcam public override float GetMaxDampTime() { float maxDamp = base.GetMaxDampTime(); UpdateComponentCache(); for (int i = 0; i < m_Components.Length; ++i) if (m_Components[i] != null) maxDamp = Mathf.Max(maxDamp, m_Components[i].GetMaxDampTime()); return maxDamp; } /// If we are transitioning from another FreeLook, grab the axis values from it. /// The camera being deactivated. May be null. /// Default world Up, set by the CinemachineBrain /// Delta time for time-based effects (ignore if less than or equal to 0) public override void OnTransitionFromCamera( ICinemachineCamera fromCam, Vector3 worldUp, float deltaTime) { base.OnTransitionFromCamera(fromCam, worldUp, deltaTime); InvokeOnTransitionInExtensions(fromCam, worldUp, deltaTime); bool forceUpdate = false; if (m_Transitions.m_InheritPosition && fromCam != null && !CinemachineCore.Instance.IsLiveInBlend(this)) { ForceCameraPosition(fromCam.State.FinalPosition, fromCam.State.FinalOrientation); } UpdateComponentCache(); for (int i = 0; i < m_Components.Length; ++i) { if (m_Components[i] != null && m_Components[i].OnTransitionFromCamera( fromCam, worldUp, deltaTime, ref m_Transitions)) forceUpdate = true; } if (forceUpdate) { InternalUpdateCameraState(worldUp, deltaTime); InternalUpdateCameraState(worldUp, deltaTime); } else UpdateCameraState(worldUp, deltaTime); if (m_Transitions.m_OnCameraLive != null) m_Transitions.m_OnCameraLive.Invoke(this, fromCam); } /// Internal use only. Called by CinemachineCore at designated update time /// so the vcam can position itself and track its targets. All 3 child rigs are updated, /// and a blend calculated, depending on the value of the Y axis. /// Default world Up, set by the CinemachineBrain /// Delta time for time-based effects (ignore if less than 0) override public void InternalUpdateCameraState(Vector3 worldUp, float deltaTime) { UpdateTargetCache(); FollowTargetAttachment = 1; LookAtTargetAttachment = 1; // Initialize the camera state, in case the game object got moved in the editor m_State = PullStateFromVirtualCamera(worldUp, ref m_Lens); // Do our stuff SetReferenceLookAtTargetInState(ref m_State); InvokeComponentPipeline(ref m_State, worldUp, deltaTime); ApplyPositionBlendMethod(ref m_State, m_Transitions.m_BlendHint); // Push the raw position back to the game object's transform, so it // moves along with the camera. if (!UserIsDragging) { if (Follow != null) transform.position = State.RawPosition; if (LookAt != null) transform.rotation = State.RawOrientation; } // Signal that it's all done InvokePostPipelineStageCallback(this, CinemachineCore.Stage.Finalize, ref m_State, deltaTime); PreviousStateIsValid = true; } /// /// Returns true, when the vcam has extensions or components that require input. /// internal override bool RequiresUserInput() { return base.RequiresUserInput() || m_Components != null && m_Components.Any(t => t != null && t.RequiresUserInput); } private Transform mCachedLookAtTarget; private CinemachineVirtualCameraBase mCachedLookAtTargetVcam; /// Set the state's refeenceLookAt target to our lookAt, with some smarts /// in case our LookAt points to a vcam protected void SetReferenceLookAtTargetInState(ref CameraState state) { Transform lookAtTarget = LookAt; if (lookAtTarget != mCachedLookAtTarget) { mCachedLookAtTarget = lookAtTarget; mCachedLookAtTargetVcam = null; if (lookAtTarget != null) mCachedLookAtTargetVcam = lookAtTarget.GetComponent(); } if (lookAtTarget != null) { if (mCachedLookAtTargetVcam != null) state.ReferenceLookAt = mCachedLookAtTargetVcam.State.FinalPosition; else state.ReferenceLookAt = TargetPositionCache.GetTargetPosition(lookAtTarget); } } protected CameraState InvokeComponentPipeline( ref CameraState state, Vector3 worldUp, float deltaTime) { UpdateComponentCache(); // Extensions first InvokePrePipelineMutateCameraStateCallback(this, ref state, deltaTime); // Apply the component pipeline for (CinemachineCore.Stage stage = CinemachineCore.Stage.Body; stage <= CinemachineCore.Stage.Finalize; ++stage) { var c = m_Components[(int)stage]; if (c != null) c.PrePipelineMutateCameraState(ref state, deltaTime); } CinemachineComponentBase postAimBody = null; for (CinemachineCore.Stage stage = CinemachineCore.Stage.Body; stage <= CinemachineCore.Stage.Finalize; ++stage) { var c = m_Components[(int)stage]; if (c != null) { if (stage == CinemachineCore.Stage.Body && c.BodyAppliesAfterAim) { postAimBody = c; continue; // do the body stage of the pipeline after Aim } c.MutateCameraState(ref state, deltaTime); } InvokePostPipelineStageCallback(this, stage, ref state, deltaTime); if (stage == CinemachineCore.Stage.Aim) { if (c == null) state.BlendHint |= CameraState.BlendHintValue.IgnoreLookAtTarget; // no aim // If we have saved a Body for after Aim, do it now if (postAimBody != null) { postAimBody.MutateCameraState(ref state, deltaTime); InvokePostPipelineStageCallback(this, CinemachineCore.Stage.Body, ref state, deltaTime); } } } return state; } // Component Cache - serialized only for copy/paste [SerializeField, HideInInspector, NoSaveDuringPlay] CinemachineComponentBase[] m_Components; /// For inspector internal CinemachineComponentBase[] ComponentCache { get { UpdateComponentCache(); return m_Components; } } /// Call this when CinemachineCompponentBase compponents are added /// or removed. If you don't call this, you may get null reference errors. public void InvalidateComponentCache() { m_Components = null; } /// Bring the component cache up to date if needed protected void UpdateComponentCache() { #if UNITY_EDITOR // Special case: if we have serialized in with some other game object's // components, then we have just been pasted so we should clone them for (int i = 0; m_Components != null && i < m_Components.Length; ++i) { if (m_Components[i] != null && m_Components[i].gameObject != gameObject) { var copyFrom = m_Components; DestroyComponents(); CopyComponents(copyFrom); break; } } #endif if (m_Components != null && m_Components.Length == (int)CinemachineCore.Stage.Finalize + 1) return; // up to date m_Components = new CinemachineComponentBase[(int)CinemachineCore.Stage.Finalize + 1]; var existing = GetComponents(); for (int i = 0; existing != null && i < existing.Length; ++i) m_Components[(int)existing[i].Stage] = existing[i]; for (int i = 0; i < m_Components.Length; ++i) { if (m_Components[i] != null) { if (CinemachineCore.sShowHiddenObjects) m_Components[i].hideFlags &= ~HideFlags.HideInInspector; else m_Components[i].hideFlags |= HideFlags.HideInInspector; } } OnComponentCacheUpdated(); } /// Notification that the component cache has just been update, /// in case a subclass needs to do something extra protected virtual void OnComponentCacheUpdated() {} /// Destroy all the CinmachineComponentBase components protected void DestroyComponents() { var existing = GetComponents(); for (int i = 0; i < existing.Length; ++i) { #if UNITY_EDITOR UnityEditor.Undo.DestroyObjectImmediate(existing[i]); #else UnityEngine.Object.Destroy(existing[i]); #endif } InvalidateComponentCache(); } #if UNITY_EDITOR // This gets called when user pastes component values void CopyComponents(CinemachineComponentBase[] copyFrom) { foreach (CinemachineComponentBase c in copyFrom) { if (c != null) { Type type = c.GetType(); var copy = UnityEditor.Undo.AddComponent(gameObject, type); UnityEditor.Undo.RecordObject(copy, "copying pipeline"); System.Reflection.BindingFlags bindingAttr = System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance; System.Reflection.FieldInfo[] fields = type.GetFields(bindingAttr); for (int i = 0; i < fields.Length; ++i) if (!fields[i].IsStatic) fields[i].SetValue(copy, fields[i].GetValue(c)); } } } #endif /// Legacy support for an old API. GML todo: deprecate these methods /// Get the component set for a specific stage. /// The stage for which we want the component /// The Cinemachine component for that stage, or null if not defined public CinemachineComponentBase GetCinemachineComponent(CinemachineCore.Stage stage) { var cache = ComponentCache; var i = (int)stage; return i >= 0 && i < cache.Length ? cache[i] : null; } /// Get an existing component of a specific type from the cinemachine pipeline. public T GetCinemachineComponent() where T : CinemachineComponentBase { var components = ComponentCache; foreach (var c in components) if (c is T) return c as T; return null; } /// Add a component to the cinemachine pipeline. public T AddCinemachineComponent() where T : CinemachineComponentBase { var components = ComponentCache; T c = gameObject.AddComponent(); var oldC = components[(int)c.Stage]; if (oldC != null) { oldC.enabled = false; RuntimeUtility.DestroyObject(oldC); } InvalidateComponentCache(); return c; } /// Remove a component from the cinemachine pipeline. public void DestroyCinemachineComponent() where T : CinemachineComponentBase { var components = ComponentCache; foreach (var c in components) { if (c is T) { c.enabled = false; RuntimeUtility.DestroyObject(c); InvalidateComponentCache(); return; } } } // This prevents the sensor size from dirtying the scene in the event of aspect ratio change internal override void OnBeforeSerialize() { if (!m_Lens.IsPhysicalCamera) m_Lens.SensorSize = Vector2.one; } } } #endif