Привет всем.
У меня возникла проблема, связанная с механикой Wall Jump и взаимодействием игрока с стенами в 2D-платформере на Unity.
Я сделал систему сбора способностей для игрока на уровне (двойной прыжок, рывок, прыжки от стен и т. д.). Изначально соответствующие скрипты добавляются к игровому персонажу и активируются при сборе триггеров (скиллов).
Когда игрок только начинает игру и имеет возможность только бегать и прыгать (это как пример, главное что игрок не имеет способности прыжков по стене), если прыгнуть на стену, то перс "прилипает" к стене, если удерживать стрелку в ее направлении или скользит, если стрелку отпустить. Как можно настроить механику таким образом, чтобы до активации скилла для прыжков по стенам персонаж не взаимодействовал со стеной вообще, а просто упирался в нее и падал?
Для наглядности скрипт контроллера с комментариями
spoiler
using UnityEngine;
// Контроллер игрока для управления движением и анимациями
public class PlayerController : MonoBehaviour
{
// Настройки движения и прыжков
public float moveSpeed = 5f;
public float jumpForce = 5f;
public float fallMultiplier = 2.5f;
public float lowJumpMultiplier = 2f;
public float maxFallSpeed = -20f;
public float coyoteTime = 0.1f;
public LayerMask groundLayer;
public Transform groundCheckPoint;
// Компоненты и переменные состояния
private Rigidbody2D rb;
private Animator animator;
private SkillManager skillManager;
private bool isGrounded;
private float groundCheckRadius = 0.2f;
private float coyoteCounter;
private bool canJump = true;
public bool CanMove { get; set; } = true;
// Вспомогательные переменные для анимации
private bool wasGrounded;
private bool jumpPressed;
private bool isRunHitting;
private bool isIdleHitting;
public bool IsJumping => animator.GetCurrentAnimatorStateInfo(0).IsName("Jump");
public bool IsFalling => animator.GetCurrentAnimatorStateInfo(0).IsName("Fall");
public bool IsRunning => animator.GetCurrentAnimatorStateInfo(0).IsName("Run");
// Геттеры и сеттеры для компонентов и свойств игрока
public Animator Animator => GetComponent<Animator>();
public bool IsGrounded => isGrounded;
public Rigidbody2D Rb => rb;
public float Horizontal => Input.GetAxisRaw("Horizontal");
[SerializeField] private float playerWeight = 5f; // вес игрока
private void Start()
{
// Инициализация компонентов
rb = GetComponent<Rigidbody2D>();
animator = GetComponent<Animator>();
skillManager = GetComponent<SkillManager>();
}
// Возвращает, смотрит ли игрок вправо
public bool IsFacingRight()
{
return transform.localScale.x > 0;
}
// Включение компонента навыка WallJump
public void EnableIWallJumpSkill()
{
IWallJumpSkill wallJumpSkill = GetComponent<IWallJumpSkill>();
if (wallJumpSkill != null)
{
(wallJumpSkill as MonoBehaviour).enabled = true;
}
}
// Возвращает вес игрока
public float GetPlayerWeight()
{
return playerWeight;
}
private void Update()
{
// Выход, если игрок не может двигаться
if (!CanMove) return;
// Обработка горизонтального ввода
float horizontalInput = Horizontal;
Vector2 velocity = rb.velocity;
velocity.x = horizontalInput * moveSpeed;
rb.velocity = velocity;
// Обновление направления игрока
if (horizontalInput > 0)
{
transform.localScale = new Vector3(1, 1, 1);
}
else if (horizontalInput < 0)
{
transform.localScale = new Vector3(-1, 1, 1);
}
// Проверка, находится ли игрок на земле
isGrounded = Physics2D.OverlapCircle(groundCheckPoint.position, groundCheckRadius, groundLayer);
isRunHitting = Input.GetButtonDown("Fire1") && Mathf.Abs(rb.velocity.x) > 0;
isIdleHitting = Input.GetButtonDown("Fire1") && Mathf.Abs(rb.velocity.x) == 0;
// Обработка времени "коиота" для прыжков
if (isGrounded)
{
coyoteCounter = coyoteTime;
canJump = true;
}
else
{
coyoteCounter -= Time.deltaTime;
}
// Выполнение прыжка, если возможно
if (coyoteCounter > 0 && Input.GetButtonDown("Jump") && canJump)
{
rb.AddForce(new Vector2(0, jumpForce), ForceMode2D.Impulse);
coyoteCounter = 0;
canJump = false;
}
// Ускорение падения и коротких прыжков
if (rb.velocity.y < 0)
{
rb.velocity += Vector2.up * Physics2D.gravity.y * (fallMultiplier - 1) * Time.deltaTime;
}
else if (rb.velocity.y > 0 && !Input.GetButton("Jump"))
{
rb.velocity += Vector2.up * Physics2D.gravity.y * (lowJumpMultiplier - 1) * Time.deltaTime;
}
// Ограничение максимальной скорости падения
rb.velocity = new Vector2(rb.velocity.x, Mathf.Clamp(rb.velocity.y, maxFallSpeed, Mathf.Infinity));
// Вызов методов выполнения скилов из SkillManager
skillManager.ExecuteSkills();
// Обновление параметров аниматора
animator.SetFloat("Speed", Mathf.Abs(rb.velocity.x));
animator.SetBool("IsGrounded", isGrounded);
animator.SetFloat("VerticalVelocity", rb.velocity.y);
// Дополнительные параметры аниматора
animator.SetBool("IsRunning", horizontalInput != 0);
animator.SetBool("IsJumping", !isGrounded && rb.velocity.y > 0);
animator.SetBool("IsFalling", !isGrounded && rb.velocity.y < 0);
// Обновление параметра JumpReleased
bool jumpButtonReleased = !Input.GetButton("Jump");
animator.SetBool("JumpReleased", jumpPressed && jumpButtonReleased);
jumpPressed = !jumpButtonReleased;
// Обновление параметра IsLanding
if (!wasGrounded && isGrounded)
{
animator.SetBool("IsLanding", true);
}
else
{
animator.SetBool("IsLanding", false);
}
// Запомнить текущее состояние isGrounded
wasGrounded = isGrounded;
}
// Разворот игрока
public void Flip()
{
Vector3 currentScale = transform.localScale;
currentScale.x *= -1;
transform.localScale = currentScale;
}
}
Так же ещё скрипт способности для прыжков по стене или по стенам
spoiler
using UnityEngine;
public class WallJumpSkill : MonoBehaviour
{
public Transform wallCheckPoint;
public LayerMask wallLayer;
public float wallJumpForce = 10f;
public Vector2 wallJumpDirection = new Vector2(1, 2);
public float wallJumpDuration = 0.2f;
public float wallSlideSpeed = 2f;
public float wallSlideDelay = 0.5f;
private PlayerController playerController;
private Rigidbody2D rb;
private bool isTouchingWall;
private bool isWallSliding;
private bool isWallJumping;
private int wallSide = -1;
private float wallSlideTimer;
private void Start()
{
playerController = GetComponent<PlayerController>();
rb = playerController.Rb;
}
private void Update()
{
Vector2 wallCheckDirection = playerController.IsFacingRight() ? Vector2.right : Vector2.left;
isTouchingWall = Physics2D.Raycast(wallCheckPoint.position, wallCheckDirection, 0.1f, wallLayer);
isWallSliding = isTouchingWall && !playerController.IsGrounded && Mathf.Abs(playerController.Horizontal) > 0 && rb.velocity.y < 0 && wallSlideTimer <= 0;
if (isWallSliding)
{
rb.velocity = new Vector2(rb.velocity.x, -wallSlideSpeed);
wallSlideTimer = wallSlideDelay; // Сброс таймера при скольжении по стене
}
else if (isTouchingWall && Mathf.Abs(playerController.Horizontal) == 0 && rb.velocity.y < 0) // Проверка отпускания клавиши
{
rb.velocity = new Vector2(rb.velocity.x, -wallSlideSpeed);
wallSlideTimer = wallSlideDelay; // Таймер при скольжении по стене
}
else if (wallSlideTimer > 0) // Таймер задержки скольжения по стене
{
wallSlideTimer -= Time.deltaTime;
}
if (isTouchingWall && !playerController.IsGrounded && Input.GetButtonDown("Jump"))
{
isWallJumping = true;
wallSide = playerController.IsFacingRight() ? -1 : 1;
Invoke("StopWallJump", wallJumpDuration);
}
if (isWallJumping)
{
rb.velocity = new Vector2(wallJumpForce * wallJumpDirection.x * wallSide, wallJumpForce * wallJumpDirection.y);
}
}
private void StopWallJump()
{
isWallJumping = false;
}
}
У меня также возникла проблема с прыжками между двумя стенами: когда персонаж пытается перепрыгнуть на другую стену, он, кажется, скользит и не всегда удается выполнить прыжок вверх между ними. Возможно, стоит внедрить таймер при смене направления стрелок или есть другой способ качественной реализации данной механики?