0
\$\begingroup\$

I am building a platformer. In this game, I need one of my platforms to constantly activate and deactivate.

Here is how I have tried to code this so far, but it causes Unity to freeze:

void Update(){
    for (i = 0; i <= 2; i++) {
        platform.SetActive (false);
        if (i >= 1 ) {
            i--;
            platform.SetActive (true);
        }
    }
}
\$\endgroup\$
  • \$\begingroup\$ Hello and welcome on this platform. Could please elaborate a little bit more on what you are trying to achieve? Are those platforms always in the scene? Do they need to be spawned and destroyed? Are they moving or should they move? \$\endgroup\$ – Nico Apr 18 '19 at 8:01
  • \$\begingroup\$ @Nico This platform is a regular platform that we jump. It is always in the scene and I'd like it to constantly disappear and appear at the same place so that the player would have hard time getting on to the platform and another platform that lies after. \$\endgroup\$ – Yo Ko Hola Apr 18 '19 at 8:07
  • 1
    \$\begingroup\$ What framework are you using? You say it doesn't work... What is wrong with these approaches? Why do you consider that they're not working? \$\endgroup\$ – Vaillancourt Apr 18 '19 at 13:00
  • \$\begingroup\$ @Alexandre Vaillancourt I don't know if they are feasible for the thing I am trying to achieve. In short, I am lost and I don't have a clue what to do to achieve that. I am doing it in unity. \$\endgroup\$ – Yo Ko Hola Apr 19 '19 at 3:13
  • \$\begingroup\$ I don't know how Uunity works, but here is a guess : the code in your update function does not terminate/release control, and that's why Unity freezes. if you want your platform to periodically activate and deactivate, create a class variable that stores the last time the platform was activates, and then in the update loop check the current time with the class variable, and if the differences exceeds a certain value flip the platform status. \$\endgroup\$ – Quazi Irfan Apr 28 '19 at 7:04
2
\$\begingroup\$

Two things to remember:

  1. Unity C# scripts like Update or coroutine methods run synchronously, on the main thread.

    They're not running in parallel/in the background while the scene is drawn and redrawn each frame. Everything else the engine wants to do - all the other update scripts and all the rendering - has to wait for its turn, after we exit the method in one of three ways:

    • We reach the final curly brace } at the end of the method
    • We hit a return statement
    • In a coroutine, we hit a yield statement that passes control back to the engine

    Until one of these three things happens, we stay in the method, and nothing else has a chance to update or draw. So if you have a loop in your method (without a yield inside), then that loop has to run all the way to completion before the frame can be drawn.

    If you have a loop that never exits (an infinite loop), then the game will hang/crash, because it can never finish drawing the current frame or move on to the next one.

  2. You can check the behaviour of your code - including spotting infinite loops - by walking through it one instruction at a time, just the same way the processor would when it executes your code.

So, let's walk through this line by line:

  1. for (i = 0; i <= 2; i++) {

    We initialize the variable i to zero.

    Since zero is less than or equal to two, we pass the loop condition and proceed inside.

  2. platform.SetActive (false);

    We disable/hide the platform.

  3. if (i >= 1 ) {

    i is still zero, so we fail this test and skip to the end of the inner if block.

  4. }

    We've reached the end of the loop, so we cycle back up to the top.

  5. for (i = 0; i <= 2; i++) {

    We increment i by 1 (i++), so now it holds a value of one.

    One is still less than or equal to two, so we pass the loop condition and proceed into the body of the loop again.

  6. platform.SetActive (false);

    We disable the platform again. It was already disabled though, so this is redundant.

  7. if (i >= 1 ) {

    This time, i holds a value of one, which is greater than or equal to one. So we step inside the if block.

  8. i--;

    We decrement i by one, so it's back to zero again.

  9. platform.SetActive (true);

    We activate the platform.

  10. }}

    We reach the end of the if block, and the end of the loop, so we return again to the top.

  11. for (i = 0; i <= 2; i++) {

    We increment i by 1, so now it holds a value of one again.

    Uh-oh... we've been here before. This is exactly the state we were in at step 5!

So now we can clearly see that this loop continues repeating steps 5-10 forever: incrementing i to one, then decrementing it back down to zero, so it never reaches a value of three that lets it exit the loop.

The whole game is stuck on hold while this infinite loop runs.

So, how can we alternate a platform off and on, while letting the engine actually keep running other updates and drawing the frame? Something like this would work:

public class ActiveAlternator : MonoBehaviour {

    [Tooltip("Target object to turn on/off (NOT this object or its parents!")]
    public GameObject target;

    [Tooltip("Seconds to wait between activating / deactivating the object")]
    public float interval = 1f;

    // IEnumerator makes this run as a coroutine we can suspend & resume with yield.
    IEnumerator Start() {
         if(target == gameObject)
             Debug.LogError("ActiveAlternator cannot target itself and still re-activate");


        // Infinite loop! But it's OK, because we'll tell it to take breaks.
        while(true) {

            // This passes control back to the engine so it can draw the frame,
            // and asks to resume this method on the next line after some time.
            yield return new WaitForSeconds(interval);

            // If the object was active, set it inactive, and vice versa.
            target.SetActive(!target.activeSelf);

            // Then loop to wait, and alternate again, forever.
        }

    }
}
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ I don't know why but that didn't work. It deactivates the moment I hit start and doesn't activate. \$\endgroup\$ – Yo Ko Hola Apr 19 '19 at 14:04
  • \$\begingroup\$ Make sure you put this script on an object that is not the one you're turning off. If you deactivate an object, you also stop its coroutines from resuming. \$\endgroup\$ – DMGregory Apr 19 '19 at 14:05
  • \$\begingroup\$ @DMGregory That shouldn't be an issue according to the docs: Note: Coroutines are not stopped when a MonoBehaviour is disabled, but only when it is definitely destroyed. You can stop a Coroutine using MonoBehaviour.StopCoroutine and MonoBehaviour.StopAllCoroutines. Coroutines are also stopped when the MonoBehaviour is destroyed. \$\endgroup\$ – Charanor Apr 19 '19 at 14:08
  • \$\begingroup\$ The docs say that disabling a MonoBehaviour does not stop the Coroutine. The docs make no such promise about setting a GameObject to inactive. \$\endgroup\$ – DMGregory Apr 19 '19 at 14:11
1
\$\begingroup\$

Coroutines should do the trick:

float blinkInterval = 1.0f; // Turn on/off every second

void Start() {
    StartCoroutine("Blink");
}

IEnumerator Blink() {
    while(true) {
        platform.SetActive(true);
        yield return new WaitForSeconds(blinkInterval / 2f);
        platform.SetActive(false);
        yield return new WaitForSeconds(blinkInterval / 2f);
    }
}
| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.