从 Unity 中的不同场景访问变量

Posted

技术标签:

【中文标题】从 Unity 中的不同场景访问变量【英文标题】:Access variable from a different scene in Unity 【发布时间】:2020-06-03 00:33:04 【问题描述】:

我对 Unity 和 C# 还很陌生,遇到了一些麻烦。我正在设计一个具有多个级别的 2d 游戏。每个关卡都包含一个LevelManager,用于存储关卡是否已完成。它们还包含 DontDestroyOnLoad 命令。我想访问我游戏中的所有 LevelManager 游戏对象,然后将它们存储在关卡选择场景中。我想使用赢/输布尔来确定关卡是否已完成,以便我可以解锁下一个关卡。明确地说,我想要一种方法来访问整个游戏中的所有 LevelManager,然后将它们作为数组存储在 GameManager 脚本中。我该怎么做?

下面是 LevelManager 脚本,它声明关卡是否已赢得。

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

public class SceneController : MonoBehaviour

    private GameObject[] StartHouseCount;
    private GameObject[] StartDragonCount;
    private GameObject[] LiveDragonCount;
    private GameObject[] FinishedHouseCount;

    public int NumOfHouses;
    public int NumOfFinishedHouse;
    public int NumOfDragons;
    public int LiveNumOfDragons;
    public GameObject[] Players;
    public GameObject CurrentPlayer;

    [Header("Player")]
    public float RefuelRate;
    public float RepairRate;

    public GameObject canvas;
    public bool GameIsPaused = false;

    private GameObject MainPlayer;
    public bool Win = false;
    public int Level;
    // Start is called before the first frame update
    void Start()
    
        CurrentPlayer = Players[0];
        StartHouseCount = GameObject.FindGameObjectsWithTag("House");
        StartDragonCount = GameObject.FindGameObjectsWithTag("Dragon");
        NumOfHouses = StartHouseCount.Length;
        NumOfDragons = StartDragonCount.Length;
        MainPlayer = Players[0];
    

    // Update is called once per frame
    void Update()
    
        GameIsPaused = canvas.GetComponent<PauseMenu>().GameIsPaused;

        LiveDragonCount = GameObject.FindGameObjectsWithTag("Dragon");
        LiveNumOfDragons = LiveDragonCount.Length;

        FinishedHouseCount = GameObject.FindGameObjectsWithTag("ThankYou");
        NumOfFinishedHouse = FinishedHouseCount.Length;

        if (Input.GetKeyDown(KeyCode.Alpha1))
        
            CurrentPlayer = Players[0];
        
        if (Input.GetKeyDown(KeyCode.Alpha2))
        
            CurrentPlayer = Players[1];
        


        if (NumOfFinishedHouse == NumOfHouses)
        
            SceneManager.LoadScene("WinScene");
        

        if (MainPlayer == null)
        
            SceneManager.LoadScene("LoseScene");
        
        if (MainPlayer.GetComponent<BasicHelicopterController>().CurrentFuel <= 0 || MainPlayer.GetComponent<BasicHelicopterController>().CurrentHealth <= 0)
        
            SceneManager.LoadScene("LoseScene");
        

    

【问题讨论】:

【参考方案1】:

您可以创建一个ScoreManager 脚本来使用 PlayerPrefs 存储每个级别的分数,并将此脚本附加到第一个场景中的 GameObject。

public class ScoreManager: MonoBehavior 

    public static ScoreManager Instance;
    const string LEVEL_KEY = "SCORE_";
    const string MAX_LEVEL_KEY = "MAX_LEVEL";

    public static int MAX_LEVEL 
        get  return PlayerPrefs.GetInt(MAX_LEVEL_KEY, 1); 
        set 
            if(value > MAX_LEVEL) 
                PlayerPrefs.SetInt(MAX_LEVEL_KEY, value);
            
        
    

    void Awake() 
        if(Instance != null) 
            Destroy(this.gameObject);
            return;
        
        Instance = this;
        DontDestroyOnLoad(this);
    

    public void SaveScore(int level, float score) 
        PlayerPrefs.SetFloat(LEVEL_KEY + level, score);
        MAX_LEVEL = level;
    

    public float GetScore(int level) 
        return PlayerPrefs.GetFloat(LEVEL_KEY + level, 0);
    

    public bool IsLevelUnlocked(int level) 
        // You can write your own level-unlock logic here.
        return level <= MAX_LEVEL;
    
    ...

然后你可以从其他脚本中调用它的函数和属性。

    ...
    public void GameOver() 
        ScoreManager.Instance.SaveScore(level, score);
    
    ...

让所有 LevelManger 脚本都带有 DontDestroyOnLoad 会导致内存泄漏,有时会影响游戏性能。

【讨论】:

你好火王,关于你的剧本我有几个问题。我是 C# 新手,所以我无法理解所有内容。首先是LevelManager如何访问ScoreManager Instance。据我所知,我需要以某种方式将包含您的 ScoreManager 实例的游戏对象附加到关卡管理器。这是如何运作的?我的下一个是 void Awake() 中发生的事情。有一些代码似乎会破坏游戏对象。这是在做什么?我的 LevelManager 还包含一个 Win bool,用于确定关卡是否已获胜。我可以用 bool won 切换浮点分数吗? 我只是再次查看您的代码。我知道如何使用 public void SaveScore,但不知道 public float 或 public bool。我还没有学过这些。我该如何使用它们? @KalaimaranBalasothy 感谢您检查我的代码。 1. 一旦您将 ScoreManager 脚本附加到第一个场景中的游戏对象,当您加载其他场景时,它将永远不会被破坏,因此您可以在任何级别访问该实例。 2.加载场景时调用Monobehavior的Awake函数,加载后调用Start函数,所以要在Awake函数中初始化该Instance 3.初始化后,您可以在任何地方访问该脚本。 ScoreManager.Instance.GetScore(); 4. 您可以修改IsLevelUnlocked() 脚本,将LevelManager 的布尔值移动到该脚本中。或者您可以创建一个新的 PlayerPrefs 密钥来存储关卡解锁状态。 让我们continue this discussion in chat。【参考方案2】:

首先,DontDestroyOnLoad 不应该这样做 - 主要用途是实现诸如 unity-singleton (easy-to-use template) 之类的东西。这意味着,您将拥有一个 levelManager 实例,而不是每个级别一个。

要按照您的方式进行,您需要附加地加载所有场景,并获取所有 LevelManager 实例。或者您可以一个接一个地加载所有场景,从当前 LevelManager 获取关卡名称和它的 isPassed 值。这可能需要一段时间,而且是错误的方式。

正确的做法是将跨场景数据存储在ScriptableObject模型中。它类似于 MonoBehaviour,但与场景无关 - 它只是位于项目中的某个位置。

因此,在您的情况下,您可以创建名为 LevelProgress 的 ScriptableObject。它将存储所有通过级别的列表。它将具有公共方法 LevelPassed(string levelName),作为参数,您可以使用任何内容 - 级别名称、构建中的级别索引、场景本身等等。每次,当玩家通过任何关卡时,您的 LevelManager(从中删除 DontDestoryOnLoad),它具有对 LevelProgress 可脚本对象的引用,将调用 LevelProgress.LevelPasses(currentLevel)。然后,如果您需要知道某个级别是否通过了,您只需检查 LevelProgress.PassedLevels 列表是否包含此级别。

此外,您需要为此可编写脚本的对象实现会话间持久性。 IMO,最好的方法是使用JSONUtility将ScriptableObject转换为JSON字符串,然后将其写入PlayerPrefs。

【讨论】:

【参考方案3】:

我好像明白了。我创建了一个包含 GameManagement 脚本的游戏对象,该脚本具有 DontDestroyOnLoad 行。我还添加了一个特定的标签。然后我在每个级别中搜索该对象并将我的值更新为该对象。 GameManagement 脚本有一系列布尔值,用于完成关卡和解锁关卡。每个级别经理决定是否赢得了该级别并对其进行更新。使用它,我确定了解锁的级别。我确实需要使用火王的觉醒命令。它确保游戏中没有其他脚本副本。解决了我的问题。

【讨论】:

以上是关于从 Unity 中的不同场景访问变量的主要内容,如果未能解决你的问题,请参考以下文章

Unity中的环境变量在开发和生产之间有所不同

Unity:无法从资产包加载场景

unity3D能否同时加载两个完全相同的场景,两个场景各有一个camera,从不同角度看到同一个画面。

Unity中的shadows

Unity中的线程以避免动画冻结

unity不同场景间怎么传值