Unity - 在场景之间传递数据

Min*_*wzy 17 c# unity-game-engine virtual-reality

如何将得分值从一个场景传递到另一个场景?

我尝试过以下方法:

场景一:

void Start () {
    score = 0;
    updateScoreView ();
    StartCoroutine (DelayLoadlevel(20));
}

public void updateScoreView(){
    score_text.text = "The Score: "+ score;
}

public void AddNewScore(int NewscoreValue){
    score = score + NewscoreValue;
    updateScoreView ();
}

IEnumerator DelayLoadlevel(float seconds){        
    yield return new WaitForSeconds(10);
    secondsLeft = seconds;
    loadingStart = true;
    do {        
        yield return new WaitForSeconds(1);
    } while(--secondsLeft >0);

    // here I should store my last score before move to level two
    PlayerPrefs.SetInt ("player_score", score);
    Application.LoadLevel (2);
}
Run Code Online (Sandbox Code Playgroud)

场景二:

public Text score_text;
private int old_score;

// Use this for initialization
void Start () {    
    old_score = PlayerPrefs.GetInt ("player_score");
    score_text.text = "new score" + old_score.ToString ();      
}
Run Code Online (Sandbox Code Playgroud)

但屏幕上没有显示任何内容,也没有错误.

这是传递数据的正确方法吗?

我正在使用Unity 5免费版,为Gear VR开发游戏(意味着游戏将在Android设备上运行).

有什么建议吗?

Pro*_*mer 50

3种方法可以做到这一点,但解决方案取决于您希望在场景之间传递的数据类型.加载新场景时甚至标记为时,组件/脚本和游戏对象都会被销毁static.

1.使用static关键字.

使用此方法如果变量传递到下一个场景是不是一个组成部分,并没有继承MonoBehaviour,而不是一个游戏物体,然后把这些变量是static.

内置基本数据类型,如int,bool,string,float,double.所有这些变量都可以变成一个static变量.

该内置基本数据类型的实施例可以被标记为静态:

static int counter = 0;
static bool enableAudio = 0;
static float timer = 100;
Run Code Online (Sandbox Code Playgroud)

这些应该没有问题.


可以标记为静态的对象示例:

public class MyTestScriptNoMonoBehaviour
{

}
Run Code Online (Sandbox Code Playgroud)

然后

static MyTestScriptNoMonoBehaviour testScriptNoMono;

void Start()
{
    testScriptNoMono = new MyTestScriptNoMonoBehaviour();
}
Run Code Online (Sandbox Code Playgroud)

请注意,该类不继承自MonoBehaviour.这应该工作.


无法标记为静态的对象示例:

凡是从继承Object,ComponentGameObject无法正常工作.

1A.任何继承自的东西MonoBehaviour

public class MyTestScript : MonoBehaviour 
{

}
Run Code Online (Sandbox Code Playgroud)

然后

static MyTestScript testScript;

void Start()
{
    testScript = gameObject.AddComponent<MyTestScript>();
} 
Run Code Online (Sandbox Code Playgroud)

这将工作,因为它从继承MonoBehaviour.

1B.所有GameObject:

static GameObject obj;

void Start()
{
    obj = new GameObject("My Object");
}  
Run Code Online (Sandbox Code Playgroud)

不会起作用,因为它是a GameObject并且GameObject继承自Object.

Object即使用static关键字声明它们,Unity也会永远销毁它.

有关解决方法,请参阅#2.


2.使用该DontDestroyOnLoad功能.

如果要保留或传递给下一个场景的数据继承Object,Component或者是a ,则只需要使用它GameObject.这解决了1A1B中描述的问题.

当场景卸载时,您可以使用它来使这个GameObject不被破坏:

void Awake() 
{
    DontDestroyOnLoad(transform.gameObject);
}
Run Code Online (Sandbox Code Playgroud)

您甚至可以将它与1A1B中static关键字解决问题一起使用:

public class MyTestScript : MonoBehaviour 
{

}
Run Code Online (Sandbox Code Playgroud)

然后

static MyTestScript testScript;

void Awake() 
{
    DontDestroyOnLoad(transform.gameObject);
}

void Start()
{
    testScript = gameObject.AddComponent<MyTestScript>();
} 
Run Code Online (Sandbox Code Playgroud)

testScript现在,当加载新场景时,将保留该变量.

3.保存到本地存储,然后在下一个场景中加载.

当这是游戏关闭并重新打开时必须保留的游戏数据时,应使用此方法.这样的例子是玩家高分,游戏设置如音乐量,对象位置,操纵杆简档数据等.

Thare是两种保存方式:

3APlayerPrefs.使用API.

如果只有几个变量可以保存,请使用.让我们说球员得分:

int playerScore = 80;
Run Code Online (Sandbox Code Playgroud)

我们想要保存playerScore:

将分数保存在OnDisable函数中

void OnDisable()
{
    PlayerPrefs.SetInt("score", playerScore);
}
Run Code Online (Sandbox Code Playgroud)

将其加载到OnEnable函数中

void OnEnable()
{
    playerScore  =  PlayerPrefs.GetInt("score");
}
Run Code Online (Sandbox Code Playgroud)

图3B .Serialize数据以JSON,XML或binaray形式然后保存使用C#文件API之一,如File.WriteAllBytesFile.ReadAllBytes保存和加载文件.

如果要保存许多变量,请使用此方法.

一般情况下,您需要创建一个不继承的类MonoBehaviour.您应该使用此类来保存游戏数据,以便可以轻松地序列化或反序列化.

要保存的数据示例:

[Serializable]
public class PlayerInfo
{
    public List<int> ID = new List<int>();
    public List<int> Amounts = new List<int>();
    public int life = 0;
    public float highScore = 0;
}
Run Code Online (Sandbox Code Playgroud)

DataSaver类,这是一个包装上File.WriteAllBytes,并File.ReadAllBytes使得从保存的数据更容易这个职位.

创建新实例:

PlayerInfo saveData = new PlayerInfo();
saveData.life = 99;
saveData.highScore = 40;
Run Code Online (Sandbox Code Playgroud)

将PlayerInfo中的数据保存到名为"players"的文件中:

DataSaver.saveData(saveData, "players");
Run Code Online (Sandbox Code Playgroud)

从名为"players"的文件加载数据:

PlayerInfo loadedData = DataSaver.loadData<PlayerInfo>("players");
Run Code Online (Sandbox Code Playgroud)

  • 这是比公认的答案更全面的答案。谢谢! (4认同)

der*_*ugo 37

还有一种方法:

ScriptableObject

ScriptableObjects 基本上是数据容器,但也可以实现自己的逻辑。他们“生活”在Assets类似的预制件中。它们不能用于永久存储数据,但它们会在一个会话期间存储数据,因此它们可用于在场景之间共享数据和引用……而且——我也经常需要——在场景和AnimatorController!

脚本

首先你需要一个类似于MonoBehaviours的脚本。一个简单的例子ScriptableObject可能看起来像

// fileName is the default name when creating a new Instance
// menuName is where to find it in the context menu of Create
[CreateAssetMenu(fileName = "Data", menuName = "Examples/ExamoleScriptableObject")]
public class ExampleScriptableObject : ScriptableObject
{
    public string someStringValue = "";
    public CustomDataClass someCustomData = null;
    public Transform someTransformReference = null;

    // Could also implement some methods to set/read data,
    // do stuff with the data like parsing between types, fileIO etc

    // Especially ScriptableObjects also implement OnEnable and Awake
    // so you could still fill them with permanent data via FileIO at the beginning of your app and store the data via FileIO in OnDestroy !!
}

// If you want the data to be stored permanently in the editor
// and e.g. set it via the Inspector
// your types need to be Serializable!
//
// I intentionally used a non-serializable class here to show that also 
// non Serializable types can be passed between scenes 
public class CustomDataClass
{
    public int example;
    public Vector3 custom;
    public Dictionary<int, byte[]> data;
}
Run Code Online (Sandbox Code Playgroud)

创建实例

您可以ScriptableObject通过脚本创建任一实例

var scriptableObject = ScriptableObject.CreateInstance<ExampleScriptableObject>();
Run Code Online (Sandbox Code Playgroud)

或者为了让事情更容易使用[CreateAssetMenu]上面的例子中所示。

由于这个创建的ScriptabeObject实例存在于Assets它没有绑定到场景中,因此可以在任何地方引用!

当您想在两个场景之间共享数据时,或者在场景之间共享数据时,AnimatorController您需要做的就是ScriptableObject在两者中引用此实例。

填充数据

我经常使用例如一个组件来填充数据

public class ExampleWriter : MonoBehaviour
{
    // Here you drag in the ScriptableObject instance via the Inspector in Unity
    [SerializeField] private ExampleScriptableObject example;

    public void StoreData(string someString, int someInt, Vector3 someVector, List<byte[]> someDatas)
    {
        example.someStringValue = someString;
        example.someCustomData = new CustomDataClass
                                 {
                                     example = someInt;
                                     custom = someVector;
                                     data = new Dictionary<int, byte[]>();
                                 };
        for(var i = 0; i < someDatas.Count; i++)
        {
            example.someCustomData.data.Add(i, someDatas[i]);
        }
        example.someTransformReference = transform;
    }
}
Run Code Online (Sandbox Code Playgroud)

消费数据

因此,在您将所需的数据写入并存储到此ExampleScriptableObject实例后,任何场景中的每个其他类AnimatorController或其他ScriptableObjects 都可以以相同的方式读取这些数据:

public class ExmpleConsumer : MonoBehaviour
{
    // Here you drag in the same ScriptableObject instance via the Inspector in Unity
    [SerializeField] private ExampleScriptableObject example;

    public void ExampleLog()
    {
        Debug.Log($"string: {example.someString}", this);
        Debug.Log($"int: {example.someCustomData.example}", this);
        Debug.Log($"vector: {example.someCustomData.custom}", this);
        Debug.Log($"data: There are {example.someCustomData.data.Count} entries in data.", this);

        Debug.Log($"The data writer {example.someTransformReference.name} is at position {example.someTransformReference.position}", this);
    }
}
Run Code Online (Sandbox Code Playgroud)

坚持

如前所述,aScriptableObject本身的变化仅在 Unity 编辑器中真正持久。

在构建中,它们仅在同一会话期间保持不变。

因此,我经常将会话持久性与某些 FileIO(如本答案的第 3b 节中所述)结合起来,以便在会话开始时(或在需要时)从硬盘加载和反序列化值,并将它们序列化并存储到文件中一次在会话结束 ( OnApplicationQuit) 或需要时。

(这当然不适用于参考。)

  • 很好的解决方案,无需任何附加。该代码也更适合单元测试。感谢您的详细回答。 (2认同)

Isj*_*Isj 10

除了playerPrefs之外,另一种脏方法是在级别加载期间通过调用DontDestroyOnLoad来保留对象.

DontDestroyOnLoad (transform.gameObject);
Run Code Online (Sandbox Code Playgroud)

附加到游戏对象的任何脚本都将存活,脚本中的变量也将存活.DontDestroyOnLoad函数通常用于保留整个GameObject,包括附加到它的组件,以及它在层次结构中具有的任何子对象.

您可以创建一个空的GameObject,并只放置包含您想要保留的变量的脚本.


Tom*_*thy 5

我使用一种称为无状态场景的函数方法。

using UnityEngine;
public class MySceneBehaviour: MonoBehaviour {
    private static MySceneParams loadSceneRegister = null;

    public MySceneParams sceneParams;

    public static void loadMyScene(MySceneParams sceneParams, System.Action<MySceneOutcome> callback) {
        MySceneBehaviour.loadSceneRegister = sceneParams;
        sceneParams.callback = callback;
        UnityEngine.SceneManagement.SceneManager.LoadScene("MyScene");
    }

    public void Awake() {
        if (loadSceneRegister != null) sceneParams = loadSceneRegister;
        loadSceneRegister = null; // the register has served its purpose, clear the state
    }

    public void endScene (MySceneOutcome outcome) {
        if (sceneParams.callback != null) sceneParams.callback(outcome);
        sceneParams.callback = null; // Protect against double calling;
    }
}

[System.Serializable]
public class MySceneParams {
    public System.Action<MySceneOutcome> callback;
    // + inputs of the scene 
}

public class MySceneOutcome {
    // + outputs of the scene 
}
Run Code Online (Sandbox Code Playgroud)

您可以将全局状态保留在调用者的范围内,因此可以最小化场景输入和输出状态(使测试变得容易)。要使用它,您可以使用匿名函数:-

MyBigGameServices services ...
MyBigGameState bigState ...

Splash.loadScene(bigState.player.name, () => {
   FirstLevel.loadScene(bigState.player, (firstLevelResult) => {
       // do something else
       services.savePlayer(firstLevelResult);
   })
)}
Run Code Online (Sandbox Code Playgroud)

更多信息请访问https://corepox.net/devlog/unity-pattern:-stateless-scenes

  • 我喜欢它,但是你应该添加一个这个逻辑的生命周期的例子,我花了一些时间来理解如何在实践中实现它 (3认同)