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

2D ECS游戏碰撞修正错误推送玩家方向的问题求助

2D ECS游戏落地碰撞修正问题解决

问题场景

采用ECS架构开发2D游戏,基于MonoGame.Extended框架实现碰撞系统,玩家可左右移动、小幅度跳跃,通过Velocity组件控制每帧移动逻辑,玩家与场景Tile均使用矩形碰撞盒。当前存在问题:玩家跳跃落地时,若碰撞交集的宽度小于高度,会被错误地横向推送,而非向上修正碰撞位置。

移动控制核心代码

private const float MaximumVerticalVelocity = 15f;
private const float VerticalAcceleration = 1f;
private const float MaximumHorizontalVelocity = 10f;
private const float HorizontalAcceleration = 1f;

private void move(GameTime gameTime, int entityID) {
    float speed = HorizontalAcceleration;
    var hitbox = (RectangleF) _colliderMapper.Get(entityID).Bounds;
    var keyInputs = _inputMapper.Get(entityID);
    var velocity = _velocityMapper.Get(entityID);
    var position = _positionMapper.Get(entityID);

    updateVelocity(velocity, 0, VerticalAcceleration);
    if (Keyboard.GetState().IsKeyDown(keyInputs[PlayerAction.Jump]) &&
        hitbox.Bottom < Game.Main.MapHeight && hitbox.Bottom > 0 && (
        !Game.Main.TileIsBlank(position.X, hitbox.Bottom) ||
        !Game.Main.TileIsBlank(hitbox.Right - 1, hitbox.Bottom)
        )) {
            velocity.DirY = -11f;
    }
    if (Keyboard.GetState().IsKeyDown(keyInputs[PlayerAction.Sprint])) {
        speed *= 2;
    }

    bool leftDown = Keyboard.GetState().IsKeyDown(keyInputs[PlayerAction.Left]);
    bool rightDown = Keyboard.GetState().IsKeyDown(keyInputs[PlayerAction.Right]);
    if (leftDown && !(rightDown && velocity.DirX <= 0)) {
        updateVelocity(velocity, -speed, 0);
    }
    else if (!leftDown && velocity.DirX <= 0) {
        updateVelocity(velocity, speed, 0, 0, MaximumVerticalVelocity);
    }
    if (rightDown && !(leftDown && velocity.DirX >= 0)) {
        updateVelocity(velocity, speed, 0);
    }
    else if (!rightDown && velocity.DirX >= 0) {
        updateVelocity(velocity, -speed, 0, 0, MaximumVerticalVelocity);
    }
    if (!leftDown && !rightDown && velocity.DirX != 0) {
        if (velocity.DirX < 0) {
            updateVelocity(velocity, speed, 0, 0, MaximumVerticalVelocity);
        }
        else {
            updateVelocity(velocity, -speed, 0, 0, MaximumVerticalVelocity);
        }
    }
    position.X += velocity.DirX;
    position.Y += velocity.DirY;
}

private void updateVelocity(Velocity velocity, float x, float y, float xLimit, float yLimit) {
    if ((x >= 0 && velocity.DirX + x < xLimit) || (x < 0 && velocity.DirX + x > -xLimit)) {
        velocity.DirX += x;
    }
    else {
        if (x >= 0) {
            velocity.DirX = xLimit;
        }
        else {
            velocity.DirX = -xLimit;
        }
    }
    
    if ((y >= 0 && velocity.DirY + y < yLimit) || (y < 0 && velocity.DirY + y > -yLimit)) {
        velocity.DirY += y;
    }
    else {
        if (y >= 0) {
            velocity.DirY = yLimit;
        }
        else {
            velocity.DirY = -yLimit;
        }
    }
}

private void updateVelocity(Velocity velocity, float x, float y) => 
        updateVelocity(velocity, x, y, MaximumHorizontalVelocity, MaximumVerticalVelocity);

碰撞处理核心代码(原问题代码)

private void onCollision(int entityID, object sender, CollisionEventArgs args) {
        if (args.Other is StaticCollider) {
            var velocity = _velocityMapper.Get(entityID);
            var position = _positionMapper.Get(entityID);
            var collider = _colliderMapper.Get(entityID);
            var intersection = collider.RectBounds.Intersection((RectangleF) args.Other.Bounds);
            var otherBounds = (RectangleF) args.Other.Bounds;

            if (intersection.Height > intersection.Width) {
                if (collider.RectBounds.X < otherBounds.Position.X) {
                    position.X -= intersection.Width;
                }
                else {
                    position.X += intersection.Width;
                }
                velocity.DirX = 0;
            }
            else {
                if (collider.RectBounds.Y < otherBounds.Y) { 
                    position.Y -= intersection.Height;
                }
                else {
                    position.Y += intersection.Height;
                }
                velocity.DirY = 0;
            }
            collider.RectBounds.X = position.X;
            collider.RectBounds.Y = position.Y;
        }
    }

问题根源

原碰撞逻辑仅通过碰撞交集的宽高比例判断修正方向,完全忽略了玩家的移动速度方向。落地时玩家处于向下移动状态(velocity.DirY > 0),此时应该优先处理垂直方向的碰撞修正,但如果此时交集宽度小于高度,逻辑会错误地触发横向修正。

解决方案

修改碰撞处理逻辑,结合玩家的速度方向和碰撞位置关系来优先处理正确的碰撞轴:

  1. 当玩家有向下的垂直速度时,优先判断是否是落地碰撞(玩家碰撞盒底部接触Tile顶部),优先处理垂直修正
  2. 其他场景再结合交集宽高和速度方向判断修正轴

修改后的碰撞处理代码

private void onCollision(int entityID, object sender, CollisionEventArgs args) {
    if (args.Other is StaticCollider) {
        var velocity = _velocityMapper.Get(entityID);
        var position = _positionMapper.Get(entityID);
        var collider = _colliderMapper.Get(entityID);
        var playerBounds = collider.RectBounds;
        var tileBounds = (RectangleF) args.Other.Bounds;
        var intersection = playerBounds.Intersection(tileBounds);

        // 优先处理落地碰撞(玩家向下移动,碰撞Tile顶部)
        bool isLanding = velocity.DirY > 0 && 
                         playerBounds.Bottom >= tileBounds.Top && 
                         playerBounds.Bottom <= tileBounds.Top + intersection.Height;

        // 优先处理头顶碰撞(玩家向上移动,碰撞Tile底部)
        bool isHeadHit = velocity.DirY < 0 && 
                         playerBounds.Top <= tileBounds.Bottom && 
                         playerBounds.Top >= tileBounds.Bottom - intersection.Height;

        if (isLanding || isHeadHit) {
            // 处理垂直碰撞
            position.Y += isLanding ? -intersection.Height : intersection.Height;
            velocity.DirY = 0;
        } 
        else if (Math.Abs(velocity.DirX) > Math.Abs(velocity.DirY) || intersection.Width < intersection.Height) {
            // 处理横向碰撞:横向速度更大 或 交集宽度更小
            position.X += playerBounds.X < tileBounds.Position.X ? -intersection.Width : intersection.Width;
            velocity.DirX = 0;
        } 
        else {
            // 剩余情况处理垂直碰撞
            position.Y += playerBounds.Y < tileBounds.Y ? -intersection.Height : intersection.Height;
            velocity.DirY = 0;
        }

        // 更新碰撞盒位置
        collider.RectBounds.X = position.X;
        collider.RectBounds.Y = position.Y;
    }
}

额外优化建议

  • 为玩家添加IsGrounded状态标记,落地时设置该状态,跳跃逻辑仅允许在IsGrounded为true时触发,避免多次跳跃
  • 调整碰撞修正的判断阈值,比如当交集高度小于某个极小值时,视为横向碰撞,避免边缘误判

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 13:41:01