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.
339 lines
10 KiB
339 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;
|
|
|
|
public int playerNumber;
|
|
|
|
//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, int playerNum) {
|
|
|
|
if (playerNum != this.playerNumber)
|
|
return;
|
|
|
|
//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, int playerNum) {
|
|
|
|
if (playerNum != playerNumber)
|
|
return;
|
|
|
|
//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,
|
|
};
|