using System; using UnityEngine; using Framework.Input; using Framework.Layers; namespace Framework.Actors.Shooter { /// Move that can be performed by the player public class PlayerMove : ShooterMove { /// Called when the actor has started the move public override void OnStarted() { base.OnStarted(); SetPresenterState(); } /// /// Switches the presenter to the animation state appropriate for this move /// /// /// This is called when the move becomes active. It is typically used to trigger /// the animation related to the move (or change exposed variables in Mecanim /// to switch to the correct animation state). /// protected virtual void SetPresenterState() {} /// Injects the instance's dependencies /// /// Input selector the instance uses to determine the active input source /// /// /// This method is called automatically by the services framework. Any parameters /// it requires will be filled out by looking up the respective services and creating /// them as needed. /// protected void Inject(IInputManager inputManager) { this.inputManager = inputManager; } /// Called once when the movement is assigned to an actor /// Actor the movement has been assigned to protected override void Awake(ActorController actor) { base.Awake(actor); this.moveRepository = actor.GetComponent(); this.abilities = ActorController.Abilities; } /// Abilities the actor possesses protected Abilities Abilities { get { return this.abilities; } } /// Input manager from which the input state can be queried protected IInputManager InputManager { get { return this.inputManager; } } /// Adds a jump impulse to the actor /// Apex height the actor should achieve from the jump protected void GiveVerticalJumpImpulse(float apexHeight) { Vector3 groundVelocity = ActorController.GroundChecker.GetMostRecentGroundVelocity(); float gravityStrength = Physics.gravity.magnitude; if(Rigidbody != null) { float jumpImpulse = PhysicsHelper.GetJumpOffImpulse( gravityStrength, apexHeight, Time.fixedDeltaTime ); float relativeImpulse = groundVelocity.y + jumpImpulse; relativeImpulse -= Rigidbody.velocity.y; Rigidbody.AddForce( new Vector3(0.0f, relativeImpulse * Rigidbody.mass, 0.0f), ForceMode.Impulse ); } else if(ActorPhysics != null) { gravityStrength *= ActorPhysics.GravityScale; float jumpImpulse = PhysicsHelper.GetJumpOffImpulse( gravityStrength, apexHeight, Time.fixedDeltaTime ); /* Debug.Log( "Gravity strength: " + gravityStrength + "| apex height: " + apexHeight + "| impulse: " + jumpImpulse ); */ float relativeImpulse = groundVelocity.y + jumpImpulse; relativeImpulse -= ActorPhysics.Velocity.y; ActorPhysics.QueueImpulse( new Vector3(0.0f, relativeImpulse * ActorPhysics.Mass, 0.0f) ); } else { Debug.LogError( "No Rigidbody and no ActorPhysics component present, cannot give jump impulse." ); } } /// Sets the vertical velocity of the actor to zero protected void ClearVerticalVelocity() { if(Rigidbody != null) { Vector3 velocity = Rigidbody.velocity; velocity.y = 0.0f; Rigidbody.velocity = velocity; } else if(ActorPhysics != null) { Vector3 velocity = ActorPhysics.Velocity; velocity.y = 0.0f; ActorPhysics.Velocity = velocity; } else { Debug.LogError( "No Rigidbody and no ActorPhysics component present, cannot clear vertical velocity." ); } } /// Changes the character's velocity to the specified velocity /// Velocity the character will assume protected void ChangeVelocity(float targetVelocity) { if(Rigidbody != null) { float currentVelocity = Rigidbody.velocity.x; float impulse = (targetVelocity - currentVelocity) * Rigidbody.mass; Rigidbody.AddForce(new Vector3(impulse, 0.0f, 0.0f), ForceMode.Impulse); } else if(ActorPhysics != null) { float currentVelocity = ActorPhysics.Velocity.x; float impulse = (targetVelocity - currentVelocity) * ActorPhysics.Mass; ActorPhysics.QueueImpulse(new Vector3(impulse, 0.0f, 0.0f)); } else { Debug.LogError( "No Rigidbody and no ActorPhysics component present, cannot set velocity." ); } } /// /// /// /// Direction that will be translated /// Forward direction in the target coordinate space /// private static Vector2 translateToLocalOrientation( Vector3 direction, Vector3 right ) { return new Vector2( (direction.x * right.x) + (direction.x * right.z), (direction.x * right.z) - (direction.y * right.x) ); } /// /// /// /// /// /// private static Vector3 rotateAroundY(Vector3 direction, float angle) { float sine = Mathf.Sin(angle); float cosine = Mathf.Cos(angle); return new Vector3( (cosine * direction.x) - (sine * direction.z), direction.y, (sine * direction.x) + (cosine * direction.z) ); } /// Accelerates the character to the specified target velocity /// /// Forward/backward velocity the character should reach /// /// Sideways velocity the character should reach /// Acceleration in units per second squared protected void AccelerateToVelocity( float targetRunningVelocity, float targetStrafingVelocity, float acceleration ) { float deltaTime = Time.fixedDeltaTime; // Get angle of direction the actor is facing. The more intuitive way would be // to simply use the forward vector (lookDirection.xz) and directly apply it to // the global axes, but this causes sidestepping when the character starts/stops. float angle; { Vector3 lookDirection = ActorController.LookDirection; angle = Mathf.Atan2(lookDirection.z, lookDirection.x); } // Math. targetStrafingVelocity = -targetStrafingVelocity; // Now accelerate the actor to achieve the movement speed in the desired direction if(Rigidbody != null) { Vector3 localVelocity = rotateAroundY(Rigidbody.velocity, -angle); // Get force that would be required to achieve target velocity instantly // and limit it to the acceleration specified by the caller localVelocity.x = (targetRunningVelocity - localVelocity.x) / deltaTime; localVelocity.x = Mathf.Clamp(localVelocity.x, -acceleration, acceleration); localVelocity.x *= Rigidbody.mass; localVelocity.z = (targetStrafingVelocity - localVelocity.z) / deltaTime; localVelocity.z = Mathf.Clamp(localVelocity.z, -acceleration, acceleration); localVelocity.z *= Rigidbody.mass; Rigidbody.AddForce(rotateAroundY(localVelocity, angle), ForceMode.Force); } else if(ActorPhysics != null) { Vector3 localVelocity = rotateAroundY(ActorPhysics.Velocity, -angle); // Get force that would be required to achieve target velocity instantly // and limit it to the acceleration specified by the caller localVelocity.x = (targetRunningVelocity - localVelocity.x) / deltaTime; localVelocity.x = Mathf.Clamp(localVelocity.x, -acceleration, acceleration); localVelocity.x *= ActorPhysics.Mass; localVelocity.z = (targetStrafingVelocity - localVelocity.z) / deltaTime; localVelocity.z = Mathf.Clamp(localVelocity.z, -acceleration, acceleration); localVelocity.z *= ActorPhysics.Mass; ActorPhysics.QueueForce(rotateAroundY(localVelocity, angle)); } else { Debug.LogError( "No Rigidbody and no ActorPhysics component present, cannot accelerate." ); } } #if MOVEMENT_WITHOUT_ATAN2_SIN_COS // I tried doing this simply by adding 'lookDirection.xz' for running, // but the outcome was that the character did a little bit of sideways // movement just before stopping (likely because the velocity calculations // aren't spot on with the physics). // // Thus I now rotate the actor's velocity into the look direction, // calculate the desired acceleration for running and strafing, // then rotate this acceleration back into the global coordinate frame. // Issue solved, one Atan2() call, two Sin() call, two Cos() call overhead. /// Accelerates the character to the specified target velocity /// /// Forward/backward velocity the character should reach /// /// Sidewaysd velocity the character should reach /// Acceleration in units per second squared protected void AccelerateToVelocity2( float targetRunningVelocity, float targetStrafingVelocity, float acceleration ) { float deltaTime = Time.fixedDeltaTime; // Get movement direction in the actor's local orientation Vector2 targetVelocity; { Vector3 lookDirection = ActorController.LookDirection; var forward = new Vector2(lookDirection.x, lookDirection.z); forward.Normalize(); // Add HorizontalDirection property to ILookHandler? targetVelocity.x = targetRunningVelocity * forward.x; targetVelocity.y = targetRunningVelocity * forward.y; targetVelocity.x += targetStrafingVelocity * forward.y; targetVelocity.y += targetStrafingVelocity * -forward.x; } if(Rigidbody != null) { Vector3 currentVelocity = Rigidbody.velocity; // Get force that would be required to achieve target velocity instantly // and limit it to the acceleration specified by the caller currentVelocity.x = (targetVelocity.x - currentVelocity.x) / deltaTime; currentVelocity.x = Mathf.Clamp(currentVelocity.x, -acceleration, acceleration); currentVelocity.x *= Rigidbody.mass; currentVelocity.z = (targetVelocity.y - currentVelocity.z) / deltaTime; currentVelocity.z = Mathf.Clamp(currentVelocity.z, -acceleration, acceleration); currentVelocity.z *= Rigidbody.mass; Rigidbody.AddForce(currentVelocity, ForceMode.Force); } else if(ActorPhysics != null) { Vector3 currentVelocity = ActorPhysics.Velocity;; // Get force that would be required to achieve target velocity instantly // and limit it to the acceleration specified by the caller currentVelocity.x = (targetVelocity.x - currentVelocity.x) / deltaTime; currentVelocity.x = Mathf.Clamp(currentVelocity.x, -acceleration, acceleration); currentVelocity.x *= ActorPhysics.Mass; currentVelocity.z = (targetVelocity.y - currentVelocity.z) / deltaTime; currentVelocity.z = Mathf.Clamp(currentVelocity.z, -acceleration, acceleration); currentVelocity.z *= ActorPhysics.Mass; ActorPhysics.QueueForce(currentVelocity); } else { Debug.LogError( "No Rigidbody and no ActorPhysics component present, cannot accelerate." ); } } #endif /// Enables or disables gravity for the actor /// True to enable gravity, false to disable it protected void EnableGravity(bool active = true) { if(Rigidbody != null) { Rigidbody.useGravity = active; } else if(ActorPhysics != null) { ActorPhysics.IsAffectedByGravity = active; } else { Debug.LogError( "No Rigidbody and no ActorPhysics component present, cannot toggle gravity." ); } } /// /// Uses the GroundChecker component to update the grounded state of the actor /// protected bool RecheckGrounding() { if(ActorPhysics != null) { return ActorController.GroundChecker.CheckIfGrounded(ActorPhysics) || ActorPhysics.UnreliableIsGrounded; } else { Collider collider = ActorController.GetComponent(); if(collider != null) { int layerMask = LayerMaskHelper.GetLayerCollisionMaskFor( ActorController.gameObject.layer ); return ActorController.GroundChecker.CheckIfGrounded(collider, layerMask); } } return false; } /// Checks whether the actor is standing on solid ground protected bool IsGrounded { get { if(ActorPhysics != null) { return ActorController.GroundChecker.WasGroundedInMostRecentCheck || ActorPhysics.UnreliableIsGrounded; } else { return ActorController.GroundChecker.WasGroundedInMostRecentCheck; } } } /// Retrieves the current velocity of the actor /// The actor's current velocity protected Vector3 GetVelocity() { if(Rigidbody != null) { return Rigidbody.velocity; } else if(ActorPhysics != null) { return ActorPhysics.Velocity; } else { Debug.LogError( "No Rigidbody and no ActorPhysics component present, velocity retrieval impossible" ); return Vector3.zero; } } /// Retrieves the current vertical velocity of the actor /// The actor's current vertical velocity protected float GetVerticalVelocity() { if(Rigidbody != null) { return Rigidbody.velocity.y; } else if(ActorPhysics != null) { return ActorPhysics.Velocity.y; } else { Debug.LogError( "No Rigidbody and no ActorPhysics component present, velocity check impossible" ); return float.NaN; } } /// Switches the actor controller to the ground move /// Velocity the actor had when hitting the ground protected void SwitchToGroundMove(float impactVelocity = 0.0f) { Move groundMode; if(this.moveRepository == null) { groundMode = new GroundMove(); } else { groundMode = this.moveRepository.GetGroundMove(); } ActorController.ActiveMove = groundMode; } /// Switches the actor controller to the air move /// Whether the character has jumped protected void SwitchToAirMove(bool jumped) { Move airMove; if(this.moveRepository == null) { airMove = new AirMove() { WasActivatedByJumping = jumped }; } else { airMove = this.moveRepository.GetAirMove(jumped); } ActorController.ActiveMove = airMove; } /// Repository storing other moves to which this one can switch private PlayerMoveRepository moveRepository; /// Input manager from which inputs states will be queried private IInputManager inputManager; /// Defines the abilities of the platformer actor private Abilities abilities; } } // namespace Framework.Actors.Platformer