【问题标题】:Find reference in difference scene unity在差异场景统一中找到参考
【发布时间】:2019-12-04 07:21:15
【问题描述】:

我对找到参考 GameObject 但场景不同并在不同场景时设置 onclick 感到困惑,所以我有 GameManager 管理所有但仅在主菜单上可用。所以我决定制作Dontdestroyonload,问题从这里开始,所以当我玩到MainGame场景时,检查员的GameManager字段会找到,但我不能拖放不同的场景,对吧?这让我很困惑。

如果 GameManager 在 MainMenu 场景中,问题是如何在 onClick 事件中拖放,比如我希望暂停按钮处于活动状态或游戏中的其他内容。

]3

我尝试使用onLloadscene(scene s, Mode mode),但没有任何反应,这里是 GameManager 的脚本。 :

public static GameManager gameManager;

[Header("Main Menu panels")]
public GameObject startPanel;
public GameObject settingPanel;
public GameObject levelPanel;

[Header("InGame Panels")]
#region Panel
public GameObject pausePanel;
public GameObject ObjectivePanel;
public GameObject shopPanel;

private int click = 0;

[Header("Int Tweaks")]
public int indexLevel;
public int onlevel;

public bool isPaused;
_levelSelect LevelSelect;
public static GameManager Instance { set; get; }
public int levelindexPlayerPrefs;

private void Awake()
{
    if (gameManager != null)
    {
        Instance = this;
        Destroy(gameObject);
    }
    else
    {
        DontDestroyOnLoad(gameObject);
    }
}

void Start()
{
    LevelSelect = FindObjectOfType<_levelSelect>();
    OnStart();
    onlevel = int.Parse(LevelSelect.levelIndex) + 1;
    indexLevel = int.Parse(LevelSelect.levelIndex);
    getPlayerData();
}


// Update is called once per frame
void Update()
{
    ExitApp();
}

public void OnStart()
{
    startPanel.SetActive(true);
    settingPanel.SetActive(false);
    levelPanel.SetActive(false);
}

#region Buttons

public void startbutton()
{
    levelPanel.SetActive(true);
    startPanel.SetActive(false);
    settingPanel.SetActive(false);
}

public void backButtonMainMenu()
{
    levelPanel.SetActive(false);
    startPanel.SetActive(true);
    settingPanel.SetActive(false);
}

public void settingbutton()
{
    levelPanel.SetActive(false);
    startPanel.SetActive(false);
    settingPanel.SetActive(true);
}

public void PauseButton()
{
    Time.timeScale = 0f;
    pausePanel.SetActive(true);
    ObjectivePanel.SetActive(false);
}

public void Resume()
{
    Time.timeScale = 1f;
}

#endregion

public void ExitApp()
{
    if (Input.GetKey(KeyCode.Escape))
    {
        click++;
        StartCoroutine(ClickTime());
        if (click>1)
        {
            print("Exit Game");
            Application.Quit();
        }
    }
}

IEnumerator ClickTime()
{
    yield return new WaitForSeconds(0.5f);
    click = 0;
}

public void getPlayerData()
{
    levelindexPlayerPrefs = PlayerPrefs.GetInt("LevelIndex", 0);
}

public void updateLevel(int Index)
{
    if (levelindexPlayerPrefs < Index)
    {
        PlayerPrefs.SetInt("LevelIndex", Index);
        levelindexPlayerPrefs = PlayerPrefs.GetInt("LevelIndex");
    }
}

#region onloadedScenePickRefferences

private void OnEnable()
{
    SceneManager.sceneLoaded += OnSceneLoaded;
}
private void OnDisable()
{
    SceneManager.sceneLoaded -= OnSceneLoaded;
}

void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
    pausePanel = GameObject.FindGameObjectWithTag("PausePanel");
    ObjectivePanel = GameObject.FindGameObjectWithTag("ObjectivePanel");
}

#endregion

//public IEnumerator EndChapter()
//{
//    updateLevel(indexLevel + 1);
//    getPlayerData();
//}

【问题讨论】:

  • Unity 不能保证同时加载两个场景,因此不允许保存跨场景引用(即在编辑器中拖放)。您将需要在运行时管理对象发现(FindObjectOfType 等),就像在 OnSceneLoaded 中所做的一样。
  • @Immersive 是的,但什么也没发生,onclickthat can't be referenced
  • @Syarifabdurrahman 您可以在运行时添加回调到onClick 以及通过AddListener 例如像someButton.onClick.AddListener(()=&gt;{ DoSomething(); }); (尽管它们不会出现在检查器中!)。但是,这个脚本是在主场景中还是在稍后加载的场景中?问题可能是 sceneLoaded 在您添加回调之前被调用
  • @derHugo 首先在主菜单中,当然,当我进入 MainGame 场景时,这个脚本也在那个场景中,这就是我使用不要破坏 onload 的原因,因为我认为如果我这样做会很好仅使用 1 个GameManager
  • 其实在Awake你为什么要Instance = this; Destroy(gameObject);??这会立即破坏您刚刚分配的引用Instance,因此它将始终为null。这也会立即导致OnDisable 被调用,所以OnSceneLoaded 可能根本不会执行?或者好吧...实际上gameManager 从来没有在我所见的范围内设置过.. 这里肯定有问题;)你应该得到这个类的多个实例,而最新加载场景中的那个可能总是保持未初始化如前所述,因为我猜 OnEnable 被称为 为时已晚 尝试 Awake 代替

标签: c# unity3d


【解决方案1】:

这是我可能会做的:

有一个static 类用于存储和共享您的所有参考。它不必在任何场景中,而只是“生活”在资产中:

public static class GlobalReferences
{
    // as example just for one reference but you can implement the rest equally yourself

    // here this class actually stores the reference
    private static GameObject startPanel;

    // A public property in order to add some logic
    // other classes will always access and set the value through this property
    public static GameObject StartPanel
    {
        get
        {
            // if the reference exists return it right away
            if(startPanel) return startPanel;

            // as a fallback try to find it 
            startPanel = GameObject.FindGameObjectWithTag("StartPanel");

            // ofcourse it might still fail when you simply try to access it 
            // in a moment it doesn't exist yet
            return startPanel;
        }

        set
        {
            startPanel = value;

            // invoke an event to tell all listeners that the startPanel
            // was just assigned
            OnStartPanelReady?.Invoke();
        }
    }

    // An event you will invoke after assigning a value making sure that
    // other scripts only access this value after it has been set
    // you can even directly pass the reference in
    public static event Action<GameObject> OnStartPanelReady;
}

所以现在在您的组件中,即在新加载的场景中,您应该尽早分配值 (Awake)。您已经可以通过 Inspector 将其存储在这里因为它是场景参考:

public class ExampleSetter : MonoBehaviour
{
    // already reference it via the Inspector
    [SerializeField] private GameObject startPanel;

    private void Awake()
    {
        // as a fallback
        if(!startPanel) startPanel = GameObject.FindObjectWithTag("startPanel");

        // assign it to the global class
        GlobalReferences.StartPanel = startPanel;
    }
}

在您添加侦听器之前已经加载的其他场景中,他们会在其他场景准备好后立即执行他们的工作:

public class ExampleConsumer : MonoBehaviour
{
    [Header("Debug")]
    [SerializeField] private GameObject startPanel;

    private void Awake()
    {
        // Try to get the reference
        startPanel = GlobalReferences.StartPanel;

        // if this failed then wait until it is ready
        if(!startPanel)
        {
            // it is save to remove callbacks even if not added yet
            // makes sure a listener is always only added once
            GlobalReferences.OnStartPanelReady -= OnStartPanelReady;
            GlobalReferences.OnStartPanelReady += OnStartPanelReady;
        }
        // otherwise already do what you want
        else
        {
            OnStartPanelReady(startPanel);
        }
    }

    private void OnDestroy()
    {
        // always make sure to clean up callbacks when not needed anymore!
        GlobalReferences.OnStartPanelReady -= OnStartPanelReady;
    }

    private void OnStartPanelReady(GameObject newStartPanel)
    {
        startPanel = newStartPanel;
        // always make sure to clean up callbacks when not needed anymore!
        GlobalReferences.OnStartPanelReady -= OnStartPanelReady;

        // NOTE: It is possible that at this point it is null anyway if another
        // class has set this actively to null ;)

        if(startPanel)
        {
            // Now do something with the startPanel
        }
    }
}

反之,当您需要在新加载的场景中从主场景中引用时......它应该已经设置,因为首先加载了主场景并且已经分配了相应的引用。


现在您可以选择这个static 类,也可以简单地为需要在相应组件中直接共享的每个引用实现相同的逻辑,您可以在其中通过drag&amp;drop 引用它们......这没有区别,因为无论如何你将使用不绑定到任何实例但类型本身的静态字段和事件。

【讨论】:

    猜你喜欢
    • 2022-10-01
    • 2017-12-28
    • 2018-08-15
    • 1970-01-01
    • 2012-10-07
    • 2012-11-09
    • 2022-11-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多