Skip to content

162. 粒子实战:小鱼吐泡泡(二)

Published:

上节实现了小鱼吐泡泡:

2025-06-02 18.23.19.gif

这节我们继续来实现游动:

我们安装下 gsap 或者 tween.js 来做缓动动画。

这次我们用 gsap:

npm install --save gsap

给 fish 和 emitter 个名字:

image.png

image.png

fish2.name = 'fish';
particles.emitter.name = 'emitter';

fish 移动的时候同步修改 bubble 的位置:

image.png

这里要 * scale

import gsap from 'gsap';
import bubbles from './bubbles';
gsap.to(fish2.position, {
    duration: 100,
    z: -300,
    onUpdate: () => {
        const emitter = bubbles.getObjectByName('emitter');
        emitter.position.z = fish2.position.z * 100;
    }
});

看下效果:

2025-06-02 18.42.01.gif

这样,鱼的游动和气泡的位置同步就完成了。

我们改下相机位置:

image.png

把 OrbitControls 注释掉,它会把 lookAt 重置:

image.png

camera.position.set(800, 500, -500);
camera.lookAt(0, 300, -500);

2025-06-02 18.47.41.gif

现在这个视角就好多了。

小鱼游的有点远,改小一点:

image.png

duration: 5,
z: -10,

因为我们放大了 100 倍,所以这就是 5s 内 z 从 0 运动到 -1000 的位置。

2025-06-02 18.51.47.gif

这样就不会游到视野之外了。

不过气泡位置不大对,z 应该再加上鱼身宽度的一半。

获取鱼身宽度可以用包围盒:

image.png

const box3 = new THREE.Box3();
box3.setFromObject(fish2);

const size = box3.getSize(new THREE.Vector3());
emitter.position.z = fish2.position.z * 100 - size.x * 100 / 2;

2025-06-02 18.57.32.gif

现在就对了。

然后游到头之后再游回来:

const tl = gsap.timeline();

fish2.rotation.y = 0;
tl.to(fish2.position, {
    duration: 5,
    z: -10,
    onUpdate: () => {
        const emitter = bubbles.getObjectByName('emitter');
        emitter.visible = true;
        emitter.position.z = fish2.position.z * 100 - size.x * 100 / 2;
    }
}).to(fish2.rotation, {
    y: Math.PI,
    duration: 1,
    onUpdate: () => {
        const emitter = bubbles.getObjectByName('emitter');
        emitter.visible = false;
    }
}).to(fish2.position, {
    z: 0,
    duration: 5,
    onUpdate: () => {
        const emitter = bubbles.getObjectByName('emitter');
        emitter.visible = true;
        emitter.position.z = fish2.position.z * 100 + size.x * 100 / 2;
    }
}).to(fish2.rotation, {
    y: 0,
    duration: 1,
    onUpdate: () => {
        const emitter = bubbles.getObjectByName('emitter');
        emitter.visible = false;
    }
}).repeat(Infinity);

用 gsap 的 timeline 来串联动画。

首先往右边游,游到头转身,再游回来,之后再转身。

转身的时候把气泡隐藏。

看下效果:

2025-06-02 19.08.45.gif

这样鱼就能无限反复游了。

发现我们忘记加气泡的帧动画了,加一下:

image.png

particles.addBehavior(
    new FrameOverLife(
        new PiecewiseBezier(
            [
                [new Bezier(36, 39, 42, 44), 0]
            ]
        )
    )
);

image.png

这个帧动画的变化不是很明显。

去掉坐标轴,看下整体效果:

2025-06-02 19.11.57.gif

效果不错。

案例代码上传了小册仓库

总结

这节我们实现了小鱼来回游动的效果。

用 gsap 实现的串联的动画,首先游动到一个位置,接下来转身,之后再游回去,然后再转身,不断循环执行这个动画就可以实现来回游动的效果。

在转身的时候气泡位置不好计算,我们直接把他隐藏,转身完之后再显示就好了。

这样,小鱼吐泡泡的效果就完成了,用到了粒子效果、gsap 串联动画、包围盒、骨骼动画,是一个比较综合的小实战。

评论