Unity开发儿童类画图应用:快速拖动卡顿线条锯齿问题求助
问题:快速拖动手指时绘制卡顿,线条呈直线且带锯齿
我正在Unity中开发一款面向儿童的画图应用,核心功能已经实现,但遇到了一个问题:当手指快速拖动时,应用会卡顿,绘制出的线条不仅是直线,还带有明显锯齿,希望能得到优化建议。
应用的基本逻辑:玩家可以选择颜色,在画笔和橡皮擦之间切换。拖动鼠标/手指划过屏幕时,代码会修改指定Image的像素。
public class PaintColor : MonoBehaviour, IPointerDownHandler, IDragHandler { [SerializeField] ColorManager manager; //manages whether the player can draw and with what color Vector2 prevPos; Image sr; [SerializeField] int brushWidth = 10; [SerializeField] float maxDistance = 5; //when dragging, max possible distance between previous stored position and current void Start() { sr = GetComponent<Image>(); ClearPanel(); } /*get pixel coordinates from mouse position*/ Vector2 PixelPos(Vector2 v) { var rt = GetComponent<RectTransform>(); int px = Mathf.Clamp(0, (int)(((v.x - rt.rect.x) * sr.sprite.texture.width) / rt.rect.width), sr.sprite.texture.width); int py = Mathf.Clamp(0, (int)(((v.y - rt.rect.y) * sr.sprite.texture.height) / rt.rect.height), sr.sprite.texture.height); return new Vector2(px, py); } public void OnPointerDown(PointerEventData eventData) { if (manager.CanColor()) { Vector2 pixelPos; if (RectTransformUtility.ScreenPointToLocalPointInRectangle(GetComponent<RectTransform>(), eventData.position, eventData.pressEventCamera, out pixelPos)) Draw(PixelPos(pixelPos)); prevPos = pixelPos; } } public void OnDrag(PointerEventData eventData) { if (manager.CanColor()) { Vector2 pixelPos; if (RectTransformUtility.ScreenPointToLocalPointInRectangle(GetComponent<RectTransform>(), eventData.position, eventData.pressEventCamera, out pixelPos)) { var distance = Mathf.Max(Vector2.Distance(prevPos, pixelPos), maxDistance); for (float lerp = 0; lerp <= 1; lerp += 1 / distance) //color all pixels in between last recorded position and current { Draw(PixelPos(Vector2.Lerp(prevPos, pixelPos, lerp))); } } prevPos = pixelPos; } } /*clear out drawing panel*/ public void ClearPanel() { var clear = new Color[(int)sr.sprite.rect.width * (int)sr.sprite.rect.height]; for (int x = 0; x < clear.Length; x++) clear[x] = Color.clear; sr.sprite.texture.SetPixels(clear); sr.sprite.texture.Apply(); } void Draw(Vector2 pixelPos) { /*color pixels in area around pointer position*/ for (int i = (int)pixelPos.x - brushWidth / 2; i < (int)pixelPos.x + brushWidth / 2; i++) { for (int j = (int)pixelPos.y - brushWidth / 2; j < (int)pixelPos.y + brushWidth / 2; j++) { sr.sprite.texture.SetPixel(i, j, manager.GetSelectedColor()); } } sr.sprite.texture.Apply(); } }
优化建议
1. 减少Texture.Apply()调用频率
当前代码在每次Draw()中都调用Apply(),这是性能卡顿的核心原因——每次修改单个像素块就提交GPU更新,会引发大量同步开销。
- 方案:移除
Draw()内的Apply(),仅在OnDrag()循环结束后调用一次,或者在Update()中每帧统一提交一次修改。 - 示例修改:
public void OnDrag(PointerEventData eventData) { if (manager.CanColor()) { Vector2 pixelPos; if (RectTransformUtility.ScreenPointToLocalPointInRectangle(GetComponent<RectTransform>(), eventData.position, eventData.pressEventCamera, out pixelPos)) { Vector2 prevPixel = PixelPos(prevPos); Vector2 currPixel = PixelPos(pixelPos); float pixelDistance = Vector2.Distance(prevPixel, currPixel); int steps = Mathf.Max(Mathf.RoundToInt(pixelDistance), 1); for (int step = 0; step <= steps; step++) { Vector2 lerpedPixel = Vector2.Lerp(prevPixel, currPixel, (float)step / steps); Draw(lerpedPixel); } // 仅在完成所有点绘制后提交一次 sr.sprite.texture.Apply(); } prevPos = pixelPos; } } void Draw(Vector2 pixelPos) { // 移除原有的sr.sprite.texture.Apply(); // ... 原有像素修改逻辑 ... }
2. 修复线条补全逻辑
当前用UI本地坐标计算距离,容易因为缩放导致采样点不足,快速拖动时线条断裂。改为基于像素坐标计算步长,确保足够的采样密度:
- 用像素坐标计算两点距离,按像素数量拆分插值步骤,保证快速拖动时也能覆盖所有缺失的像素点
- 避免用
maxDistance强制限制步长,改为动态根据像素距离生成步骤数
3. 批量修改像素提升效率
原Draw()的双层循环逐个设置像素效率极低,改用SetPixels()批量修改:
- 提前计算画笔覆盖的有效矩形范围,创建对应大小的颜色数组,批量赋值后一次性设置到纹理
- 提前做边界 clamp,避免循环内重复判断
- 示例修改:
void Draw(Vector2 pixelPos) { int startX = Mathf.Clamp((int)pixelPos.x - brushWidth / 2, 0, sr.sprite.texture.width - 1); int endX = Mathf.Clamp((int)pixelPos.x + brushWidth / 2, 0, sr.sprite.texture.width - 1); int startY = Mathf.Clamp((int)pixelPos.y - brushWidth / 2, 0, sr.sprite.texture.height - 1); int endY = Mathf.Clamp((int)pixelPos.y + brushWidth / 2, 0, sr.sprite.texture.height - 1); int width = endX - startX + 1; int height = endY - startY + 1; Color[] brushPixels = new Color[width * height]; Array.Fill(brushPixels, manager.GetSelectedColor()); sr.sprite.texture.SetPixels(startX, startY, width, height, brushPixels); }
4. 纹理配置优化
- 确保纹理的
Read/Write Enabled已勾选(必须开启才能修改像素) - 选择
RGBA32/ARGB32格式,避免使用压缩纹理(压缩纹理修改像素开销更大) - 移动端可适当降低纹理分辨率,或改用
RenderTexture替代普通纹理,渲染纹理的更新效率更高
5. 其他细节优化
- 缓存
manager.GetSelectedColor()结果,避免每次Draw()都调用获取 - 开启Unity的
Multi Touch Support(Edit > Project Settings > Input System Package),避免移动端触摸事件丢失 - 检查UI的
Raycast Target,仅保留必要的UI元素开启该选项,减少事件检测开销
内容的提问来源于stack exchange,提问作者Filippo Biancone
相关产品推荐
相关产品推荐

