I've been trying to create a Paint-like app through Unity for some kids and it's almost fine. The issue is that if you drag the finger too fast it will lag a bit and the lines will be straight and jagged. Any suggestions on how to improve it?
Basically, it works in the following way: the player can choose which color he wants to use and he can switch through a pencil and an eraser. When the player will drag his mouse/finger across the screen, the code will change the pixels of the image to which its assigned.
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();
}
}
This is likely a result of poor performance. You should run a profiler to identify the reason for the poor performance, but I'm going to make a guess:
Setting individual pixels is likely a slow operation, and may involve getting locks, etc. And you run "Apply" for each pixel, uploading the entire texture to the GPU for each modified pixel.
I have not used Unity enough to suggest the most appropriate solution. In most regular UI frameworks you would have a simple Draw Line method you could call. But I would at the very least fix the code to only call
Apply()once per drag event, and you may consider usingGetPixelDatato get direct access to the texture memory. But I would try to find some more suitable methods for drawing lines, and if nothing more suitable exist, maybe consider some other framework?