我尝试了以下操作:
场景之一:
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设备上运行)。
有什么建议吗?
#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
。 内置原始数据类型,例如
int
,bool
,string
,float
,double
。所有这些变量都可以作为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
。 不能标记为静态的对象示例:
从
Object
,Component
或GameObject
继承的任何内容均不起作用。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");
}
这将不起作用,因为它是
GameObject
和GameObject
继承自Object
。 即使使用
Object
关键字进行声明,Unity始终会销毁其static
。 有关变通办法,请参见#2。
2.使用
DontDestroyOnLoad
函数。仅当要保留或传递到下一个场景的数据继承自
Object
,Component
或是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.WriteAllBytes
和File.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.WriteAllBytes
和File.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);
}
}
这里是完整的教程,还有其他示例。
评论
到底是什么问题?是第二个场景没有加载吗?还是显示的分数不正确?没有加载下一个场景但没有我的分数,这是存储数据的正确方法吗?是否像android中的sharedPreference
old_score的值是什么?您可以通过添加Debug.Log(old_score);来查看它。在您的Start()方法中。
哦,我很笨,我没有在我的脚本上附加文本画布来显示分数