You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

331 lines
10 KiB

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(UnitState))]
[RequireComponent(typeof(CapsuleCollider))]
public class PlayerMovement : MonoBehaviour {
[Header("Linked Components")]
private UnitAnimator animator;
private Rigidbody rb;
private UnitState playerState;
private CapsuleCollider capsule;
[Header("Settings")]
public float walkSpeed = 3f;
public float runSpeed = 6f;
public float ZSpeed = 1.5f;
public float JumpForce = 8f;
public bool AllowDepthJumping;
public float AirAcceleration = 3f;
public float AirMaxSpeed = 3f;
public float rotationSpeed = 15f;
public float jumpRotationSpeed = 30f;
public float lookAheadDistance = .2f;
public float landRecoveryTime = .1f;
public float landTime = 0;
public LayerMask CollisionLayer;
[Header("Audio")]
public string jumpUpVoice = "";
public string jumpLandVoice = "";
[Header("Stats")]
public DIRECTION currentDirection;
public Vector2 inputDirection;
public bool jumpInProgress;
private bool isDead = false;
public bool JumpNextFixedUpdate;
private float jumpDownwardsForce = .3f;
private float lastJumpTime = 0f;
//a list of states where movement can take place
private List<UNITSTATE> MovementStates = new List<UNITSTATE> {
UNITSTATE.IDLE,
UNITSTATE.WALK,
UNITSTATE.RUN,
UNITSTATE.JUMPING,
UNITSTATE.JUMPKICK,
UNITSTATE.LAND,
UNITSTATE.DEFEND,
};
//--
void OnEnable() {
InputManager.onInputEvent += OnInputEvent;
InputManager.onDirectionInputEvent += OnDirectionInputEvent;
}
void OnDisable() {
InputManager.onInputEvent -= OnInputEvent;
InputManager.onDirectionInputEvent -= OnDirectionInputEvent;
}
void Start(){
//find components
if(!animator) animator = GetComponentInChildren<UnitAnimator>();
if(!rb) rb = GetComponent<Rigidbody>();
if(!playerState) playerState = GetComponent<UnitState>();
if(!capsule) capsule = GetComponent<CapsuleCollider>();
//error messages for missing components
if(!animator) Debug.LogError("No animator found inside " + gameObject.name);
if(!rb) Debug.LogError("No Rigidbody component found on " + gameObject.name);
if(!playerState) Debug.LogError("No UnitState component found on " + gameObject.name);
if(!capsule) Debug.LogError("No Capsule Collider found on " + gameObject.name);
}
void FixedUpdate() {
if(!MovementStates.Contains(playerState.currentState) || isDead) return;
//defend
if(playerState.currentState == UNITSTATE.DEFEND){
TurnToCurrentDirection();
return;
}
//start a jump
if(JumpNextFixedUpdate){
Jump();
return;
}
//land after a jump
if(jumpInProgress && IsGrounded()){
HasLanded();
return;
}
//A short recovery time after landing
if(playerState.currentState == UNITSTATE.LAND && Time.time - landTime > landRecoveryTime) playerState.SetState(UNITSTATE.IDLE);
//air and ground Movement
bool isGrounded = IsGrounded();
animator.SetAnimatorBool("isGrounded", isGrounded);
if(isGrounded) animator.SetAnimatorBool("Falling", false);
if(isGrounded){
MoveGrounded();
} else {
MoveAirborne();
}
//always turn towards the current direction
TurnToCurrentDirection();
}
//movement on the ground
void MoveGrounded(){
//do nothing when landing
if(playerState.currentState == UNITSTATE.LAND) return;
//move when there is no wall in front of us and input is detected
if(rb != null && (inputDirection.sqrMagnitude>0 && !WallInFront())) {
//set movement speed to run speed or walk speed depending on the current state
float movementSpeed = playerState.currentState == UNITSTATE.RUN? runSpeed : walkSpeed;
rb.velocity = new Vector3( inputDirection.x * -movementSpeed, rb.velocity.y + Physics.gravity.y * Time.fixedDeltaTime, inputDirection.y * -ZSpeed);
if(animator) animator.SetAnimatorFloat("MovementSpeed", rb.velocity.magnitude);
} else {
//stop moving, but still apply gravity
rb.velocity = new Vector3(0, rb.velocity.y + Physics.gravity.y * Time.fixedDeltaTime, 0);
if(animator) animator.SetAnimatorFloat("MovementSpeed", 0);
playerState.SetState(UNITSTATE.IDLE);
}
//sets the run state in the animator to true or false
animator.SetAnimatorBool("Run", playerState.currentState == UNITSTATE.RUN);
}
//movement in the air
void MoveAirborne(){
//falling down
if(rb.velocity.y < 0.1f && playerState.currentState != UNITSTATE.KNOCKDOWN) animator.SetAnimatorBool("Falling", true);
if(!WallInFront()) {
//movement direction based on current input
int dir = Mathf.Clamp(Mathf.RoundToInt(-inputDirection.x), -1, 1);
float xpeed = Mathf.Clamp(rb.velocity.x + AirMaxSpeed * dir * Time.fixedDeltaTime * AirAcceleration, -AirMaxSpeed, AirMaxSpeed);
float downForce = rb.velocity.y>0? 0 : jumpDownwardsForce; //adds a small downwards force when going down
//apply movement
if(AllowDepthJumping) {
rb.velocity = new Vector3(xpeed, rb.velocity.y - downForce, -inputDirection.y * ZSpeed);
} else {
rb.velocity = new Vector3(xpeed, rb.velocity.y - downForce, 0);
}
}
}
//perform a jump
void Jump(){
playerState.SetState(UNITSTATE.JUMPING);
JumpNextFixedUpdate = false;
jumpInProgress = true;
rb.velocity = Vector3.up * JumpForce;
lastJumpTime = Time.time;
//play animation
animator.SetAnimatorBool("JumpInProgress", true);
animator.SetAnimatorBool("Run", false);
animator.SetAnimatorTrigger("JumpUp");
animator.ShowDustEffectJump();
//play sfx
if(jumpUpVoice != "") GlobalAudioPlayer.PlaySFXAtPosition(jumpUpVoice, transform.position);
}
//player has landed after a jump
void HasLanded(){
jumpInProgress = false;
playerState.SetState(UNITSTATE.LAND);
rb.velocity = Vector2.zero;
landTime = Time.time;
//set animator properties
animator.SetAnimatorFloat("MovementSpeed", 0f);
animator.SetAnimatorBool("JumpInProgress", false);
animator.SetAnimatorBool("JumpKickActive", false);
animator.SetAnimatorBool("Falling", false);
animator.ShowDustEffectLand();
//sfx
GlobalAudioPlayer.PlaySFX("FootStep");
if(jumpLandVoice != "") GlobalAudioPlayer.PlaySFXAtPosition(jumpLandVoice, transform.position);
}
#region controller input
//set current direction to input direction
void OnDirectionInputEvent(Vector2 dir, bool doubleTapActive) {
//ignore input when we are dead or when this state is not active
if(!MovementStates.Contains(playerState.currentState) || isDead) return;
//set current direction based on the input vector. Mathf.sign is used because we want the player to stay in the left or right direction when moving up/down)
int dir2 = Mathf.RoundToInt(Mathf.Sign((float)-inputDirection.x));
if(Mathf.Abs(inputDirection.x) > 0) SetDirection((DIRECTION)dir2);
inputDirection = dir;
//start running on double tap
if(doubleTapActive && IsGrounded() && Mathf.Abs(dir.x)>0) playerState.SetState(UNITSTATE.RUN);
}
public bool JumpIsRecent()
{
return lastJumpTime > Time.time - PlayerCombat.SIMULTANEOUS_BUTTON_SPACING;
}
//input actions
void OnInputEvent(string action, BUTTONSTATE buttonState) {
//special attack (jump + attack) if jump is hit second
PlayerCombat pc = GetComponent<PlayerCombat>();
if (action == "Jump" && buttonState == BUTTONSTATE.PRESS && playerState.currentState == UNITSTATE.PUNCH && pc.AttackIsRecent())
{
pc.DoSpecialAttack(this);
}
//ignore input when we are dead or when this state is not active
if (!MovementStates.Contains(playerState.currentState) || isDead)
return;
if (action == "Jump" && buttonState == BUTTONSTATE.PRESS && IsGrounded() && playerState.currentState != UNITSTATE.JUMPING) JumpNextFixedUpdate = true;
//start running when a run button is pressed (e.g. Joypad controls)
if(action == "Run") playerState.SetState(UNITSTATE.RUN);
}
#endregion
//interrups an ongoing jump
public void CancelJump(){
jumpInProgress = false;
}
//set current direction
public void SetDirection(DIRECTION dir) {
currentDirection = dir;
if(animator) animator.currentDirection = currentDirection;
}
//returns the current direction
public DIRECTION getCurrentDirection() {
return currentDirection;
}
//returns true if the player is grounded
public bool IsGrounded() {
//check for capsule collisions with a 0.1 downwards offset from the capsule collider
Vector3 bottomCapsulePos = transform.position + (Vector3.up) * (capsule.radius - 0.1f);
return Physics.CheckCapsule(transform.position + capsule.center, bottomCapsulePos, capsule.radius, CollisionLayer);
}
//look (and turns) towards a direction
public void TurnToCurrentDirection() {
if(currentDirection == DIRECTION.Right || currentDirection == DIRECTION.Left) {
float turnSpeed = jumpInProgress? jumpRotationSpeed : rotationSpeed;
Vector3 newDir = Vector3.RotateTowards(transform.forward, Vector3.forward * -(int)currentDirection, turnSpeed * Time.fixedDeltaTime, 0.0f);
transform.rotation = Quaternion.LookRotation(newDir);
}
}
//update the direction based on the current input
public void updateDirection() {
TurnToCurrentDirection();
}
//the player has died
void Death() {
isDead = true;
rb.velocity = Vector3.zero;
}
//returns true if there is a environment collider in front of us
bool WallInFront() {
var MovementOffset = new Vector3(inputDirection.x, 0, inputDirection.y) * lookAheadDistance;
var c = GetComponent<CapsuleCollider>();
Collider[] hitColliders = Physics.OverlapSphere(transform.position + Vector3.up * (c.radius + .1f) + -MovementOffset, c.radius, CollisionLayer);
int i = 0;
bool hasHitwall = false;
while(i < hitColliders.Length) {
if(CollisionLayer == (CollisionLayer | 1 << hitColliders[i].gameObject.layer)) hasHitwall = true;
i++;
}
return hasHitwall;
}
//draw a lookahead sphere in the unity editor
#if UNITY_EDITOR
void OnDrawGizmos() {
var c = GetComponent<CapsuleCollider>();
Gizmos.color = Color.yellow;
Vector3 MovementOffset = new Vector3(inputDirection.x, 0, inputDirection.y) * lookAheadDistance;
Gizmos.DrawWireSphere(transform.position + Vector3.up * (c.radius + .1f) + -MovementOffset, c.radius);
}
#endif
}
public enum DIRECTION {
Right = -1,
Left = 1,
Up = 2,
Down = -2,
};