cocos 2d粒子采样图片色彩爆炸 让粒子根据图片颜色爆炸shader
我在B站我有一个小喷壶持续更新Shader特效视频所有代码免费欢迎关注思路是让粒子用自己做的shader 这个shader让每个粒子的颜色由脚本输入这是视频展示我在B站我有一个小喷壶持续更新Shader特效视频所有代码免费欢迎关注这是shader让粒子根据图片颜色爆炸完整工程文件通过网盘分享的文件zha.zip链接: https://pan.baidu.com/s/1YFYgL7hhu7P4CtBr08XYwg?pwdoooo 提取码: oooo 完整工程免费自取// Effect Syntax Guide: https://docs.cocos.com/creator/manual/zh/shader/index.htmlCCEffect%{techniques:-name:glow passes:-vert:vs:vert frag:fs:frag blendState:targets:-blend:trueblendSrc:src_alpha blendDst:one_minus_src_alpha depthStencilState:depthTest:falsedepthWrite:falserasterizerState:cullMode:none properties:lightColor:{value:[1.0,0.0,0.0,1.0],editor:{type:color}}lightIntensity:{value:3.0,editor:{slide:true,range:[1,10]}}glowsize:{value:0.0,editor:{slide:true,range:[0,10]}}pulseSpeed:{value:2.0,editor:{slide:true,range:[0,17]}}isBlinking:{value:0,editor:{type:boolean}}}%CCProgram vs%{precision highp float;#includecc-global#ifUSE_LOCAL #includebuiltin/uniforms/cc-local#endif in vec3 a_position;in vec2 a_texCoord;in vec4 a_color;// 来自脚本的每粒子颜色out vec2 uv;out vec4 color;// 传递给片元的颜色名称统一为 colorvec4vert(){gl_Positioncc_matViewProj*vec4(a_position,1.0);uva_texCoord;colora_color;// 将顶点颜色传递给片元returngl_Position;// 必须返回 gl_Position}}%CCProgram fs%{precision highp float;#includesprite-texture#includecc-globalin vec2 uv;in vec4 color;// 接收顶点传来的颜色uniform sampler2D mainTexture;uniform UBO{vec4 lightColor;float lightIntensity;float pulseSpeed;float time;float isBlinking;float glowsize;};vec4frag(){vec4 texColortexture(cc_spriteTexture,uv);// 最终颜色 顶点颜色 * 纹理颜色或直接用顶点颜色// 这里保留 alpha 混合returncolor;}}%然后在粒子prefab中用这个shader的材质然后是脚本这个脚本挂载粒子prefab作用是接受图片脚本传来的图片色彩 然后给每个粒子用上图片的颜色import{_decorator,Color,Component,ImageAsset,Node,ParticleSystem2D,Sprite,SpriteFrame,Texture2D,UITransform}fromcc;const{ccclass,property}_decorator;ccclass(l)export class l extends Component{property(ParticleSystem2D)particle:ParticleSystem2D;property(Node)colorSourceNode:Nodenull;property(ImageAsset)private colorSourceImage:ImageAssetnull;private pixelData:Uint8Arraynull;private texWidth0;private texHeight0;private uvRect:{x:number,y:number,w:number,h:number}|nullnull;// 子图矩形private readyfalse;private fallbackColors:Color[][/* ... */];width:number0;height:number0;start(){console.log(l start);(this.particle as any).customtrue;// 不在此处读取等待外部调用}readTexturePixels(){let imageAsset:ImageAssetnull;this.uvRectnull;if(this.colorSourceNode){constspritethis.colorSourceNode.getComponent(Sprite);consttranthis.colorSourceNode.getComponent(UITransform);this.widthtran.width;this.heighttran.height;if(spritesprite.spriteFrame){constspriteFramesprite.spriteFrame;consttexturespriteFrame.texture;// 使用 as Texture2D 进行类型断言imageAssetthis.extractImageAssetFromTexture(texture as Texture2D);if(imageAsset){constrectspriteFrame.rect;if(rectrect.width0rect.height0){this.uvRect{x:rect.x,y:rect.y,w:rect.width,h:rect.height};}}else{console.warn(colorSourceNode 的纹理无法提取 ImageAsset);}}else{console.warn(colorSourceNode 缺少 Sprite 组件或 spriteFrame);}}// 2. 后备直接使用 colorSourceImageif(!imageAssetthis.colorSourceImage){imageAssetthis.colorSourceImage;}if(!imageAsset){console.warn(未设置有效的颜色源将使用后备颜色);this.readyfalse;return;}constimgimageAsset.data as HTMLImageElement;if(!img){console.warn(ImageAsset 数据无效);this.readyfalse;return;}if(img.completeimg.naturalWidth0){this.processImage(img);}else{img.onload()this.processImage(img);}}privateextractImageAssetFromTexture(texture:Texture2D):ImageAsset|null{constprops[_image,image,_img,_textureSource];for(constprop of props){constasset(texture as any)[prop];if(asset instanceof ImageAsset)returnasset;}if(typeof(texture as any).getImagefunction){constasset(texture as any).getImage();if(asset instanceof ImageAsset)returnasset;}returnnull;}privateprocessImage(img:HTMLImageElement){this.texWidthimg.width;this.texHeightimg.height;constcanvasdocument.createElement(canvas);canvas.widththis.texWidth;canvas.heightthis.texHeight;constctxcanvas.getContext(2d);ctx.drawImage(img,0,0);constimageDatactx.getImageData(0,0,this.texWidth,this.texHeight);this.pixelDatanewUint8Array(imageData.data);this.readytrue;console.log(图片加载完成尺寸${this.texWidth}x${this.texHeight});}lateUpdate(){constpsthis.particle as any;constsimulatorps._simulator;if(!simulator)return;constparticlessimulator.particles as any[];if(!particles||particles.length0)return;// 设置粒子发射区域大小同前if(this.texWidth0this.texHeight0){if(this.uvRect){this.particle.posVar.xthis.width/2;this.particle.posVar.ythis.height/2;}else{this.particle.posVar.xthis.texWidth/2;this.particle.posVar.ythis.texHeight/2;}}else{// console.log(使用编辑器设置的粒子大小);}// 定义绝对安全的默认颜色纯白色constDEFAULT_COLOR{r:255,g:255,b:255,a:255};for(let i0;iparticles.length;i){constpparticles[i];if(!p||p.timeToLive0)continue;let r:number,g:number,b:number,a:number;if(this.readythis.pixelData){// 从图片采样颜色let px:number,py:number;if(this.uvRect){constuMath.random();constvMath.random();pxMath.floor(this.uvRect.xu*this.uvRect.w);pyMath.floor(this.uvRect.yv*this.uvRect.h);}else{pxMath.floor(Math.random()*this.texWidth);pyMath.floor(Math.random()*this.texHeight);}constidx(py*this.texWidthpx)*4;rthis.pixelData[idx];gthis.pixelData[idx1];bthis.pixelData[idx2];athis.pixelData[idx3];}else{// 使用后备颜色但要确保数组有效constfallbackthis.fallbackColors?.[i%this.fallbackColors.length];if(fallback){rfallback.r;gfallback.g;bfallback.b;afallback.a;}else{// 后备颜色无效时使用默认颜色rDEFAULT_COLOR.r;gDEFAULT_COLOR.g;bDEFAULT_COLOR.b;aDEFAULT_COLOR.a;}}// 关键检查 p.color 是否存在再调用 setif(p.color){p.color.set(r,g,b,a);}else{// 如果 color 不存在可以尝试赋值一个临时颜色但通常不会发生// 或者忽略该粒子后续系统会处理console.warn(粒子 color 属性不存在跳过颜色设置);}}if(simulator.renderData){simulator.renderData.uvDirtytrue;if(simulator.renderData.dataHash!undefined){simulator.renderData.dataHash0;}}}}最后是点击sprite爆炸粒子脚本 将sprite的图片颜色传给上面的脚本import{_decorator,Component,EventTouch,ImageAsset,instantiate,Node,ParticleSystem2D,Prefab,Sprite,SpriteFrame,UITransform,Vec3}fromcc;import{l}from./l;const{ccclass,property}_decorator;ccclass(zha)export class zha extends Component{property(Prefab)particlePrefab:Prefabnull!;// 粒子预制体property particleOffset:Vec3newVec3(0,0,0);// 粒子位置偏移property autoDestroy:booleantrue;// 是否自动销毁粒子property destroyDelay:number2.0;// 粒子自动销毁延迟时间property respawnDelay:number1.5;// Sprite重新显示延迟时间property disableTouchDuringEffect:booleantrue;// 效果期间禁用点击private isAnimating:booleanfalse;// 是否正在播放动画private originalScale:Vec3newVec3(1,1,1);// 保存原始缩放property(ImageAsset)particleColorSource:ImageAssetnull!;// 新增粒子颜色源图片start(){// 保存原始缩放this.originalScalethis.node.scale.clone();// 添加触摸事件this.node.on(Node.EventType.TOUCH_START,this.onTouchStart,this);this.node.on(Node.EventType.TOUCH_END,this.onTouchEnd,this);this.node.on(Node.EventType.TOUCH_CANCEL,this.onTouchCancel,this);}onDestroy(){// 移除事件监听this.node.off(Node.EventType.TOUCH_START,this.onTouchStart,this);this.node.off(Node.EventType.TOUCH_END,this.onTouchEnd,this);this.node.off(Node.EventType.TOUCH_CANCEL,this.onTouchCancel,this);}onTouchStart(event:EventTouch){if(this.isAnimatingthis.disableTouchDuringEffect)return;// 触摸开始添加按下效果this.node.scalenewVec3(0.95,0.95,1);}onTouchEnd(event:EventTouch){if(this.isAnimatingthis.disableTouchDuringEffect)return;// 恢复缩放this.node.scalethis.originalScale.clone();// 获取触摸位置世界坐标consttouchPosevent.getUILocation();// 将世界坐标转换为节点本地坐标constuiTransformthis.getComponent(UITransform);if(!uiTransform)return;constlocalPosuiTransform.convertToNodeSpaceAR(newVec3(touchPos.x,touchPos.y,0));// 开始爆炸效果this.startExplosionEffect(localPos);}onTouchCancel(event:EventTouch){// 触摸取消恢复缩放this.node.scalethis.originalScale.clone();}/** * 开始爆炸效果 */startExplosionEffect(position:Vec3){if(this.isAnimating)return;this.isAnimatingtrue;// 1. Sprite消失可以添加消失动画this.hideSprite();// 2. 生成粒子效果this.spawnParticle(position);// 3. 延迟后重新显示Spritethis.scheduleOnce((){this.showSprite();this.isAnimatingfalse;},this.respawnDelay);}/** * 隐藏Sprite带动画效果 */hideSprite(){// 方法1直接隐藏this.node.activefalse;// 方法2淡出效果如果有Sprite组件// const sprite this.getComponent(Sprite);// if (sprite) {// sprite.color sprite.color.clone();// sprite.color.a 0;// }// 方法3缩放消失动画// this.node.scale new Vec3(0, 0, 1);// 或者使用tween动画}/** * 显示Sprite带动画效果 */showSprite(){// 方法1直接显示this.node.activetrue;// 方法2淡入效果// const sprite this.getComponent(Sprite);// if (sprite) {// sprite.color sprite.color.clone();// sprite.color.a 255;// }// 方法3缩放出现动画this.node.scalenewVec3(0,0,1);// 使用缓动动画恢复// tween(this.node)// .to(0.3, { scale: this.originalScale })// .start();// 简单版本直接恢复this.scheduleOnce((){this.node.scalethis.originalScale.clone();},0.1);}/** * 生成粒子效果 */spawnParticle(position:Vec3){if(!this.particlePrefab){console.warn(请设置粒子预制体);return;}// 实例化粒子constparticleNodeinstantiate(this.particlePrefab);// 设置父节点为当前节点的父节点这样Sprite隐藏时粒子还能显示if(this.node.parent){this.node.parent.addChild(particleNode);}else{particleNode.parentthis.node;}// 设置位置世界坐标constworldPosthis.node.getWorldPosition();particleNode.setWorldPosition(worldPos);// 获取粒子系统组件constparticleSystemparticleNode.getComponent(ParticleSystem2D);// 获取 l 脚本组件constlScriptparticleNode.getComponent(l)as any;// 使用组件名或类型if(lScript){// 直接将当前节点作为颜色源节点传递lScript.colorSourceNodethis.node;// 如果 l 脚本需要立即读取纹理像素调用其方法if(lScript.readTexturePixels){lScript.readTexturePixels();}}if(particleSystem){// 播放粒子效果particleSystem.resetSystem();// 如果需要自动销毁if(this.autoDestroy){this.scheduleOnce((){if(particleNodeparticleNode.isValid){particleNode.destroy();}},this.destroyDelay);}}returnparticleNode;}onLoad(){if(this.node.name.includes(o)){this.node.setSiblingIndex(14);}constothis.node.getSiblingIndex();console.log(this.node.name,/,o);console.log(zha);}}