/// --------------------------------------------- /// Ultimate Character Controller /// Copyright (c) Opsive. All Rights Reserved. /// https://www.opsive.com /// --------------------------------------------- namespace Opsive.UltimateCharacterController.ThirdPersonController.Character.Abilities { using Opsive.Shared.Events; using Opsive.UltimateCharacterController.Character.Abilities; using Opsive.UltimateCharacterController.Character.MovementTypes; using Opsive.UltimateCharacterController.Utility; using UnityEngine; /// /// Follows a path specified by the 2.5D movement type. Ensures the character stays at a consistant horizontal offset. /// [DefaultStartType(AbilityStartType.Manual)] public class FollowPseudo3DPath : Ability { public override bool IsConcurrent { get { return true; } } private MovementTypes.Pseudo3D m_Pseudo3DMovementType; private int m_PathIndex; private float m_Offset; /// /// Initialize the default values. /// public override void Awake() { base.Awake(); EventHandler.RegisterEvent(m_GameObject, "OnCharacterChangeMovementType", OnCharacterChangeMovementType); } /// /// The specified movement type has been activated or deactivated. /// /// The movement type that has been activated or deactivated. /// Is the movement type active? private void OnCharacterChangeMovementType(MovementType movementType, bool active) { if (!(movementType is MovementTypes.Pseudo3D)) { return; } if (!IsActive && active) { // If the movement type is active then the ability should start if a path exists. var pseudo3DMovementType = movementType as MovementTypes.Pseudo3D; if (pseudo3DMovementType.Path != null) { StartAbility(); return; } } else if (IsActive && !active) { // If the movement type is no longer active then the ability should stop. StopAbility(); } } /// /// Called when the ablity is tried to be started. If false is returned then the ability will not be started. /// /// True if the ability can be started. public override bool CanStartAbility() { if (!base.CanStartAbility()) { return false; } return (m_CharacterLocomotion.ActiveMovementType is MovementTypes.Pseudo3D) && (m_CharacterLocomotion.ActiveMovementType as MovementTypes.Pseudo3D).Path != null; } /// /// The ability has started. /// protected override void AbilityStarted() { base.AbilityStarted(); m_Pseudo3DMovementType = m_CharacterLocomotion.ActiveMovementType as MovementTypes.Pseudo3D; // Store the horizontal offset between the character and the path. This offset will ensure the character stays at a constant horizontal position. var pathRotation = Quaternion.LookRotation(m_Pseudo3DMovementType.Path.GetTangent(m_Transform.position, ref m_PathIndex)); var closestPoint = m_Pseudo3DMovementType.Path.GetClosestPoint(m_Transform.position, ref m_PathIndex); m_Offset = MathUtility.InverseTransformPoint(m_Transform.position, pathRotation, closestPoint).x; } /// /// Stop the ability if the path no longer exists. /// public override void Update() { if (m_Pseudo3DMovementType.Path == null) { StopAbility(); } } /// /// Update the controller's position values. /// public override void UpdatePosition() { // Retireve the closest point and tangent of the path at the target position. This position will be used when determining if the character needs to change // horizontal offsets. var targetPosition = m_Transform.position + m_CharacterLocomotion.MoveDirection; var closestPoint = m_Pseudo3DMovementType.Path.GetClosestPoint(targetPosition, ref m_PathIndex); var pathRotation = Quaternion.LookRotation(m_Pseudo3DMovementType.Path.GetTangent(targetPosition, ref m_PathIndex)); // Update the offset if the character can move along the relative x axis. The depth term is used in relation to the camera's depth axis. if (m_Pseudo3DMovementType.AllowDepthMovement && m_CharacterLocomotion.RawInputVector.y != 0) { m_Offset -= MathUtility.InverseTransformDirection(m_CharacterLocomotion.MotorThrottle, pathRotation).x; } // Update the target position based on the difference between the current offset and the stored offset. var offset = MathUtility.InverseTransformPoint(targetPosition, pathRotation, closestPoint).x; targetPosition = MathUtility.TransformPoint(targetPosition, pathRotation, Vector3.right * (offset - m_Offset)); m_CharacterLocomotion.MoveDirection = targetPosition - m_Transform.position; } /// /// The character has been destroyed. /// public override void OnDestroy() { base.OnDestroy(); EventHandler.UnregisterEvent(m_GameObject, "OnCharacterChangeMovementType", OnCharacterChangeMovementType); } } }