【Laya】下雨环境效果

下雨环境效果

一个基于 LayaAir 的 2D 下雨粒子特效系统,包含雨滴下落、地面溅射效果。

效果预览

快速使用

方式一:IDE 中挂载脚本

  1. 在 IDE 中创建一个 2D Sprite 节点
  2. 添加 RainEnvironmentScript 组件
  3. (可选)在属性面板中拖入雨滴纹理图片
  4. 在属性面板中调整参数

方式二:代码中使用

typescript 复制代码
import { RainEnvironmentScript } from "./environment/RainSystem";

// 添加下雨脚本
const rainScript = sprite.addComponent(RainEnvironmentScript);

// 动态调整参数
rainScript.setRainIntensity(10);     // 设置雨量
rainScript.setRainSpeed(15, 25);     // 设置速度
rainScript.setWind(-3);              // 设置风力

配置参数

参数 类型 说明 默认值
rainTexture Texture 雨滴纹理图片(可选,不设置则使用默认线条) null
rainIntensity Number 雨量大小 (0-20) 4
minSpeed Number 最小下落速度 8
maxSpeed Number 最大下落速度 14
wind Number 风力(负数向左,正数向右) -1
minLength Number 最小雨滴长度 10
maxLength Number 最大雨滴长度 25
enableSplash Boolean 是否启用地面溅射 true
splashRate Number 溅射频率 (0-1) 0.3

雨量参考

效果 雨量 速度范围
毛毛雨 1-3 3-6
小雨 3-6 6-10
中雨 6-10 8-14
大雨 10-15 12-20
暴雨 15-20 18-30

实现原理

核心架构

复制代码
RainEnvironmentScript (主脚本)
    │
    ├── RainSystem (雨滴系统)
    │       └── RainDrop[] (雨滴粒子数组)
    │
    └── SplashSystem (溅射系统)
            └── SplashParticle[] (溅射粒子数组)

1. 雨滴粒子 (RainDrop)

每个雨滴是一个独立的粒子对象,支持纹理和默认绘制两种模式:

typescript 复制代码
class RainDrop {
    sprite: Laya.Sprite;        // 精灵对象
    texture: Laya.Texture;      // 纹理(可选)
    vx: number;                 // X方向速度(风力)
    vy: number;                 // Y方向速度(下落)
    length: number;             // 雨滴长度
    scale: number;              // 缩放比例
    inUse: boolean;             // 使用状态
}

渲染方式:根据是否有纹理选择绘制方式

typescript 复制代码
// 有纹理时:使用 drawTexture 绘制 PNG 图片
if (this.texture) {
    this.sprite.graphics.drawTexture(
        this.texture,
        0, 0,
        this.texture.width,
        this.texture.height
    );
    this.sprite.pivotX = this.texture.width / 2;
    this.sprite.pivotY = this.texture.height / 2;
}
// 无纹理时:使用 drawLine 绘制默认线条
else {
    this.sprite.graphics.drawLine(0, 0, 0, this.length, "#aaddff", 2);
}

纹理绑定 :使用 @property({type: Laya.Texture}) 装饰器实现 IDE 拖拽绑定

typescript 复制代码
@property({type: Laya.Texture, tooltip: "雨滴纹理(拖入图片资源)"})
rainTexture: Laya.Texture | null = null;

2. 雨滴系统 (RainSystem)

职责:管理所有雨滴粒子的创建、更新和销毁

2.1 雨滴生成

每帧根据 intensity 参数生成新雨滴:

typescript 复制代码
update() {
    const texture = this.texture;
    // 产生新雨滴
    for (let i = 0; i < this.intensity; i++) {
        const x = Math.random() * (this.stageWidth + 100) - 50;
        const y = -20;
        const drop = RainDrop.acquire(x, y, this.config, texture);
        this.drops.push(drop);
        this.container.addChild(drop.sprite);
    }

    // 更新现有雨滴...
}

3. 溅射系统 (SplashSystem)

职责:管理地面溅射水花效果

typescript 复制代码
class SplashParticle {
    init(x: number, y: number): void {
        // 绘制小水珠
        this.sprite.graphics.drawCircle(0, 0, 2, "#cceeff");
        this.sprite.pos(x, y);
        this.sprite.alpha = 1;

        // 向上溅射
        const angle = -Math.PI / 2 + (Math.random() - 0.5);
        const speed = 1 + Math.random() * 2;
        this.vx = Math.cos(angle) * speed;
        this.vy = Math.sin(angle) * speed;

        this.life = 10 + Math.random() * 10;
    }

    update(): boolean {
        this.life--;
        if (this.life <= 0) {
            this.recycle();
            return false;
        }

        this.sprite.x += this.vx;
        this.sprite.y += this.vy;
        this.vy += 0.2;  // 重力
        this.sprite.alpha = this.life / 20;  // 淡出

        return true;
    }
}

4. 参数配置系统

使用接口定义配置,支持灵活扩展:

typescript 复制代码
interface RainDropConfig {
    minSpeed: number;      // 最小速度
    maxSpeed: number;      // 最大速度
    minLength: number;     // 最小长度
    maxLength: number;     // 最大长度
    windX: number;         // 风力
}

// 默认配置 + 自定义覆盖
this.config = {...DEFAULT_RAIN_CONFIG, ...config};

5. 性能优化

5.1 对象池

使用 LayaAir 内置的对象池复用粒子对象:

typescript 复制代码
// 获取雨滴
static acquire(x: number, y: number, config: RainDropConfig, texture: Laya.Texture | null): RainDrop {
    let drop = Laya.Pool.getItemByClass("RainDrop", RainDrop);
    if (!drop) {
        drop = new RainDrop();
    }
    drop.init(x, y, config, texture);
    return drop;
}

// 回收雨滴
recycle(): void {
    this.inUse = false;
    this.sprite.visible = false;
    this.sprite.graphics.clear();
    this.texture = null;
    if (this.sprite.parent) {
        this.sprite.parent.removeChild(this.sprite);
    }
    Laya.Pool.recover("RainDrop", this);
}

优点

  • 减少对象创建和销毁的开销
  • 降低垃圾回收压力
  • 提高帧率稳定性
5.2 批量更新

使用反向循环遍历,安全删除元素:

typescript 复制代码
for (let i = this.drops.length - 1; i >= 0; i--) {
    const alive = this.drops[i].update(this.stageHeight);
    if (!alive) {
        this.drops.splice(i, 1);
    }
}

API 参考

RainEnvironmentScript

方法 说明
setRainIntensity(value) 设置雨量 (0-20)
getRainIntensity() 获取当前雨量
setRainSpeed(min, max) 设置下落速度范围
setWind(windX) 设置风力

RainSystem

方法 说明
setIntensity(value) 设置雨量
getIntensity() 获取当前雨量
setSpeed(min, max) 设置速度
setWind(windX) 设置风力
setLength(min, max) 设置雨滴长度
destroy() 销毁系统

RainDrop

静态方法 说明
RainDrop.acquire(x, y, config, texture) 从对象池获取雨滴
RainDrop.clearPool() 清空雨滴对象池

SplashParticle

静态方法 说明
SplashParticle.acquire(x, y) 从对象池获取溅射粒子
SplashParticle.clearPool() 清空溅射对象池

使用场景

游戏中的下雨场景

typescript 复制代码
// 在游戏场景中添加下雨效果
const rainScript = scene2D.addComponent(RainEnvironmentScript);
rainScript.setRainIntensity(12);
rainScript.setRainSpeed(12, 20);
rainScript.setWind(-2);

动态天气变化

typescript 复制代码
class WeatherSystem extends Laya.Script {
    private rain!: RainEnvironmentScript;

    async onAwake() {
        this.rain = this.owner.addComponent(RainEnvironmentScript);

        // 模拟天气变化:从小雨到大雨
        Laya.timer.loop(5000, this, this.changeWeather);
    }

    changeWeather() {
        const intensity = Math.random() * 15;
        this.rain.setRainIntensity(intensity);
        this.rain.setWind((Math.random() - 0.5) * 4);
    }
}

扩展建议

添加闪电效果

typescript 复制代码
private flash(): void {
    const originalColor = Laya.stage.bgColor;
    Laya.stage.bgColor = "#555577";
    Laya.timer.once(50, this, () => {
        Laya.stage.bgColor = originalColor;
    });
}

// 在 onUpdate 中随机触发(大雨时)
if (Math.random() < 0.005 && this.rainIntensity > 10) {
    this.flash();
}

添加风向变化

typescript 复制代码
private updateWind(): void {
    // 缓慢变化风向
    const time = Laya.timer.currTimer * 0.0001;
    const wind = Math.sin(time) * 2;
    this.rainSystem.setWind(wind);
}

添加雨滴音效

typescript 复制代码
// 根据雨量播放不同音效
setRainIntensity(value: number) {
    this.rainIntensity = value;
    if (value > 10) {
        // Laya.SoundManager.playSound("sounds/heavy_rain.mp3", 0);
    } else if (value > 5) {
        // Laya.SoundManager.playSound("sounds/moderate_rain.mp3", 0);
    } else if (value > 0) {
        // Laya.SoundManager.playSound("sounds/light_rain.mp3", 0);
    }
}

夜晚下雨效果

typescript 复制代码
async onAwake() {
    // 深蓝色夜空
    Laya.stage.bgColor = "#0a1520";

    // 添加水坑反射效果
    const puddle = new Laya.Sprite();
    puddle.graphics.drawCircle(0, 0, 30, "rgba(100, 150, 200, 0.3)");
    puddle.pos(Laya.stage.width / 2, Laya.stage.height - 50);
    this.owner.addChild(puddle);
}
相关推荐
源码宝2 天前
制造工厂如何挑选MES?有哪些注意事项?
源码·mes
我是大卫3 天前
【图】React源码解析-从数据结构、调度器、源码设计模式到状态计算引擎,深挖useReducer原理
前端·react.js·源码
我是大卫5 天前
【图】React源码解析-从底层数据结构、渲染调度到闭包原理深挖useRef的核心机制
前端·react.js·源码
我是大卫6 天前
【图】React源码解析-从数据结构、依赖追踪机制、值传播与更新触发、以及性能陷阱与优化,深挖useContext的底层原理
前端·react.js·源码
我是大卫7 天前
【图】React源码解析-从“上帝视角”俯瞰React的认知框架
前端·react.js·源码
我是大卫7 天前
【图】React源码解析-从源码数据结构、执行机制对比、闭包陷阱与最佳实践四个维度,深挖useMemo和useCallback的底层原理
前端·react.js·源码
天若有情6739 天前
Dev_Portfolio 个人主页html网页源码分享!!!
前端·html·源码·个人主页
我是大卫11 天前
【图】React源码解析-第六部分:高级算法与执行链路
react.js·源码
我是大卫12 天前
【图】React源码解析-从源码数据结构、依赖算法、调度流水线到内存管理,深挖useEffect的全部核心机制
react.js·源码
我是大卫14 天前
【图】React源码解析-从底层数据结构、调度器、闭包原理到源码设计模式,深挖useState的全部核心机制
react.js·源码