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

我尝试了以下操作:

场景之一:

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设备上运行)。

有什么建议吗?

评论

到底是什么问题?是第二个场景没有加载吗?还是显示的分数不正确?

没有加载下一个场景但没有我的分数,这是存储数据的正确方法吗?是否像android中的sharedPreference

old_score的值是什么?您可以通过添加Debug.Log(old_score);来查看它。在您的Start()方法中。

哦,我很笨,我没有在我的脚本上附加文本画布来显示分数

#1 楼

除了playerPrefs之外,另一种肮脏的方法是通过在其上调用DontDestroyOnLoad在对象上保存对象。


DontDestroyOnLoad函数通常用于保留整个GameObject,包括附加到它的组件以及它在层次结构中具有的任何子对象。

您可以创建一个空白GameObject,然后仅将包含要保留的变量的脚本放在其上。

评论


还有一个不错的选择-“信息对象”answers.unity3d.com/questions/532656/…

– Jviaches
16年7月12日在22:08



#2 楼

有3种方法可以执行此操作,但是解决方案取决于要在场景之间传递的数据类型。加载新场景时,甚至将其标记为static时,组件/脚本和GameObject也会被破坏。

1。使用static关键字。如果传递给下一个场景的变量不是组件,不是从MonoBehaviour继承且不是GameObject,则使用此方法,然后将变量设为static

内置原始数据类型,例如intboolstringfloatdouble。所有这些变量都可以作为static变量。

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

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


这些应该可以正常工作。


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

public class MyTestScriptNoMonoBehaviour
{

}


然后

static MyTestScriptNoMonoBehaviour testScriptNoMono;

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


请注意,该类不继承自MonoBehaviour


不能标记为静态的对象示例:

ObjectComponentGameObject继承的任何内容均不起作用。

1A。从MonoBehaviour继承的任何内容

public class MyTestScript : MonoBehaviour 
{

}


然后

static MyTestScript testScript;

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


这不会工作,因为它继承自MonoBehaviour

1B。all GameObject

static GameObject obj;

void Start()
{
    obj = new GameObject("My Object");
}  


这将不起作用,因为它是GameObjectGameObject继承自Object

即使使用Object关键字进行声明,Unity始终会销毁其static

有关变通办法,请参见#2。


2.使用DontDestroyOnLoad函数。

仅当要保留或传递到下一个场景的数据继承自ObjectComponent或是GameObject。这解决了1A和1B中描述的问题。

您可以使用它来使此GameObject在场景卸载时不被破坏:

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



甚至可以将其与static关键字一起使用来解决1A和1B中的问题:

public class MyTestScript : MonoBehaviour 
{

}


然后

static MyTestScript testScript;

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

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


现在,在加载新场景时将保留testScript变量。

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

当游戏数据必须关闭并重新打开时必须保留时,应使用此方法。例如玩家高分,游戏设置(例如音乐音量,对象位置,操纵杆配置文件数据等)。

有两种保存方法:

3A。请使用PlayerPrefs API。

如果要保存的变量很少,请使用。假设玩家得分:

int playerScore = 80;


我们想保存玩家得分:

将得分保存在OnDisable函数中

void OnDisable()
{
    PlayerPrefs.SetInt("score", playerScore);
}


将其加载到OnEnable函数
void OnEnable()
{
    playerScore  =  PlayerPrefs.GetInt("score");
}


3B中。将数据序列化为json,xml或binaray格式,然后使用一个保存C#文件API(例如File.WriteAllBytesFile.ReadAllBytes)用于保存和加载文件。

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

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

要保存的数据示例:

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


获取DataSaver类,该类是对File.WriteAllBytesFile.ReadAllBytes的包装,使保存数据更加容易发布。

创建新实例:

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


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

DataSaver.saveData(saveData, "players");


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

PlayerInfo loadedData = DataSaver.loadData<PlayerInfo>("players");


评论


这是比公认的答案更全面的答案。谢谢!

–克里斯·布莱克威尔
19-2-27在23:29

嗨@Programmer,这个#3B方法可以在WebGL游戏中使用吗?

– Sakuna Madushanka
1月21日14:56

我经常使用第四种方法。看我的答案

– derHugo
2月26日7:17

#3 楼

还有另一种方法:
ScriptableObject基本上是数据容器,但也可以实现自己的逻辑。它们仅像预制件一样“存在”于ScriptableObject中。它们不能用于永久存储数据,但是它们可以在一个会话中存储数据,因此可以用于在场景之间共享数据和引用,以及-我也经常需要在场景和Assets之间共享数据。
脚本
首先,您需要一个类似于AnimatorController的脚本。 MonoBehaviour的一个简单示例如下所示:
// 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;
}

创建实例
您可以通过脚本
var scriptableObject = ScriptableObject.CreateInstance<ExampleScriptableObject>();

来创建ScriptableObject的实例,或者使使用ScriptableObject变得更容易如上面的示例所示。
由于此创建的[CreateAssetMenu]实例位于ScriptabeObject中,因此它没有绑定到场景,因此可以在任何地方引用!
要在两个场景之间共享数据或也例如
填充数据
我经常使用例如,将场景和一个Assets两者都引用。一个组件可以填充数据,例如
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;
    }
}

消费数据
因此,在将所需数据写入并存储到此AnimatorController实例中之后,任何Scene或ScriptableObject或其他ExampleScriptableObject的其他所有类都可以以相同的方式读取此数据:
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);
    }
}


持久性
如前所述,AnimatorController本身的更改仅在Unity编辑器中才真正持久。
构建它们仅在同一会话中持久。
因此,出于需要,我经常将会话持久性与一些FileIO结合使用,以便在会话开始时(或在需要时)从硬盘驱动器加载和反序列化值,并对其进行序列化和存储一次在会话末尾(ScriptableObject)或需要时将其保存到文件。
(这当然不适用于引用。)

评论


好的解决方案,没有任何附加功能。该代码也更适合于单元测试。感谢您提供详细的答案。

– picolino
2月26日14:17

#4 楼

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

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

评论


我喜欢它,但是您应该添加此逻辑的生命周期示例,我花了一些时间来了解如何在实践中实现它

– Haelle
2月3日12:31

#5 楼

有多种方法,但是假设您只需要传递一些基本数据,则可以创建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);
    }
}

这里是完整的教程,还有其他示例。