Cocos 2D Shader 实现攻击范围合并效果 作者: ciniao 时间: 2026-02-10 分类: AI文摘 *摘自:https://mp.weixin.qq.com/s/-pBI1GkQAqEO1Zav4vYDpg* 本文介绍如何在Cocos Creator中使用Shader实现游戏中箭塔攻击范围的合并效果,即多个攻击范围重叠时呈现拼接效果而非简单叠加。 ## 实现思路分析 要实现4个箭塔攻击范围互相重叠的拼接效果,传统方法存在局限性: - 遮罩(Mask)组件:性能影响较大 - Sprite组件的filled模式:只能在垂直或水平一个方向裁切,无法满足4个范围互相重叠的需求 因此采用Shader实现增强版的Sprite Filled功能。 ## Shader实现核心原理 ### 1. 着色器文件结构 以Cocos Creator内置的Sprite材质和着色器为模板开始,内置着色器主要包含: - 接受纹理(texture)和透明阈值的传参 - 在片段着色器中导入节点颜色"v_color"和自定义参数"texture" - CCTexture读取纹理当前像素,乘以v_color实现颜色控制 ### 2. 多方向fill效果的shader实现 要实现任意角度的fill效果,需要增加几个关键参数: ```glsl fillRange: { value: [0.0, 1.0] } // [start, end] 填充范围,0-1之间 fillAngle: { value: 0.0 } // 填充角度,0-360度 fillCenter: { value: [0.5, 0.5] } // 填充中心点,归一化坐标 ``` 在片段着色器中根据fillAngle和fillRange计算需要裁掉的像素。核心计算逻辑: ```glsl // 任意方向fill range效果 #if USE_TEXTURE // 将角度转换为弧度 float angleRadians = radians(fillAngle); // 计算当前像素相对于中心点的向量 vec2 dir = v_uv0 - fillCenter; // 旋转向量到指定角度 float cosAngle = cos(angleRadians); float sinAngle = sin(angleRadians); vec2 rotatedDir = vec2( dir.x * cosAngle - dir.y * sinAngle, dir.x * sinAngle + dir.y * cosAngle ); // 计算在填充方向上的投影长度(归一化到0-1范围) float projection = clamp((rotatedDir.x + 0.5), 0.0, 1.0); // 根据填充范围决定是否丢弃像素 if (projection < fillRange.x || projection > fillRange.y) { discard; // 丢弃当前像素 } #endif ``` ### 3. 支持多方向的实现方案 由于Shader不支持多维数组传参,采用变通方案:将fillRange的起止位置和角度3个值放在一个vec4中,一个vec4为一组: ```glsl properties: texture: { value: white } // 默认纹理为白色 alphaThreshold: { value: 0.5 } // alpha测试阈值 // 填充范围参数 - 实现多个方向的fill range效果 range1: { value: [0.0, 1.0, 0.0, 0.0] } // range start, range end, angle, enable range2: { value: [0.0, 1.0, 0.0, 0.0] } range3: { value: [0.0, 1.0, 0.0, 0.0] } range4: { value: [0.0, 1.0, 0.0, 0.0] } ``` 通过代码控制Shader参数: ```javascript let mat = this.node.getComponent(cc.Sprite).getMaterial(0); mat.setProperty('range1', new cc.Vec4(0, .8, 30, 1)); ``` ## 攻击范围合并效果实现 ### 计算重叠逻辑 1. **检测重叠**:每当范围变化时计算当前范围与其他范围的距离,判断是否重叠 2. **计算裁切参数**:若重叠则计算要裁切的长度和与重叠范围中心的角度 3. **更新Shader**:将计算得到的两个关键值传递给Shader,同时通知"被重叠"的范围更新Shader ### 重叠长度计算算法 假设两个圆的圆心在x轴上,当前范围(selfNode)的圆心为(0,0),另一个在x+方向: ```javascript // 计算两个圆的交点 let d = distance; let r1 = selfNode.width / 2 * selfNode.scale; let r2 = item.width / 2 * item.scale; let a = (r1 * r1 - r2 * r2 + d * d) / (2 * d); let h = Math.sqrt(r1 * r1 - a * a); let t0 = cc.v2(1, 0); // 交点坐标 let x = 0 + a * t0.x - h * t0.y; ``` 这样计算的好处是简化算法,交点的x坐标就是圆心到两个交点连线的垂直距离。 ### 伪3D透视效果 为了实现更真实的视觉效果,给所有范围的共同父节点添加Y轴缩放:   ## 完整着色器代码 以下是完整的着色器实现代码: ```glsl // Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. // 效果定义块 - 定义整个shader的效果参数和技术 CCEffect %{ // 技术列表 - 可以定义多个渲染技术 techniques: // 渲染通道 - 定义具体的渲染步骤 - passes: - vert: vs // 顶点着色器使用vs程序 frag: fs // 片段着色器使用fs程序 // 混合状态配置 - 控制颜色混合 blendState: targets: - blend: true // 启用颜色混合 // 光栅化状态配置 - 控制多边形剔除 rasterizerState: cullMode: none // 禁用背面剔除,渲染所有面 // 材质属性 - 可以在编辑器调整的参数 properties: texture: { value: white } // 默认纹理为白色 alphaThreshold: { value: 0.5 } // alpha测试阈值,低于此值的像素会被丢弃 // 填充范围参数 - 实现任意方向的fill range效果 fillRange: { value: [0.0, 1.0] } // [start, end] 填充范围,0-1之间 fillAngle: { value: 0.0 } // 填充角度,0-360度 fillCenter: { value: [0.5, 0.5] } // 填充中心点,归一化坐标 }% // 顶点着色器程序 - 处理每个顶点的位置和颜色 CCProgram vs %{ precision highp float; // 使用高精度浮点数 #include // 包含Cocos Creator全局变量 #include // 包含Cocos Creator局部变量 in vec3 a_position; // 输入:顶点位置坐标 in vec4 a_color; // 输入:顶点颜色 out vec4 v_color; // 输出:传递给片段着色器的颜色 #if USE_TEXTURE // 如果使用纹理 in vec2 a_uv0; // 输入:纹理坐标 out vec2 v_uv0; // 输出:传递给片段着色器的纹理坐标 #endif // 顶点着色器主函数 - 每个顶点执行一次 void main () { vec4 pos = vec4(a_position, 1); // 将3D位置转换为4D齐次坐标 #if CC_USE_MODEL // 如果使用模型矩阵 pos = cc_matViewProj * cc_matWorld * pos; // 应用模型、视图、投影变换 #else pos = cc_matViewProj * pos; // 只应用视图和投影变换 #endif #if USE_TEXTURE v_uv0 = a_uv0; // 传递纹理坐标到片段着色器 #endif v_color = a_color; // 传递颜色到片段着色器 gl_Position = pos; // 设置最终顶点位置 } }% // 片段着色器程序 - 处理每个像素的颜色 CCProgram fs %{ precision highp float; // 使用高精度浮点数 #include // 包含alpha测试功能 #include // 包含纹理采样功能 in vec4 v_color; // 输入:从顶点着色器传递的颜色 #if USE_TEXTURE // 如果使用纹理 in vec2 v_uv0; // 输入:从顶点着色器传递的纹理坐标 uniform sampler2D texture; // 纹理采样器 #endif // 填充范围参数 - 必须使用uniform块声明 uniform FillRangeParams { vec2 fillCenter; // 填充中心点 vec2 fillRange; // [start, end] 填充范围 float fillAngle; // 填充角度,弧度制 }; // 片段着色器主函数 - 每个像素执行一次 void main () { vec4 o = vec4(1, 1, 1, 1); // 初始化输出颜色为白色 #if USE_TEXTURE CCTexture(texture, v_uv0 ,o); // 直接采样纹理并使用纹理颜色 #endif o *= v_color; // 将顶点颜色与纹理颜色相乘 // 任意方向fill range效果 #if USE_TEXTURE // 将角度转换为弧度(属性中的角度是0-360度) float angleRadians = radians(fillAngle); // 计算当前像素相对于中心点的向量 vec2 dir = v_uv0 - fillCenter; // 旋转向量到指定角度 float cosAngle = cos(angleRadians); float sinAngle = sin(angleRadians); vec2 rotatedDir = vec2( dir.x * cosAngle - dir.y * sinAngle, dir.x * sinAngle + dir.y * cosAngle ); // 计算在填充方向上的投影长度(归一化到0-1范围) // 将旋转后的x坐标从[-0.5, 0.5]映射到[0, 1] float projection = clamp((rotatedDir.x + 0.5), 0.0, 1.0); // 根据填充范围决定是否丢弃像素 if (projection < fillRange.x || projection > fillRange.y) { discard; // 丢弃当前像素 } #endif // 只在裁剪区域外的像素执行alpha测试 // 这样可以避免半透明纹理在裁剪区域外被错误处理 // if (o.a > 0.0) { ALPHA_TEST(o); // 执行alpha测试,丢弃透明度低于阈值的像素 // } #if USE_BGRA // 如果使用BGRA颜色格式(某些平台需要) gl_FragColor = o.bgra; // 输出BGRA格式的颜色 #else gl_FragColor = o.rgba; // 输出RGBA格式的颜色 #endif } }% ``` ## 工程地址 完整工程代码可在以下地址获取:https://gitee.com/dubox/cocos-AttackRange 通过这种Shader实现方式,可以高效地实现游戏中多个攻击范围的合并效果,相比传统遮罩方案具有更好的性能表现,同时支持更灵活的视觉效果定制。 标签: none
评论已关闭