Skip to content

168. 实战:躲避汽车(四)

Published:

上节完成了碰撞检测:

2025-06-03 21.27.34.gif

这节我们加上碰撞之后的鲜血喷射效果。

安装下粒子动画库:

npm install --save three.quarks

创建 blood.js

import * as THREE from "three";
import { ApplyCollision, ApplyForce, BatchedParticleRenderer,ConstantValue, GridEmitter, IntervalValue, ParticleSystem, PointEmitter, RandomColor, RenderMode } from "three.quarks";

const group = new THREE.Group();

const batchRenderer = new BatchedParticleRenderer();
group.add(batchRenderer);

const loader = new THREE.TextureLoader();
const texture = loader.load('./point.png');

const particles = new ParticleSystem({
    duration: 10,
    looping: true,
    startLife: new ConstantValue(5, 9),
    startSpeed: new IntervalValue(30, 50),
    startSize: new IntervalValue(5, 10),
    startColor: new RandomColor(
        new THREE.Vector4(1, 0, 0, 1),
        new THREE.Vector4(1, 0, 0, 0.1)
    ),
    emissionOverTime: new IntervalValue(300, 500),
    shape: new PointEmitter(),
    material: new THREE.MeshBasicMaterial({
        map: texture,
        transparent: true,
        side: THREE.DoubleSide
    })
});

group.add(particles.emitter);

batchRenderer.addSystem(particles);

particles.emitter.position.y = 100;

export {
    batchRenderer
}

export default group;

颜色是红色,透明度随机 0.1 到 1。

鲜血喷射用点状粒子发射器。

point.png 从这里下载:

https://github.com/QuarkGluonPlasma/threejs-course-code/blob/main/all-shape-three-quarks/public/point.png

image.png

image.png

在 main.js 引入下:

import blood, { batchRenderer } from './blood.js';
scene.add(blood);

image.png

const clock = new THREE.Clock();
const delta = clock.getDelta();

if(batchRenderer) {
  batchRenderer.update(delta);
}

2025-06-03 22.39.53.gif

和鲜血的效果还是很像的。

我们给它加上重力以及反弹:

image.png

particles.addBehavior(
    new ApplyForce(
        new THREE.Vector3(0, -1, 0),
        new ConstantValue(70)
    )
)

particles.addBehavior(new ApplyCollision({
    resolve(pos, normal) {
        if(pos.y < -100) {
            normal.set(0, 1, 0);
            return true;
        } else {
            return false;
        }
    }
}, 0.1));

重力 70,血的反弹力度比较小 0.1 就好了。

看下效果:

2025-06-03 22.42.30.gif

现在留血的效果就比较真实了。

我们在碰撞的时候,在碰撞的位置流血:

首先给 emitter 隐藏,设置下 name:

image.png

// particles.emitter.position.y = 100;
particles.emitter.visible = false;
particles.emitter.name = 'bloodEmitter';

碰撞的时候用 intersect 计算相交的包围盒,然后用 getCenter 取中间位置。

把血放在碰撞中间位置开始流:

image.png

const box3 = manBox3.intersect(carBox3);
const emitter = blood.getObjectByName('bloodEmitter');
emitter.visible = true;
emitter.position.copy(box3.getCenter(new THREE.Vector3()));

试下效果:

2025-06-03 23.07.28.gif

再从侧面撞一下:

2025-06-03 23.08.25.gif

位置也是对的。

但是我们血液反弹是固定的 -100

从侧面可以看出来,位置不大对:

image.png

我们改一下:

image.png

function addCollisionBehavior(y) {
    particles.addBehavior(new ApplyCollision({
        resolve(pos, normal) {
            if(pos.y < y) {
                normal.set(0, 1, 0);
                return true;
            } else {
                return false;
            }
        }
    }, 0.1));
}

export {
    batchRenderer,
    addCollisionBehavior
}

导出一个函数,在碰撞的时候传入 y:

image.png

const pos = box3.getCenter(new THREE.Vector3())
emitter.position.copy(pos);

addCollisionBehavior(-pos.y);

再试下:

2025-06-03 23.15.41.gif

2025-06-03 23.16.28.gif

现在的流血效果就比较真实了。

案例代码上传了小册仓库

总结

这节我们实现了碰撞时的流血粒子效果。

用点状粒子发射器发射血液粒子,设置重力让粒子下落,并且在碰到地面的时候反弹。

我们用包围盒 Box3 的 intersect 方法拿到相交部分,然后 getCenter 拿到碰撞的中心位置,把粒子发射器移动到这个位置来喷射血液。

这样,就实现了碰撞时的流血效果。

评论