0

I am trying to populate various objects in Unity based on some triggers and some data. The data that I am using looks something like this Key === O2 Values are ==== { collected, collected, collected, absent}Key === O3 Values are ==== { collected, collected, present } stored in a Dictionary object Dictionary<string, List<string>> textMap. Following script is attached to these objects:

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

public class SensePlayerProximity : MonoBehaviour {

    bool disableEntry = false;
    bool disableExit = false;
    public bool isCollected = false;
    //List<Collider2D> triggerList = new List<Collider2D>();
    // Use this for initialization
    static Dictionary<string, List<string>> textMap = new Dictionary<string, List<string>>
    {
        { "O2", new List<string>() { "present", "absent", "absent", "absent" }},
        { "O3",new List<string>() { "absent", "absent", "absent" }}
    };

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (disableEntry || isCollected)
            return;
        StartCoroutine(disableTriggersForThisCollectible(10));

        List<String> values;
        foreach (KeyValuePair<string, List<string>> kvp in textMap)
        {
            values = kvp.Value;
            int foundAtIndex = values.IndexOf("absent");
            if (foundAtIndex > -1)
            {
                gameObject.GetComponent<TextMeshProUGUI>().text = kvp.Key;
                values[foundAtIndex] = "present";
                textMap.Remove(kvp.Key);
                textMap.Add(kvp.Key, values);
                logTextMap();
                return;
            }
        }
        // if nothing is found, then default the text to empty, since nothing left to be collected now
        gameObject.GetComponent<TextMeshProUGUI>().text = "";
    }

    //called when something exits the trigger
    private void OnTriggerExit2D(Collider2D collision)
    {
        if (disableExit || isCollected)
            return;
        //Debug.Log("Player is leaving me.. :(");
        string key = gameObject.GetComponent<TextMeshProUGUI>().text;
        StartCoroutine(disableTriggersForThisCollectible(0.1f));
        List<string> values = textMap[key];
        int index = -1;
        if (values != null)
            index = values.IndexOf("present");
        if(index >= 0)
        {
            values[index] = "absent";
            textMap.Remove(key);
            textMap.Add(key, values);
            gameObject.GetComponent<TextMeshProUGUI>().text = "";
            logTextMap();
        }
    }

    IEnumerator disableEntryTrigger(float t)
    {
        disableEntry = true;
        // disable the trigger collider for t seconds
        yield return new WaitForSeconds(t);
        disableEntry = false;
    }

    IEnumerator disableExitTrigger(float t)
    {
        disableExit = true;
        // disable the trigger collider for t seconds
        yield return new WaitForSeconds(t);
        disableExit = false;
    }

    void logTextMap()
    {
        string debugString = "";
        foreach (KeyValuePair<string, List<string>> kvp in textMap)
        {
            debugString += "Key === " + kvp.Key + " Values are ==== { " + String.Join(", ", kvp.Value.ToArray()) + " }";
        }
        Debug.Log(debugString);
    }
}

The script detects trigger collisions with BoxCollider2D attached to my player, and it has "Sensor" tag name attached to it. I disable the triggers for 10s whenever OnTriggerEnter2D event occurs and for 0.1s whenever OnTriggerExit2D event occurs.

I have some fixed textobjects scattered in my level and trying to populate the text in them on the basis of this script above. This script is attached to every such text object. With the help of these events, I detect if the player is in the vicinity of a text object. If the player is found, then a random key will be populated from the textMap provided that key has at least one value which says "absent". Each key has a list of values, which could be "absent", "present", or "collected". "absent" means that the key is absent from the camera view and thus can be assigned to new collectible text objects. "present" means that the key is present in the current camera view and is not available for the other text objects. "collected" means that the key has already been collected and is not available either. In the example value of textMap which I shown above, for example, there can be 4 copies of the key "O2" in the map, and 3 copies of the key "O3". Out of these, 3 "O2" and 2 "O3" have already been collected. Only 1 copy of "O2" can be assigned to newly triggered text objects and no copy of "O3" is available for them. The script works mostly as expected, except a few time which I am not able to debug. The debug log show that one copy of "O3" is already present in the view, but I went to my scene and could not find "O3" anywhere. I am afraid that this might be happening because all the triggered text objects (to which the above script is attached) are trying to modify the textMap at the same time. I have wasted a lot of time trying to figure this out, but I am just banging my head in the wall. I'd really appreciate if someone could point me in the right direction. My scene with these objects is shown below: enter image description here

Edit: I found the problem to be DontDestroyOnLoad. The said gameobjest to which SensePlayerProximity script is attached are all children of a DontDestroyOnLoad gameobject called ScenePersist. The problem is happening only when I reload the scene, upon player death. When the scene reloads, new ScenePersist is loaded in the scene, and just before it called the triggerentry method on children before being destroyed. Because of this, OnTriggerEnter2D is called twice, instead of once. How do I fix this problem? One way to fix this would be to keep all these objects far away from the player spawn point, so that the triggers doesn't occur, but that is not a good way to fix it. Another is to run a coroutine enableTriggers in start method, i.e. disable the triggers by default, but that is not a good solution either.

void Start () {
    disableEntry=true;
    disableExit = true;
    StartCoroutine(enableTriggers());
}
IEnumerator enableTriggers()
{
    yield return new WaitForSeconds(0);
    disableEntry = false;
    disableExit = false;
}

This is how my scene hierarchy looks like:

enter image description here

Here the ScenePersist is set to DontDestroyOnLoad, and it has a lots of sub-child objects (highlighted as collectible) to which the SensePlayerProximity is attached.

7
  • Would you not want some form of event manager, so on triggering the onenter, it sets a variable on the manager which means that theres a first line of ever enter that says ignorethis and a coroutine so after 10s turns it off? Commented Apr 10, 2019 at 7:26
  • I am not sure what you mean, could you please elaborate a little? :( Commented Apr 10, 2019 at 7:29
  • Have a manager object, have a boolean property of "dontdothis" or something on it, on setting it to true, trigger coroutine to turn it off 10s later, or if you need it to do 10s after "the last trigger" update a time of last hit so that the coroutine can do 10s later.. then in the trigger if managerobject.dontdothis return; so it doesnt do anything... Commented Apr 10, 2019 at 7:55
  • This is already happening in the current script that I attached above using the disableTriggersForThisCollectible method. Commented Apr 10, 2019 at 8:00
  • I think a little minimal reproducible example is needed - as theres a lot of irrelevant code, and missing code .. Commented Apr 10, 2019 at 8:06

2 Answers 2

1

The problem is because you disable the trigger, the Enter and Exit methods are not called in pairs. But no code to protect it.

Enter -> present +1, absent -1
Exit -> present -1, absent +1

If the method Enter is called twice, but the method Exit is skipped, now 2 elements in the dictionary become present, but there is only one object (with text O2/O3) in the scene.

Enter -> present +1, absent -1
Left without trigger Exit
Wait 10 seconds
Enter -> present +1, absent -1
Exit -> present -1, absent +1
Sign up to request clarification or add additional context in comments.

7 Comments

Oh, I think you are correct. Thank you for the pointer. May be I should have separate disablers for each of them.
What would you suggest to fix this?
I added the code to disable the entry and exit separately, they do not interfere with each other now. but the problem still persists. Although I initially thought that this might be causing the issue. But even if the exit is disabled although exit has happened already, the corresponding text object will stay there in my scene. What i mean is, if the object says "O2" and the textMap has a value which says "present", then the object will remain in scene forever until an entry or exit is again called on it.
Apparently the problem is with the DontDestroyOnLoad. all these objects are child of a DontDestroyOnLoad gameobject. When the level reloads (during some circumstance), the trigger is being called twice. once on the actual object, and again on the object which is about to get destroyed. because of this, the textMap variable is modified with two "present" values, just before that object is destroyed. how do I fix this?
If the problem is DontDestroyOnLoad then my opinion is don't use it, I think this method is not necessary when the level needs reload.
|
0

I solved it by using the OnDestroy method. Before getting destroyed, the object was triggering the OnTriggerEnter2D method. So on getting destroyed I did something similar to what OnTriggerExit2D was doing, as shown below.

void OnDestroy()
{
    string key = gameObject.GetComponent<TextMeshProUGUI>().text;
    Debug.Log("destroed with key "+ key);
    if (!textMap.ContainsKey(key))
        return;
    List<string> values = textMap[key];
    int index = -1;
    if (values != null)
        index = values.IndexOf("present");
    if (index >= 0)
    {
        values[index] = "absent";
        textMap.Remove(key);
        textMap.Add(key, values);
        gameObject.GetComponent<TextMeshProUGUI>().text = "";
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.