如何简化Java小行星游戏中的多重IF碰撞判断代码?
解决方案:告别
instanceof地狱,优雅处理多对象碰撞 哥们,你这个问题太有代表性了——硬编码一堆instanceof分支的碰撞逻辑,别说拓展到100种对象,再加5种都能让代码变成维护噩梦。下面给你两个Java里实用的简化方案,从轻量到复杂场景都能覆盖:
方案1:碰撞处理器映射表(轻量首选)
核心思路是用哈希表存储「对象类型组合」与「对应碰撞处理逻辑」的映射,把分散的else if分支变成可动态注册的处理器,后续加新类型只需要加注册代码,完全不用碰碰撞循环的核心逻辑。
步骤1:定义碰撞处理器接口
先用函数式接口封装碰撞处理逻辑:
import java.util.Objects; import java.util.function.BiConsumer; // 用BiConsumer简化,也可以自定义更贴合业务的接口 @FunctionalInterface interface CollisionHandler<T extends GameObject, U extends GameObject> extends BiConsumer<T, U> {}
步骤2:创建类型组合的唯一键
为了避免Ship-Meteor和Meteor-Ship被当成两个不同的键,我们自定义一个CollisionKey类,自动排序类型来保证键的唯一性:
class CollisionKey { private final Class<? extends GameObject> typeA; private final Class<? extends GameObject> typeB; public CollisionKey(Class<? extends GameObject> a, Class<? extends GameObject> b) { // 按类名字母顺序排序,确保A-B和B-A是同一个键 if (a.getName().compareTo(b.getName()) <= 0) { this.typeA = a; this.typeB = b; } else { this.typeA = b; this.typeB = a; } } // 必须重写equals和hashCode,保证Map能正确识别相同的类型组合 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; CollisionKey that = (CollisionKey) o; return Objects.equals(typeA, that.typeA) && Objects.equals(typeB, that.typeB); } @Override public int hashCode() { return Objects.hash(typeA, typeB); } }
步骤3:注册碰撞处理器
在你的MyGame类里初始化映射表,把原来的ship_hit_meteor等方法注册进去:
import java.util.HashMap; import java.util.Map; public class MyGame { private final Map<CollisionKey, CollisionHandler<?, ?>> collisionHandlers = new HashMap<>(); // 初始化方法里注册所有碰撞逻辑 private void initCollisionHandlers() { // 注册飞船撞流星 collisionHandlers.put( new CollisionKey(Ship.class, Meteor.class), (ship, meteor) -> ship_hit_meteor(ship, meteor) ); // 注册子弹撞流星 collisionHandlers.put( new CollisionKey(Bullet.class, Meteor.class), (bullet, meteor) -> bullet_hit_meteor(bullet, meteor) ); // 后续加新碰撞(比如飞船捡硬币),只需要加这一行 collisionHandlers.put( new CollisionKey(Ship.class, Coin.class), (ship, coin) -> ship_collect_coin(ship, coin) ); } // 原来的碰撞处理方法保留 private void ship_hit_meteor(Ship ship, Meteor meteor) { /* 逻辑不变 */ } private void bullet_hit_meteor(Bullet bullet, Meteor meteor) { /* 逻辑不变 */ } private void ship_collect_coin(Ship ship, Coin coin) { /* 新增逻辑 */ } }
步骤4:简化碰撞循环
原来的一堆else if直接替换成查表执行:
// 碰撞循环内 GameObject a = objArray[i]; GameObject b = objArray[j]; CollisionKey key = new CollisionKey(a.getClass(), b.getClass()); CollisionHandler<?, ?> handler = collisionHandlers.get(key); if (handler != null) { // 因为注册时保证了类型匹配,所以强制转换是安全的 @SuppressWarnings("unchecked") CollisionHandler<GameObject, GameObject> castHandler = (CollisionHandler<GameObject, GameObject>) handler; castHandler.accept(a, b); } // 循环结束
方案2:访问者模式(复杂场景首选)
如果你的游戏后续会有大量复杂的对象交互逻辑(比如流星撞硬币会分裂、子弹打不同流星有不同效果),访问者模式能把每个对象的碰撞逻辑集中管理,代码结构更清晰。
步骤1:定义碰撞访问者接口
interface CollisionVisitor { void visitShip(Ship ship); void visitMeteor(Meteor meteor); void visitBullet(Bullet bullet); void visitCoin(Coin coin); // 新增对象类型时,在这里加对应的visit方法 }
步骤2:让GameObject子类支持访问者
在父类中定义accept方法,子类实现它:
abstract class GameObject { // ...你的原有属性和方法 public abstract void accept(CollisionVisitor visitor); } class Ship extends GameObject { @Override public void accept(CollisionVisitor visitor) { visitor.visitShip(this); } } class Meteor extends GameObject { @Override public void accept(CollisionVisitor visitor) { visitor.visitMeteor(this); } } // 其他子类(Bullet、Coin)同理实现accept方法
步骤3:实现具体的碰撞处理访问者
在MyGame里创建内部类,集中处理所有碰撞逻辑:
class CollisionHandlerVisitor implements CollisionVisitor { private GameObject otherObject; // 每次处理碰撞前,设置另一个碰撞对象 public void setOtherObject(GameObject other) { this.otherObject = other; } @Override public void visitShip(Ship ship) { if (otherObject instanceof Meteor) { ship_hit_meteor(ship, (Meteor) otherObject); } else if (otherObject instanceof Coin) { ship_collect_coin(ship, (Coin) otherObject); } // 其他和Ship相关的碰撞逻辑都放这里 } @Override public void visitMeteor(Meteor meteor) { if (otherObject instanceof Bullet) { bullet_hit_meteor((Bullet) otherObject, meteor); } else if (otherObject instanceof Ship) { ship_hit_meteor((Ship) otherObject, meteor); } // 其他和Meteor相关的碰撞逻辑都放这里 } // 实现其他visit方法... }
步骤4:简化碰撞循环
CollisionHandlerVisitor visitor = new CollisionHandlerVisitor(); // 碰撞循环内 GameObject a = objArray[i]; GameObject b = objArray[j]; // 处理a和b的碰撞 visitor.setOtherObject(b); a.accept(visitor); // 循环结束
方案选择建议
- 如果只是想快速解决代码冗余、后续拓展简单,方案1的映射表更轻量,不需要修改现有GameObject子类的结构。
- 如果游戏的碰撞逻辑复杂多变,每个对象和其他对象的交互都有独特规则,方案2的访问者模式能让代码结构更清晰,便于维护。
内容的提问来源于stack exchange,提问作者hamdirizal
相关产品推荐
相关产品推荐

