I’ve built a script that takes a picture that it feeds into an Array that increases its size with each new picture.
I need to be able to reset this array so that I can start over taking pictures again.
In the script below TakeAFrame is fired with a button, and NewAnimation is fired with another. All works well until the NewAnimation function is fired (which seems to successfully empties and resizes the array). But when TakeAFrame is fired again - and the PlaybackCoroutine is fired, I get:
IndexOutOfRangeException: Array index is out of range for the line:
playbackPanel.texture = savedImages[i];
Remember, this worked fine until the NewAnimation function was fired to reset everything.
I’m clearly not understanding something about Coroutines or Loops or Arrays (or all three). Thanks for any help.
The entire script is here:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.IO;
public class StopMotionCameraController : MonoBehaviour {
public RawImage cameraPanel;
WebCamTexture webcamTexture;
public Texture[] savedImages;
int imageCount;
public RawImage playbackPanel;
public bool loopPlaybackPanel;
public IEnumerator playback;
void Start ()
{
webcamTexture = new WebCamTexture(null, 1280, 720);
cameraPanel.texture = webcamTexture;
webcamTexture.Play();
playback = PlaybackCoroutine();
}
public void TakeAFrame()
{
StopCoroutine(playback);
Texture2D texture = new Texture2D(cameraPanel.texture.width, cameraPanel.texture.height, TextureFormat.ARGB32, false);
texture.SetPixels(webcamTexture.GetPixels());
texture.Apply();
System.Array.Resize(ref savedImages, imageCount+1);
savedImages[imageCount] = texture;
imageCount ++;
StartCoroutine(playback);
}
IEnumerator PlaybackCoroutine()
{
while (loopPlaybackPanel)
{
for (int i = 0; i < savedImages.Length; i++)
{
yield return new WaitForSeconds (.1f);
playbackPanel.texture = savedImages[i];
}
}
}
public void NewAnimation()
{
StopCoroutine(playback);
imageCount = 0;
for (int i=0; i<savedImages.Length; i++)
{
savedImages[i]=null;
}
System.Array.Resize(ref savedImages, imageCount);
}
}
List<Texture> savedImages = new List<Texture>();
savedImages.Add(aNewTexture); //Adds to the list
savedImages.Clear(); //Empties the list
savedImages.Count; //Gives you the number of objects in the list.
Anytime you want an array but need it to be flexable in size, then you probably want a list instead. Array is best for a fixed size of objects.
List allow you to add, remove, etc and you don’t have to resize it yourself, it takes care of that for you. So you can remove a single element, but the Clear call will just empty the entire list.
Thanks for the lead. I wasn’t even aware of Lists, and it seems to be much more elegant for my uses here than arrays.
I’ve rebuild using the Lists methodology you suggested:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.IO;
public class StopMotionCameraController : MonoBehaviour {
public RawImage cameraPanel;
WebCamTexture webcamTexture;
public List<Texture> savedImages;
public RawImage playbackPanel;
public bool loopPlaybackPanel;
public IEnumerator playback;
void Start ()
{
webcamTexture = new WebCamTexture(null, 1280, 720);
cameraPanel.texture = webcamTexture;
webcamTexture.Play();
playback = PlaybackCoroutine();
}
public void TakeAFrame()
{
StopCoroutine(playback);
Texture2D texture = new Texture2D(cameraPanel.texture.width, cameraPanel.texture.height, TextureFormat.ARGB32, false);
texture.SetPixels(webcamTexture.GetPixels());
texture.Apply();
savedImages.Add(texture);
StartCoroutine(playback);
}
IEnumerator PlaybackCoroutine()
{
while (loopPlaybackPanel)
{
for (int i = 0; i < savedImages.Count; i++)
{
yield return new WaitForSeconds (.1f);
playbackPanel.texture = savedImages[i];
}
}
}
public void NewAnimation()
{
StopCoroutine(playback);
savedImages.Clear();
}
}
But alas, it still breaks at the same place although the error this time is:
ArgumentOutOfRangeException: Argument is out of range.
Parameter name : index
It doesn’t provide a line number, but happens again, only when trying to fire the TakeAFrame() after clearing the list with NewAnimaion(). I’m assuming its still having problems in the PlaybackCoroutine().
A list will auto-resize itself when you add new elements, but it must be initialized first, before you can add to it.
Also, to keep track of your coroutine… You might try :
Coroutine playback;
instead of IEnumerator. Just remember to : A) check it’s not null before stopping it, and B) re-assign it when you run a new coroutine.
Extra note: It’s good you’re now aware of Lists, they can be a lot easier to use.However, there is nothing wrong with resizing your own array (List uses built-in resizing rules that may be more efficient). I believe you also forgot to initialize your array when you were using it instead of the List.
He actually doesn’t have to initialize the List in this case because the list is public, so Unity handles the initialization so that you can add stuff through the inspector.
This is also why he’s not getting a null error, but an ArgumentOutOfRangeException. In this case, chances are you are clearing your list while it’s still being looped through, thus the count changes and you are now trying to access an index that doesn’t exist.
Cool, that’s a good point - didn’t think of that.
Also re-read the StopCoroutine docs for a reminder of the IEnumerator example to store the variable (I knew this before).
So, in his example - after looking again at it… if he’s stopping the coroutine before clearing the list, why’s he still out of range, I wonder?
Honestly, I’m not sure. I would be curious how stop coroutine actually works. At what point does it stop it? How does it stop it? Honestly, I haven’t had a chance to really dig into it. I’m just suspecting that this is the reason for the outofrange.
Ya, I understand… I’m not too sure, either, unless there is something missing from the data provided or I’ve overlooked something.
I’m fairly certain that the coroutine stops “right away” when you call stop on it. (won’t run again when it’s its turn in execution).
fires, it does indeed clear the list and the Size attribute in the inspector returns to 0. So at least at that level, the List has indeed cleared.
Because the TakeAFrame() function is called sometime later, I’m sure at least the frame where StopCoroutine has fired has completed; so it seems like that part would have finished up.