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(); playerBehavior = this.gameObject.GetComponent(); grapplingRope = playerBehavior.grapplingRope; audioSource = GetComponent(); gameUI = GameObject.FindGameObjectWithTag("GameUICanvas").GetComponent(); 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(); } 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 { 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; _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; Jump(); // determine if trumpet jump if (!IsGrounded() && in_range && trumpet > 0 && !inCoyoteTime) { StartCoroutine(ActivateTrumpetSprite()); gameObject.transform.Find("Trumpet").GetComponent().Play(); enemy.GetComponent().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().Play(); } } // stop sound if needed if (soundPlaying && (isRegFalling || IsJumping || _isJumpFalling)) { 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 (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) { audioSource.Play(); soundPlaying = true; } } else if (soundPlaying && audioSource.clip.name == "footsteps") { 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; // accelerate or decelerate if (LastOnGroundTime > 0) { // if grounded accelRate = (Mathf.Abs(targetSpeed) > 0.01f) ? Data.runAccelAmount : Data.runDeccelAmount; } else if (wasGrappling) { // if grappling accelRate = (Mathf.Abs(targetSpeed) > 0.01f) ? Data.runAccelAmount * Data.accelInAir : Data.runDeccelAmount * (Data.deccelInAir / 5); } else { // if in air 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() { 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().Play("poof"); yield return new WaitForSeconds(.5f); trumpetAnimationObject.SetActive(false); trumpetSprite.enabled = false; trumpetActive = false; } } #endregion }