using UnityEngine; namespace ECM2.Examples.Jump { /// /// This example shows how to extend a Character (through composition) implementing a jump ability. /// This is the exact jump available in the Character class before v1.4. /// public class JumpAbility : MonoBehaviour { [Space(15f)] [Tooltip("Is the character able to jump ?")] [SerializeField] private bool _canEverJump; [Tooltip("Can jump while crouching ?")] [SerializeField] private bool _jumpWhileCrouching; [Tooltip("The max number of jumps the Character can perform.")] [SerializeField] private int _jumpMaxCount; [Tooltip("Initial velocity (instantaneous vertical velocity) when jumping.")] [SerializeField] private float _jumpImpulse; [Tooltip("The maximum time (in seconds) to hold the jump. eg: Variable height jump.")] [SerializeField] private float _jumpMaxHoldTime; [Tooltip("How early before hitting the ground you can trigger a jump (in seconds).")] [SerializeField] private float _jumpPreGroundedTime; [Tooltip("How long after leaving the ground you can trigger a jump (in seconds).")] [SerializeField] private float _jumpPostGroundedTime; private Character _character; protected bool _jumpButtonPressed; protected float _jumpButtonHeldDownTime; protected float _jumpHoldTime; protected int _jumpCount; protected bool _isJumping; /// /// Is the character able to jump ? /// public bool canEverJump { get => _canEverJump; set => _canEverJump = value; } /// /// Can jump while crouching ? /// public bool jumpWhileCrouching { get => _jumpWhileCrouching; set => _jumpWhileCrouching = value; } /// /// The max number of jumps the Character can perform. /// public int jumpMaxCount { get => _jumpMaxCount; set => _jumpMaxCount = Mathf.Max(1, value); } /// /// Initial velocity (instantaneous vertical velocity) when jumping. /// public float jumpImpulse { get => _jumpImpulse; set => _jumpImpulse = Mathf.Max(0.0f, value); } /// /// The maximum time (in seconds) to hold the jump. eg: Variable height jump. /// public float jumpMaxHoldTime { get => _jumpMaxHoldTime; set => _jumpMaxHoldTime = Mathf.Max(0.0f, value); } /// /// How early before hitting the ground you can trigger a jump (in seconds). /// public float jumpPreGroundedTime { get => _jumpPreGroundedTime; set => _jumpPreGroundedTime = Mathf.Max(0.0f, value); } /// /// How long after leaving the ground you can trigger a jump (in seconds). /// public float jumpPostGroundedTime { get => _jumpPostGroundedTime; set => _jumpPostGroundedTime = Mathf.Max(0.0f, value); } /// /// True is _jumpButtonPressed is true, false otherwise. /// public bool jumpButtonPressed => _jumpButtonPressed; /// /// This is the time (in seconds) that the player has held the jump button. /// public float jumpButtonHeldDownTime => _jumpButtonHeldDownTime; /// /// Tracks the current number of jumps performed. /// public int jumpCount => _jumpCount; /// /// This is the time (in seconds) that the player has been holding the jump. /// Eg: Variable height jump. /// public float jumpHoldTime => _jumpHoldTime; /// /// Is the Character jumping ? /// public virtual bool IsJumping() { return _isJumping; } /// /// Start a jump. /// Call this from an input event (such as a button 'down' event). /// public void Jump() { _jumpButtonPressed = true; } /// /// Stop the Character from jumping. /// Call this from an input event (such as a button 'up' event). /// public void StopJumping() { // Input state _jumpButtonPressed = false; _jumpButtonHeldDownTime = 0.0f; // Jump holding state _isJumping = false; _jumpHoldTime = 0.0f; } /// /// Returns the current jump count. /// public virtual int GetJumpCount() { return _jumpCount; } /// /// Determines if the Character is able to perform the requested jump. /// public virtual bool CanJump() { // Is character even able to jump ? if (!canEverJump) return false; // Can jump while crouching ? if (_character.IsCrouched() && !jumpWhileCrouching) return false; // Cant jump if no jumps available if (jumpMaxCount == 0 || _jumpCount >= jumpMaxCount) return false; // Is fist jump ? if (_jumpCount == 0) { // On first jump, // can jump if is walking or is falling BUT withing post grounded time bool canJump = _character.IsWalking() || _character.IsFalling() && jumpPostGroundedTime > 0.0f && _character.fallingTime < jumpPostGroundedTime; // Missed post grounded time ? if (_character.IsFalling() && !canJump) { // Missed post grounded time, // can jump if have any 'in-air' jumps but the first jump counts as the in-air jump canJump = jumpMaxCount > 1; if (canJump) _jumpCount++; } return canJump; } // In air jump conditions... return _character.IsFalling(); } /// /// Determines the jump impulse vector. /// protected virtual Vector3 CalcJumpImpulse() { Vector3 worldUp = -_character.GetGravityDirection(); float verticalSpeed = Vector3.Dot(_character.GetVelocity(), worldUp); float actualJumpImpulse = Mathf.Max(verticalSpeed, jumpImpulse); return worldUp * actualJumpImpulse; } /// /// Attempts to perform a requested jump. /// protected virtual void DoJump(float deltaTime) { // Update held down timer if (_jumpButtonPressed) _jumpButtonHeldDownTime += deltaTime; // Wants to jump and not already jumping.. if (_jumpButtonPressed && !IsJumping()) { // If jumpPreGroundedTime is enabled, // allow to jump only if held down time is less than tolerance if (jumpPreGroundedTime > 0.0f) { bool canJump = _jumpButtonHeldDownTime <= jumpPreGroundedTime; if (!canJump) return; } // Can perform the requested jump ? if (CanJump()) { // Jump! _character.SetMovementMode(Character.MovementMode.Falling); _character.PauseGroundConstraint(); _character.LaunchCharacter(CalcJumpImpulse(), true); _jumpCount++; _isJumping = true; } } } /// /// Handle jumping state. /// Eg: check input, perform jump hold (jumpMaxHoldTime > 0), etc. /// protected virtual void Jumping(float deltaTime) { // Is character allowed to jump ? if (!canEverJump) { // If not allowed but was jumping, stop jump if (IsJumping()) StopJumping(); return; } // Check jump input state and attempts to do the requested jump DoJump(deltaTime); // Perform jump hold, applies an opposite gravity force proportional to _jumpHoldTime. if (IsJumping() && _jumpButtonPressed && jumpMaxHoldTime > 0.0f && _jumpHoldTime < jumpMaxHoldTime) { Vector3 actualGravity = _character.GetGravityVector(); float actualGravityMagnitude = actualGravity.magnitude; Vector3 actualGravityDirection = actualGravityMagnitude > 0.0f ? actualGravity / actualGravityMagnitude : Vector3.zero; float jumpProgress = Mathf.InverseLerp(0.0f, jumpMaxHoldTime, _jumpHoldTime); float proportionalForce = Mathf.LerpUnclamped(actualGravityMagnitude, 0.0f, jumpProgress); Vector3 proportionalJumpForce = -actualGravityDirection * proportionalForce; _character.AddForce(proportionalJumpForce); _jumpHoldTime += deltaTime; } } protected virtual void OnMovementModeChanged(Character.MovementMode prevMovementMode, int prevCustomMode) { if (_character.IsWalking()) _jumpCount = 0; else if (_character.IsFlying() || _character.IsSwimming()) StopJumping(); } /// /// If overriden, base method MUST be called. /// protected virtual void Reset() { _canEverJump = true; _jumpWhileCrouching = true; _jumpMaxCount = 1; _jumpImpulse = 5.0f; } /// /// If overriden, base method MUST be called. /// protected virtual void OnValidate() { jumpMaxCount = _jumpMaxCount; jumpImpulse = _jumpImpulse; jumpMaxHoldTime = _jumpMaxHoldTime; jumpPreGroundedTime = _jumpPreGroundedTime; jumpPostGroundedTime = _jumpPostGroundedTime; } /// /// If overriden, base method MUST be called. /// protected virtual void Awake() { _character = GetComponent(); } /// /// If overriden, base method MUST be called. /// protected virtual void OnEnable() { _character.MovementModeChanged += OnMovementModeChanged; _character.BeforeSimulationUpdated += Jumping; } /// /// If overriden, base method MUST be called. /// protected virtual void OnDisable() { _character.BeforeSimulationUpdated -= Jumping; _character.MovementModeChanged -= OnMovementModeChanged; } /// /// If overriden, base method MUST be called. /// protected virtual void Start() { // Disable Character built-in jump _character.canEverJump = false; } } }