子弹碰撞检测异常:本应无交集时Area对象却非空的问题排查
子弹碰撞检测异常:未靠近玩家就触发伤害判定的问题分析与解决
问题描述
开发子弹碰撞玩家造成伤害的功能时出现异常:子弹尚未靠近玩家就触发了伤害判定。玩家的hitbox位置设置正确,hit()方法负责对玩家造成伤害并缩小子弹尺寸。
碰撞检测代码
Area a = new Area(AffineTransform.getRotateInstance(r, getX() + getWidth() / 2, getY() + getHeight() / 2) .createTransformedShape(new Rectangle(getX() + (int)(getWidth()-sx) / 2, getY() + (int)(getHeight()-sy) / 2, (int)sx, (int)sy))); a.intersect(new Area(Root.game.player.hitbox)); if (!a.isEmpty()) hit(Root.game.player);
子弹绘制代码
public void paint(Graphics g){ Graphics2D g2 = (Graphics2D)g; AffineTransform a = g2.getTransform(); a.rotate(Math.toRadians(r), getWidth() / 2, getHeight() / 2); int sx = (int)Math.round(this.sx), sy = (int)Math.round(this.sy); g2.setTransform(a); int dx = (getWidth()-sx) / 2, dy = (getHeight()-sy) / 2; g2.setPaint(new RadialGradientPaint(new Rectangle(sx / 6 + dx, sy / 6 + dy, Math.max(1, sx * 2 / 3), Math.max(1, sy * 2 / 3)), new float[]{0, 1}, new Color[]{Color.CYAN, new Color(0, 255, 255, 0)}, CycleMethod.NO_CYCLE)); g2.setStroke(new BasicStroke(sy / 5, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); g2.drawRect(sx / 3 + dx, sy / 3 + dy, sx / 3, sy / 3); g2.setPaint(new RadialGradientPaint(new Rectangle(sx / 6 + dx, sy / 6 + dy, Math.max(1, sx * 2 / 3), Math.max(1, sy * 2 / 3)), new float[]{0, 1}, new Color[]{c, Color.BLUE}, CycleMethod.NO_CYCLE)); g2.fillRoundRect(sx / 3 + dx, sy / 3 + dy, sx / 3, sy / 3, sx / 20, sy / 15); }
问题根源
1. 坐标空间与旋转逻辑不匹配
绘制时,旋转操作基于子弹自身的局部坐标中心(getWidth()/2, getHeight()/2),但碰撞检测时,直接在全局坐标下创建Rectangle并以全局坐标的子弹中心旋转,导致碰撞区域的位置计算出现偏移,和实际绘制的子弹位置不对应。
2. 碰撞区域范围远大于实际图形
实际绘制的子弹核心区域是sx/3 * sy/3的圆角矩形,但碰撞检测用的是sx * sy的完整Rectangle,范围比可视子弹大得多,自然会提前触发碰撞判定。
修复方案
统一碰撞检测与绘制的坐标逻辑,让碰撞区域严格匹配实际绘制的图形:
// 1. 按照绘制逻辑创建局部坐标下的碰撞形状(和fillRoundRect一致) int sx = (int)Math.round(this.sx); int sy = (int)Math.round(this.sy); int dx = (getWidth() - sx) / 2; int dy = (getHeight() - sy) / 2; RoundRectangle2D localHitShape = new RoundRectangle2D.Double( sx/3 + dx, sy/3 + dy, sx/3, sy/3, sx/20, sy/15 ); // 2. 构建正确的变换:先平移到全局位置,再以子弹自身中心旋转 AffineTransform transform = new AffineTransform(); transform.translate(getX(), getY()); transform.rotate(Math.toRadians(r), getWidth()/2, getHeight()/2); // 3. 转换为全局坐标的碰撞形状 Shape globalHitShape = transform.createTransformedShape(localHitShape); // 4. 执行碰撞检测 Area bulletArea = new Area(globalHitShape); Area playerArea = new Area(Root.game.player.hitbox); bulletArea.intersect(playerArea); if (!bulletArea.isEmpty()) { hit(Root.game.player); }
关键调整点
- 用
RoundRectangle2D替代Rectangle,让碰撞区域和绘制的圆角矩形完全一致。 - 先基于局部坐标创建碰撞形状,再通过平移+旋转转换到全局坐标,和绘制时的变换逻辑对齐。
- 旋转中心保持为子弹自身的局部中心,避免坐标空间混淆。
内容的提问来源于stack exchange,提问作者Tech of the Absence
相关产品推荐
相关产品推荐

