Unity中如何检测Sprite边界内的涂色接近完成状态?
在Unity中检测Sprite边界内的涂色完成度
针对你的涂色游戏场景,这里提供几种实用方案来检测Sprite边界内的涂色是否接近完成:
方案1:基于纹理像素统计(最直接易用)
核心思路是统计Sprite有效区域内,已涂色像素占总需涂色像素的比例,当比例达到设定阈值时判定接近完成。
实现步骤
- 准备可读写纹理:Unity中Sprite原纹理默认不可写,需创建可读写副本用于像素检测。
- 统计总需涂色像素:遍历Sprite的纹理矩形区域,只统计不透明区域内的初始底色像素(比如立方体Sprite未涂色时的颜色)。
- 实时统计已涂色像素:每次涂色操作后,再次遍历Sprite区域,统计非底色的像素数量。
- 计算完成度:用已涂色像素数除以总需涂色像素数,与阈值(如95%)对比判断。
代码示例
using UnityEngine; public class PaintCompletionChecker : MonoBehaviour { [SerializeField] private SpriteRenderer targetSpriteRenderer; [SerializeField] private Color baseUnpaintedColor = Color.white; [SerializeField] private float completionThreshold = 0.95f; private int totalTargetPixels; private Texture2D editablePaintTexture; private void Start() { Sprite targetSprite = targetSpriteRenderer.sprite; Texture2D originalTex = targetSprite.texture; // 创建可读写的纹理副本 editablePaintTexture = new Texture2D(originalTex.width, originalTex.height, originalTex.format, false); Graphics.CopyTexture(originalTex, editablePaintTexture); editablePaintTexture.filterMode = FilterMode.Point; // 统计Sprite不透明区域内的总需涂色像素 Rect spriteTexRect = targetSprite.textureRect; for (int x = (int)spriteTexRect.x; x < spriteTexRect.x + spriteTexRect.width; x++) { for (int y = (int)spriteTexRect.y; y < spriteTexRect.y + spriteTexRect.height; y++) { Color pixel = editablePaintTexture.GetPixel(x, y); // 只统计不透明的底色像素 if (pixel.a > 0.1f && ColorUtility.AreColorsEqual(pixel, baseUnpaintedColor, 0.01f)) { totalTargetPixels++; } } } } // 调用此方法判断是否接近完成 public bool IsNearCompletion() { if (totalTargetPixels == 0) return false; Rect spriteTexRect = targetSpriteRenderer.sprite.textureRect; int paintedPixels = 0; for (int x = (int)spriteTexRect.x; x < spriteTexRect.x + spriteTexRect.width; x++) { for (int y = (int)spriteTexRect.y; y < spriteTexRect.y + spriteTexRect.height; y++) { Color pixel = editablePaintTexture.GetPixel(x, y); if (pixel.a > 0.1f && !ColorUtility.AreColorsEqual(pixel, baseUnpaintedColor, 0.01f)) { paintedPixels++; } } } float completionRate = (float)paintedPixels / totalTargetPixels; return completionRate >= completionThreshold; } // 与你的涂色逻辑结合,每次涂色后更新纹理副本 public void SyncPaintTexture(Texture2D updatedTexture) { Graphics.CopyTexture(updatedTexture, editablePaintTexture); } }
注意事项
- 确保纹理的
Read/Write Enabled选项在Import Settings中开启,或通过代码创建副本(如上示例)。 - 如果涂色支持多种颜色,只需修改判断条件为“非底色即可”。
- 大纹理下遍历像素会卡顿,可以分多帧完成统计(比如每帧只遍历一部分行)。
方案2:基于区域掩码的碰撞记录(适合交互驱动的涂色)
如果你的指针涂色是通过碰撞交互实现的,可以用掩码数组记录已涂色的区域,避免重复统计像素。
实现步骤
- 创建Sprite碰撞轮廓:给SpriteRenderer添加
PolygonCollider2D,并使用Edit Collider工具匹配Sprite的边界。 - 初始化涂色掩码:创建布尔数组,对应Sprite有效区域的每个像素,初始标记为未涂色。
- 记录涂色区域:当指针接触立方体时,将世界坐标转换为纹理坐标,标记掩码数组中对应位置为已涂色。
- 计算完成度:统计掩码中已标记的数量占总需涂色像素的比例。
代码示例
using UnityEngine; public class PaintMaskCompletion : MonoBehaviour { [SerializeField] private SpriteRenderer targetSpriteRenderer; [SerializeField] private float completionThreshold = 0.95f; private bool[,] paintedMask; private int totalTargetPixels; private int paintedPixelCount; private void Start() { Sprite targetSprite = targetSpriteRenderer.sprite; Rect spriteTexRect = targetSprite.textureRect; int rectWidth = (int)spriteTexRect.width; int rectHeight = (int)spriteTexRect.height; paintedMask = new bool[rectWidth, rectHeight]; totalTargetPixels = 0; // 初始化掩码和总需涂色像素数 for (int x = 0; x < rectWidth; x++) { for (int y = 0; y < rectHeight; y++) { Color pixel = targetSprite.texture.GetPixel((int)spriteTexRect.x + x, (int)spriteTexRect.y + y); if (pixel.a > 0.1f) { totalTargetPixels++; paintedMask[x, y] = false; } else { paintedMask[x, y] = true; // 透明区域跳过 } } } } // 指针涂色时调用,传入世界坐标的接触点 public void MarkPaintedArea(Vector3 worldContactPoint) { Sprite targetSprite = targetSpriteRenderer.sprite; Rect spriteTexRect = targetSprite.textureRect; // 转换为Sprite本地坐标 Vector2 localPos = targetSpriteRenderer.WorldToLocalPoint(worldContactPoint); // 转换为纹理坐标(注意Sprite的Pivot影响,需调整) Vector2 texturePos = new Vector2( spriteTexRect.x + (localPos.x + targetSprite.bounds.size.x / 2) * targetSprite.pixelsPerUnit, spriteTexRect.y + (localPos.y + targetSprite.bounds.size.y / 2) * targetSprite.pixelsPerUnit ); int x = (int)texturePos.x; int y = (int)texturePos.y; // 检查坐标是否在掩码范围内 if (x >= 0 && x < paintedMask.GetLength(0) && y >= 0 && y < paintedMask.GetLength(1)) { if (!paintedMask[x, y]) { paintedMask[x, y] = true; paintedPixelCount++; } } } public bool IsNearCompletion() { if (totalTargetPixels == 0) return false; float completionRate = (float)paintedPixelCount / totalTargetPixels; return completionRate >= completionThreshold; } }
注意事项
- 坐标转换时需注意Sprite的Pivot设置,避免偏移错误。
- 如果指针是管状(有宽度),可以标记一个区域的像素,而不是单个点。
方案3:GPU计算优化(适合大纹理场景)
如果你的Sprite纹理尺寸较大,CPU遍历像素会导致帧率下降,可以用Compute Shader在GPU端完成像素统计。
实现思路
- 编写Compute Shader,定义用于存储计数的结构化缓冲区。
- 遍历Sprite的纹理区域,统计符合条件的像素数量,将结果累加到缓冲区。
- 在C#代码中读取计算结果,判断完成度。
关键代码片段(Compute Shader)
#pragma kernel CountPaintedPixels Texture2D inputTexture; RWStructuredBuffer<int> pixelCountBuffer; float4 baseColor; int spriteX; int spriteY; int spriteWidth; int spriteHeight; [numthreads(8,8,1)] void CountPaintedPixels (uint3 id : SV_DispatchThreadID) { if (id.x < spriteWidth && id.y < spriteHeight) { int texX = spriteX + id.x; int texY = spriteY + id.y; float4 pixel = inputTexture[uint2(texX, texY)]; // 判断是否为已涂色像素(非底色且不透明) if (pixel.a > 0.1f && distance(pixel, baseColor) > 0.01f) { InterlockedAdd(pixelCountBuffer[0], 1); } } }
C#端调用逻辑
// 初始化Compute Shader和缓冲区 ComputeShader countShader; ComputeBuffer pixelCountBuffer; private void Start() { pixelCountBuffer = new ComputeBuffer(1, sizeof(int)); int[] initCount = {0}; pixelCountBuffer.SetData(initCount); } public int GetPaintedPixelCount(Texture2D tex, Rect spriteRect) { countShader.SetTexture(0, "inputTexture", tex); countShader.SetBuffer(0, "pixelCountBuffer", pixelCountBuffer); countShader.SetVector("baseColor", baseUnpaintedColor); countShader.SetInt("spriteX", (int)spriteRect.x); countShader.SetInt("spriteY", (int)spriteRect.y); countShader.SetInt("spriteWidth", (int)spriteRect.width); countShader.SetInt("spriteHeight", (int)spriteRect.height); // 调度线程组 int threadGroupsX = Mathf.CeilToInt(spriteRect.width / 8f); int threadGroupsY = Mathf.CeilToInt(spriteRect.height / 8f); countShader.Dispatch(0, threadGroupsX, threadGroupsY, 1); // 读取结果 int[] result = new int[1]; pixelCountBuffer.GetData(result); return result[0]; }
注意事项
- 需要熟悉Compute Shader的基础语法和线程组调度规则。
- 确保纹理在GPU端可访问,设置正确的纹理格式。
内容的提问来源于stack exchange,提问作者Axay Gadhia
相关产品推荐
相关产品推荐

