Handling Level Resets in Unity

I’ve been polishing up the non-VR enabled version of KittenVR in order to learn about the various ways different things are exported from Unity and the quirks with WebGL and other formats. I’ve been working on fixing a few bugs with the WebGL build, in particular, and figured it was a good opportunity to share what I’d tried and how things worked out with handling a level reset in Unity. In the future, I’ll likely use these techniques for loading in new, more complex levels, but for now, I’m restarting the game instead since I only have one scene created.

The goal: Display a finishing message to the user, wait a few seconds, and then reset the level

Method 1: Thread.Sleep() – For initial (temporary) use only

I’m not sure if it’s just my own personal experience with Thread.Sleep() throughout my years of development, or if it’s just hands down a bad way to write code, but Thread.Sleep() just about never works for me. This case was not an exception. This paused, but did not actually display the message – the Thread.Sleep() was called before the method for showing the message completed, so it just waited, flashed the message on screen, and immediately reset the level.

    message = "You've found them all! Thank you so meouch!";
    isVisible = true;
    System.Threading.Thread.Sleep (2000);
    Application.LoadLevel (Application.loadedLevel)

As I mentioned, this method is okay for initially just wanting to do a wait and reset, but it actually is a pretty bad way of trying to time things in order and ends up just feeling like a lazy workaround for poorly structured functions. That said, it tends to be the way I initially hack things together, and I went on to method number 2: writing each function into a separate coroutine.

Method 2: Coroutine Functions (great, but broken on WebGL build)

The second (better) way that I wrote the pause functionality into my application involved creating two separate functions to operate in coroutines. This allowed me to actually have the code wait properly and the method to reset the level would correctly load in after the message was displayed.

In Update():

StartCoroutine(DisplayEndText(5f));
StartCoroutine(ResetLevel());	

DisplayEndText():

IEnumerator DisplayEndText(float wait)
{
    message = "You've found them all! Thank you so meouch!";
    isVisible = true;
    yield return new WaitForSeconds (wait);
    hasDisplayedFinal = true;
}

ResetLevel():

IEnumerator ResetLevel()
{
    while (!hasDisplayedFinal) {
    yield return new WaitForSeconds(0.1f);
    }
    Application.LoadLevel (Application.loadedLevel);
}

Note: hasDisplayedFinal is a global bool object set to false until DisplayEndText() has finished.

As I mentioned in the header, this strategy works beautifully on most builds – but there’s something about the compiled code when exporting to WebGL that prevents the level from being reloaded when the call is nested in a coroutine. I spent some time debugging and found this error in the browser console:

Error: WebGL drawElement: no VBO bound to enabled vertex attrib index 1

After spending a few hours attempting to find any information about this in how it related to Unity’s export, I made the decision to change the behavior slightly – it didn’t really make sense to force a reset anyway, so I chose to make it so that the user was able to trigger the reset themselves. It ends up working better in the long run, anyway, since it gives a little more control over the pacing of the game.

Method 3: Player Input for new/reload Levels

With a little bit of refactoring, I was able to make it fairly easy for the player to reset their level themselves, which fixed the WebGL issue and provided an additional layer of player pacing. For now, it just works with keyboard input, but I mapped the J key to a reset function following the completion of the game, which the user is informed of when he/she clicks on the main kitten after collecting the rest. I created a function, ListenForReset() in Update() , in which I confirm that the game was successfully completed by checking against the hasDisplayedFinal variable, and listen for the key press.

Note: A better way would probably be to only check ListenForReset() after the hasDisplayedFinal variable returns true. This is my first pass ‘hack-a-solution’ result, but I’ll focus on tidying this up in a future update.

The ListenForReset() Function:

void ListenForReset()
{
    if (hasDisplayedFinal == true) {
        if (Input.GetKeyDown (KeyCode.J)) {
            Application.LoadLevel(Application.loadedLevel);
	}

    }
}

Update():

void Update ()
	{
		MouseClickListener ();
		ResetOnFall();
		ListenForReset ();
	}

What I like about this design is that it allows the user to self-pace when playing a progressive game – with the VR version, this becomes increasingly more important because the general idea behind this is to create a quick game to show off VR and there needs to be a simple reset mechanism. As I build out more functionality, I plan on making this more robust, but it works just fine for the time being.

As always, the full code is up on GitHub and you can find the latest information about the project at KittenVR.com!

Related Posts

Leave a Reply

Your email address will not be published.