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

快速使用
方式一:IDE 中挂载脚本
- 在 IDE 中创建一个 2D Sprite 节点
- 添加
RainEnvironmentScript组件 - (可选)在属性面板中拖入雨滴纹理图片
- 在属性面板中调整参数
方式二:代码中使用
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);
}