Unity - 在场景之间传递数据

时间:2015-08-31 08:13:57

标签: c# unity3d 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);
}

场景二:

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

但屏幕上没有显示,也没有错误。

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

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

有什么建议吗?

5 个答案:

答案 0 :(得分:28)

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

<强> 1。使用static关键字。

如果要传递给下一个场景的变量不是某个组件,请使用此方法,是否继承自MonoBehaviour并且不是GameObject,然后将变量设为{{1 }}。

内置原始数据类型,例如staticintboolstringfloat。所有这些变量都可以成为double变量。

可以标记为静态的内置基元数据类型的示例

static

这些应该没有问题。

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

static int counter = 0;
static bool enableAudio = 0;
static float timer = 100;

然后

public class MyTestScriptNoMonoBehaviour
{

}

请注意,该类不会从MonoBehaviour继承。这应该有用。

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

ObjectComponentGameObject继承的任何内容都

1A 。任何继承自MonoBehaviour

的内容
static MyTestScriptNoMonoBehaviour testScriptNoMono;

void Start()
{
    testScriptNoMono = new MyTestScriptNoMonoBehaviour();
}

然后

public class MyTestScript : MonoBehaviour 
{

}

这将有效,因为它继承自MonoBehaviour

1B 。所有GameObject

static MyTestScript testScript;

void Start()
{
    testScript = gameObject.AddComponent<MyTestScript>();
} 

这将工作,因为它是GameObjectGameObject继承自Object

Unity即使使用static GameObject obj; void Start() { obj = new GameObject("My Object"); } 关键字声明它们也会销毁其Object

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

2.使用DontDestroyOnLoad功能

如果要保留或传递到下一个场景的数据继承自ObjectComponentGameObject,则只需使用此项。这解决了 1A 1B 中描述的问题。

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

static

您甚至可以将其与 1A 1B 中的void Awake() { DontDestroyOnLoad(transform.gameObject); } 关键字解决问题一起使用:

static

然后

public class MyTestScript : MonoBehaviour 
{

}

现在,当加载新场景时,static MyTestScript testScript; void Awake() { DontDestroyOnLoad(transform.gameObject); } void Start() { testScript = gameObject.AddComponent<MyTestScript>(); } 变量将被保留。

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

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

Thare是两种保存方式:

3A 。使用PlayerPrefs API。

如果只保存少量变量,请使用。让我们说球员得分:

testScript

我们想要保存playerScore:

将分数保存在int playerScore = 80; 功能

OnDisable

将其加载到void OnDisable() { PlayerPrefs.SetInt("score", playerScore); } 函数

OnEnable

3B 。将数据转换为json,xml或binaray形式,然后使用其中一个C#文件API(例如File.WriteAllBytesFile.ReadAllBytes进行保存,以保存和加载文件。

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

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

要保存的数据示例:

void OnEnable()
{
    playerScore  =  PlayerPrefs.GetInt("score");
}

抓住[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; } 类,它是File.WriteAllBytesFile.ReadAllBytes的包装,可以使this帖子更容易保存数据。

创建新实例:

DataSaver

将数据从PlayerInfo保存到名为“players”的文件:

PlayerInfo saveData = new PlayerInfo();
saveData.life = 99;
saveData.highScore = 40;

从名为“players”的文件中加载数据:

DataSaver.saveData(saveData, "players");

答案 1 :(得分:23)

还有另一种方式:

ScriptableObject

ScriptableObject基本上是数据容器,但也可以实现自己的逻辑。它们仅在Assets中像预制件一样“存在”。它们不能不能用于永久存储,但是可以在一个会话期间存储数据,因此可以用于在场景之间共享数据。 ..和-我也经常需要的-在场景和AnimatorController之间!

脚本

首先,您需要一个类似于MonoBehaviour的脚本。 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;

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

创建实例

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

var scriptableObject = ScriptableObject.CreateInstance<ExampleScriptableObject>();

或使用上面的示例中的[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]);
        }
    }
}

消费数据

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

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

持久性

如前所述,ScriptableObject本身的更改仅在Unity编辑器中真正持久存在。

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

因此,我经常需要将会话持久性与一些FileIO结合使用,以便在会话开始时(或在需要时)从硬盘驱动器加载和反序列化一次值,并在会话结束时将它们序列化并存储到文件中({{1 }})或需要的时候。

答案 2 :(得分:8)

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

DontDestroyOnLoad (transform.gameObject);

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

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

答案 3 :(得分:2)

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

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 
}

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

MyBigGameServices services ...
MyBigGameState bigState ...

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

更多信息,位于https://corepox.net/devlog/unity-pattern:-stateless-scenes

答案 4 :(得分:1)

有各种各样的方法,但是假设您只需要传递一些基本数据,则可以创建GameController的singelton实例并使用该类来存储数据。

,当然,DontDestroyOnLoad是必须的!

public class GameControl : MonoBehaviour
{
    //Static reference
public static GameControl control;

//Data to persist
public float health;
public float experience;

void Awake()
{
    //Let the gameobject persist over the scenes
    DontDestroyOnLoad(gameObject);
    //Check if the control instance is null
    if (control == null)
    {
        //This instance becomes the single instance available
        control = this;
    }
    //Otherwise check if the control instance is not this one
    else if (control != this)
    {
        //In case there is a different instance destroy this one.
        Destroy(gameObject);
    }
}

这里是full tutorial,还有其他示例。