RPG游戏技能预警实现原理教程 RPG 技能预警实现原理教程1. 引言为什么不用特效 Mesh传统做法是在角色脚下生成一个带圆环贴图的 Quad / 环形 Mesh / 粒子特效贴着地面播放。平坦地面看起来没问题但地形一旦起伏Mesh 是一张平面会穿进山坡或悬空漂浮用投影、多段贴地、实时变形 Mesh 成本高还容易闪烁多技能同时预警时DrawCall / 特效实例数量容易爆更稳妥的做法是不额外建 Mesh把预警画在地面 Shader 里。地面本来就要渲染片元着色器里按世界坐标判断「这个像素离技能中心多远」——落在圆环带内就染成红色否则保持地面原色。这样做的好处对比项特效 Mesh地面 Shader起伏地形易穿插 / 悬空跟地面网格走天然贴地额外几何每个预警一个 Mesh0多实例多 DrawCall一个 Buffer 里塞多个中心点扩展性每种效果各做一套特效同一套「世界坐标 距离判断」阴影、弹坑、飙血、脚印等「贴在地面上」的效果本质都是这个原理。2. 核心原理一句话 一张图CPU 把技能中心的世界坐标写入 ComputeBufferGPU 在地面片元里读 StructuredBuffer用当前像素世界坐标做距离判断决定是否染色。角色 / 技能中心 (World Pos) │ ▼ C# ComputeBuffer.SetData │ ▼ Material.SetBuffer → StructuredBufferfloat4 │ ▼ ┌─────────────────────────────────────┐ │ 地面 Vertex算出 worldPos │ │ 地面 Fragment │ │ dist |pixel.xz - center.xz| │ │ if inner ≤ dist ≤ outer → 红色 │ │ else → 地面原色 │ └─────────────────────────────────────┘关键点有三个世界坐标空间中心点和像素都在 World Space和角色高度 Y 解耦用 XZ 平面距离即可。不生成圆环 Mesh圆环是数学区域innerRadius ~ outerRadius不是模型。StructuredBuffer支持同时传入多个预警中心AOE 连招、多人施法比SetVector单个变量更可扩展。3. 工程结构本仓库 Demo资源作用Assets/New Folder/NewUnlitShader.shaderURP 地面 Unlit Shader片元里画圆环Assets/New Folder/NewBehaviourScript.cs创建/更新 ComputeBuffer把角色坐标推给材质Assets/New Folder/New Material.mat挂在 Plane 上使用上述 ShaderAssets/New Folder/testwarm.unity测试场景Plane Cube挂载关系脚本挂在地面 Plane 上改的是地面材质cubeTransform指向角色 / Cube不要挂在角色上再去改角色材质——预警画在地上不是画在角色身上4. Shader 核心讲解4.1 为什么要#pragma target 4.5StructuredBuffer需要较新的 Shader Model。URP 下写#pragma target 4.5 #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl并加上 Forward 通道标签确保 URP 真的会画这个 PassTags { LightMode UniversalForward }4.2 Vertex把世界坐标传给片元片元要做距离判断必须知道当前像素在世界里的位置。顶点阶段算一次光栅化后插值到每个像素struct Varyings { float2 uv : TEXCOORD0; float3 worldPos : TEXCOORD1; // 关键世界坐标 float4 positionCS : SV_POSITION; }; Varyings vert(Attributes v) { Varyings o; VertexPositionInputs posInputs GetVertexPositionInputs(v.positionOS.xyz); o.positionCS posInputs.positionCS; o.worldPos posInputs.positionWS; // Object → World o.uv TRANSFORM_TEX(v.uv, _MainTex); return o; }GetVertexPositionInputs是 URP 标准写法比手写mul(UNITY_MATRIX_M, ...)更稳妥。4.3 Buffer 与材质属性怎么声明// StructuredBuffer 不能放进 UnityPerMaterial StructuredBufferfloat4 _CenterPositions; float _PositionCount; // 运行时数量C# 每帧 SetFloat CBUFFER_START(UnityPerMaterial) float4 _MainTex_ST; float4 _BaseColor; float _RingOuterRadius; float _RingInnerRadius; float4 _RingColor; CBUFFER_END注意StructuredBuffer / ComputeBuffer 绑定不能写进CBUFFER单独声明即可。半径、颜色等常规属性放进UnityPerMaterial方便 SRP Batcher 识别本 Shader 因 Buffer 本身会打破 Batcher但写法仍建议规范。4.4 Fragment逐像素画圆环最核心half4 frag(Varyings i) : SV_Target { half4 col SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv) * _BaseColor; const int kMax 64; int count clamp((int)_PositionCount, 0, kMax); for (int j 0; j count; j) { float3 center _CenterPositions[j].xyz; // 只比 XZ角色跳起来、中心点 Y 不同也不影响贴地圆环 float dist distance(i.worldPos.xz, center.xz); // 圆环带内径以外、外径以内 if (dist _RingInnerRadius dist _RingOuterRadius) { col.rgb lerp(col.rgb, _RingColor.rgb, _RingColor.a); return col; } } return col; // 不在任何预警里 → 地面原色 }原理拆解外圆 outerRadius ┌─────────────────┐ │ 红色预警环 │ ← inner ≤ dist ≤ outer │ ┌─────────┐ │ │ │ 空洞 │ │ ← dist inner不染色 │ │ center │ │ │ └─────────┘ │ └─────────────────┘distance(xz, xz)二维欧氏距离等价于水平面投影距离。硬判断if足够做 Demo正式项目可用smoothstep做软边抗锯齿。for上界用常量kMaxGPU 更友好真实数量由_PositionCount控制。这就是「不创建 Mesh」的全部秘密圆环是片元里算出来的数学区域。5. C# 核心讲解ComputeBuffer → StructuredBuffer5.1 创建 Buffer每个元素用float4xyz 世界坐标w 可留作自定义数据如强度、剩余时间constintMaxPositions64;ComputeBuffer_buffer;Vector4[]_positionsnewVector4[1];_buffernewComputeBuffer(MaxPositions,sizeof(float)*4,ComputeBufferType.Structured);stride 16字节与 Shader 里float4对齐。容量按「同时存在的最大预警数」预留避免每帧重建 Buffer。5.2 每帧写入并绑定必做voidPushToMaterial(){if(_buffernull||_runtimeMatnull)return;_buffer.SetData(_positions,0,0,_positions.Length);// Unity 有时会丢失 StructuredBuffer 绑定建议每帧 SetBuffer_runtimeMat.SetBuffer(_CenterPositions,_buffer);_runtimeMat.SetFloat(_RingOuterRadius,outerRadius);_runtimeMat.SetFloat(_RingInnerRadius,innerRadius);_runtimeMat.SetColor(_RingColor,ringColor);_runtimeMat.SetFloat(_PositionCount,_positions.Length);}// Update 里把角色坐标塞进数组Vector3pcubeTransform.position;_positions[0]newVector4(p.x,p.y,p.z,0f);PushToMaterial();数据流对应关系C#ShaderComputeBufferSetData(Vector4[])StructuredBufferfloat4 _CenterPositionsSetFloat(_PositionCount, n)float _PositionCountSetFloat(_RingOuterRadius, …)float _RingOuterRadius5.3 生命周期创建 / 还原 / 释放voidOnEnable(){// 克隆材质避免改脏工程里的 .mat_runtimeMatnewMaterial(_renderer.sharedMaterial);_renderer.sharedMaterial_runtimeMat;_buffernewComputeBuffer(...);}voidOnDisable(){// 先还原资源材质再 Destroy 运行时实例// 否则退出 Play 时 Inspector 容易报 SerializedObject Disposed_renderer.sharedMaterial_sharedMat;Destroy(_runtimeMat);_buffer.Release();}要点ComputeBuffer 必须Release()否则显存泄漏。用运行时材质克隆不要直接改sharedMaterial上的 Buffer会污染资源、影响所有引用者。脚本挂在地面cubeTransform指向角色。5.4 多预警怎么扩展把_positions改成动态列表即可// 伪代码本帧所有技能预警中心_positionsnewVector4[skillCenters.Count];for(inti0;iskillCenters.Count;i){varpskillCenters[i];_positions[i]newVector4(p.x,p.y,p.z,skillCenters[i].intensity);}_buffer.SetData(_positions);_mat.SetFloat(_PositionCount,_positions.Length);Shader 里w分量可参与透明度、闪烁、倒计时缩放等。6. 同一原理扩展到其他地面效果所有「贴地表现」都可以抽象成地面像素世界坐标 一组中心点 / 贴花数据 → 片元里混合颜色或采样贴花纹理效果判断 / 采样方式Buffer 里可带的数据技能预警环inner ≤ dist ≤ outer染红中心、内外径、颜色、剩余时间圆形阴影dist radius压暗角色脚底投影点、半径、强度弹坑圆域内采样弹坑法线/颜色贴图或压暗扰动 UV命中点、半径、朝向角、衰减时间飙血 / 血迹椭圆或贴花 UV 投影到 XZ采样血渍贴图落点、旋转、缩放、透明度脚印与血迹相同换成脚印纹理可做左右脚交替落脚点、朝向、左右脚标记6.1 从「距离圆环」到「贴花 UV」圆环只用距离贴花类效果多一步把世界 XZ 转到以中心为原点的局部 UV// 伪代码世界 XZ → 贴花 UV float2 local i.worldPos.xz - center.xz; // 可选按 yaw 旋转 local local mul(rotation2x2, local); float2 uv local / radius * 0.5 0.5; // 映射到 [0,1] if (uv.x 0 uv.x 1 uv.y 0 uv.y 1) { half4 decal SAMPLE_TEXTURE2D(_DecalTex, sampler_DecalTex, uv); col.rgb lerp(col.rgb, decal.rgb, decal.a * fade); }弹坑、脚印、血迹都是这套Decal贴花思路预警环只是把「采样贴图」简化成了「纯色圆环」。6.2 和 URP Decal Projector 的关系URP 自带 Decal 系统也是「往表面上投影」适合复杂场景。本教程的做法更轻数据全在 StructuredBuffer适合大量、短生命周期、逻辑驱动的地面痕迹不额外生成 Decal 投影器 GameObject和玩法系统技能、受击、脚步事件耦合更直接量特别大时可再升级成离屏 RT 累积贴花地面只采样一张痕迹图仍是同一思想的批处理版。7. 实现检查清单地面材质 Shader 是 URP且 Pass 带LightMode UniversalForward#pragma target 4.5声明StructuredBufferfloat4顶点输出worldPos片元用worldPos.xz算距离C#ComputeBuffer→SetData→Material.SetBuffer数量用_PositionCount脚本挂在地面中心点用角色世界坐标OnDisable/OnDestroy里ReleaseBuffer并还原材质起伏地形只要地面 Mesh 本身贴合地形预警就会跟着 Mesh 起伏不会穿插8. 小结技能预警的「正确打开方式」不是再造一个圆环模型而是CPU用 ComputeBuffer 广播「哪里有预警」GPU在地面片元里用世界坐标回答「我在不在圆环里」同一管道换成距离衰减、贴花 UV就能做阴影、弹坑、飙血、脚印特效 Mesh 适合夸张的三维演出贴地逻辑表现优先走 Shader Buffer。两者可以并存预警圈用地面 Shader爆炸火光仍用特效。