Jump to content
Unity Insider Forum

UnloadSceneAsynchron


Kojote

Recommended Posts

Grüße! :)


Ich wollte bei meinem Spiel gerne die Möglichkeit einräumen das Intro zu überspringen. Nun besteht mein Intro aus zwei Teilen bzw. zwei Scenen. So lange der erste Teil läuft wird asynchron der zweite Teil des intros geleaden. So, nun wollte ich, wenn ESC gedrückt wird, dass erste Level laden.

Idee war folgende:

private void Update() {
        if(Input.GetKey(KeyCode.Escape) & escSicherung == false) {
            escSicherung = true;
            StopAllCoroutines();
            SceneManager.UnloadSceneAsync("Hauptspiel Intro Teil 2");
            MOTR_System_Ladebildschirm.LoadScreen(4);
        }
    }

So, wenn ich ESC drücke wird auch der Ladebildschirm aufgerufen und im Editor gibt es nun zwei Scenen die geladen werden. Problem ist, der zweite Teil des Intros wird nicht entladen und der Slider der den Ladezyklus für das erste Level anzeigt, macht nix, sprich das Level wird nicht geladen. Fehler kommen keine, aber anscheinend blockiert hier was.

Könnt ihr mir hier weiter helfen?

Grüße von Kojote

Link to post
Share on other sites

Wenn du eine Szene asynchron lädst, dann gibt dir die LoadSceneAsync-Funktion eine AsyncOperation zurück. Das Ding hat die Einstellung "allowSceneActivation". Wenn du die auf false stellst, sollte die geladene Szene warten, bis du sie aktivierst. Ich denke, das kannst du nutzen, um im Abbruch-Fall die komplette Notbremse zu ziehen.

Link to post
Share on other sites

Hi,

wie wird das neue Level geladen? Ich nehme an, es gibt eine Szene mit dem Ladebildschirm und während dieser wird das eigentliche Level asynchron geladen?

Dann muss die Szene mit dem Ladebildschirm vermutlich synchron geladen werden oder zumindest sobald sie geladen ist, auch als aktive Szene markiert werden (SceneManager.SetActiveScene). Wobei das natürlich auch automatisch passieren sollte, sobald nur noch 1 Szene geladen ist (sprich das Intro entladen wurde).

Intressant dürfte auch der Parameter LoadSceneMode von LoadScene sein. Dort kannst du angeben dass du eine neue Szene laden willst (die mit dem Ladebildschirm) und das die einzige sein soll (LoadSceneMode.Single). Dann werden automatisch alle anderen entladen.

Link to post
Share on other sites

Also zum Wechsel der Scenen nutze ich diese Klasse:
 

using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class MOTR_System_Ladebildschirm : MonoBehaviour {

    [Header("Instanz")]
    private static MOTR_System_Ladebildschirm instance;

    [Header("GameObject")]
    public GameObject ladebildschirm;
    public GameObject slider;

    [Header("Slider")]
    private Slider sliderAnzeige;

    [Header("AsyncOperation")]
    private AsyncOperation async;

    [Header("Color")]
    private Color tmp;

    [Header("Sprites")]
    public Sprite[] sprites;

    [Header("Image")]
    public Image loadingHintergrundbild;


    private void Awake() {
        instance = this;
        int index = Random.Range(0, sprites.Length);
        loadingHintergrundbild.sprite = sprites[index];
        sliderAnzeige = slider.GetComponent<Slider>();
    }

    public static void LoadScreen(int scene) {
        Cursor.visible = false;
        instance.StartCoroutine(instance.LoadingScreen(scene));
        instance.StartCoroutine(instance.Anzeige());
    }

    public static void LoadScreen(string scene) {
        Cursor.visible = false;
        instance.StartCoroutine(instance.LoadingScreen(scene));
        instance.StartCoroutine(instance.Anzeige());
    }

    private IEnumerator Anzeige() {
        // Ladebildschirm aktivieren
        ladebildschirm.SetActive(true);

        // Bildschirm ausblenden
        MOTR_System_Bildschirmblenden.BildschirmAusblendenCallback();
        while (MOTR_System_Bildschirmblenden.callbackCheck == false) {
            yield return new WaitForFixedUpdate(); // Warten bis das Ausblenden fertig ist
        }
        MOTR_System_Bildschirmblenden.callbackCheck = true; // Auf Startwert zuruecksetzen

        // Hintergrundbild einblenden
        tmp = loadingHintergrundbild.color;
        while (loadingHintergrundbild.color.a < 0.98f) {
            tmp.a += 0.025f;
            loadingHintergrundbild.color = tmp;
            yield return new WaitForFixedUpdate();
        }
        tmp.a = 1f;
        loadingHintergrundbild.color = tmp;
        slider.SetActive(true);

        // Kurze Wartezeit
        yield return new WaitForSeconds(2);

        // Warten bis der Ladezyklus beendet ist
        do {
            yield return new WaitForFixedUpdate();
        } while (async.progress < 0.9f);

        // Hintergrundbild ausblenden
        slider.SetActive(false);
        tmp = loadingHintergrundbild.color;
        while (loadingHintergrundbild.color.a > 0.05f) {
            tmp.a -= 0.025f;
            loadingHintergrundbild.color = tmp;
            yield return new WaitForFixedUpdate();
        }
        tmp.a = 0f;
        loadingHintergrundbild.color = tmp;
        async.allowSceneActivation = true;
    }

    private IEnumerator LoadingScreen(int scene) {
        yield return new WaitForSeconds(1.5f);
        ladebildschirm.SetActive(true);
        async = SceneManager.LoadSceneAsync(scene, LoadSceneMode.Additive);
        async.allowSceneActivation = false;
        do {
            float progress = Mathf.Clamp01(async.progress / 0.9f);
            sliderAnzeige.value = progress;
            yield return null;
        } while (!async.isDone);
    }

    private IEnumerator LoadingScreen(string scene) {
        yield return new WaitForSeconds(1.5f);
        ladebildschirm.SetActive(true);
        async = SceneManager.LoadSceneAsync(scene, LoadSceneMode.Additive);
        async.allowSceneActivation = false;
        do {
            float progress = Mathf.Clamp01(async.progress / 0.9f);
            sliderAnzeige.value = progress;
            yield return null;
        } while (!async.isDone);
    }
}

Damit lade ich die Scenen und das funktionierte bisher sehr gut. LoadSceneMode habe ich nun hinzugefügt.

Im Intro wird asynchron der zweite Teil des Intros geladen.

    private IEnumerator AsynchronesLadenIntroTeil2() {
        asyncIntroTeil2 = SceneManager.LoadSceneAsync("Hauptspiel Intro Teil 2", LoadSceneMode.Additive);
        asyncIntroTeil2.allowSceneActivation = false;
        while (asyncIntroTeil2.progress < 0.9f) {
            yield return null;
        }
    }

Hier nutze ich allowSceneActivation erst, wenn der erste Teil des Intros beendet ist.

Nun habe ich das Script des ersten Intros folgendermaßen erweitert:

    private void Update() {
        if(Input.GetKey(KeyCode.Escape) & escSicherung == false) {
            escSicherung = true;
            StopAllCoroutines();
            MOTR_System_Ladebildschirm.LoadScreen(4);
            StartCoroutine("WindLautstaerkeOutCoroutine");
            StartCoroutine("MusikLautstaerkeOutCoroutine");           
        }
    }

Ich stoppe alle Coroutinen, die für das Intrp zuständig sind, Lade das erste Level und blende Wind und Musik aus.

Durch LoadSceneMode passiert nun folgendes. Musik und Wind klingen aus und der Ladebildschirm wird geladen, das erste Spiellevel wird geladen, der Ladebildschirm ausgeblendet und alles bleibt schwarz. Keine Fehlermeldung und nichts. Level 1 wurde zwar geladen (async.allowSceneActivation ist true und progress bei 0.9), jedoch befindet man sich immer noch in der Intro Scene 1. Er starte einfach nicht Level 1.

EDIT: Habs nun mal so versucht:

using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class MOTR_System_Ladebildschirm : MonoBehaviour {

    [Header("Instanz")]
    public static MOTR_System_Ladebildschirm instance;

    [Header("GameObject")]
    public GameObject ladebildschirm;
    public GameObject slider;

    [Header("Slider")]
    private Slider sliderAnzeige;

    [Header("AsyncOperation")]
    public AsyncOperation async;

    [Header("Color")]
    private Color tmp;

    [Header("Sprites")]
    public Sprite[] sprites;

    [Header("Image")]
    public Image loadingHintergrundbild;


    private void Awake() {
        instance = this;
        int index = Random.Range(0, sprites.Length);
        loadingHintergrundbild.sprite = sprites[index];
        sliderAnzeige = slider.GetComponent<Slider>();
    }

    public static void LoadScreen(int scene) {
        Cursor.visible = false;
        instance.StartCoroutine(instance.LoadingScreen(scene));
        instance.StartCoroutine(instance.Anzeige());
    }

    public static void LoadScreen(string scene) {
        Cursor.visible = false;
        instance.StartCoroutine(instance.LoadingScreen(scene));
        instance.StartCoroutine(instance.Anzeige());
        //SceneManager.SetActiveScene(SceneManager.GetSceneByName(scene));
    }

    public static void LoadScreenOffScreen(int scene) {
        Cursor.visible = false;
        instance.StartCoroutine(instance.LoadingScreenOffScreen(scene));
    }

    private IEnumerator Anzeige() {
        // Ladebildschirm aktivieren
        ladebildschirm.SetActive(true);

        // Bildschirm ausblenden
        MOTR_System_Bildschirmblenden.BildschirmAusblendenCallback();
        while (MOTR_System_Bildschirmblenden.callbackCheck == false) {
            yield return new WaitForFixedUpdate(); // Warten bis das Ausblenden fertig ist
        }
        MOTR_System_Bildschirmblenden.callbackCheck = true; // Auf Startwert zuruecksetzen

        // Hintergrundbild einblenden
        tmp = loadingHintergrundbild.color;
        while (loadingHintergrundbild.color.a < 0.98f) {
            tmp.a += 0.025f;
            loadingHintergrundbild.color = tmp;
            yield return new WaitForFixedUpdate();
        }
        tmp.a = 1f;
        loadingHintergrundbild.color = tmp;
        slider.SetActive(true);

        // Kurze Wartezeit
        yield return new WaitForSeconds(2);

        // Warten bis der Ladezyklus beendet ist
        do {
            yield return new WaitForFixedUpdate();
        } while (async.progress < 0.9f);

        // Hintergrundbild ausblenden
        slider.SetActive(false);
        tmp = loadingHintergrundbild.color;
        while (loadingHintergrundbild.color.a > 0.05f) {
            tmp.a -= 0.025f;
            loadingHintergrundbild.color = tmp;
            yield return new WaitForFixedUpdate();
        }
        tmp.a = 0f;
        loadingHintergrundbild.color = tmp;

        // Scene freigeben
        async.allowSceneActivation = true;
    }

    private IEnumerator LoadingScreen(int scene) {
        yield return new WaitForSeconds(1.5f);
        ladebildschirm.SetActive(true);
        async = SceneManager.LoadSceneAsync(scene, LoadSceneMode.Additive);
        async.allowSceneActivation = false;
        do {
            float progress = Mathf.Clamp01(async.progress / 0.9f);
            sliderAnzeige.value = progress;
            yield return null;
        } while (!async.isDone);
    }

    private IEnumerator LoadingScreen(string scene) {
        yield return new WaitForSeconds(1.5f);
        ladebildschirm.SetActive(true);
        async = SceneManager.LoadSceneAsync(scene, LoadSceneMode.Additive);
        async.allowSceneActivation = false;
        do {
            float progress = Mathf.Clamp01(async.progress / 0.9f);
            sliderAnzeige.value = progress;
            yield return null;
        } while (!async.isDone);
    }

    private IEnumerator LoadingScreenOffScreen(int scene) {
        yield return new WaitForSeconds(1.5f);
        async = SceneManager.LoadSceneAsync(scene, LoadSceneMode.Additive);
        async.allowSceneActivation = false;
        do {
            yield return null;
        } while (!async.isDone);
    }
}

Alles was Loading Scene ist, habe ich aus der Intro Script Datei entfernt und Rufe nun für den normalen Introverlauf auf:

MOTR_System_Ladebildschirm.LoadScreenOffScreen(3);

Und wenn Intro Teil 1 fertig ist:

MOTR_System_Ladebildschirm.instance.async.allowSceneActivation = true;

Bei ESC, also Abbruch rufe ich nun das auf:
 

MOTR_System_Ladebildschirm.LoadScreen("Hauptspiel Level 1");

Problem sitzt nun hier:
 

    public static void LoadScreen(string scene) {
        Cursor.visible = false;
        instance.StartCoroutine(instance.LoadingScreen(scene));
        instance.StartCoroutine(instance.Anzeige());
        //SceneManager.SetActiveScene(SceneManager.GetSceneByName(scene));
    }

Er meint bei SetActiveScene, er würde den Scenennamen nicht kennen und bricht ab.

Link to post
Share on other sites
  • 2 weeks later...

Sorry, für die späte Antwort ;)

Das Problem ist, dass SceneManager.GetSceneByName nur geladene Szenen findet. In LoadScreen() startest du aber 2 Coroutinen gleichzeitig und versuchst auch die Szene bereits aktiv zu setzen, während sie noch am Laden ist.

Ich würde dir raten, dort nur die Coroutine Anzeige() zu starten. Das LoadingScreen() würde ich dann innerhalb der Methode Anzeige() anstelle von

// Warten bis der Ladezyklus beendet ist
do {
    yield return new WaitForFixedUpdate();
} while (async.progress < 0.9f);

aufrufen, mit:

yield return StartCoroutine(LoadingScreen(scene));

Auf diese Weise wartet Unity ähnlich wie bei yield return new WaitForSeconds() nicht eine bestimmte Anzahl Sekunden, sondern so lange bis die andere Coroutine fertig ist.

 

Dann anstelle von

// Scene freigeben
async.allowSceneActivation = true;

einfach die Szene aktivieren, da sie ja nun geladen wurde:

// Scene aktivieren
SceneManager.SetActiveScene(SceneManager.GetSceneByName(scene));

 

Ich hoffe das hilft, falls du das Problem nicht bereits selber lösen konntest :)

Link to post
Share on other sites

Archived

This topic is now archived and is closed to further replies.

×
×
  • Create New...