Unity:附加到DontDestroyOnLoad对象的子对象的脚本被调用两次

时间:2019-04-10 06:43:01

标签: c# unity3d

我试图基于一些触发器和一些数据在Unity中填充各种对象。我正在使用的数据看起来像这样Key === O2 Values are ==== { collected, collected, collected, absent}Key === O3 Values are ==== { collected, collected, present },存储在Dictionary对象Dictionary<string, List<string>> textMap中。以下脚本已附加到这些对象:

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);
    }
}

该脚本检测到与BoxCollider2D附加到我的播放器上的触发器冲突,并且脚本上附加了“ Sensor”标签名称。每当OnTriggerEnter2D事件发生时,我将禁用触发器10秒钟,而OnTriggerExit2D事件发生时将禁用0.1s触发器。

我在我的关卡中散布了一些固定的文本对象,并根据上述脚本尝试在其中填充文本。该脚本附加到每个此类文本对象。 借助这些事件,我可以检测播放器是否在文本对象附近。如果找到了玩家,则将从textMap中填充一个随机密钥,前提是该密钥至少具有一个表示“不存在”的值。每个键都有一个值列表,可以是“不存在”,“存在”或“收集”。 “不存在”表示摄像机视图中不存在该键,因此可以将其分配给新的可收集文本对象。 “存在”表示该键存在于当前摄像机视图中,并且不适用于其他文本对象。 “已收集”表示密钥已被收集,也不可用。例如,在上面显示的textMap的示例值中,映射中可以有4个键“ O2”的副本和3个键“ O3”的副本。其中,已经收集了3个“ O2”和2个“ O3”。只能将1个“ O2”副本分配给新触发的文本对象,而没有“ O3”副本可用于它们。该脚本的工作原理与预期的一样,除了一些我无法调试的时间。调试日志显示视图中已经存在“ O3”的一个副本,但是我去了我的场景,在任何地方都找不到“ O3”。恐怕可能会发生这种情况,因为所有触发的文本对象(上面的脚本已附加到该对象上)都试图同时修改textMap。我浪费了很多时间试图解决这个问题,但是我只是在脑海里摸索。如果有人能指出我正确的方向,我将不胜感激。这些对象的场景如下所示: enter image description here

编辑:我发现问题是DontDestroyOnLoad。附着了SensePlayerProximity脚本的所述gameobjest是名为DontDestroyOnLoad的{​​{1}}游戏对象的所有子对象。仅当我在玩家死亡时重新加载场景时,才会出现问题。重新加载场景时,新的ScenePersist将被加载到场景中,并且在它被销毁之前在子对象上调用triggerentry方法之前。因此,ScenePersist被调用两次,而不是一次。我该如何解决这个问题? 解决此问题的一种方法是使所有这些对象都远离播放器生成点,以免触发发生,但这不是修复它的好方法。另一种方法是在start方法中运行协程OnTriggerEnter2D,即默认情况下禁用触发器,但这也不是一个好的解决方案。

enableTriggers

这是我的场景层次结构的样子:

enter image description here

此处void Start () { disableEntry=true; disableExit = true; StartCoroutine(enableTriggers()); } IEnumerator enableTriggers() { yield return new WaitForSeconds(0); disableEntry = false; disableExit = false; } 设置为ScenePersist,并且它具有许多附加了DontDestroyOnLoad的子子对象(突出显示为可收藏)。

2 个答案:

答案 0 :(得分:1)

问题是因为禁用了触发器,所以Enter和Exit方法不是成对调用的。但是没有代码可以保护它。

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

如果两次调用Enter方法,但是跳过Exit方法,则现在词典中出现了2个元素,但是场景中只有一个对象(文本为O2 / O3)。

Enter -> present +1, absent -1
Left without trigger Exit
Wait 10 seconds
Enter -> present +1, absent -1
Exit -> present -1, absent +1

答案 1 :(得分:0)

我通过使用OnDestroy方法解决了它。在销毁对象之前,该对象正在触发OnTriggerEnter2D方法。因此,在被销毁后,我做了与OnTriggerExit2D类似的事情,如下所示。

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 = "";
    }
}