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; } } } } }
问题根源与修复方案
原代码核心问题是逻辑顺序错误,以及碰撞处理时未正确重置垂直速度,导致穿模或跳跃异常。以下是关键修复点:
- 调整逻辑执行顺序:先检测碰撞更新
grounded状态,再处理跳跃和移动,确保状态判断基于当前帧实际碰撞情况 - 落地时重置垂直速度:清除向下的速度,避免持续穿透地面
- 修正跳跃触发时机:基于当前帧的
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
相关产品推荐
相关产品推荐

