particle-modifier
npx skills add https://github.com/958877748/skills --skill particle-modifier
Agent 安装分布
Skill 文档
ç²å修饰å¨ç³»ç»å¼åæå
æ¦è¿°
æ¬æè½æä¾ Cocos Creator 2.4.x ç²åç³»ç»çèªå®ä¹ä¿®é¥°å¨å¼åæ¡æ¶ãéè¿ç¼å修饰å¨ç±»ï¼å¯ä»¥ç²¾ç¡®æ§å¶æ¯ä¸ªç²åçè¡ä¸ºï¼å®ç°å¤æçè§è§ææã
æ ¸å¿æä»¶ç»æ
é¡¹ç®æ ¹ç®å½/
âââ ParticleModifier.ts # ç²å修饰å¨è¶æ°´ç»ä»¶ï¼æ¦æªç²åç³»ç»æ´æ°ï¼
âââ ParticleModifierBase.ts # 修饰å¨åºç±»ï¼æä¾é©åæ¹æ³ï¼
âââ SKILL.md # æ¬æè½ææ¡£
âââ js/ # å¼ææ ¸å¿æºç ï¼Cocos Creator 2.4.13ï¼
â âââ CCParticleAsset.js # ç²åèµæºç±»
â âââ CCParticleSystem.js # ç²åç³»ç»ç»ä»¶ï¼ä¸»ç»ä»¶ï¼
â âââ particle-simulator.js # ç²å模æå¨ï¼æ ¸å¿é»è¾ï¼
â âââ particle-system-assembler.js # æ¸²ææ±ç¼å¨
âââ examples/
âââ ColorFadeModifier.ts # é¢è²æ¸å修饰å¨ç¤ºä¾
âââ GravityModifier.ts # éå修饰å¨ç¤ºä¾
âââ LeafRotationModifier.ts # å¶åæè½¬ä¿®é¥°å¨ç¤ºä¾
å¿«éå¼å§
æ¥éª¤ 1ï¼å¤å¶æ ¸å¿æä»¶
å° ParticleModifier.ts å ParticleModifierBase.ts å¤å¶å°é¡¹ç®çä»»æç®å½ï¼å»ºè®®æ¾å¨ assets/scripts/ ä¸ï¼
æ¥éª¤ 2ï¼å建èªå®ä¹ä¿®é¥°å¨
å建继æ¿èª ParticleModifierBase çæ°ç±»ï¼å®ç° onParticleUpdate æ¹æ³
æ¥éª¤ 3ï¼å¨ç¼è¾å¨ä¸é ç½®
- å¨ç²åç³»ç»èç¹ä¸æ·»å
ParticleModifierç»ä»¶ - æ·»å èªå®ä¹ä¿®é¥°å¨ç»ä»¶
- é ç½®åæ°å¹¶è¿è¡
修饰å¨å¼å模æ¿
åºç¡æ¨¡æ¿
import { IParticle } from "./ParticleModifier";
import ParticleModifierBase from "./ParticleModifierBase";
const { ccclass, property } = cc._decorator;
@ccclass
export class MyCustomModifier extends ParticleModifierBase {
@property({ tooltip: 'æ§è¡ä¼å
çº§ï¼æ°å¼è¶å°è¶å
æ§è¡ï¼' })
priority: number = 0;
@property({ tooltip: '屿§è¯´æ' })
myProperty: number = 1.0;
onParticleEmit(particle: IParticle, system: cc.ParticleSystem): void {
// ç²ååå°æ¶è°ç¨ï¼å¯éï¼
// åå§åç²åèªå®ä¹æ°æ®
}
onParticleUpdate(particle: IParticle, dt: number, system: cc.ParticleSystem): void {
// ç²åæ¯å¸§æ´æ°æ¶è°ç¨ï¼å¿
éï¼
// æ´æ°ç²åç¶æ
}
}
IParticle æ¥å£
interface IParticle {
// ä½ç½®
pos: cc.Vec2; // å½åä½ç½®ï¼ç¸å¯¹äºåå°å¨ï¼
startPos: cc.Vec2; // èµ·å§ä½ç½®
drawPos: cc.Vec2; // ç»å¶ä½ç½®
// é¢è²
color: cc.Color; // å½åé¢è²
deltaColor: { r, g, b, a }; // é¢è²ååç
preciseColor: { r, g, b, a }; // 精确é¢è²ï¼æµ®ç¹ï¼
// 大å°
size: number; // å½å大å°
deltaSize: number; // 大å°ååç
aspectRatio: number; // 宽髿¯
// æè½¬
rotation: number; // å½åæè½¬è§åº¦ï¼åº¦ï¼
deltaRotation: number; // æè½¬ååçï¼åº¦/ç§ï¼
// çå½å¨æ
timeToLive: number; // å©ä½çå½å¨æï¼ç§ï¼
// Mode A: é忍¡å¼
dir: cc.Vec2; // é度åé
radialAccel: number; // å¾åå é度
tangentialAccel: number; // ååå é度
// Mode B: å徿¨¡å¼
angle: number; // å½åè§åº¦ï¼å¼§åº¦ï¼
degreesPerSecond: number; // è§é度
radius: number; // å½ååå¾
deltaRadius: number; // åå¾ååç
}
注æï¼IParticle æ¥å£å¯ä»¥è¢«æ©å±ä»¥åå¨èªå®ä¹æ°æ®ï¼ä½å¿
é¡»å¨ onParticleEmit ä¸åå§åã
ææ¯åç
ç²åä¿®é¥°å¨æ¦æªæºå¶
ç²å修饰å¨ç³»ç»éè¿ä»¥ä¸æ¹å¼æ¦æªç²åæ´æ°ï¼
-
è·åç²åç³»ç»å¼ç¨
const simulator = (system as any)._simulator; -
ä¿ååå§å½æ°
this._originalEmitParticle = simulator.emitParticle; this._originalStep = simulator.step; -
è¦çå ³é®å½æ°
simulator.emitParticle = (pos: cc.Vec2) => { this._originalEmitParticle.call(simulator, pos); this._onParticleEmit(particle); }; simulator.step = (dt: number) => { this._originalStep.call(simulator, dt); this._onParticleUpdate(particle, dt); };
aspectRatio å¤çé»è¾
弿åºå±ç updateParticleBuffer 彿°ï¼js/particle-simulator.js:257ï¼ï¼
let aspectRatio = particle.aspectRatio;
aspectRatio > 1 ? (height = width / aspectRatio) : (width = height * aspectRatio);
è¿å°±æ¯é¿æ¹å½¢å¾çæè½¬è·³åçæ ¹æ¬åå ï¼å½ aspectRatio ä» >1 åå° <1 æ¶ï¼å¼æä¼åæ¢éç¹ã
常è§ä¿®é¥°å¨æ¨¡å¼
1. ä½ç½®ä¿®æ¹
onParticleUpdate(particle: IParticle, dt: number, system: cc.ParticleSystem): void {
// X è½´æå¨
particle.pos.x += Math.sin(Date.now() / 1000) * 10 * dt;
}
2. é¢è²æ¸å
// éè¦å
å®ä¹æ©å±æ¥å£
interface IColorFadeOptions extends IParticle {
maxTimeToLive: number; // è®°å½åå§çå½å¨æ
}
onParticleEmit(particle: IColorFadeOptions, system: cc.ParticleSystem): void {
particle.maxTimeToLive = particle.timeToLive;
}
onParticleUpdate(particle: IColorFadeOptions, dt: number, system: cc.ParticleSystem): void {
const lifeRatio = 1 - (particle.timeToLive / particle.maxTimeToLive);
particle.color.r = Math.floor(255 * lifeRatio);
particle.color.g = Math.floor(255 * (1 - lifeRatio));
}
3. 大å°å¼å¸
// åºäºç²åçå½å¨æè®¡ç®å¼å¸ææ
interface IBreathingSizeOptions extends IParticle {
maxTimeToLive: number; // è®°å½åå§çå½å¨æ
baseSize: number; // è®°å½åå§å¤§å°
}
onParticleEmit(particle: IBreathingSizeOptions, system: cc.ParticleSystem): void {
particle.maxTimeToLive = particle.timeToLive;
particle.baseSize = particle.size;
}
onParticleUpdate(particle: IBreathingSizeOptions, dt: number, system: cc.ParticleSystem): void {
const lifeRatio = 1 - (particle.timeToLive / particle.maxTimeToLive);
// åºäºçå½å¨æçå¼å¸ææï¼æ¯ä¸ªç²åæç¬ç«ç¸ä½
particle.size = particle.baseSize + Math.sin(lifeRatio * Math.PI * 4) * 20;
}
4. 3D æè½¬ï¼å ³é®ï¼
â ï¸ éè¦ï¼aspectRatio è·³åé®é¢
é¿æ¹å½¢å¾çæè½¬æ¶ä¼åºç°è§è§è·³åï¼å ä¸ºå¼æåºå±ç¡¬åæ¢éç¹ï¼
aspectRatio > 1ï¼å®½åº¦åºå®ä¸ºparticle.sizeaspectRatio < 1ï¼é«åº¦åºå®ä¸ºparticle.size
è§£å³æ¹æ¡ï¼é¿æ¹å½¢å¾çï¼AR > 1ï¼åªä½¿ç¨ X è½´æè½¬
interface ILeafRotationOptions extends IParticle {
maxTimeToLive: number; // èªå®ä¹ï¼è®°å½åå§çå½å¨æ
originalAspectRatio: number; // èªå®ä¹ï¼è®°å½åå§å®½é«æ¯
rotationAngle: number; // èªå®ä¹ï¼å½åæè½¬è§åº¦
rotationSpeed: number; // èªå®ä¹ï¼æè½¬é度
randomOffset: number; // èªå®ä¹ï¼éæºåç§»
}
@ccclass
export class LeafRotationModifier extends ParticleModifierBase {
@property({ tooltip: 'æå°æè½¬é度ï¼å/ç§ï¼' })
minRotationSpeed: number = 0.5;
@property({ tooltip: 'æå¤§æè½¬é度ï¼å/ç§ï¼' })
maxRotationSpeed: number = 2.0;
@property({ tooltip: 'æè½¬è½´ï¼X=ä¸ä¸ç¿»è½¬ï¼Y=å·¦å³ç¿»è½¬ï¼' })
rotationAxis: 'X' | 'Y' = 'X';
onParticleEmit(particle: ILeafRotationOptions, system: cc.ParticleSystem): void {
// â ï¸ éè¦ï¼å¿
é¡»å¨ onParticleEmit ä¸åå§åèªå®ä¹æ°æ®
particle.maxTimeToLive = particle.timeToLive;
particle.originalAspectRatio = particle.aspectRatio || 1.0;
particle.rotationAngle = 0;
const randomSpeed = this.minRotationSpeed + Math.random() * (this.maxRotationSpeed - this.minRotationSpeed);
particle.rotationSpeed = Math.PI * 2 * randomSpeed;
particle.randomOffset = Math.random() * Math.PI * 2;
}
onParticleUpdate(particle: ILeafRotationOptions, dt: number, system: cc.ParticleSystem): void {
particle.rotationAngle += particle.rotationSpeed * dt;
const currentAngle = particle.rotationAngle + particle.randomOffset;
const cosValue = Math.abs(Math.cos(currentAngle));
const safeCos = Math.max(0.01, cosValue);
if (this.rotationAxis === 'X') {
// X è½´æè½¬ï¼å®½åº¦ä¸åï¼é«åº¦ç¼©æ¾
particle.aspectRatio = particle.originalAspectRatio / safeCos;
} else {
// Y è½´æè½¬ï¼â ï¸ é¿æ¹å½¢å¾çä¼è·³å
particle.aspectRatio = particle.originalAspectRatio * safeCos;
}
}
}
使ç¨å»ºè®®ï¼
- é¿æ¹å½¢å¾çï¼AR > 1ï¼ï¼ä½¿ç¨
rotationAxis = 'X' - æ£æ¹å½¢å¾çï¼AR â 1ï¼ï¼å¯ä»¥ä½¿ç¨ä»»æè½´
- é
åç²å Z è½´æè½¬ï¼åªå X è½´ 3D 翻转 + ç²å
rotation屿§
é«çº§åè½
访é®é¡¶ç¹ç¼å²åº
â ï¸ è¦åï¼æ¤åè½éè¦æ·±å ¥äºè§£å¼æé¡¶ç¹æ ¼å¼ï¼ä» ç¨äºé«çº§åºæ¯
onParticleUpdate(particle: IParticle, dt: number, system: cc.ParticleSystem): void {
const simulator = (system as any)._simulator;
const buffer = system._assembler.getBuffer();
const vbuf = buffer._vData;
const particleIndex = simulator.particles.indexOf(particle);
// vfmtPosUvColor æ ¼å¼ï¼æ¯é¡¶ç¹ 8 floats (pos:2 + uv:2 + color:4)ï¼æ¯ç²å 4 é¡¶ç¹ = 32 floats
const FLOAT_PER_PARTICLE = 32;
const offset = particleIndex * FLOAT_PER_PARTICLE;
// ä¿®æ¹ç¬¬ä¸ä¸ªé¡¶ç¹ç UV åæ ï¼offset + 8, offset + 9ï¼
// 第äºä¸ªé¡¶ç¹ï¼offset + 16, offset + 17
// 第ä¸ä¸ªé¡¶ç¹ï¼offset + 24, offset + 25
// 第å个顶ç¹ï¼offset + 32, offset + 33
vbuf[offset + 8] = 0.0;
vbuf[offset + 9] = 0.0;
// step 彿°ä¼èªå¨ä¸ä¼ æ°æ®ï¼æ éæå¨è°ç¨ uploadData
}
å¤ä¿®é¥°å¨åå
å¨ç¼è¾å¨ä¸æ·»å å¤ä¸ªä¿®é¥°å¨ï¼æ priority é¡ºåºæ§è¡ï¼æ°å¼è¶å°è¶å
æ§è¡ï¼ï¼
ParticleSystem
âââ ParticleModifier (è¶æ°´ç»ä»¶ï¼èªå¨æ¶éå¹¶æåºå修饰å¨)
âââ GravityModifier (priority: 0) # å
åºç¨éå
âââ LeafRotationModifier (priority: 1) # ååºç¨ 3D æè½¬
âââ ColorFadeModifier (priority: 2) # æååºç¨é¢è²æ¸å
æ§è¡é¡ºåºå½±åï¼
- 妿å åºç¨é¢è²æ¸åï¼ååºç¨ 3D æè½¬ï¼æè½¬ä¸ä¼å½±åé¢è²
- ä¼å 级å³å®äºä¿®é¥°å¨çåºç¨é¡ºåºï¼å¯è½å½±åæç»ææ
è°è¯æå·§
æ¥çç²åç³»ç»ç¶æ
â ï¸ æ§è½è¦åï¼ä»¥ä¸è°è¯æ¹æ³ä¼äº§ç大鿥å¿ï¼ä» å¨å¼åç¯å¢ä½¿ç¨ï¼ç产ç¯å¢å¡å¿ ç§»é¤
onParticleUpdate(particle: IParticle, dt: number, system: cc.ParticleSystem): void {
// ä»
卿¯ 100 帧è¾åºä¸æ¬¡ï¼é¿å
æ¥å¿çç¸
if (Date.now() % 100 < 16) { // 约 6fps
const simulator = (system as any)._simulator;
console.log('ç²åæ°é:', simulator.particles.length);
console.log('æ¿æ´»ç¶æ:', simulator.active);
}
}
çæ§ aspectRatio
if (particle.aspectRatio > 1.1 || particle.aspectRatio < 0.9) {
console.log('aspectRatio å¼å¸¸:', particle.aspectRatio);
}
æ§è½åæ
const startTime = Date.now();
// ... 修饰å¨é»è¾
const endTime = Date.now();
if (endTime - startTime > 1) {
console.warn('æ§è¡æ¶é´è¿é¿:', endTime - startTime, 'ms');
}
è´¨éæ ¡éªæ¸ å
å¨åå»ºä¿®é¥°å¨æ¶ï¼è¯·ç¡®ä¿æ»¡è¶³ä»¥ä¸æ åï¼
- æ§è½æ£æ¥ï¼é¿å
å¨
onParticleUpdateä¸å建æ°å¯¹è±¡ - åå§åæ£æ¥ï¼ç²åèªå®ä¹æ°æ®å¨
onParticleEmitä¸åå§å - çå½å¨ææ£æ¥ï¼ä¸ä¿®æ¹ç²åæ°ç»ï¼åªä¿®æ¹å个ç²å屿§
- å ¼å®¹æ§æ£æ¥ï¼ç¡®è®¤ç²åç³»ç»æ¨¡å¼ï¼Gravity æ Radiusï¼
- aspectRatio æ£æ¥ï¼é¿æ¹å½¢å¾çä½¿ç¨ X è½´æè½¬é¿å è·³å
- priority 设置ï¼åç设置ä¼å 级æ§å¶æ§è¡é¡ºåº
常è§é®é¢ä¸è§£å³æ¹æ¡
é®é¢ 1ï¼ä¿®é¥°å¨æªçæ
æ£æ¥æ¸ åï¼
- æ¯å¦æ·»å äº
ParticleModifierç»ä»¶ï¼ - ä¿®é¥°å¨æ¯å¦å·²æ·»å å°ç²åç³»ç»èç¹ï¼
-
priorityæ¯å¦æ£ç¡®ï¼æé¡ºåºæ§è¡ï¼ï¼
é®é¢ 2ï¼æ§è½é®é¢
ä¼å建议ï¼
- åå°ç²åæ°éï¼å»ºè®® < 1000ï¼
- ç®å修饰å¨é»è¾
- é¿å å¤æçæ°å¦è¿ç®
é®é¢ 3ï¼é¿æ¹å½¢å¾çæè½¬è·³å
è§£å³æ¹æ¡ï¼
- 使ç¨
rotationAxis = 'X' - å
¬å¼ï¼
aspectRatio = originalAspectRatio / cos
é®é¢ 4ï¼ç²åé´äº¤äºéæ±
å®ç°æ¹æ³ï¼
- å¨ä¿®é¥°å¨ä¸ç»´æ¤ç²åå表
- å¨
onParticleUpdateä¸éå计ç®
é®é¢ 5ï¼èªå®ä¹æ°æ®ä¸¢å¤±
è§£å³æ¹æ¡ï¼
- å¿
é¡»å¨
onParticleEmitä¸åå§åèªå®ä¹æ°æ® - ä½¿ç¨ TypeScript æ¥å£æ©å± IParticle
åèèµæº
ç¤ºä¾æä»¶ï¼ä½äºæ¬æè½å
ç examples/ ç®å½ï¼
examples/ColorFadeModifier.ts– é¢è²æ¸åexamples/GravityModifier.ts– éåä¿®æ¹examples/LeafRotationModifier.ts– 3D æè½¬
注æï¼è¿äºç¤ºä¾æä»¶ä½äºç²åä¿®é¥°å¨æè½å ä¸ï¼å¯åèå®ç°æ¹å¼
弿æºç ï¼ä½äºæ¬æè½å
ç js/ ç®å½ï¼ä¾çè§£åç使ç¨ï¼
js/particle-simulator.js – ç²å模æå¨
- Particle ç±»å®ä¹ï¼ç²åå¯¹è±¡ç»æï¼å å«ä½ç½®ãé¢è²ã大å°ãæè½¬ãçå½å¨æç屿§
- emitParticle 彿°ï¼åå°æ°ç²åï¼åå§åç²å屿§
- step 彿°ï¼æ¯å¸§æ´æ°ææç²åï¼å æ¬åå°ãæ´æ°ãåæ¶
- updateParticleBuffer 彿°ï¼æ´æ°é¡¶ç¹ç¼å²åºï¼å å« aspectRatio å¤ççå ³é®é»è¾ï¼æ ¹æ® aspectRatio 忢宽é«éç¹ï¼
- å¯¹è±¡æ± ï¼ç²åå¯¹è±¡æ± ç®¡çï¼é¿å é¢ç¹åå»ºéæ¯
js/CCParticleSystem.js – ç²åç³»ç»ç»ä»¶
- 屿§å®ä¹ï¼ç²åç³»ç»ææå¯é ç½®å±æ§ï¼totalParticlesãdurationãemissionRateãlifeãstartColorãendColorãgravityãspeed çï¼
- _simulator åå§åï¼å建ç²å模æå¨å®ä¾
- lateUpdate 彿°ï¼æ¯å¸§è°ç¨æ¨¡æå¨ç step æ¹æ³
- emitterModeï¼GRAVITYï¼é忍¡å¼ï¼å RADIUSï¼å徿¨¡å¼ï¼
- positionTypeï¼FREEï¼èªç±æ¨¡å¼ï¼ãRELATIVEï¼ç¸å¯¹æ¨¡å¼ï¼ãGROUPEDï¼æ´ç»æ¨¡å¼ï¼
js/particle-system-assembler.js – æ¸²ææ±ç¼å¨
- getBuffer 彿°ï¼è·åé¡¶ç¹ç¼å²åºï¼QuadBufferï¼
- fillBuffers 彿°ï¼æäº¤æ¸²ææ°æ®å° GPU
- é¡¶ç¹æ ¼å¼ï¼vfmtPosUvColorï¼ä½ç½® + UV + é¢è²ï¼
- æ¯ç²åé¡¶ç¹æ°ï¼4 个顶ç¹ï¼6 个索å¼ï¼2 个ä¸è§å½¢ï¼
js/CCParticleAsset.js – ç²åèµæº
- spriteFrame 屿§ï¼ç²åè´´å¾èµæº
- æ¯ææ ¼å¼ï¼plist æ ¼å¼çç²åé ç½®æä»¶
注æï¼è¿äºæ¯ Cocos Creator 弿æºç ï¼ä½äºæè½å ä¸ä¾çè§£åç使ç¨
æä½³å®è·µ
- ä¼å ä½¿ç¨ X è½´æè½¬ï¼é¿æ¹å½¢å¾çé¿å è·³å
- é¿å
对象å建ï¼å¨
onParticleUpdateä¸ä¸å建æ°å¯¹è±¡ - åçæ§å¶ç²åæ°ï¼å»ºè®® < 1000 个ç²å
- ä½¿ç¨ priority æ§å¶é¡ºåºï¼ä¿®é¥°å¨æ
priorityä»å°å°å¤§æ§è¡ - å¨ onParticleEmit åå§åï¼ç²åèªå®ä¹æ°æ®å¨åå°æ¶åå§å
- æ©å± IParticle æ¥å£ï¼ä½¿ç¨ TypeScript æ¥å£æ©å±èªå®ä¹æ°æ®ç±»å