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