BASIC TELEPORTING QUEST: ‘Blink World Light’ - Solutions

Feel free to share your solutions, ideas and creations below. If you get stuck, you can find some ideas here for completing this challenge.

For my blinking light I just had a secondary directional light left disabled with default values that i’d just flick on and off with a co-routine. (I was using the primary one to set a lighting tint to the darkness :stuck_out_tongue:

1 Like

I got stuck for a few hours on this one but it works now! finally!

My coroutine kept getting interrupted by the DeactivateObject(); bit so i used an invoke on that last bit.

I always thought that a coroutine made all other bits of the code stop/pause until the coroutine was done. I guess I was wrong there.

I ended up writing a Game Controller script and dropping it on the Gameplay object. This let me better control elements that weren’t specific to the teleporters. This let me adjust the last challenge so that I could designate the “First Teleporter” and make it glow without breaking up the prefab.

Specific to this challenge:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameController : MonoBehaviour {

    [SerializeField] Teleport FirstTeleportPad;
    [SerializeField] Light worldLight;
    [SerializeField] Light dimLight; // Ignore me, I want to be able to see the editor but make the game pitch black otherwise

    void Start()
    {
        FirstTeleportPad.Initialize();
        StartCoroutine ("BlinkWorldLight");
        dimLight.enabled = false; 
    }

    void Update()
    {
                
    }

    IEnumerator BlinkWorldLight()
    {
		while (true)
		{
            if (worldLight.enabled)
			{
                yield return new WaitForSeconds(1);
            }
			else
			{
                yield return new WaitForSeconds(3);
            }
            worldLight.enabled = !worldLight.enabled;
        }

    }
}

2 Likes

I activated the teleport script on both teleporters so the start() method is called. Each has one of the directional lights hooked up.
At the start I turn off all lights, then start the coroutines to blink them on for a couple of seconds (adjustable per SerializedField).

Here are the adjustments I made:

    [SerializeField] float worldLightBlinkTime = 5f;

    void Start() 
    {
        mainWorldLight.enabled = false;
        areaLight.enabled = false;
        StartCoroutine(nameof(BlinkWorldLight));
    }

    IEnumerator BlinkWorldLight()
    {
        mainWorldLight.enabled = true;
        yield return new WaitForSeconds(worldLightBlinkTime);
        mainWorldLight.enabled = false;
    }
2 Likes
    IEnumerator BlinkWorldLight(float timer)
    {
        mainWorldLight.enabled = true;
        yield return new WaitForSecondsRealtime(timer);
        mainWorldLight.enabled = false;

    }
3 Likes

I also used a coroutine, but i added variables for the amountToBlink and durationToBlink

    [SerializeField] private int amountToBlink = 24;
    [SerializeField] private float durationToBlinkOn = .1f;
    [SerializeField] private float durationToBlinkOff = .05f;
    IEnumerator BlinkWorldLight()
    {
        mainWorldLight.enabled = false;

        for (var i = 0; i < amountToBlink; i++)
        {
            mainWorldLight.enabled = true;
            yield return new WaitForSeconds(durationToBlinkOn);
            mainWorldLight.enabled = false;
            yield return new WaitForSeconds(durationToBlinkOff);
        }
    }
1 Like

I used something similar to one of the other ones here. Only because I got stuck trying to get it to work the Unity Scripting API was a little confusing.

  [SerializeField] Light mainWorldLight;
  [SerializeField] float worldlightBlinkTime = 3;
   void Start() 
    {
        StartCoroutine(BlinkWorldLight());
    }
void TeleportPlayer(Collider other)
    {
        if(player || hasTriggered == false)
        {
            player.transform.position = teleportTarget.position;
            IlluminateArea();
            StartCoroutine(BlinkWorldLight());
            DeactivateObject();
        }
    }
 IEnumerator BlinkWorldLight()
    {
        yield return new WaitForSeconds(worldlightBlinkTime);
        mainWorldLight.enabled = true;
        yield return new WaitForSeconds(worldlightBlinkTime);
        mainWorldLight.enabled = false;
    }
2 Likes

Nice

Nice and clear code :slight_smile:

Welcome

Thats what this is all about having fun learning :slight_smile:

Co-routines are great for many things! Spawners, flashing lights and running things for a period of time.

Kurt

I implemented a paradigm very similar to @DGR_Dev.

Added properties for blink rate and number of blinks to Teleport:

[Range(2, 10)] [SerializeField] int LightBlinkCount = 3;
[Range(0.1f, 2f)] [SerializeField] float LightBlinkRate = 0.5f;

Took care of initial light states in Teleport :: Start():

        if (!AreaLight || !MainWorldLight) return;
        if (AreaLight.enabled)
            AreaLight.enabled = false;
        if (MainWorldLight.enabled)
            MainWorldLight.enabled = false;

Then implemented the desired light blinking in Teleport :: BlinkWorldLight():

    IEnumerator BlinkWorldLight()
    {
        if (!MainWorldLight)
            yield return null;

        var numBlinks = LightBlinkCount > 2 && LightBlinkCount % 2 == 0 ? -1 : 0;
        do
        {
              MainWorldLight.enabled = !MainWorldLight.enabled;
              yield return new WaitForSeconds(LightBlinkRate);
        } while (numBlinks++ <= LightBlinkCount);

        MainWorldLight.enabled = false;
    }

The modulo arithmetic used in the initial value assignment of the local counter variable numBlinks properly reconciles an odd or even LightBlinkCount property setting with the fact that a “blink” consists of the light being turned on and then off.

1 Like

Interesting approach with the modulo and thanks for using my code that makes me feel
like something was working :smiley:

A small addition to your variables. In Unity its common to use camelCase,
i see that you are using PascalCase.

PascalCase vs camelCase
example: LightBlinkCount = lightBlinkCount

:+1: Thanks.

Yes, I’m aware of the semi-codified Unity standard practice of camelCase’ing fields. However, at the studio I work at its written into OUR engineering standard practices to use PascalCase. So that’s what I do in all circumstances, since I have my IDE (Jetbrains Rider) syntax checking parameters setup to flag camelCase fields as an error.

Honestly, in the 30 years I’ve been programming the whole case’ing nonsense has struck meas a giant waste of energy. Your going to end up using whatever standards your employer dictate and like it (or at least tolerate it), because that’s what your paycheck is predicated on. LOL.

1 Like

Hi there bsafarik,

one day you could show me how to sytax check with Rider, havent found enough time to add my special taste to the editor yet, sounds interesting.

The other side of the coin of case’ing was, if i recall correctly.

“Choose your style and stay consistent.”

Linking the fact, that you are going for the Gamedev tutorials. Now i am kinda curious, why you start making games after 30yrs of coding experince and getting payed already.

Cheers,
DGR

:+1: Indeed! …Consistency - in general - in pretty valuable in both life and career.

Game development - and technical art especially (I’m a technical art director/manager) - is a process of never-ending learning. Regardless of how long you’ve been making games - which in my case is 25 years - to assume you can’t take something valuable away from doing tutorials/exercises geared towards any skill level would be a mistake. So, during downtime between projects or tasks, I’ll often take a short detour and delve into practice or other learning materials, on GameDevTv and elsewhere. It gives me the opportunity to think about “old” problems in new ways, scrape the rust off disused aspects of the larger technical art development skill set, learn new things and/or new tech, and generally helps prevent burnout.

Despite having been at this stuff a VERY long time, and at times growing rather weary of the whole industry in general, I like to approach what remains of my tenure in this crazy business with “beginner’s mind”, i.e. the acknowledgement that I won’t live long enough to be able to profess “knowing everything” within my discipline and, therefore, to approach everything as if for the first time is the key to not only happiness but also sanity.

Cheers.

I changed my code a little
Is it ok to run foreach loop inside Coroutine that runs on Update?

public class Lights : MonoBehaviour
{
    [SerializeField] Light[] mainWorldLight;

    [SerializeField] float blinkWorldTime;
    WaitForSecondsRealtime WaitBlinkWorldTime;

    private void Awake()
    {
        WaitBlinkWorldTime = new WaitForSecondsRealtime(blinkWorldTime);
    }

    private void Update()
    {
        StartCoroutine(BlinkWorldLights());
    }

    IEnumerator BlinkWorldLights()
    {
        yield return WaitBlinkWorldTime;
        foreach (var light in mainWorldLight)
        {
            light.enabled = !light.enabled;
        }
    }
}
1 Like

I wonder what that looks like as I’d expect it to trigger lots of Coroutines (1 each update) that would be turning each others lights on and off…(try extending blinkWorldTime if its not obvious)
If its a continuous blink you are after then you really only want to call the coroutine once in my opinion - look at some of the other solutions if you get stuck

I wonder what that looks like as I’d expect it to trigger lots of Coroutines (1 each update) that would be turning each others lights on and off…(try extending blinkWorldTime if its not obvious)
If its a continuous blink you are after then you really only want to call the coroutine once in my opinion - look at some of the other solutions if you get stuck

That is an interesting solution! I don’t think I’ve ever tried doing a coroutine in Update.