Jak przekazywać dane między scenami w Unity

Jak mogę przekazać wartość wyniku z jednej sceny do drugiej?

Próbowałem:

Scena pierwsza:

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

Scena druga:

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

Ale nic nie wyświetla się na ekranie i nie ma błędu.

Czy to prawidłowy sposób przekazywania danych ?

Używam Unity 5 free edition, rozwijać grę dla Gear VR (co oznacza, że gra będzie działać na urządzeniach z Androidem).

Jakieś sugestie?
 54
Author: derHugo, 2015-08-31

5 answers

Poza playerPrefs innym brudnym sposobem jest zachowanie obiektu podczas ładowania poziomu przez wywołanie na nim DontDestroyOnLoad.

DontDestroyOnLoad (transform.gameObject);

Każdy skrypt dołączony do obiektu gry przetrwa, podobnie jak zmienne w skrypcie. Funkcja Dontdestroyonload jest zwykle używana do zachowania całego obiektu GameObject, łącznie z dołączonymi do niego komponentami i wszystkimi obiektami potomnymi, które posiada w hierarchii.

Można utworzyć pusty obiekt GameObject i umieścić tylko skrypt zawierający zmienne, które chcesz zachować na nim.

 20
Author: Isj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-31 09:57:02

3 sposoby na to, ale rozwiązanie zależy od rodzaju danych, które chcesz przekazać między scenami. Komponenty / skrypty i Obiekty GameObjects są niszczone podczas ładowania nowej sceny, a nawet gdy są oznaczone jako static.

1. Użyj słowa kluczowego static.

Użyj tej metody, jeśli zmienna przekazywana do następnej sceny nie jest składową, nie Nie dziedziczy z MonoBehaviour and is not a GameObject then make the variable to be static.

Wbudowane prymitywne typy danych, takie jak int, bool, string, float, double. Wszystkie te zmienne można utworzyć zmienną static.

Przykład wbudowanych prymitywnych typów danych, które mogą być oznaczone jako statyczne:

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

Powinny działać bez problemów.


Przykład obiektów, które mogą być oznaczone jako statyczne:

public class MyTestScriptNoMonoBehaviour
{

}

Then

static MyTestScriptNoMonoBehaviour testScriptNoMono;

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

Zauważ, że klasa nie dziedziczy z MonoBehaviour. To powinno zadziałać.


Przykład obiektów, które nie mogą być oznaczone jako statyczne:

Wszystko, co dziedziczy z Object, Component lub GameObject czy Nie zadziała.

1A .Wszystko, co dziedziczy po MonoBehaviour

public class MyTestScript : MonoBehaviour 
{

}

Then

static MyTestScript testScript;

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

To będzie a nie działać, ponieważ dziedziczy z MonoBehaviour.

1B .Wszystkie GameObject:

static GameObject obj;

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

To będzie Nie też działać, ponieważ jest to GameObject oraz GameObject dziedziczenie z Object.

Jedność zawsze zniszczy jej Object nawet jeśli są zadeklarowane za pomocą słowa kluczowego static.

Zobacz #2 na obejście.


2.Użyj DontDestroyOnLoad function .

Musisz użyć tego tylko wtedy, gdy dane do przechowywania lub przekazywania do Następna scena dziedziczy z Object, Component lub jest GameObject. Rozwiązuje to problem opisany w 1A i 1B .

Możesz go użyć, aby ten obiekt gry nie niszczył, gdy scena się rozładuje:]}
void Awake() 
{
    DontDestroyOnLoad(transform.gameObject);
}

Możesz go nawet użyć za pomocą static słowa kluczowego Rozwiąż problem z 1A i 1B :

public class MyTestScript : MonoBehaviour 
{

}

Then

static MyTestScript testScript;

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

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

Zmienna testScript zostanie zachowana, gdy nowa scena mnóstwo.

3.Zapisz w pamięci lokalnej, a następnie załaduj podczas następnej sceny.

Ta metoda powinna być używana, gdy są to DANE GRY, które muszą być zachowane, gdy gra jest zamknięta i ponownie otwarta. Przykładem tego jest wysoki wynik gracza, ustawienia gry, takie jak głośność muzyki, lokalizacje obiektów, dane profilu joysticka i tak dalej.

Thare są dwa sposoby, aby to zapisać:

3A .Użyj PlayerPrefs API.

Użyj, jeśli masz tylko kilka zmienne do zapisania. Powiedzmy wynik gracza:

int playerScore = 80;

I chcemy zapisać wynik gracza:

Zapisz wynik w funkcji OnDisable

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

Załaduj go w funkcji OnEnable

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

3B .Serializuj dane do postaci json, xml lub binaray, a następnie zapisz za pomocą jednego z API plików C#, takich jak File.WriteAllBytes oraz File.ReadAllBytes aby zapisać i załadować pliki.

Użyj tej metody, jeśli istnieje wiele zmiennych do zapisania.

Generale, potrzebujesz aby utworzyć klasę, która nie dziedziczy z MonoBehaviour. Ta klasa powinna być używana do przechowywania danych gry, aby można ją było łatwo serializować lub de-serializować.

Przykład danych do zapisania:

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

Grab the DataSaver class which is a wrapper over File.WriteAllBytes oraz File.ReadAllBytes to ułatwia zapisywanie danych z tego posta .

Utwórz nową instancję:

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

Zapisz dane z PlayerInfo do pliku o nazwie "gracze":

DataSaver.saveData(saveData, "players");

załaduj dane z pliku o nazwie "Odtwarzacze":

PlayerInfo loadedData = DataSaver.loadData<PlayerInfo>("players");
 111
Author: Programmer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-14 10:52:41

Jest inny sposób:

ScriptableObject

ScriptableObjects są w zasadzie kontenerami danych, ale mogą również implementować własną logikę. "Żyją" tylko w Assets Jak prefabrykaty. Mogą Nie być używane do przechowywania danych na stałe, ale przechowują dane podczas jednej sesji, więc mogą być używane do dzielenia danych i odniesień między scenami ... i-coś, czego często potrzebowałem-między scenami i AnimatorController!

Skrypt

Pierwszy potrzebujesz skryptu podobnego do MonoBehaviour s. prosty przykład ScriptableObject może wyglądać jak

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

Tworzenie Instancji

Można tworzyć instancje ScriptableObject za pomocą skryptu

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

Lub aby ułatwić korzystanie z [CreateAssetMenu] Jak pokazano w powyższym przykładzie.

Ponieważ stworzona instancja ScriptabeObject żyje w Assets, nie jest powiązana ze sceną i dlatego może być odwołana wszędzie!

To, gdy chcesz udostępnić dane między dwoma scenami lub również np. scena i AnimatorController wszystko, co musisz zrobić, to odwołać się do tej instancji ScriptableObject w obu.

Wypełnij Dane

Często używam np. jednego komponentu do wypełniania danych jak

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

Zużyć Dane

Więc po zapisaniu i zapisaniu wymaganych danych w tej instancji ExampleScriptableObject każda inna klasa w dowolnej scenie lub AnimatorController lub również inne ScriptableObject może odczytać te dane w ten sam sposób:

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

Trwałość

Jak powiedział zmiany w samym ScriptableObject są tylko w edytor Unity bardzo wytrwały.

W kompilacji są trwałe tylko podczas tej samej sesji.

Dlatego też często łączę trwałość sesji z jakimś plikiem do ładowania i deserializacji wartości po rozpoczęciu sesji (lub w razie potrzeby) z dysku twardego i serializuję je i zapisuję do pliku po zakończeniu sesji (OnApplicationQuit) lub w razie potrzeby.

(to nie będzie działać z referencjami oczywiście.)

 27
Author: derHugo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-12-27 17:23:38

Używam podejścia funkcjonalnego, które nazywam scenami Bezpaństwowymi.

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 
}

Możesz zachować stan Globalny w zakresie wywołującego , dzięki czemu Stany wejść i wyjść sceny mogą być zminimalizowane (ułatwia testowanie). Aby go użyć, możesz użyć funkcji anonimowych: -

MyBigGameServices services ...
MyBigGameState bigState ...

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

Więcej informacji na https://corepox.net/devlog/unity-pattern:-stateless-scenes

 2
Author: Tom Larkworthy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-23 22:25:55

Istnieją różne sposoby, ale zakładając, że musisz przekazać tylko niektóre podstawowe dane, możesz utworzyć pojedynczą instancję kontrolera GameController i użyć tej klasy do przechowywania danych.

I oczywiście DontDestroyOnLoad jest obowiązkowy!

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

Oto pełny samouczek z innym przykładem.

 1
Author: Max_Power89,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-30 18:35:52