Skip to content

触发器与碰撞事件

刚体可以作为触发器(Sensor)检测物体进出区域,也可以监听碰撞接触事件。两者都通过 Rigidbody 上的回调实现。

注意

isSensorenableEvents 必须在组件 start() 之前设置(即添加组件后、物体加入场景前)。

触发器(Sensor)

触发器不产生物理碰撞响应,只检测其它物体的进入/离开,常用于区域检测(进入区域、拾取道具等):

ts
import { Object3D, Vector3 } from '@orillusion/core';
import { Rigidbody, BodyType, CollisionShapeUtil } from '@orillusion/physics-rapier';

const sensor = obj.addComponent(Rigidbody);
sensor.bodyType = BodyType.Static;
sensor.shape = CollisionShapeUtil.createBoxShape(obj, new Vector3(2, 2, 2));
sensor.isSensor = true;       // 设为触发器
sensor.enableEvents = true;   // 开启事件

sensor.onTriggerEnter = (other) => console.log('进入', other.object3D.name);
sensor.onTriggerExit  = (other) => console.log('离开', other.object3D.name);
回调触发时机
onTriggerEnter(other)有物体进入触发器
onTriggerExit(other)有物体离开触发器

碰撞接触事件

在动态刚体上开启 enableEvents,可监听与其它物体的接触:

ts
const rb = body.addComponent(Rigidbody);
rb.bodyType = BodyType.Dynamic;
rb.mass = 1;
rb.shape = CollisionShapeUtil.createBoxShape(body, new Vector3(1, 1, 1));
rb.enableEvents = true;

rb.onContactBegin = (other) => { /* 开始接触(一次性) */ };
rb.onContactStay  = (other) => { /* 持续接触(每帧) */ };
rb.onContactEnd   = (other) => { /* 结束接触(一次性) */ };
回调触发时机
onContactBegin(other)刚开始接触
onContactStay(other)接触持续中(每帧)
onContactEnd(other)接触结束

回调参数 other 为对方的 Rigidbody,可通过 other.object3D 访问其所属节点。

示例

WebGPU is not supported in your browser
Please upgrade to latest Chrome/Edge

<
ts
import { Scene3D, CameraUtil, HoverCameraController, View3D, DirectLight, AtmosphericComponent, Object3D, LitMaterial, Engine3D, BoxGeometry, MeshRenderer, Vector3, PlaneGeometry, Color, SphereGeometry, BlendMode } from "@orillusion/core";
import { Physics, Rigidbody, BodyType, CollisionShapeUtil } from "@orillusion/physics-rapier";

class Sample_RapierTriggers {
    async run() {
        await Physics.init();
        const engine = await Engine3D.init({ renderLoop: () => Physics.update() });

        let scene = new Scene3D();

        // Setup camera
        let camera = CameraUtil.createCamera3DObject(scene);
        camera.perspective(60, engine.aspect, 0.1, 800.0);

        let hoverCtrl = camera.object3D.addComponent(HoverCameraController);
        hoverCtrl.setCamera(0, -25, 100);
        hoverCtrl.dragSmooth = 4;

        // Create directional light
        let lightObj3D = new Object3D();
        lightObj3D.localRotation = new Vector3(-35, -143, 92);

        let light = lightObj3D.addComponent(DirectLight);
        light.lightColor = Color.COLOR_WHITE;
        light.castShadow = true;
        light.enableCSM = true;
        light.intensity = 2.2;
        scene.addChild(light.object3D);

        // init sky
        let atmosphericSky = scene.addComponent(AtmosphericComponent);
        atmosphericSky.sunY = 0.6;

        // Floor
        const floor = new Object3D();
        const fr = floor.addComponent(MeshRenderer);
        fr.geometry = new PlaneGeometry(40, 40);
        const fm = new LitMaterial(); fm.baseColor = new Color(0.4, 0.4, 0.45); fr.material = fm;
        const fb = floor.addComponent(Rigidbody);
        fb.bodyType = BodyType.Static; fb.shape = CollisionShapeUtil.createPlaneShape(20, 0.05);
        scene.addChild(floor);

        // Trigger zone (transparent box)
        const zone = new Object3D(); zone.y = 2;
        const zmr = zone.addComponent(MeshRenderer);
        zmr.geometry = new BoxGeometry(6, 4, 6);
        const zmat = new LitMaterial();
        zmat.baseColor = new Color(0.2, 0.9, 0.4, 0.25);
        zmat.blendMode = BlendMode.NORMAL;
        zmat.transparent = true;
        zmr.material = zmat;
        const zrb = zone.addComponent(Rigidbody);
        zrb.bodyType = BodyType.Static;
        zrb.shape = CollisionShapeUtil.createBoxShape(zone, new Vector3(6, 4, 6));
        zrb.isSensor = true;
        zrb.enableEvents = true;

        let inside = 0;
        zrb.onTriggerEnter = (other) => {
            inside++;
            console.log('triggerEnter', other.object3D.name, 'count:', inside);
            zmat.baseColor = new Color(1.0, 0.4, 0.4, 0.35);
        };
        zrb.onTriggerExit = (other) => {
            inside--;
            console.log('triggerExit', other.object3D.name, 'count:', inside);
            if (inside <= 0) zmat.baseColor = new Color(0.2, 0.9, 0.4, 0.25);
        };
        scene.addChild(zone);

        // Drop balls through the trigger
        let id = 0;
        const dropInterval = setInterval(() => {
            const o = new Object3D();
            o.name = 'ball_' + (id++);
            o.x = (Math.random() - 0.5) * 4;
            o.z = (Math.random() - 0.5) * 4;
            o.y = 12;
            const mr = o.addComponent(MeshRenderer);
            mr.geometry = new SphereGeometry(0.4, 16, 16);
            const m = new LitMaterial();
            m.baseColor = new Color(Math.random(), Math.random(), Math.random()); mr.material = m;
            const rb = o.addComponent(Rigidbody);
            rb.bodyType = BodyType.Dynamic; rb.mass = 1; rb.restitution = 0.3;
            rb.shape = CollisionShapeUtil.createSphereShape(o, 0.4);
            rb.enableEvents = true;
            scene.addChild(o);
            if (id > 30) clearInterval(dropInterval);
        }, 250);

        let view = new View3D();
        view.camera = camera;
        view.scene = scene;
        engine.startRenderView(view);
    }
}

new Sample_RapierTriggers().run();