Jump to content
Unity Insider Forum

Coroutine lässt Unity crashen


MustafGames

Recommended Posts

Grüße,

bin gerade verwirrt, wenn ich einen Button drücke wird, Smelting ausgeführt (was ein Lagerfeuer-Effekt einschaltet und dann die Funktion des Feuers aktiviert).

Sobald ich den Button drücke hängt sich Unity auf.

Es wird für jedes Rezept geprüft ob die benötigten Dinge vorhanden sind, dann werden die benötigten Items entfernt, es wird gewartet (verarbeitet) und dann erhält man das Endprodukt.

    private bool smelting;
    public void Smelting (DB_RecipeList recipes) {
        smelting = !smelting;
        transform.Find("Fire").gameObject.SetActive(smelting);

        StartCoroutine(UseRecipeSmelting(recipes));
    }

    private IEnumerator UseRecipeSmelting (DB_RecipeList recipes) {
        while (true) {
            UI_Inventory inv = transform.GetComponent<UI_Inventory>();
            foreach (DB_Recipe recipe in recipes.recipes) {
                if (inv.HasItems(recipe.inputItems)) {
                    for (int i = 0; i < recipe.inputItems.Count; i++) {
                        inv.RemoveItem(recipe.inputItems[i]);
                    }
                    yield return new WaitForSeconds(recipe.outputItem.baseItem.value.y);
                    inv.AddItem(recipe.outputItem);
                }
            }
        }
    }

Vielen Dank,

MfG Mustaf

Link zu diesem Kommentar
Auf anderen Seiten teilen

while(true) ist prinzipiell okay, da yield return quasi als break fungieren kann. Ob man das jetzt stilistisch akzeptabel findet, kann jeder für sich entscheiden, aber die Schleife kann auf jeden Fall abbrechen, solange eine yield-Anweisung darin steckt.

@MustafGames Dein Problem ist, dass du die Coroutine startest und recipes.recipes leer ist. Das heißt, dass die foreach-Schleife kein Mal durchläuft und entsprechend das yield return, mit dem das while(true) pausiert werden würde, niemals drankommt.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Deine ForEach-Schleife wird Durchlaufen und leert dabei das Inventar (also die Zutaten des Rezeptes). Sobald keine Rezeptzutaten mehr im Inventar sind,  wird das "yield return" nicht mehr erreicht und die Coroutine läuft ohne Unterbrechnung in der while true-Schleife und damit blockierst du den Unity-Mainthread...

Link zu diesem Kommentar
Auf anderen Seiten teilen

Du hängst trotzdem in einer Endlosschleife, ich erkenne auch nicht den Sinn diese Schliefe nicht.
Wenn Recipes.recipes leer ist, erreichst du nie den yield.
Wenn im Inventar keine items sind, erreichst du nie den yield.
WaitForSeconds() wartet nur und macht dann weiter, beendet aber nie den Iterator.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Also soll ich while (true) entfernen und dann müsste es klappen?

 

Recipe.recipes ist außerdem nie leer, da es eine festgelegte Anzahl an Rezepten ist und diese sich eigentlich nicht ändert, es könnte höchstens sein, dass die Items im Inventar alle verbraucht werden.

 

Edit:

private IEnumerator UseRecipeSmelting (DB_RecipeList recipes) {
        UI_Inventory inv = transform.GetComponent<UI_Inventory>();
        while (true) {
            foreach (DB_Recipe recipe in recipes.recipes) {
                if (inv.HasItems(recipe.inputItems) && !inv.IsFull()) {
                    yield return new WaitForSeconds(recipe.outputItem.baseItem.value.y);
                    for (int i = 0; i < recipe.inputItems.Count; i++) {
                        inv.RemoveItem(recipe.inputItems[i]);
                    }
                    inv.AddItem(recipe.outputItem);
                } else {
                    yield break;
                }
            }
        }
    }

Klappt alles soweit, selbst wenn keine Items vorhanden sind.

Möchte jetzt nur noch etwas verfeinern, so das wenn Rezept Nummer 1 nicht möglich ist da keine Items im Inventar sind, soll es Rezept 2 probieren und so weiter.

Bei yield break; müsste es eigentlich in der ForEach Schleife das nächste Rezept auswählen oder von vorne beginnen ohne das ich den Knopf noch einmal drücken muss. (Mit dem Knopf schalte ich es, ein und aus)

Link zu diesem Kommentar
Auf anderen Seiten teilen

Es wird nur einmal aufgerufen, das ist dann wie das anzünden eines Lagerfeuers.

Möchte halt das wenn die Coroutine läuft und die Items fehlen, sie nicht abgebrochen wird, sondern immer wieder überprüft ob die Items da sind, jedoch für das nächste Rezept und dann im Kreislauf, den man kann ja während das Feuer läuft neue Items hinzugeben.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Wie würdest du das machen, das immer erstmal ein Rezept ausgeführt wird und wenn die benötigten Items nicht mehr vorhanden sind das nächste?

Habe da bisher 2 Ideen:

ForEach sagen das es zum nächsten springen soll, jedoch wenn es alle durch ist kann es nicht von vorne beginnen

oder einfach einen int der +1 gerechnet wird wenn das aktuelle Rezept nicht möglich ist, dann wird zum nächsten Rezept gesetzt und wenn es beim letzten der Liste angekommen ist soll es beim ersten wieder anfangen?

 

private IEnumerator UseRecipeSmelting (DB_RecipeList recipes) {
        int cur = 0;
        UI_Inventory inv = transform.GetComponent<UI_Inventory>();
        while (true) {
            DB_Recipe recipe = recipes.recipes[cur];
            if (inv.HasItems(recipe.inputItems) && !inv.IsFull()) {
                yield return new WaitForSeconds(recipe.outputItem.baseItem.value.y);
                if (inv.HasItems(recipe.inputItems)) {
                    for (int i = 0; i < recipe.inputItems.Count; i++) {
                        inv.RemoveItem(recipe.inputItems[i]);
                    }
                    inv.AddItem(recipe.outputItem);
                }
            } else {
                Debug.Log(cur + " " + (recipes.recipes.Count - 1) +"");
                if (cur < recipes.recipes.Count-1) {
                    cur++;
                } else {
                    cur = 0;
                }
            }
        }
    }

Variante 2, leider crasht Unity dabei, da es ja ewig weiter läuft bis man es manuell stoppt.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Für eine Art wende alle Rezepte an und produziere Items?
Diese Lösung produziert alle Items für alle Rezepte und läuft endlos. Soll er dies nur 1x machen, dann while-true entfernen:

private IEnumerator UseRecipeSmelting (DB_RecipeList recipes, UI_Inventory inv) {
  while (true) { // Couroutine läuft endlos und produziert jede Sekunde neue Items wenn neue Zutaten im Inventar auftauchen
    foreach (DB_Recipe recipe in recipes.recipes)  
    {   
       yield return StartCoroutine(ProcessRecipe(recipe, inv)); // Warte auf Verarbeitung Rezept
    }
    yield return new WaitForSeconds(1); // Wartezeit neuer Durchlauf (1 Sekunde)
  }
}
IEnumerator ProcessRecipe(DB_Recipe recipe, UI_Inventory inv) {
   bool recipeApplied = false;
   // Produziere Items des Rezeptes solange Inventar nicht voll ist und Zutaten vorhanden
   while (inv.HasItems(recipe.inputItems) && !inv.IsFull()) { 
      yield return new WaitForSeconds(recipe.outputItem.baseItem.value.y); // Wartezeit Itemproduktion "baseItem.value.y" Sekunden
      for (int i = 0; i < recipe.inputItems.Count; i++) {
           inv.RemoveItem(recipe.inputItems[i]);
      }
      inv.AddItem(recipe.outputItem);
      recipeApplied = true;
   } 
   if (recipeApplied) yield return new WaitForSeconds(0.5f); // Wartezeit zwischen den Rezepten wenn etwas produziert wurde (0.5 Sekunden)
}


 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Wenn ein Spieler 50 Lagerfeuer platziert, laufen auch 50 Courotine, oder/und das selbe Prinzip dann auf andere Verabeitungsobjekte (Schmelzofen z.B.) zutrifft, kann das am ende einiges an Overhead erzeugen. 

Eine Möglichkeit wäre, die Courotine zu beenden sobald nichts mehr hergestellt wir und wieder zu starten wenn neue Items in Inventar kommen.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Es soll ja trotzdem dauerhaft überprüfen ob eines der Rezepte möglich ist (Genügend Items im Inventar sind).

Das macht es ja und es wird der Rest erst aufgeführt wenn der Fall eintritt.

 

Zusätzlich schreibe ich noch eine Abfrage ob genügend Brennstoff vorhanden ist und erst dann soll es prüfen ob Rezepte möglich sind, wenn kein Brennstoff vorhanden ist geht das Feuer aus und muss manuell neugestartet werden.

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 29 Minuten schrieb MustafGames:

Es soll ja trotzdem dauerhaft überprüfen ob eines der Rezepte möglich ist (Genügend Items im Inventar sind).

@runner78 hat schon Recht. Wenn dein Feuer in diesem Moment feststellt, dass die Inventarsystem-Situation nichts zum vercraften hergibt, und sich die Situation nicht ändert, dann wird beim nächsten Mal auch nichts dabei herumkommen. Warum also dauerhaft checken, wenn du in dem Moment, wo sich das Inventar ändert, ein einzelnes Mal checken kannst?

Link zu diesem Kommentar
Auf anderen Seiten teilen

Zumindest könnte man einbauen, daß es sich beendet, wenn das Feuer aus ist oder der Spieler außer Reichweite des Feuers ist:

private IEnumerator UseRecipeSmelting (DB_RecipeList recipes, UI_Inventory inv, Fireplace fire) {
  while (fire.isBurning && fire.inRange) { // Nur solange verarbeiten wie das Feuer brennt und Spieler in Reichweite des Feuers
    foreach (DB_Recipe recipe in recipes.recipes)  
    {   
       yield return StartCoroutine(ProcessRecipe(recipe, inv)); // Warte auf Verarbeitung Rezept
    }
    yield return new WaitForSeconds(1); // Wartezeit neuer Durchlauf (1 Sekunde)
  }
}

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

        private bool smelting;
    public void Smelting (DB_RecipeList recipes) {
        UI_Inventory inv = transform.GetComponent<UI_Inventory>();

        smelting = !smelting;

        if (smelting) {
            if (inv.inv.HasFuel()) {
                transform.Find("Fire").GetComponent<ParticleSystem>().Play(true);
                StartCoroutine(inv.inv.UseFuel(inv));
                StartCoroutine(ProcessSmelting(recipes, inv));
            } else {
                transform.Find("Fire").GetComponent<ParticleSystem>().Stop(true);
            }
        } else {
            StopCoroutine(inv.inv.UseFuel(inv));
            StopCoroutine(ProcessSmelting(recipes, inv));
            transform.Find("Fire").GetComponent<ParticleSystem>().Stop(true);
        }
    }

    public IEnumerator ProcessSmelting (DB_RecipeList recipes, UI_Inventory inv) {
        while (true) {
            if (inv.inv.HasFuel()) {
                foreach (DB_Recipe recipe in recipes.recipes) {
                    yield return StartCoroutine(inv.inv.ProcessRecipe(recipe, inv));
                }
                yield return new WaitForSeconds(1);
            } else {
                transform.Find("Fire").GetComponent<ParticleSystem>().Stop(true);
                yield break;
            }
        }
    }
          
        public IEnumerator UseFuel (UI_Inventory inv) {
        while (true) {
            if (HasFuel()) {
                for (int i = 0; i < items.Count; i++) {
                    if (items[i].baseItem.specific_typ == DB_Item.Specific_Typ.Fuel) {
                        float x = items[i].baseItem.value.x;
                        inv.RemoveItem(new Item(items[i].baseItem, 1));
                        yield return new WaitForSeconds(x);
                    }
                }
            } else {
                yield break;
            }
        }
    }

    public IEnumerator ProcessRecipe (DB_Recipe recipe, UI_Inventory inv) {
        bool recipeApplied = false;
        while (HasItems(recipe.inputItems) && !IsFull() && HasFuel()) {
            yield return new WaitForSeconds(recipe.craftingtime);
            for (int i = 0; i < recipe.inputItems.Count; i++) {
                inv.RemoveItem(recipe.inputItems[i]);
            }
            inv.AddItem(recipe.outputItem);
            recipeApplied = true;
        }
        if (recipeApplied) yield return new WaitForSeconds(0.5f);
    }

So sieht es aktuell aus, wenn ich das Feuer anzünde, wird überprüft ob Brennstoff vorhanden ist, dann wird die Verbrennung gestartet (UseFuel), gleichzeitig wird das Verarbeiten gestartet (in ProcessSmelting -> ProcessRecipe).

Sieht gerade alles sehr durcheinander aus, bin gerade dabei es kompakter zu schreiben.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Das Brennen und das Verarbeiten der Items, die ja letztendes nur unsichtbare Objekte im RAM sind, sind ja nicht aneinander gebunden.

Der Check, ob es etwas zum Verbrennen gibt, ist ja unsichtbar für den Spieler. Nur das Verbrennen, wenn der Check feststellt, dass es etwas gibt, kriegt der Spieler mit. Es ist also egal, ob du zwischen den Checks eine, zehn, oder 60 Sekunden vergehen lässt, solange der Check jedes Mal "gibt nix zu tun" ergibt. Den Unterschied merkt der Spieler nicht.

Also warum machst du den Check nicht immer genau dann, wenn sich im Inventar etwas ändert? Den Unterschied merkt der User nach wie vor nicht... abgesehen davon, dass er nicht bis zu einer Sekunde warten muss, damit das Craften anfängt.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Archiviert

Dieses Thema ist jetzt archiviert und für weitere Antworten gesperrt.

×
×
  • Neu erstellen...