Reputation: 3
I'm pretty new to coding, and I'm working on this game on Unity in c#, where you can draw a line with your mouse and it automatically erases after 1.5s, progressively from start point to end point.
The script works normally, but when I draw a second line while the first one is erasing, they both suddenly vanish.
Here's the code for creating the line:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Threading;
using System.Threading.Tasks;
public class LineDraw : MonoBehaviour
{
public GameObject linePrefab;
public GameObject currentLine;
public LineRenderer lineRenderer;
public EdgeCollider2D edgeCollider;
public List<Vector2> fingerPositions;
public List<GameObject> currentLines;
public int testenum = 1;
private IEnumerator coroutineErase;
void Update()
{
if(Input.GetMouseButtonDown(0))
{
CreateLine();
}
if(Input.GetMouseButton(0))
{
Vector2 tempFingerPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
if(Vector2.Distance(tempFingerPos, fingerPositions[fingerPositions.Count - 1]) > .1f)
{
UpdateLine(tempFingerPos);
}
}
}
void CreateLine()
{
currentLine = Instantiate(linePrefab, Vector3.zero, Quaternion.identity);
currentLine.name = "line number "+testnum;
testnum++;
lineRenderer = currentLine.GetComponent<LineRenderer>();
edgeCollider = currentLine.GetComponent<EdgeCollider2D>();
fingerPositions.Clear();
fingerPositions.Add(Camera.main.ScreenToWorldPoint(Input.mousePosition));
fingerPositions.Add(Camera.main.ScreenToWorldPoint(Input.mousePosition));
lineRenderer.SetPosition(0, fingerPositions[0]);
lineRenderer.SetPosition(1, fingerPositions[1]);
edgeCollider.points = fingerPositions.ToArray();
currentLines.Add(currentLine);
int lineListPos = currentLines.IndexOf(currentLine);
//Test with async function:
//EraseLine2(fingerPositions, lineListPos);
coroutineErase = EraseLine(fingerPositions, lineListPos);
StartCoroutine(coroutineErase);
}
void UpdateLine(Vector2 newFingerPos)
{
fingerPositions.Add(newFingerPos);
lineRenderer.positionCount++;
lineRenderer.SetPosition(lineRenderer.positionCount - 1, newFingerPos);
edgeCollider.points = fingerPositions.ToArray();
}
[...]
}
Afterwards, I tried the erase function either with coroutine or with async function as it follows:
public IEnumerator EraseLine(List<Vector2> myPoints, int posListObj)
{
yield return new WaitForSeconds(1.5f);
LineRenderer line = currentLines[posListObj].GetComponent<LineRenderer>();
EdgeCollider2D colisor = currentLines[posListObj].GetComponent<EdgeCollider2D>();
int numPoints = myPoints.Count;
while(true)
{
if(numPoints >= 1)
{
myPoints.RemoveAt(0);
line.positionCount = myPoints.Count;
for(int i = 0;i<myPoints.Count;i++){
line.SetPosition(i,myPoints[i]);
colisor.points = myPoints.ToArray();
}
numPoints = line.positionCount;
}
else
{
if (line != null)
{
line.positionCount = 0;
Destroy(currentLines[posListObj]);
//currentLines.RemoveAt(posListObj);
StopCoroutine(coroutineErase);
}
}
yield return new WaitForSeconds(0.02f);
}
}
async void EraseLine2(List<Vector2> myPoints, int posListObj)
{
CancellationTokenSource cancellationToken = new CancellationTokenSource();
await Task.Delay(1500, cancellationToken.Token);
try
{
LineRenderer line = currentLines[posListObj].GetComponent<LineRenderer>();
EdgeCollider2D colisor = currentLines[posListObj].GetComponent<EdgeCollider2D>();
int numPoints = myPoints.Count;
while(true)
{
if(numPoints >= 1)
{
myPoints.RemoveAt(0);
line.positionCount = myPoints.Count;
for(int i = 0;i<myPoints.Count;i++){
line.SetPosition(i,myPoints[i]);
colisor.points = myPoints.ToArray();
}
numPoints = line.positionCount;
}
else
{
if (line != null)
{
line.positionCount = 0;
Destroy(currentLines[posListObj]);
currentLines.RemoveAt(posListObj);
}
}
await Task.Delay(20, cancellationToken.Token);
}
}
catch
{
Debug.Log("Task was cancelled!");
return;
}
finally
{
cancellationToken.Dispose();
cancellationToken = null;
}
}
I opted for a list of lines (and passing to the function the position of the line on the list) because I thought that the function could be trying to access the variable already occupied by the elements of the first line, but It didn't solve the problem. :(
I guess the issue is in the code for erasing the line, but I can't solve it. Does anyone have an idea how I can get this to work?
Thanks!!!
EDIT:
After the answer from @derHugo I was able to solve the problem, with some alterations in the code they suggested. As I stated in the comment to the answer, I had to use a Vector3 List to make the GetPositions/SetPositions methods of the LineRenderer work, while the Collider2D points were Vector2.. So I simply used both lists. Here's the final code:
using System.Linq;
...
private IEnumerator EraseLine(LineRenderer line)
{
yield return new WaitForSeconds(1.5f);
Vector3[] positionsv3 = new Vector3[line.positionCount];
line.GetPositions(positionsv3);
var pointsv3 = positionsv3.ToList();
var positionsv2 = positionsv3.Select((v3) => (Vector2)v3).ToList();
var pointsv2 = positionsv2.ToList();
var colisor = line.GetComponent<EdgeCollider2D>();
while(pointsv2.Count > 0)
{
pointsv2.RemoveAt(0);
pointsv3.RemoveAt(0);
line.positionCount = pointsv2.Count;
line.SetPositions(pointsv3.ToArray());
colisor.points = pointsv2.ToArray();
yield return new WaitForSeconds(0.02f);
}
Destroy(line.gameObject);
}
Upvotes: 0
Views: 51
Reputation: 90580
Your issue is that the fingerPositions
list appears to be the same instance for all your lines.
Also you going by the passed index has flaws as the length of the list could be changed at many points during one execution of the corotuine.
And finally your routine might never finish - if you start a second routine you overwrite the coroutineErase
so it can't be stopped anymore and your while loop keeps running forever.
Instead I would pass in all the relevant information into the Coroutine as parameters so one instance of the Coroutine handles everything related to one line. Actually all you need is the reference to the correct LineRenderer
- all the rest you get from it
Something like e.g.
private IEnumerator EraseLine(LineRenderer line)
{
yield return new WaitForSeconds(1.5f);
var points = new Vector3[line.positionsCount];
line.GetPositions(points);
var pointsList = points.ToList();
var colisor = line.GetComponent<EdgeCollider2D>();
while(pointsList.Count > 0)
{
pointsList.RemoveAt(0);
var pointsArray = pointsList.ToArray();
line.positionCount = pointsList.Count;
line.SetPositions(pointsArray);
colisor.points = pointsArray.Cast<Vector2>().ToArray();
yield return new WaitForSeconds(0.02f);
}
Destroy(line.gameObject);
}
And then you simply pass in
StartCoroutine(EraseLine(lineRenderer));
Upvotes: 0