Don’t do a check against a point but against a line segment. The only difference is that before you do the distance check you have to project the coordinate of the current pixel you’re testing onto the line segment. Of course this projected point need to be clamped between the start and end points of the line. Once you have that point you can simply subtract the projected point from x and y instead of just the new point. This will naturally draw a “line” with a width of twice your radius. It’s basically the shadow of a capsule.
So all you need is to save the last position while dragging so you have that line segment you need. Note that currently you iterate over all pixels in the image. This could be optimised by just iterating over the bounding rectangle of your line segment. So just figure out the min and max values of your start and end coordinates and add / subract the radius and finally clamp the result to the image resolution. Finally you shouldn’t use SetPixel
in a loop. It’s much more efficient to use GetPixels
once in Start
, modify the array and use SetPixels
after your processing.
Also you shouldn’t create a new texture each time. Unused Textures need to be destroyed or you will run out of memory. You may also want to create a single texture which you are going to modify in start and reuse that texture…
EDIT
I just created a script based on your approach but heavily modified it with the changes i’ve mentioned.
using UnityEngine;
public class DrawLines : MonoBehaviour
{
private Texture2D m_Texture;
private Color[] m_Colors;
RaycastHit2D hit;
SpriteRenderer spriteRend;
Color zeroAlpha = Color.clear;
public int erSize;
public Vector2Int lastPos;
public bool Drawing = false;
void Start ()
{
spriteRend = gameObject.GetComponent<SpriteRenderer>();
var tex = spriteRend.sprite.texture;
m_Texture = new Texture2D(tex.width, tex.height, TextureFormat.ARGB32, false);
m_Texture.filterMode = FilterMode.Bilinear;
m_Texture.wrapMode = TextureWrapMode.Clamp;
m_Colors = tex.GetPixels();
m_Texture.SetPixels(m_Colors);
m_Texture.Apply();
spriteRend.sprite = Sprite.Create(m_Texture, spriteRend.sprite.rect, new Vector2(0.5f, 0.5f));
}
void Update()
{
if (Input.GetMouseButton(0))
{
hit = Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition), Vector2.zero);
if (hit.collider != null)
{
UpdateTexture();
Drawing = true;
}
}
else
Drawing = false;
}
public void UpdateTexture()
{
int w = m_Texture.width;
int h = m_Texture.height;
var mousePos = hit.point - (Vector2)hit.collider.bounds.min;
mousePos.x *= w / hit.collider.bounds.size.x;
mousePos.y *= h / hit.collider.bounds.size.y;
Vector2Int p = new Vector2Int((int)mousePos.x, (int)mousePos.y);
Vector2Int start = new Vector2Int();
Vector2Int end = new Vector2Int();
if(!Drawing)
lastPos = p;
start.x = Mathf.Clamp(Mathf.Min(p.x, lastPos.x) - erSize, 0, w);
start.y = Mathf.Clamp(Mathf.Min(p.y, lastPos.y) - erSize, 0, h);
end.x = Mathf.Clamp(Mathf.Max(p.x, lastPos.x) + erSize, 0, w);
end.y = Mathf.Clamp(Mathf.Max(p.y, lastPos.y) + erSize, 0, h);
Vector2 dir = p - lastPos;
for (int x = start.x; x < end.x; x++)
{
for (int y = start.y; y < end.y; y++)
{
Vector2 pixel = new Vector2(x, y);
Vector2 linePos = p;
if (Drawing)
{
float d = Vector2.Dot(pixel - lastPos, dir) / dir.sqrMagnitude;
d = Mathf.Clamp01(d);
linePos = Vector2.Lerp(lastPos, p, d);
}
if ((pixel - linePos).sqrMagnitude <= erSize * erSize)
{
m_Colors[x + y * w] = zeroAlpha;
}
}
}
lastPos = p;
m_Texture.SetPixels(m_Colors);
m_Texture.Apply();
spriteRend.sprite = Sprite.Create(m_Texture, spriteRend.sprite.rect, new Vector2(0.5f, 0.5f));
}
}
Here’s an example of the script in action. The first radius is 10, the second is 40
original gif link
Note the image used here is “1024x240” and works smoothly. Though if you have a larger image you may run into performance issues as updating such large images is always a problem as it has to be uploaded to the GPU each time. You might get better performance when creating an actual mask mesh and only apply the drawn lines every now and then. Though for relatively small images the current approach seems to work fine.
Thank you so much for your answer. I never thought you would modify the script. I had a hard time understanding your explanation because I am new in Unity and it is advanced for me. But based on your script I kinda understand it now. It worked perfectly. Again, thank you so much.
– JugalAmazing answer, but could you please explain for real newbie: 1) Is this script should be attached to Image that have Sprite Renderer? 2) What type of collider i have to use? I create sprite renderer object, assign to sprite my texture, in texture setting made it readable and type of Sprite then add your script and looks likes it needs collider 2d? Because "Drawing" bool variable never true without it. After adding Box 2d collider i can only cut center part, but can't drag or something like you show. Could you help please?
– Heathenish@Deaton thank you so much. It is working perfectly. I have one request. Would you please tell us here — how to calculate the pixel amount which have been erased (how much portion is erased). For my game I want to show LEVEL COMPLETE after fully erased the sprite. Sorry for my bad English. Btw I am very new in unity. Thanks.
– TatmanSo cmd works Now you need to set the rpc just as it is done in cmd How differently have you done for the playre? Maybe some info to [read][1] [1]: https://mirror-networking.gitbook.io/docs/guides/communications/remote-actions#bypassing-authority
– Pilafyour link script was dead, can you give other link ?
– Mariandi