4
\$\begingroup\$

Now this may be a physics issue as it seems more of a collider issue - specifically using multiple box colliders inside 1 empty gameobject. Oh and aplogies about the lengthy thread.

My game (for my degree dissertation) consists of two 3D levels the players walks around and each level consists of 4 zones. What i want to do is start and stop individual timers for each zone so i can work out how long they spend in each area.

I have sussed this part using coroutines to do my timers (see code below) and the whole things works as intended except that the issue is, as these zones are made up of multiple box colliders in an empty gameobject (say one zone is an L-shaped, meaning its made up of 2 cuboids), each zone is not exactly simple shapes. Basically a maze of 10x10 floors (planes), set out in a maze like fashion with various zones (see image below). For each of these zones, i have an empty gameobject with multiple box colliders to make up the volume of each zone. These are set to IsTrigger so that the player can wander through but doing so trigger the OnTriggerxxx() events to start and stop the coroutines.

The trouble is where these box colliders join internally inside the same zone. The timer stops when the player crosses from one collider to another, even though they're in the same game object (zone) and therefore compound. The timer either stops or it seems to multiply the timer speed when you cross between collider borders, multipling each time a boundary is crossed, so walking backwards and forwards between colliders makes the timer keep going faster and faster and obviously not correct time data.

So questions are: Is there something i can add to the OnTriggerStay() events to keep the timers going or to stop the build up of speed in the timers? Which is obviously a build up of the triggered coroutines stacking on each other, escalating the speed of the clock whenever the player keeps crosses ANY colliders.

or......

Is there a way to correct the issue with the colliders? Like create them as one single collider? This by far seems the most obvious issue. If i could make a single collider the shape of the zone, so like a 3D L-shape or other bespoke shape. Think tetris in 3D as a maze, similar shapes basically.

Box Colliders making up the "Stone" zone. Made of 2 shapes

This is the script i've written for the timer routine. I bet its something really obvious haha.

Thanks,

Paul..

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

public class AreaTime_A : MonoBehaviour {

    private float startTime;
    public Text timerText;
    public Text timerText_HW;
    public Text timerText_ST;
    public Text timerText_IZ;
    public Text timerText_OUT;
    private bool TimerOn = false;
    private bool TimerOn_HW = false;
    private bool TimerOn_ST = false;
    private bool TimerOn_IZ = false;
    private bool TimerOn_OUT = false;

    public int minutes;
    public int seconds;
    public int seconds_HW;
    public int minutes_HW;
    public int seconds_ST;
    public int minutes_ST;
    public int seconds_IZ;
    public int minutes_IZ;
    public int seconds_OUT;
    public int minutes_OUT;

    // Use this for initialization
    void Start () {

        startTime = Time.time;
        TimerStart();
    }

    void Awake () {
        DontDestroyOnLoad (this);
    }

    void Update () {
    }

    public void TimerStart() {
        TimerOn = true;
        StartCoroutine (TOTAL_Timer());
    }

    public void TimerEnd() {
        StopCoroutine (TOTAL_Timer());
        TimerOn = false;
        TimerOn_HW = false;
        TimerOn_ST = false;
        TimerOn_IZ = false;
        TimerOn_OUT = false;
    }

    private IEnumerator TOTAL_Timer() {
        while (TimerOn) {
            yield return new WaitUntil (()=> TimerOn);
            seconds++;

            if (seconds == 60) {
                minutes++;
                seconds = 0;
            }
            timerText.text = minutes.ToString() + ":" + seconds.ToString();
        }
    }

    private IEnumerator HW_Timer() {
        while (TimerOn_HW) {
            yield return new WaitUntil (()=> TimerOn_HW);
            seconds_HW++;

            if (seconds_HW == 60) {
                minutes_HW++;
                seconds_HW = 0;
            }
            timerText_HW.text = minutes_HW.ToString() + ":" + seconds_HW.ToString();
        }
    }

    private IEnumerator ST_Timer() {
        while (TimerOn_ST) {
            yield return new WaitUntil (()=> TimerOn_ST);
            seconds_ST++;

            if (seconds_ST == 60) {
                minutes_ST++;
                seconds_ST = 0;
            }
            timerText_ST.text = minutes_ST.ToString() + ":" + seconds_ST.ToString();
        }
    }

    private IEnumerator IZ_Timer() {
        while (TimerOn_IZ) {
            yield return new WaitUntil (()=> TimerOn_IZ);
            seconds_IZ++;

            if (seconds_IZ == 60) {
                minutes_IZ++;
                seconds_IZ = 0;
            }
            timerText_IZ.text = minutes_IZ.ToString() + ":" + seconds_IZ.ToString();
        }
    }

    private IEnumerator OUT_Timer() {
        while (TimerOn_OUT) {
            yield return new WaitUntil (()=> TimerOn_OUT);
            seconds_OUT++;

            if (seconds_OUT == 60) {
                minutes_OUT++;
                seconds_OUT = 0;
            }
            timerText_OUT.text = minutes_OUT.ToString() + ":" + seconds_OUT.ToString();
        }
    }

    void OnTriggerEnter(Collider other)
    {
        if (other.gameObject.name == "HW_Collider") 
        {
            TimerOn_HW = true;
            StartCoroutine (HW_Timer());
            timerText_HW.color = Color.yellow;
        }
        else if (other.gameObject.name == "ST_Collider")
        {
            TimerOn_ST = true;
            StartCoroutine (ST_Timer());
            timerText_ST.color = Color.magenta;
        }
        else if (other.gameObject.name == "IZ_Collider")
        {
            TimerOn_IZ = true;
            StartCoroutine (IZ_Timer());
            timerText_IZ.color = Color.green;
        }
        else if (other.gameObject.name == "OUT_Collider")
        {
            TimerOn_OUT = true;
            StartCoroutine (OUT_Timer());
            timerText_OUT.color = Color.blue;
        }
    }

    void OnTriggerExit(Collider other)
    {
        if (other.gameObject.name == "HW_Collider") 
        {
            TimerOn_HW = false;
            StopCoroutine (HW_Timer());
            timerText_HW.color = Color.white;
        }
        else if (other.gameObject.name == "ST_Collider")
        {
            TimerOn_ST = false;
            StopCoroutine (ST_Timer());
            timerText_ST.color = Color.white;
        }
        else if (other.gameObject.name == "IZ_Collider")
        {
            TimerOn_IZ = false;
            StopCoroutine (IZ_Timer());
            timerText_IZ.color = Color.white;
        }
        else if (other.gameObject.name == "OUT_Collider")
        {
            TimerOn_OUT = false;
            StopCoroutine (OUT_Timer());
            timerText_OUT.color = Color.white;
        }
    }

    void OnTriggerStay(Collider other)
    {
        if (other.gameObject.name == "HW_Collider") 
        {
            timerText_HW.color = Color.yellow;
        }
        else if (other.gameObject.name == "ST_Collider")
        {
            timerText_ST.color = Color.magenta;
        }
        else if (other.gameObject.name == "IZ_Collider")
        {
            timerText_IZ.color = Color.green;
        }
        else if (other.gameObject.name == "OUT_Collider")
        {
            timerText_OUT.color = Color.blue;
        }
    }
}
\$\endgroup\$
  • \$\begingroup\$ How about keeping a count of the number of triggers the player is inside for each zone? Add one for OnTriggerEnter and subtract one for OnTriggerExit. Don't start or stop coroutines, just count. In AreaTime_A.Update(), check the count for each (count_HW > 0 and so on) to decide whether each coroutine should run. You'll check that against your TimerOn_HW and so on to decide whether each coroutine should start, stop, or stay in the state its in. \$\endgroup\$ – Victor T. Apr 18 '17 at 22:06
  • \$\begingroup\$ Hey Victor, thanks for the quick reply. I have added some If statements to my Update() function but where do i start and stop the coroutines? Because if i have them in the update(), its goes made as its doing it every frame \$\endgroup\$ – hardwiredstudios Apr 18 '17 at 22:58
  • \$\begingroup\$ I have this code in my Update() if (Count_HW > 0) { if (TimerOn_HW) StartCoroutine (HW_Timer ()); } else if (Count_HW == 0) StopCoroutine (HW_Timer ()); \$\endgroup\$ – hardwiredstudios Apr 18 '17 at 23:01
1
\$\begingroup\$

This happens because you're not stopping the coroutines that you started in the first place, because you have no reference to them.

In order to fix the problem, you need to declare IEnumerator variables that will store the reference to the coroutines, so that you can stop the exact same one referenced by the variable.

Like this:

IEnumerator totalTimer;
IEnumerator hwTimer;
IEnumerator stTimer;
IEnumerator izTimer;
IEnumerator outTimer;

void Awake() {
    totalTimer = TOTAL_Timer();
    hwTimer = HW_Timer();
    stTimer = ST_Timer();
    izTimer = IZ_Timer();
    outTimer = OUT_Timer();     
}

Then, when you need to start and stop one of those coroutines, just use, for example when starting and stopping TOTAL_Timer(), StartCoroutine(totalTimer) and StopCoroutine(totalTimer).

\$\endgroup\$
0
\$\begingroup\$

Do your coroutines really stop when you do stopcoroutine? You might have to save them like Coroutine coroutine1 = StartCoroutine(1); and then stop it with StopCoroutine(coroutine1);

\$\endgroup\$
  • \$\begingroup\$ The best practice is to use the IEnumerator type to keep references to coroutines instead of the Coroutine class, because it's easier to read (i.e.: you don't need to wait the start of the coroutine to assign a reference to the variable) and you can use the methods/property of the IEnumerator type if needed, whereas with Coroutine you can't. \$\endgroup\$ – Galandil Feb 21 '18 at 14:47
0
\$\begingroup\$

I suggest tracking the count of colliders from each zone that the player is in with OnTriggerEnter and OnTriggerExit, and only starting or stopping a coroutine in Update.

The code you posted in a comment will start a new timer if the timer is running, not if it isn't. A small change would fix it. Try this for your Update:

void Update() {
    if (Count_HW > 0) {
        if (!TimerOn_HW) {
            TimerOn_HW = true;
            StartCoroutine(HW_Timer());
        }
    } else {
        if (TimerOn_HW) {
            TimerOn_HW = false;
            // coroutine ends automatically
        }

        // bonus: with this kind of enter/exit code, it's always good to check for "impossible" conditions

        if (Count_HW < 0) {
            Debug.LogError("Somehow, we exited " + -Count_HW + " more HW colliders than we entered!");
            Count_HW = 0;
        }
    }

    // same idea for the other timers
}
\$\endgroup\$
  • \$\begingroup\$ Hey Victor, cool, thank you for spotting my code error. I've tried yours but still not different. This should be so simple. I can't believe that you can't combine colliders in unity to create bespoke shapes. \$\endgroup\$ – hardwiredstudios Apr 23 '17 at 11:29
  • \$\begingroup\$ I updated my answer to be a little more clear about what I'm suggesting. If this continues to not work for you, could you edit the current version of the code into your question? \$\endgroup\$ – Victor T. Apr 23 '17 at 16:15

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.