I was making a 2D platformer prototype code. I tried to add coyote timing so many times on the code but it always caused many different bugs (i.e infinite jump window, triple jump, not being able to jump and etc.). So I’ve decided to make something else - now the code is a lot more complete, but even then I can’t use coyote timing. Right now, my double jump is only working a few moments after the jump. What should I do?
(Notes: if you have any overall hints on how to improve the code, I’m taking those no problem.)
using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices.ComTypes;
using DG.Tweening;
using Unity.PlasticSCM.Editor.WebApi;
using UnityEditor;
public class movimento : MonoBehaviour
{
#region Variáveis de controle
private bool isJumping = false;
private bool isRunning = false;
private bool isDashing = false;
private bool isDashCooldown = false;
private bool isSprintDash = false;
private bool isFacingRight = true;
private int jumpCount = 0; // variável de contagem de pulo
private const int maxJumpCount = 2; // número máximo de pulos
#endregion
#region Variáveis de entrada
private bool jumpKeyPressed;
private bool runKeyPressed;
private bool dashKeyPressed;
private float horizontalAxis;
#endregion
#region Componentes
[SerializeField] private Rigidbody2D rb;
[SerializeField] private TrailRenderer td;
[SerializeField] private Transform groundCheck;
[SerializeField] private LayerMask groundLayer;
[SerializeField] private Transform wallCheck;
[SerializeField] private LayerMask wallLayer;
#endregion
#region Parâmetros ajustáveis
[SerializeField]
private float forçaDoPulo = 15f;
[SerializeField]
private float jumpForceDecreaseFactor = 0.5f; // fator de diminuição da força do pulo
[SerializeField]
private float gravidade = 60f;
[SerializeField]
private float velocidade = 12f;
[SerializeField]
private float velocidadeCorrida = 18f;
[SerializeField]
private float transicaoVelocidade = 10f;
[SerializeField]
private float dashSpeed = 20f;
[SerializeField]
private float dashDuration = 0.2f;
[SerializeField]
private float dashCooldown = 0.1f;
[SerializeField]
private float jumpBufferLength = 0.1f;
[SerializeField]
private float fallSpeedMultiplier = 2f;
[SerializeField]
private float defaultGravityScale = 5f;
#endregion
#region Variáveis privadas
private float jumpBufferCount;
private Vector2 dashVelocity;
private float fallTime;
private bool isDashAvailable = true;
private float originalDashSpeed;
private bool isWallSliding;
[SerializeField] private float wallSlidingSpeed = 2f;
private bool isWallJumping;
private float wallJumpingDirection;
[SerializeField]
private float wallJumpingTime = 0.2f;
private float wallJumpingCounter;
[SerializeField]
private float wallJumpingDuration = 0.4f;
[SerializeField]
private Vector2 wallJumpingPower = new Vector2(8f, 16f);
private bool hasAirJumped = false; // Se o personagem já pulou no ar
[SerializeField] private float coyoteJumpDelay = 0.2f;
private float coyoteTimer;
private float jumpTimer;
[SerializeField] private float jumpDelay = 0.25f;
#endregion
#region Unity Callbacks
private void Start()
{
if (!TryGetComponent<Rigidbody2D>(out rb))
{
Debug.LogError("Componente Rigidbody2D não encontrado!");
this.enabled = false;
}
if (!TryGetComponent<TrailRenderer>(out td))
{
Debug.LogError("Componente TrailRenderer não encontrado!");
this.enabled = false;
}
originalDashSpeed = dashSpeed;
}
private void Update()
{
ReadInput();
HandleInput();
WallSlide();
WallJump();
if (!isWallJumping)
{
Flip();
}
if (isGrounded() || IsWalled())
{
coyoteTimer = Time.time + coyoteJumpDelay;
if (isJumping) // Se estava pulando e agora está no chão
{
isJumping = false;
jumpCount = 0; // Resetamos a contagem de pulos
hasAirJumped = false; // Resetamos o pulo no ar
}
}
else
{
isJumping = true;
}
}
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Chao"))
{
isJumping = false;
isDashAvailable = true;
}
}
private void OnCollisionExit2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Chao"))
{
isJumping = true;
}
}
#endregion
#region Leitura das Entradas
private void ReadInput()
{
jumpKeyPressed = Input.GetKeyDown(KeyCode.Space);
runKeyPressed = Input.GetKey(KeyCode.LeftShift); // Alterado para GetKey
dashKeyPressed = Input.GetKeyDown(KeyCode.X);
horizontalAxis = Input.GetAxis("Horizontal");
}
#endregion
#region Lógica do jogador
private void HandleInput()
{
if (jumpKeyPressed)
{
jumpBufferCount = jumpBufferLength;
}
jumpBufferCount -= Time.deltaTime;
// Adicionamos a condição de que o jogador só pode pular se o tempo de coyote ainda não tiver passado
if ((jumpCount < maxJumpCount && jumpBufferCount >= 0) && coyoteTimer > Time.time)
{
Pulo();
}
if (runKeyPressed) // Corre quando a tecla está pressionada
{
isRunning = true;
}
else // Para de correr quando a tecla é solta
{
isRunning = false;
}
if (!isDashing && dashKeyPressed)
{
if (!isJumping || (isJumping && isDashAvailable)) // Permite o dash se não estiver no ar ou se estiver no ar, mas o dash estiver disponível
{
StartCoroutine(Dash(horizontalAxis));
isDashAvailable = false; // Desativa o dash após ser executado
}
}
}
private void Pulo()
{
// Se o jogador já fez o número máximo de pulos, retornamos
if (jumpCount >= maxJumpCount) return;
rb.velocity = new Vector2(rb.velocity.x, 0f);
// A força do pulo diminui a cada pulo adicional
rb.AddForce(Vector2.up * forçaDoPulo * (1 - jumpForceDecreaseFactor * (jumpCount - 1)), ForceMode2D.Impulse);
isJumping = true;
jumpBufferCount = 0;
// Incrementamos a contagem de pulos
jumpCount++;
}
private IEnumerator Dash(float direction)
{
float usedDashSpeed = dashSpeed;
if (isRunning && !isSprintDash)
{
usedDashSpeed += ((velocidadeCorrida + velocidade) / 3);
isSprintDash = true;
Dash(horizontalAxis);
Camera.main.transform.DOComplete();
Camera.main.transform.DOShakePosition(.3f, 1.2f, 14, 90, false, true);
}
if (!isRunning)
Camera.main.transform.DOComplete();
Camera.main.transform.DOShakePosition(.2f, .5f, 14, 90, false, true);
isDashing = true;
isDashCooldown = true;
rb.gravityScale = 0f;
float dashDirection = 0f;
if (direction < 0)
dashDirection = -1f;
else if (direction > 0)
dashDirection = 1f;
else
dashDirection = transform.localScale.x;
dashVelocity = new Vector2(usedDashSpeed * dashDirection, 0f);
float endTime = Time.time + dashDuration;
while (Time.time < endTime)
{
float t = (endTime - Time.time) / dashDuration;
rb.velocity = Vector2.Lerp(rb.velocity, dashVelocity, 1f - t);
yield return null;
}
rb.gravityScale = defaultGravityScale;
isDashing = false;
isSprintDash = false;
dashSpeed = originalDashSpeed;
yield return new WaitForSeconds(dashCooldown);
isDashCooldown = false;
}
#endregion
#region Física
private void FixedUpdate()
{
float velocidadeAtual = isRunning ? velocidadeCorrida : velocidade;
Vector2 velocidadeHorizontal = new Vector2(horizontalAxis * velocidadeAtual, rb.velocity.y);
if (isDashing)
{
velocidadeHorizontal = dashVelocity;
}
rb.velocity = Vector2.Lerp(rb.velocity, velocidadeHorizontal, transicaoVelocidade * Time.fixedDeltaTime);
if (!isJumping)
{
fallTime += Time.deltaTime;
float fallSpeed = Mathf.Sqrt(fallTime) * fallSpeedMultiplier;
if (rb.velocity.y < -fallSpeed)
{
rb.velocity = new Vector2(rb.velocity.x, -fallSpeed);
}
}
else
{
fallTime = 0;
}
ApplyGravity();
}
private void ApplyGravity()
{
if (!isDashing)
{
// Aqui a gravidade é aplicada independente da direção vertical do personagem
rb.AddForce(new Vector2(0f, -gravidade), ForceMode2D.Force);
}
}
private void Flip()
{
if (!isFacingRight && horizontalAxis > 0f || isFacingRight && horizontalAxis < 0f)
{
isFacingRight = !isFacingRight;
Vector3 localscale = transform.localScale;
localscale.x *= -1f;
transform.localScale = localscale;
}
}
private bool isGrounded()
{
return Physics2D.OverlapCircle(groundCheck.position, 0.2f, groundLayer);
}
private bool IsWalled()
{
return Physics2D.OverlapCircle(wallCheck.position, 0.2f, wallLayer);
}
private void WallSlide()
{
if (IsWalled() && !isGrounded() && horizontalAxis != 0f)
{
isWallSliding = true;
rb.velocity = new Vector3(rb.velocity.x, Mathf.Clamp(rb.velocity.y, -wallSlidingSpeed, float.MaxValue));
}
else
{
isWallSliding = false;
}
}
private void WallJump()
{
if (isWallSliding)
{
isWallJumping = false;
wallJumpingDirection = -transform.localScale.x;
wallJumpingCounter = wallJumpingTime;
CancelInvoke(nameof(StopWallJumping));
}
else
{
wallJumpingCounter -= Time.deltaTime;
}
if (jumpKeyPressed && wallJumpingCounter > 0f)
{
isWallJumping = true;
jumpCount = 0; // Resetamos a contagem de pulos após um wall jump
hasAirJumped = false; // Resetamos o pulo no ar após um wall jump
rb.velocity = new Vector2(wallJumpingDirection * wallJumpingPower.x, wallJumpingPower.y);
wallJumpingCounter = 0f;
if (transform.localScale.x != wallJumpingDirection)
{
isFacingRight = !isFacingRight;
Vector3 localScale = transform.localScale;
localScale.x *= -1f;
transform.localScale = localScale;
}
Invoke(nameof(StopWallJumping), wallJumpingDirection);
}
}
private void StopWallJumping()
{
isWallJumping = false;
}
#endregion
}