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

Sceneform碰撞实现求助:碰撞时播放音效并销毁两类对象

嘿,我明白你在Sceneform里实现碰撞响应时遇到的卡点了——直接重写碰撞形状的交集方法确实不是正确的路子,因为Sceneform的Collision API设计里,碰撞事件的监听是通过CollisionSystem和CollisionListener来处理的,而不是靠重写形状的交集判断逻辑。

为什么你之前的方案没效果?

你自定义PassiveCollider和BallCollision并重写boxIntersection的思路,其实只是在修改形状的碰撞判定规则,但Sceneform不会主动把这个判定结果转化为可监听的事件回调。简单说,你写的碰撞逻辑只在内部检测时生效,但没有触发任何对外的通知,所以你看不到预期的销毁和音效播放效果。

正确的高效实现方案

Sceneform内置的CollisionSystem已经帮你做好了碰撞检测的优化(比如空间划分、帧内批量检测),你只需要注册监听器来接收碰撞事件即可,完全不需要暴力检测。下面是完整的实现步骤:

1. 简化Node定义(不用自定义CollisionShape)

首先,我们只需要给节点设置默认的碰撞形状,并开启碰撞检测开关,不用自定义CollisionShape子类:

public class PassiveNode extends Node {
    public PassiveNode() {
        // 根据你的模型尺寸设置碰撞盒大小
        setCollisionShape(new Box(new Vector3(1, 1, 1)));
        setCollisionEnabled(true); // 必须开启,否则CollisionSystem不会处理
    }
}

public class ActiveNode extends Node {
    private Node target;
    private static final float metersPerSecond = 1F;

    public ActiveNode(Node target) {
        this.target = target;
        // 用球体碰撞形状,也可以换成Box,根据你的模型选择
        setCollisionShape(new Sphere(0.5f));
        setCollisionEnabled(true); // 开启碰撞检测
    }

    @Override
    public void onUpdate(FrameTime frameTime) {
        super.onUpdate(frameTime);
        Vector3 currPos = getWorldPosition();
        Vector3 targetPos = target.getWorldPosition();
        Vector3 direction = Vector3.subtract(targetPos, currPos).normalized();
        setWorldPosition(Vector3.add(currPos, direction.scaled(metersPerSecond * frameTime.getDeltaSeconds())));
    }
}

2. 注册碰撞监听器处理事件

在你的AR场景容器(比如ArFragment或SceneView所在的Activity)中,获取CollisionSystem并注册监听器,处理碰撞逻辑:

// 获取SceneView和核心碰撞系统
SceneView sceneView = arFragment.getArSceneView();
Scene scene = sceneView.getScene();
CollisionSystem collisionSystem = scene.getCollisionSystem();

// 添加碰撞监听器
collisionSystem.addCollisionListener(new CollisionListener() {
    @Override
    public void onCollision(CollisionEvent collisionEvent) {
        Node nodeA = collisionEvent.getNodeA();
        Node nodeB = collisionEvent.getNodeB();

        // 判断是否是我们需要的两种类型节点碰撞
        boolean isTargetCollision = 
            (nodeA instanceof ActiveNode && nodeB instanceof PassiveNode) ||
            (nodeA instanceof PassiveNode && nodeB instanceof ActiveNode);

        if (isTargetCollision) {
            // 1. 播放碰撞音效
            playCollisionSound();
            
            // 2. 销毁两个节点:先禁用再从场景移除,避免内存泄漏
            removeNodeSafely(nodeA);
            removeNodeSafely(nodeB);
        }
    }

    @Override
    public void onCollisionPause(CollisionEvent collisionEvent) {
        // 碰撞结束的回调,不需要的话留空即可
    }
});

// 安全移除节点的工具方法
private void removeNodeSafely(Node node) {
    node.setEnabled(false);
    node.setCollisionEnabled(false);
    node.setParent(null);
}

// 音效播放示例(记得把音效文件放到res/raw目录下)
private void playCollisionSound() {
    MediaPlayer mediaPlayer = MediaPlayer.create(this, R.raw.collision_sound);
    mediaPlayer.setOnCompletionListener(MediaPlayer::release); // 播放完自动释放资源
    mediaPlayer.start();
}

关键注意事项

  • 必须开启碰撞开关:所有需要参与碰撞检测的Node都要调用setCollisionEnabled(true),否则CollisionSystem会忽略它。
  • 碰撞形状匹配模型:确保CollisionShape的大小、位置和你的3D模型匹配,避免出现“看起来没碰到但触发碰撞”或者“碰到了没触发”的问题,可以用setCollisionShapePosition()调整碰撞形状的偏移。
  • 避免重复触发:如果你的节点会持续碰撞(比如ActiveNode卡在PassiveNode上),可以给节点加一个isDestroyed标记,在onCollision里判断后只执行一次销毁逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:42:58