You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity 2D角色控制器穿模问题求助,附代码及尝试过程

2D角色控制器穿模问题修复求助

本人是Unity新手,编写的CharacterController2D脚本存在角色穿模(控制器穿过地面)问题。多次调整代码逻辑顺序后,要么无法跳跃,要么仍存在穿模情况,附上代码寻求修复帮助:

using UnityEngine;

[RequireComponent(typeof(BoxCollider2D))]
public class CharacterController2D : MonoBehaviour
{
    [SerializeField, Tooltip("Max speed, in units per second, that the character moves.")]
    float speed = 9;

    [SerializeField, Tooltip("Acceleration while grounded.")]
    float walkAcceleration = 75;

    [SerializeField, Tooltip("Acceleration while in the air.")]
    float airAcceleration = 30;

    [SerializeField, Tooltip("Deceleration applied when the character is grounded and not attempting to move.")]
    float groundDeceleration = 70;

    [SerializeField, Tooltip("Max height the character will jump regardless of gravity")]
    float jumpHeight = 4;

    private BoxCollider2D boxCollider;
    private Vector2 velocity;
    private bool grounded;

    private void Awake()
    {
        boxCollider = GetComponent<BoxCollider2D>();
    }

    private void Update()
    {
        // Handle character movement
        float moveInput = Input.GetAxisRaw("Horizontal");
        float Acceleration = grounded ? walkAcceleration : airAcceleration;
        float Deceleration = grounded ? groundDeceleration : 0;

        // Calculate velocity based on input
        if (moveInput != 0)
        {
            velocity.x = Mathf.MoveTowards(velocity.x, speed * moveInput, Acceleration * Time.deltaTime);
        }
        else
        {
            velocity.x = Mathf.MoveTowards(velocity.x, 0, Deceleration * Time.deltaTime);
        }

        // Apply gravity
        velocity.y += Physics2D.gravity.y * Time.deltaTime;

        // Move the character
        transform.Translate(velocity * Time.deltaTime);

        // Handle jumping
        if (grounded && Input.GetButtonDown("Jump"))
        {
            velocity.y = Mathf.Sqrt(2 * jumpHeight * Mathf.Abs(Physics2D.gravity.y));
        }

        // Detect collisions and update grounded status
        grounded = false;
        Collider2D[] hits = Physics2D.OverlapBoxAll(transform.position, boxCollider.size, 0);

        foreach (Collider2D hit in hits)
        {
            if (hit == boxCollider)
            {
                continue;
            }

            ColliderDistance2D colliderDistance = hit.Distance(boxCollider);

            if (colliderDistance.isOverlapped)
            {
                transform.Translate(colliderDistance.pointA - colliderDistance.pointB);

                if (Vector2.Angle(colliderDistance.normal, Vector2.up) < 90 && velocity.y < 0)
                {
                    grounded = true;
                }
            }
        }
    }
}

问题根源与修复方案

原代码核心问题是逻辑顺序错误,以及碰撞处理时未正确重置垂直速度,导致穿模或跳跃异常。以下是关键修复点:

  1. 调整逻辑执行顺序:先检测碰撞更新grounded状态,再处理跳跃和移动,确保状态判断基于当前帧实际碰撞情况
  2. 落地时重置垂直速度:清除向下的速度,避免持续穿透地面
  3. 修正跳跃触发时机:基于当前帧的grounded状态判断,解决无法跳跃的问题

修改后的完整代码

using UnityEngine;

[RequireComponent(typeof(BoxCollider2D))]
public class CharacterController2D : MonoBehaviour
{
    [SerializeField, Tooltip("Max speed, in units per second, that the character moves.")]
    float speed = 9;

    [SerializeField, Tooltip("Acceleration while grounded.")]
    float walkAcceleration = 75;

    [SerializeField, Tooltip("Acceleration while in the air.")]
    float airAcceleration = 30;

    [SerializeField, Tooltip("Deceleration applied when the character is grounded and not attempting to move.")]
    float groundDeceleration = 70;

    [SerializeField, Tooltip("Max height the character will jump regardless of gravity")]
    float jumpHeight = 4;

    private BoxCollider2D boxCollider;
    private Vector2 velocity;
    private bool grounded;

    private void Awake()
    {
        boxCollider = GetComponent<BoxCollider2D>();
    }

    private void Update()
    {
        // 1. 优先检测碰撞,更新当前帧的 grounded 状态
        grounded = false;
        Collider2D[] hits = Physics2D.OverlapBoxAll(transform.position, boxCollider.size, 0);

        foreach (Collider2D hit in hits)
        {
            if (hit == boxCollider) continue;

            ColliderDistance2D colliderDistance = hit.Distance(boxCollider);
            if (colliderDistance.isOverlapped)
            {
                // 修正碰撞重叠
                transform.Translate(colliderDistance.pointA - colliderDistance.pointB);

                // 判断是否落地:碰撞法线向上,且角色有向下运动趋势
                if (Vector2.Angle(colliderDistance.normal, Vector2.up) < 90 && velocity.y < 0)
                {
                    grounded = true;
                    velocity.y = 0; // 落地时重置垂直速度,彻底停止向下运动
                }
            }
        }

        // 2. 基于当前帧的 grounded 状态处理跳跃
        if (grounded && Input.GetButtonDown("Jump"))
        {
            velocity.y = Mathf.Sqrt(2 * jumpHeight * Mathf.Abs(Physics2D.gravity.y));
        }

        // 3. 处理水平移动输入
        float moveInput = Input.GetAxisRaw("Horizontal");
        float acceleration = grounded ? walkAcceleration : airAcceleration;
        float deceleration = grounded ? groundDeceleration : 0;

        if (moveInput != 0)
        {
            velocity.x = Mathf.MoveTowards(velocity.x, speed * moveInput, acceleration * Time.deltaTime);
        }
        else
        {
            velocity.x = Mathf.MoveTowards(velocity.x, 0, deceleration * Time.deltaTime);
        }

        // 4. 应用重力
        velocity.y += Physics2D.gravity.y * Time.deltaTime;

        // 5. 最后执行角色移动
        transform.Translate(velocity * Time.deltaTime);
    }
}

关键改动说明

  • 将碰撞检测移至逻辑最前端,确保后续跳跃、移动使用当前帧的真实状态
  • 落地时添加velocity.y = 0,避免残留向下速度导致穿模
  • 调整跳跃逻辑到碰撞检测之后,解决原代码中跳跃判断依赖上一帧状态的问题
  • 统一变量命名为小写开头,符合C#编码规范

内容的提问来源于stack exchange,提问作者Rashmika Bandara

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 01:04:56