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),此时应该优先处理垂直方向的碰撞修正,但如果此时交集宽度小于高度,逻辑会错误地触发横向修正。
解决方案
修改碰撞处理逻辑,结合玩家的速度方向和碰撞位置关系来优先处理正确的碰撞轴:
- 当玩家有向下的垂直速度时,优先判断是否是落地碰撞(玩家碰撞盒底部接触Tile顶部),优先处理垂直修正
- 其他场景再结合交集宽高和速度方向判断修正轴
修改后的碰撞处理代码
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
相关产品推荐
相关产品推荐

