using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; [RequireComponent(typeof(MoveController))] [RequireComponent(typeof(BoxCollider2D))] public class PlayerController : MonoBehaviour { public float jumpHeight = 4f; public float maxForwardJumpBoost = 2.50f; public float timeToJumpApex = 0.4f; public float lastDashTime = 0f; public float dashTime = 0.125f; public float dashSpeed = 50f; public float dashCooldown = 1.25f; public float moveSpeed = 6f; public float maxFallSpeed = -20f; public float timeToMaxRunSpeed = 0.15f; public float timeToMaxAirmoveSpeed = 0.025f; public float maxApexTime = 0.05f; // amount of time spent at max jump height before falling again public float coyoteTime = 0.1f; public int maxJumps = 2; public GameObject tracerPrefab; public GameObject leftDashIndicator; public GameObject rightDashIndicator; public PlayerControls controls; // this has to be public to be readable by the display, which is a code // smell. public JumpState jumpState; public JumpDirection jumpDirection; private float jumpStateStart; private float jumpStartTime; private float jumpHeldDuration; public int jumpCount; private bool wasHoldingJump; public float apexTime; private float jumpVelocity = 8f; private float gravity = -20f; private Vector3 velocity; private MoveController moveController; private float velocityXSmoothing; private GameObject tracers; void Start() { if (!tracers) { tracers = new GameObject(); tracers.name = "Player Debug Tracers"; } // Debug.Log("Creating new controls in Start!"); // controls = new PlayerControls(); GameObject[] checkpoints = GameObject.FindGameObjectsWithTag("Checkpoint"); foreach (GameObject cp in checkpoints) { if (cp.GetComponent().isLastCheckpoint) { transform.position = new Vector3( cp.transform.position.x, cp.transform.position.y, 0f ); } } setJumpState(JumpState.Falling); moveController = GetComponent(); gravity = -(2* jumpHeight) / Mathf.Pow(timeToJumpApex, 2); jumpVelocity = Mathf.Abs(gravity) * timeToJumpApex; } void Awake() { Debug.Log("Creating new controls in Awake!"); } void Update() { InputActionPhase jumpPhase = InputActionPhase.Waiting; bool jumpTriggered = false; if (controls == null) { Debug.Log("HOW IS GAMEPLAY NULL"); } if (controls.Gameplay.Jump != null) { jumpPhase = controls.Gameplay.Jump.phase; jumpTriggered = controls.Gameplay.Jump.triggered; } // Debug.LogFormat("Jump Phase: {0} Triggered: {1}", jumpPhase, jumpTriggered); GameObject tracerObj = Instantiate(tracerPrefab, transform.position, Quaternion.identity); tracerObj.transform.parent = tracers.transform; TracerDot tracer = tracerObj.GetComponent(); Vector2 input = controls.Gameplay.Move.ReadValue(); // Vector2 input = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical")); float targetX = input.x * moveSpeed; if (Mathf.Abs(input.x) < 0.1) { targetX = 0; } Vector3 initialVelocity = velocity; if (moveController.collisions.above) { velocity.y = 0; } if (moveController.collisions.left || moveController.collisions.right) { velocity.x = 0; } switch (jumpState) { case JumpState.Grounded: tracer.color = Color.white; if (dash()) { break; } else if (controls.Gameplay.Jump.phase == InputActionPhase.Started) { // } else if (Input.GetButton("Jump")) { velocity.y = jumpVelocity; if (input.x >= 0.25f) { velocity.x = moveSpeed*maxForwardJumpBoost; } else if (input.x <= -0.25f) { velocity.x = -moveSpeed*maxForwardJumpBoost; } else { velocity.x = 0f; } setJumpState(JumpState.Ascending); break; } else { velocity.y = gravity * Time.deltaTime; velocity.x = Mathf.SmoothDamp(velocity.x, targetX, ref velocityXSmoothing, timeToMaxRunSpeed); } break; case JumpState.Dash: tracer.color = Color.red; velocity.x = Mathf.SmoothDamp(velocity.x, velocity.x >= 0 ? moveSpeed : -moveSpeed, ref velocityXSmoothing, dashTime); velocity.y = 0; float timeDashing = Time.time - lastDashTime; if (timeDashing >= dashTime) { if (moveController.isGrounded) { setJumpState(JumpState.Grounded); } else { setJumpState(JumpState.Falling); } } break; case JumpState.Ascending: tracer.color = Color.green; // n starts at 0 and goes to 1 as we ascend float n = Mathf.InverseLerp(0, timeToJumpApex, Time.time - jumpStateStart); // the drag coefficient gets bigger as we ascend, terminating at 1 float dragCoefficient = n * n; if (dash()) { break; // } else if (Input.GetButtonDown("Jump") && jumpCount < maxJumps) { } else if (controls.Gameplay.Jump.triggered && jumpCount < maxJumps) { velocity.y = jumpVelocity; if (input.x >= 0.25f) { velocity.x = moveSpeed*maxForwardJumpBoost; } else if (input.x <= -0.25f) { velocity.x = -moveSpeed*maxForwardJumpBoost; } else { velocity.x = 0f; } setJumpState(JumpState.Ascending); break; // } else if (Input.GetButton("Jump")) { } else if (controls.Gameplay.Jump.phase == InputActionPhase.Started) { float jumpDrag = jumpVelocity * dragCoefficient; if (n <= 0.7) { jumpDrag = 0; } velocity.y = jumpVelocity - jumpDrag; } else { // if we're not pressing jump any more we add all the gravity velocity.y += 4 * gravity * Time.deltaTime; // the amount of time spent floating is equal to the amount of // time the player held down the jump button. if (wasHoldingJump) { apexTime = Time.time - jumpStartTime; } } float forwardBoost = maxForwardJumpBoost - (maxForwardJumpBoost * dragCoefficient); forwardBoost = Mathf.Clamp(forwardBoost, 1, maxForwardJumpBoost); switch (jumpDirection) { case JumpDirection.Neutral: // you can wiggle out of a neutral jump in either direction velocity.x = Mathf.SmoothDamp(velocity.x, targetX, ref velocityXSmoothing, timeToMaxAirmoveSpeed); break; case JumpDirection.Left: velocity.x = Mathf.SmoothDamp(velocity.x, targetX < 0 ? targetX * forwardBoost : 0, ref velocityXSmoothing, timeToMaxAirmoveSpeed); break; case JumpDirection.Right: velocity.x = Mathf.SmoothDamp(velocity.x, targetX > 0 ? targetX * forwardBoost : 0, ref velocityXSmoothing, timeToMaxAirmoveSpeed); break; } // if we were rising in the last frame but will be falling in this // frame, we should zero out the velocity to float instead. if (initialVelocity.y >= 0 && velocity.y <= 0) { velocity.y = 0; // if (wasHoldingJump && Input.GetButton("Jump")) { if (wasHoldingJump && controls.Gameplay.Jump.phase == InputActionPhase.Started) { apexTime = maxApexTime; } setJumpState(JumpState.Apex); } break; case JumpState.Apex: tracer.color = Color.magenta; if (dash()) { break; // } else if (Input.GetButtonDown("Jump") && jumpCount < maxJumps) { } else if (controls.Gameplay.Jump.triggered && jumpCount < maxJumps) { velocity.y = jumpVelocity; if (input.x >= 0.25f) { velocity.x = moveSpeed*maxForwardJumpBoost; } else if (input.x <= -0.25f) { velocity.x = -moveSpeed*maxForwardJumpBoost; } else { velocity.x = 0f; } setJumpState(JumpState.Ascending); break; } else { switch (jumpDirection) { case JumpDirection.Neutral: velocity.x = Mathf.SmoothDamp(velocity.x, targetX, ref velocityXSmoothing, timeToMaxAirmoveSpeed); break; case JumpDirection.Left: velocity.x = Mathf.SmoothDamp(velocity.x, targetX < 0 ? targetX : 0, ref velocityXSmoothing, timeToMaxAirmoveSpeed); break; case JumpDirection.Right: velocity.x = Mathf.SmoothDamp(velocity.x, targetX > 0 ? targetX : 0, ref velocityXSmoothing, timeToMaxAirmoveSpeed); break; } float timeAtApex = Time.time - jumpStateStart; float apexTimeRemaining = apexTime - timeAtApex; if (apexTimeRemaining < 0) { velocity.y += gravity * -apexTimeRemaining; setJumpState(JumpState.Descending); } else { velocity.y = 0; } } break; case JumpState.Descending: tracer.color = Color.yellow; // if (Input.GetButtonDown("Jump") && jumpCount < maxJumps) { if (controls.Gameplay.Jump.triggered && jumpCount < maxJumps) { velocity.y = jumpVelocity; if (input.x >= 0.25f) { velocity.x = moveSpeed*maxForwardJumpBoost; } else if (input.x <= -0.25f) { velocity.x = -moveSpeed*maxForwardJumpBoost; } else { velocity.x = 0f; } setJumpState(JumpState.Ascending); break; } float n2 = (Time.time - jumpStateStart) / timeToJumpApex; n2 = Mathf.Clamp(n2, 0, 1); switch (jumpDirection) { case JumpDirection.Neutral: velocity.x = Mathf.SmoothDamp(velocity.x, targetX, ref velocityXSmoothing, timeToMaxAirmoveSpeed); break; case JumpDirection.Left: velocity.x = Mathf.SmoothDamp(velocity.x, targetX < 0 ? targetX : 0, ref velocityXSmoothing, timeToMaxAirmoveSpeed); break; case JumpDirection.Right: velocity.x = Mathf.SmoothDamp(velocity.x, targetX > 0 ? targetX : 0, ref velocityXSmoothing, timeToMaxAirmoveSpeed); break; } // fall speed is increasing when descending velocity.y += gravity * Time.deltaTime * (Mathf.Clamp(4 * n2 * n2, 1, 4)); break; case JumpState.CoyoteTime: tracer.color = Color.blue; velocity.x = Mathf.SmoothDamp(velocity.x, targetX, ref velocityXSmoothing, timeToMaxRunSpeed); if (dash()) { break; // } else if (Input.GetButtonDown("Jump")) { } else if (controls.Gameplay.Jump.triggered) { setJumpState(JumpState.Ascending); velocity.y = jumpVelocity; } else { float elapsedCoyoteTime = Time.time - jumpStateStart; float coyoteTimeRemaining = coyoteTime - elapsedCoyoteTime; if (coyoteTimeRemaining < 0) { setJumpState(JumpState.Falling); } velocity.y += gravity * Time.deltaTime; } break; case JumpState.Falling: tracer.color = Color.grey; // if (Input.GetButtonDown("Jump") && jumpCount < maxJumps) { if (controls.Gameplay.Jump.triggered && jumpCount < maxJumps) { velocity.y = jumpVelocity; if (input.x >= 0.25f) { velocity.x = moveSpeed*maxForwardJumpBoost; } else if (input.x <= -0.25f) { velocity.x = -moveSpeed*maxForwardJumpBoost; } else { velocity.x = 0f; } setJumpState(JumpState.Ascending); break; } switch (jumpDirection) { case JumpDirection.Neutral: velocity.x = Mathf.SmoothDamp(velocity.x, targetX, ref velocityXSmoothing, timeToMaxAirmoveSpeed); break; case JumpDirection.Left: velocity.x = Mathf.SmoothDamp(velocity.x, targetX < 0 ? targetX : 0, ref velocityXSmoothing, timeToMaxAirmoveSpeed); break; case JumpDirection.Right: velocity.x = Mathf.SmoothDamp(velocity.x, targetX > 0 ? targetX : 0, ref velocityXSmoothing, timeToMaxAirmoveSpeed); break; } velocity.y += gravity * Time.deltaTime; break; default: throw new System.Exception("bad jump state: " + jumpState); } if (velocity.y < maxFallSpeed) { velocity.y = maxFallSpeed; } moveController.Move(velocity * Time.deltaTime); if (jumpState == JumpState.Grounded && !moveController.isGrounded) { setJumpState(JumpState.CoyoteTime); } if (jumpState != JumpState.Grounded && moveController.isGrounded) { setJumpState(JumpState.Grounded); } // wasHoldingJump = Input.GetButton("Jump"); wasHoldingJump = controls.Gameplay.Jump.phase == InputActionPhase.Started; CheckCollisions(); } void CheckCollisions() { if (moveController.collisions.below) { Crumble crumble = moveController.collisions.below.GetComponent(); if (crumble) { crumble.Hit(); } CollideWith(moveController.collisions.below); } CollideWith(moveController.collisions.above); CollideWith(moveController.collisions.left); CollideWith(moveController.collisions.right); } void CollideWith(Collider2D other) { if (other == null) { return; } TouchHazard hazard = other.GetComponent(); if (hazard) { if (jumpState == JumpState.Dash) { Destroy(hazard.gameObject); } else { SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex) ; } } Smashable smashable = other.GetComponent(); if (smashable) { if (jumpState == JumpState.Dash) { Destroy(smashable.gameObject); } } Seeker seeker = other.GetComponent(); if (seeker) { SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex) ; } } public void OnEnable() { Debug.Log("Enabling the controls!"); if (controls == null) { controls = new PlayerControls(); } controls.Enable(); controls.Gameplay.Enable(); controls.Gameplay.Jump.Enable(); } public void OnDisable() { Debug.Log("Disabling the controls!"); controls.Disable(); controls.Gameplay.Disable(); controls.Gameplay.Jump.Disable(); } void OnDestroy() { } bool dash() { // if (!Input.GetButtonDown("Fire1")) { if (!controls.Gameplay.Dash.triggered) { return false; } if ((Time.time - lastDashTime) < dashCooldown) { return false; } if (velocity.x >= 0) { velocity.x = dashSpeed; } else { velocity.x = -dashSpeed; } velocity.y = 0; lastDashTime = Time.time; setJumpState(JumpState.Dash); StartCoroutine(dashFade()); return true; } public IEnumerator dashFade() { Material m = gameObject.GetComponent().materials[0]; float dt = 0; float h = 0; float s = 0; float v = 0; Color.RGBToHSV(m.color, out h, out s, out v); float maxSaturation = s; while (dt < dashCooldown) { s = Mathf.Clamp(dt / dashCooldown, 0f, maxSaturation); m.color = Color.HSVToRGB(h, s, v); dt += Time.deltaTime; yield return true; } } void setJumpState(JumpState state) { leftDashIndicator.SetActive(state == JumpState.Dash && velocity.x < 0); rightDashIndicator.SetActive(state == JumpState.Dash && velocity.x >= 0); if (jumpState != JumpState.Ascending && state == JumpState.Ascending) { jumpStartTime = Time.time; } jumpState = state; jumpStateStart = Time.time; if (velocity.x == 0) { jumpDirection = JumpDirection.Neutral; } else if (velocity.x > 0) { jumpDirection = JumpDirection.Right; } else { jumpDirection = JumpDirection.Left; } if (state == JumpState.Grounded) { jumpCount = 0; } if (state == JumpState.Ascending) { jumpCount++; } } /* Possible JumpState transitions: Grounded -> Ascending : a normal jump Grounded -> Dash : a normal dash Grounded -> CoyoteTime : player has walked off ledge CoyoteTime -> Ascending : player has jumped after leaving a ledge CoyoteTime -> Falling : player has walked off of a ledge and is now falling Ascending -> Apex : player has reached the top of their jump normally Apex -> Descending : player has reached the top of their jump and is now falling */ public enum JumpState { // The player is on the ground Grounded, // The player has not jumped; they have walked off of a platform without // jumping CoyoteTime, // The player is rising in a jump Ascending, // The player has reached the apex of their jump, where they will // briefly remain to give a feeling of lightness Apex, // The player, having jumped, is in a controlled descent following the // jump Descending, // The player is descending but without control; they are falling but // did not initially jump. Falling, Dash, } public enum JumpDirection { Neutral, Left, Right, } }