Grayfox90
@Grayfox90
Пушистый дядь

Как улучшить систему взаимодействия перса со стенами в 2д метроидвании?

Привет всем.

У меня возникла проблема, связанная с механикой 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;
    }
}



У меня также возникла проблема с прыжками между двумя стенами: когда персонаж пытается перепрыгнуть на другую стену, он, кажется, скользит и не всегда удается выполнить прыжок вверх между ними. Возможно, стоит внедрить таймер при смене направления стрелок или есть другой способ качественной реализации данной механики?
  • Вопрос задан
  • 47 просмотров
Пригласить эксперта
Ответы на вопрос 1
@Altere
НАЧИНАЮЩИЙ разработчик на Unity
тут проблема не в том, что механика заранее активирована, а в том самом скрипте передвижения базового. У меня такая же проблема и я понятия не имею, что с этим делать. Короче, твои доп. перки тут не причём, это всё из-за самой ходьбы (да, ходьбы, ведь даже если убрать скрипт для прыжка и врезаться в стену когда в воздухе всё равно прилипнешь)
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы