Unity 刮刮乐插件(Scratch Card)完整解析 + 实战使用
一、插件核心架构
这套插件一共分 4 层,完全对应你提供的脚本:
- 交互层:
ScratchCardInput.cs—— 处理触摸 / 鼠标 - 数据层:
BaseData / ImageData / SpriteRendererData / MeshRendererData—— 坐标→UV 计算 - 渲染层:
ScratchCardRenderer.cs—— 往 RenderTexture 画刮痕 - 显示层:
ScratchCard.cs + Shader—— 用 RT 做遮罩显示隐藏 - 辅助层:
EraseProgress.cs—— 计算刮开百分比
二、核心原理
插件不修改 Sprite 原始 UV,而是:通过屏幕坐标 → 局部坐标 → 精确 UV 映射,在RenderTexture上绘制刮痕,再通过Shader 遮罩控制刮层显示 / 隐藏。
三、核心脚本逐行解析
1. ScratchCard.cs(总控制脚本)
作用: 插件的大脑,管理 RT、输入、渲染。关键代码:
csharp
运行
// 创建渲染纹理 RT
private void CreateRenderTexture()
{
RenderTexture = new RenderTexture(..., renderTextureFormat);
// 把 RT 传给 Shader 当遮罩
SurfaceMaterial.SetTexture(Constants.MaskShader.MaskTexture, RenderTexture);
}
// 接收输入,调用渲染
private void TryScratchHole(Vector2 position, float pressure)
{
cardRenderer.ScratchHole(position, pressure);
}
2. ScratchCardRenderer.cs(真正画刮痕的地方)
作用: 使用 CommandBuffer 往 RT 画四边形(刮痕)。关键代码:
csharp
运行
// 画一个刮痕(四边形)
public void ScratchHole(Vector2 position, float pressure = 1f)
{
// 计算在 RT 上的矩形位置
var positionRect = new Rect(...)
// 设置 Mesh 顶点
meshHole.vertices = new[]
{
new Vector3(positionRect.xMin, positionRect.yMax, 0),
new Vector3(positionRect.xMax, positionRect.yMax, 0),
...
};
// 渲染到 RT
commandBuffer.SetRenderTarget(scratchCard.RenderTarget);
commandBuffer.DrawMesh(meshHole, ...);
Graphics.ExecuteCommandBuffer(commandBuffer);
}
3. ScratchCardInput.cs(输入系统)
作用: 鼠标 / 触摸输入,支持新输入系统与旧输入系统。关键:
- 不直接输出屏幕坐标
- 输出纹理空间坐标(已经算好 UV 对应位置)
csharp
运行
private void Scratch()
{
for (var i = 0; i < isScratching.Length; i++)
{
if (isScratching[i])
{
OnScratchHole?.Invoke(endInputData[i].Position, ...);
}
}
}
4. BaseData / ImageData / SpriteRendererData(UV 计算核心!)
这是你最关心的:坐标怎么转 UV?
核心算法:射线 + 重心坐标三角形映射
csharp
运行
// BaseData.cs
public virtual Vector2 GetScratchPosition(Vector2 position)
{
var ray = Camera.ScreenPointToRay(position);
if (plane.Raycast(ray, out enter))
{
var pointLocal = Surface.InverseTransformPoint(ray.GetPoint(enter));
var uv = Triangle.GetUV(pointLocal); // 重心坐标算UV
return Vector2.Scale(TextureSize, uv);
}
}
Triangle.cs(重心坐标算法)
作用:无论形状是什么,都能算出精确 UV。这就是不规则 Sprite 也能精准刮擦的原因!
csharp
运行
public Vector2 GetUV(Vector3 point)
{
var va = Vector3.Cross(v0 - v1, v0 - v2);
var va1 = Vector3.Cross(distance1, distance2);
...
var a1 = va1.magnitude / area * sign;
var uv = uv0 * a1 + uv1 * a2 + uv2 * a3;
return uv;
}
5. EraseProgress.cs(刮开进度计算)
作用: 读取 RenderTexture 像素,统计白色占比。
csharp
运行
private IEnumerator CalcProgress()
{
var request = AsyncGPUReadback.RequestIntoNativeArray(...);
yield return ...;
for (var i = 0; i < pixelsBuffer.Length; i += bitsPerPixel)
{
progress += pixelsBuffer[i] / 255f;
}
progress /= 总像素数;
}
四、最重要问题:
修改 Sprite 形状(圆形 / 心形 / 不规则)还能正常涂抹
为什么能?
因为插件不依赖 Sprite 形状,不依赖碰撞体,不依赖矩形。它依赖的是:
1. UV 映射(Triangle 重心坐标)
ImageData / SpriteRendererData / MeshRendererData 都会计算精确 UV→ 只在纹理有效区域绘制
2. Shader 会自动处理透明通道
plaintext
涂层显示 = 原始涂层颜色 * 遮罩RTAlpha * Sprite原图Alpha
3. 即使是透明镂空图,刮擦也不会溢出
插件在 BaseData 层就把坐标限制在 UV (0~1) 内。
五、非遗剪纸互动涂抹效果核心算法


本章节仅聚焦涂抹擦除算法本身,UI、动画、控制逻辑均简化说明,完全结合插件源码与不规则剪纸形状的实际交互场景。
5.1 剪纸涂抹需求说明
- 上层:白色剪纸遮罩(圆形 / 八边形等不规则 Sprite)
- 下层:红色剪纸底图
- 交互逻辑:手指涂抹 → 擦除遮罩 → 露出剪纸 → 达到进度触发展开
- 核心要求:仅在剪纸形状内响应涂抹,透明区域不生效
5.2 涂抹算法整体流程
涂抹效果由插件 5 个核心脚本协同实现,标准执行链路:
plaintext
触摸输入 → 坐标转换 → UV 精确映射 → 刮痕绘制到 RT → Shader 遮罩混合显示
5.3 阶段一:触摸输入采集(ScratchCardInput.cs)
插件兼容新旧输入系统,获取屏幕坐标并进行射线遮挡检测,保证涂抹只在有效区域触发。
csharp
运行
// 核心:捕获触摸/鼠标,输出纹理空间坐标
private void SetInputData(int fingerId, Vector2 position, float pressure = 1f)
{
// 射线检测:过滤剪纸外部透明区域误触
if (CheckCanvasRaycasts && raycastController.IsBlock(position))
return;
// 坐标转换:屏幕坐标 → 纹理坐标
scratchPosition = OnScratch(position);
// 触发涂抹逻辑
OnScratchHole?.Invoke(endInputData[i].Position, pressure);
}
作用:精准过滤无效区域触摸,只响应剪纸范围内的涂抹操作。
5.4 阶段二:坐标到 UV 精确映射(BaseData + ImageData + Triangle)
这是不规则剪纸能精准涂抹的核心算法,也是整套方案的关键。
(1)坐标转换逻辑(BaseData.cs)
屏幕坐标 → 局部坐标 → 标准 UV 坐标
csharp
运行
public virtual Vector2 GetScratchPosition(Vector2 position)
{
var ray = Camera.ScreenPointToRay(position);
if (plane.Raycast(ray, out var enter))
{
// 世界坐标转局部坐标
var pointLocal = Surface.InverseTransformPoint(ray.GetPoint(enter));
// 重心坐标算法计算精确UV
var uv = Triangle.GetUV(pointLocal);
// 输出纹理像素位置
return Vector2.Scale(TextureSize, uv);
}
return Vector2.zero;
}
(2)不规则形状 UV 计算(Triangle.cs)
采用三角形重心坐标算法,不依赖碰撞体、不限制矩形,支持任意形状 Sprite。
csharp
运行
public Vector2 GetUV(Vector3 point)
{
// 计算重心权重
var va = Vector3.Cross(v0 - v1, v0 - v2);
var va1 = Vector3.Cross(distance1, distance2);
var area = va.magnitude;
var a1 = va1.magnitude / area * sign;
var a2 = va2.magnitude / area * sign;
var a3 = va3.magnitude / area * sign;
// 输出最终UV
return uv0 * a1 + uv1 * a2 + uv2 * a3;
}
结论:无论剪纸是圆形、八边形、心形或复杂镂空,UV 映射均准确,涂抹不溢出、不错位。
5.5 阶段三:刮痕绘制到 RenderTexture(ScratchCardRenderer.cs)
插件使用 CommandBuffer 高性能渲染,将笔刷痕迹实时绘制到遮罩纹理(RT)。
csharp
运行
// 涂抹单个刮痕(核心方法)
public void ScratchHole(Vector2 position, float pressure = 1f)
{
// 1. 计算笔刷在RT上的绘制区域
var positionRect = new Rect(
(position.x - brushSize * pressure) / textureWidth,
(position.y - brushSize * pressure) / textureHeight,
...);
// 2. 构建笔刷四边形Mesh
meshHole.vertices = new[]
{
new Vector3(positionRect.xMin, positionRect.yMax, 0),
new Vector3(positionRect.xMax, positionRect.yMax, 0),
new Vector3(positionRect.xMax, positionRect.yMin, 0),
new Vector3(positionRect.xMin, positionRect.yMin, 0)
};
// 3. 渲染到遮罩纹理RT
commandBuffer.SetRenderTarget(scratchCard.RenderTarget);
commandBuffer.DrawMesh(meshHole, Matrix4x4.identity, scratchCard.BrushMaterial);
Graphics.ExecuteCommandBuffer(commandBuffer);
}
作用:RT 白色 = 已刮开,RT 黑色 = 未刮开,用遮罩记录所有涂抹痕迹。
5.6 阶段四:Shader 遮罩混合(最终显示效果)
插件内置 Shader 对两层纹理做透明度混合:
- 上层:剪纸白色遮罩
- 下层:RT 涂抹遮罩
plaintext
最终透明度 = 剪纸遮罩Alpha × RT遮罩Alpha
- RT 白色 → 透明度为 0 → 露出下层剪纸
- RT 黑色 → 透明度为 1 → 显示白色遮罩实现直观的 “涂抹即擦除” 视觉效果。
5.7 阶段五:涂抹进度计算(EraseProgress.cs)
通过 AsyncGPUReadback 异步读取 RT 像素,统计刮开区域占比,得到涂抹进度。
csharp
运行
private IEnumerator CalcProgress()
{
// 异步读取RT像素数据
var request = AsyncGPUReadback.RequestIntoNativeArray(ref pixelsBuffer, card.RenderTexture);
yield return new WaitUntil(() => request.done);
// 统计已刮开(白色)像素占比
for (var i = 0; i < pixelsBuffer.Length; i += bitsPerPixel)
{
progress += pixelsBuffer[i] / 255f;
}
// 计算最终进度百分比
progress /= 总像素数;
}
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)