420 lines
15 KiB
C#
420 lines
15 KiB
C#
using System.Collections;
|
|
using UnityEngine;
|
|
using UnityEngine.InputSystem;
|
|
|
|
public class PlayerMovement : MonoBehaviour
|
|
{
|
|
//Scriptable object which holds all the player's movement parameters. If you don't want to use it
|
|
//just paste in all the parameters, though you will need to manuly change all references in this script
|
|
|
|
//HOW TO: to add the scriptable object, right-click in the project window -> create -> Player Data
|
|
//Next, drag it into the slot in playerMovement on your player
|
|
|
|
public PlayerData Data;
|
|
|
|
#region Variables
|
|
//Components
|
|
public Rigidbody2D RB { get; private set; }
|
|
|
|
//Variables control the various actions the player can perform at any time.
|
|
//These are fields which can are public allowing for other sctipts to read them
|
|
//but can only be privately written to.
|
|
public bool IsFacingRight { get; private set; }
|
|
public bool IsJumping { get; private set; }
|
|
|
|
//Timers (also all fields, could be private and a method returning a bool could be used)
|
|
public float LastOnGroundTime { get; private set; }
|
|
|
|
// trumpet
|
|
public int trumpet = 0;
|
|
public bool in_range = false;
|
|
public GameObject enemy;
|
|
bool unlockedTrumpet;
|
|
[SerializeField] SpriteRenderer trumpetSprite;
|
|
GameObject trumpetAnimationObject;
|
|
bool trumpetActive = false;
|
|
|
|
//clarinet
|
|
private float tempFallSpeed;
|
|
|
|
//Jump
|
|
private bool _isJumpCut;
|
|
private bool _isJumpFalling;
|
|
private bool isRegFalling;
|
|
|
|
private Vector2 _moveInput;
|
|
public float LastPressedJumpTime { get; private set; }
|
|
|
|
Tutorial_GrapplingRope grapplingRope;
|
|
bool wasGrappling = false;
|
|
|
|
//Set all of these up in the inspector
|
|
[Header("Checks")]
|
|
//Size of groundCheck depends on the size of your character generally you want them slightly small than width (for ground) and height (for the wall check)
|
|
[SerializeField] private Vector2 _groundCheckSize = new Vector2(0.49f, 0.03f);
|
|
[SerializeField] private float _groundCheckOffset;
|
|
[Space(5)]
|
|
[SerializeField] private Vector2 _wallCheckSize = new Vector2(0.5f, 1f);
|
|
|
|
[Header("Layers & Tags")]
|
|
[SerializeField] private LayerMask _groundLayer;
|
|
|
|
// Static classes
|
|
[HideInInspector] private PlayerBehavior playerBehavior;
|
|
|
|
[HideInInspector] private AudioSource audioSource;
|
|
[HideInInspector] private bool soundPlaying = false;
|
|
|
|
[HideInInspector] private GameUIController gameUI;
|
|
#endregion
|
|
|
|
private void Awake() {
|
|
RB = GetComponent<Rigidbody2D>();
|
|
playerBehavior = this.gameObject.GetComponent<PlayerBehavior>();
|
|
grapplingRope = playerBehavior.grapplingRope;
|
|
audioSource = GetComponent<AudioSource>();
|
|
gameUI = GameObject.FindGameObjectWithTag("GameUICanvas").GetComponent<GameUIController>();
|
|
trumpetSprite.enabled = false;
|
|
trumpetAnimationObject = trumpetSprite.gameObject.transform.GetChild(0).gameObject;
|
|
trumpetAnimationObject.SetActive(false);
|
|
}
|
|
|
|
private void Start() {
|
|
SetGravityScale(Data.gravityScale);
|
|
IsFacingRight = true;
|
|
tempFallSpeed = Data.maxFallSpeed;
|
|
}
|
|
|
|
void OnMove(InputValue value) {
|
|
if (playerBehavior.playerIsAlive) {
|
|
this._moveInput = value.Get<Vector2>();
|
|
}
|
|
else {
|
|
this._moveInput = Vector2.zero;
|
|
}
|
|
}
|
|
|
|
void OnJump() {
|
|
if (playerBehavior.playerIsAlive) {
|
|
OnJumpInput();
|
|
}
|
|
}
|
|
|
|
private void Update() {
|
|
unlockedTrumpet = StateController.Instance.HasTrumpet();
|
|
#region TIMERS
|
|
LastOnGroundTime -= Time.deltaTime;
|
|
|
|
LastPressedJumpTime -= Time.deltaTime;
|
|
#endregion
|
|
|
|
#region COLLISION CHECKS
|
|
if (!IsJumping) {
|
|
//Ground Check
|
|
if (IsGrounded()) {
|
|
//checks if set box overlaps with ground
|
|
LastOnGroundTime = Data.coyoteTime; //if so sets the lastGrounded to coyoteTime
|
|
if (unlockedTrumpet) {
|
|
trumpet = 1;
|
|
gameUI.ToggleTrumpet(true);
|
|
}
|
|
else {
|
|
trumpet = -1;
|
|
}
|
|
wasGrappling = false;
|
|
isRegFalling = false;
|
|
}
|
|
else {
|
|
// print("not jumping");
|
|
if (!_isJumpFalling && !isRegFalling) {
|
|
if (unlockedTrumpet) {
|
|
trumpet = 1;
|
|
gameUI.ToggleTrumpet(true);
|
|
}
|
|
else {
|
|
trumpet = -1;
|
|
}
|
|
isRegFalling = true;
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region JUMP CHECKS
|
|
if (IsJumping && RB.velocity.y <= 0) {
|
|
IsJumping = false;
|
|
// print("isJumping " + IsJumping);
|
|
_isJumpFalling = true;
|
|
}
|
|
|
|
if (LastOnGroundTime > 0 && !IsJumping) {
|
|
_isJumpCut = false;
|
|
|
|
if (!IsJumping)
|
|
_isJumpFalling = false;
|
|
}
|
|
|
|
|
|
//Jump
|
|
if (CanJump() && LastPressedJumpTime > 0) {
|
|
IsJumping = true;
|
|
_isJumpCut = false;
|
|
_isJumpFalling = false;
|
|
|
|
bool inCoyoteTime = LastOnGroundTime > 0;
|
|
//print("coyote time: " + inCoyoteTime);
|
|
|
|
Jump();
|
|
|
|
// determine if trumpet jump
|
|
if (!IsGrounded() && in_range && trumpet > 0 && !inCoyoteTime) {
|
|
StartCoroutine(ActivateTrumpetSprite());
|
|
gameObject.transform.Find("Trumpet").GetComponent<AudioSource>().Play();
|
|
enemy.GetComponent<EnemyPatrol>().DefeatEnemy();
|
|
enemy = null;
|
|
in_range = false;
|
|
}
|
|
else if (!IsGrounded() && !in_range && trumpet > 0 && !inCoyoteTime) {
|
|
trumpet -= 1;
|
|
}
|
|
// check if double jump, play sound
|
|
if (trumpet == 0) {
|
|
StartCoroutine(ActivateTrumpetSprite());
|
|
gameObject.transform.Find("Trumpet").GetComponent<AudioSource>().Play();
|
|
}
|
|
}
|
|
|
|
// stop sound if needed
|
|
if (soundPlaying && (isRegFalling || IsJumping || _isJumpFalling)) {
|
|
print("footsteps stop");
|
|
audioSource.Stop();
|
|
soundPlaying = false;
|
|
}
|
|
#endregion
|
|
|
|
#region GRAPPLE CHECKS
|
|
// set wasGrappling to true if the player starts grappling
|
|
if (grapplingRope.isGrappling) {
|
|
wasGrappling = true;
|
|
}
|
|
#endregion
|
|
|
|
#region GRAVITY
|
|
//Higher gravity if we've released the jump input or are falling
|
|
// if (IsSliding)
|
|
// {
|
|
// SetGravityScale(0);
|
|
// }
|
|
if (RB.velocity.y < 0 && _moveInput.y < 0) {
|
|
//Much higher gravity if holding down
|
|
SetGravityScale(Data.gravityScale * Data.fastFallGravityMult);
|
|
//Caps maximum fall speed, so when falling over large distances we don't accelerate to insanely high speeds
|
|
RB.velocity = new Vector2(RB.velocity.x, Mathf.Max(RB.velocity.y, -Data.maxFastFallSpeed));
|
|
}
|
|
else if (_isJumpCut) {
|
|
//Higher gravity if jump button released
|
|
SetGravityScale(Data.gravityScale * Data.jumpCutGravityMult);
|
|
RB.velocity = new Vector2(RB.velocity.x, Mathf.Max(RB.velocity.y, -Data.maxFallSpeed));
|
|
}
|
|
else if ((IsJumping || _isJumpFalling) && Mathf.Abs(RB.velocity.y) < Data.jumpHangTimeThreshold) {
|
|
SetGravityScale(Data.gravityScale * Data.jumpHangGravityMult);
|
|
}
|
|
else if (RB.velocity.y < 0) {
|
|
//Higher gravity if falling
|
|
SetGravityScale(Data.gravityScale * Data.fallGravityMult);
|
|
//Caps maximum fall speed, so when falling over large distances we don't accelerate to insanely high speeds
|
|
RB.velocity = new Vector2(RB.velocity.x, Mathf.Max(RB.velocity.y, -Data.maxFallSpeed));
|
|
}
|
|
else {
|
|
//Default gravity if standing on a platform or moving upwards
|
|
SetGravityScale(Data.gravityScale);
|
|
}
|
|
#endregion
|
|
|
|
#region SOUND CHECKS
|
|
if (!IsJumping && !_isJumpFalling && !isRegFalling && _moveInput.x != 0) {
|
|
if (!soundPlaying) {
|
|
// print("footsteps PLAY");
|
|
audioSource.Play();
|
|
soundPlaying = true;
|
|
}
|
|
}
|
|
else if (soundPlaying && audioSource.clip.name == "footsteps") {
|
|
// print("footsteps stop");
|
|
audioSource.Stop();
|
|
soundPlaying = false;
|
|
}
|
|
#endregion
|
|
|
|
#region UPDATE UI
|
|
if (trumpet == 0) {
|
|
gameUI.ToggleTrumpet(false);
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
private void FixedUpdate() {
|
|
Run(1);
|
|
}
|
|
|
|
#region INPUT CALLBACKS
|
|
//Methods which whandle input detected in Update()
|
|
public void OnJumpInput() {
|
|
LastPressedJumpTime = Data.jumpInputBufferTime;
|
|
}
|
|
|
|
public void OnJumpUpInput() {
|
|
if (CanJumpCut()) {
|
|
_isJumpCut = true;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region GENERAL METHODS
|
|
public void SetGravityScale(float scale) {
|
|
RB.gravityScale = scale;
|
|
}
|
|
#endregion
|
|
|
|
//MOVEMENT METHODS
|
|
#region RUN METHODS
|
|
private void Run(float lerpAmount) {
|
|
//Calculate the direction we want to move in and our desired velocity
|
|
float targetSpeed = _moveInput.x * Data.runMaxSpeed;
|
|
//We can reduce are control using Lerp() this smooths changes to are direction and speed
|
|
targetSpeed = Mathf.Lerp(RB.velocity.x, targetSpeed, lerpAmount);
|
|
|
|
#region Calculate AccelRate
|
|
float accelRate;
|
|
|
|
//Gets an acceleration value based on if we are accelerating (includes turning)
|
|
//or trying to decelerate (stop). As well as applying a multiplier if we're air borne.
|
|
if (LastOnGroundTime > 0) {
|
|
accelRate = (Mathf.Abs(targetSpeed) > 0.01f) ? Data.runAccelAmount : Data.runDeccelAmount;
|
|
}
|
|
else if (wasGrappling) {
|
|
accelRate = (Mathf.Abs(targetSpeed) > 0.01f) ? Data.runAccelAmount * Data.accelInAir : Data.runDeccelAmount * (Data.deccelInAir / 5);
|
|
}
|
|
else {
|
|
accelRate = (Mathf.Abs(targetSpeed) > 0.01f) ? Data.runAccelAmount * Data.accelInAir : Data.runDeccelAmount * Data.deccelInAir;
|
|
}
|
|
#endregion
|
|
|
|
#region Add Bonus Jump Apex Acceleration
|
|
//Increase are acceleration and maxSpeed when at the apex of their jump, makes the jump feel a bit more bouncy, responsive and natural
|
|
if ((IsJumping || _isJumpFalling) && Mathf.Abs(RB.velocity.y) < Data.jumpHangTimeThreshold) {
|
|
accelRate *= Data.jumpHangAccelerationMult;
|
|
targetSpeed *= Data.jumpHangMaxSpeedMult;
|
|
}
|
|
#endregion
|
|
|
|
#region Conserve Momentum
|
|
//We won't slow the player down if they are moving in their desired direction but at a greater speed than their maxSpeed
|
|
if ((Data.doConserveMomentum && Mathf.Abs(RB.velocity.x) > Mathf.Abs(targetSpeed) && Mathf.Sign(RB.velocity.x) == Mathf.Sign(targetSpeed) && Mathf.Abs(targetSpeed) > 0.01f && LastOnGroundTime < 0) || grapplingRope.isGrappling) {
|
|
//Prevent any deceleration from happening, or in other words conserve are current momentum
|
|
//You could experiment with allowing for the player to slightly increae their speed whilst in this "state"
|
|
accelRate = 0;
|
|
}
|
|
#endregion
|
|
|
|
//Calculate difference between current velocity and desired velocity
|
|
float speedDif = targetSpeed - RB.velocity.x;
|
|
//Calculate force along x-axis to apply to thr player
|
|
|
|
float movement = speedDif * accelRate;
|
|
|
|
//Convert this to a vector and apply to rigidbody
|
|
RB.AddForce(movement * Vector2.right, ForceMode2D.Force);
|
|
|
|
/*
|
|
* For those interested here is what AddForce() will do
|
|
* RB.velocity = new Vector2(RB.velocity.x + (Time.fixedDeltaTime * speedDif * accelRate) / RB.mass, RB.velocity.y);
|
|
* Time.fixedDeltaTime is by default in Unity 0.02 seconds equal to 50 FixedUpdate() calls per second
|
|
*/
|
|
}
|
|
|
|
private void Turn() {
|
|
//stores scale and flips the player along the x axis,
|
|
Vector3 scale = transform.localScale;
|
|
scale.x *= -1;
|
|
transform.localScale = scale;
|
|
|
|
IsFacingRight = !IsFacingRight;
|
|
}
|
|
#endregion
|
|
|
|
#region JUMP METHODS
|
|
private void Jump() {
|
|
//Ensures we can't call Jump multiple times from one press
|
|
LastPressedJumpTime = 0;
|
|
LastOnGroundTime = 0;
|
|
|
|
#region Perform Jump
|
|
//We increase the force applied if we are falling
|
|
//This means we'll always feel like we jump the same amount
|
|
//(setting the player's Y velocity to 0 beforehand will likely work the same, but I find this more elegant :D)
|
|
float force = Data.jumpForce;
|
|
if (RB.velocity.y < 0) {
|
|
force -= RB.velocity.y;
|
|
}
|
|
RB.AddForce(Vector2.up * force, ForceMode2D.Impulse);
|
|
#endregion
|
|
}
|
|
#endregion
|
|
|
|
|
|
#region CHECK METHODS
|
|
private bool CanJump() {
|
|
if (!IsGrounded() && trumpet > 0) {
|
|
return true;
|
|
}
|
|
return LastOnGroundTime > 0 && !IsJumping;
|
|
}
|
|
|
|
private bool CanJumpCut() {
|
|
return IsJumping && RB.velocity.y > 0;
|
|
}
|
|
|
|
public bool IsGrounded() {
|
|
// print(Physics2D.OverlapBox(this.transform.position, _groundCheckSize, 0, _groundLayer) && !IsJumping);
|
|
return (Physics2D.OverlapBox(new Vector2(this.transform.position.x, this.transform.position.y - _groundCheckOffset), _groundCheckSize, 0, _groundLayer) && !IsJumping);
|
|
}
|
|
|
|
public void FloatGravity(float grav) {
|
|
Data.maxFallSpeed = grav;
|
|
}
|
|
|
|
public void EndFloatGravity() {
|
|
Data.maxFallSpeed = tempFallSpeed;
|
|
}
|
|
#endregion
|
|
|
|
|
|
#region EDITOR METHODS
|
|
private void OnDrawGizmosSelected() {
|
|
Gizmos.color = Color.green;
|
|
Gizmos.DrawWireCube(new Vector2(this.transform.position.x, this.transform.position.y - _groundCheckOffset), _groundCheckSize);
|
|
Gizmos.color = Color.blue;
|
|
Gizmos.DrawWireCube(this.transform.position, _wallCheckSize);
|
|
Gizmos.DrawWireCube(this.transform.position, _wallCheckSize);
|
|
}
|
|
#endregion
|
|
|
|
#region ADDITIONAL TRUMPET METHODS
|
|
IEnumerator ActivateTrumpetSprite() {
|
|
if (!trumpetActive) {
|
|
trumpetActive = true;
|
|
trumpetSprite.enabled = true;
|
|
trumpetAnimationObject.SetActive(true);
|
|
trumpetAnimationObject.GetComponent<Animator>().Play("poof");
|
|
yield return new WaitForSeconds(.5f);
|
|
|
|
trumpetAnimationObject.SetActive(false);
|
|
trumpetSprite.enabled = false;
|
|
trumpetActive = false;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
}
|