Jump to content
Unity Insider Forum

Gameobject im Szenenwechsel


nordseekrabe

Recommended Posts

Moin, moin,

kann bislang keine entsprechenden Themen in anderen Foren finden, daher meine Frage an Euch:

in der Szene 1 liegen 2 GameObjects auf dem Boden (ein Zettel, eine Ente); diese nehme ich auf in mein Inventory. Sie sind nicht mehr auf dem Boden, sondern nur im Inventory. Mit diesen beiden Objekten gehe ich in den nächsten Raum(Szene2). Im Inventory (mit DontDestroyOnLoad attached) befinden sich auch jetzt noch die "mitgenommenen" Objekte. Soweit so gut, gehe ich jetzt aber wieder in Raum 1 zurück, befinden sich mein Zettel und meine Ente weiterhin im Inventory (okay), aber auf dem Boden liegen sie nun auch wieder !(nicht gut). Meine Frage, wie kann ich die Objekte nach dem Aufnehmen in das Inventory tatsächlich "löschen", dass sie nicht wieder erscheinen. (Bei einem Neustart sind sie natürlich wieder da!)

Besten Dank im voraus und Gruß von der Ostseeküste  

Link zu diesem Kommentar
Auf anderen Seiten teilen

Du hast wahrscheinlich diese Objekte von der ersten Szene mit der Szene zusammen abgespeichert. SOmit sind sie natürlich bein Szenenstart wieder da.

Es gibt da jetzt 2 Möglichkeiten für dich. Du erstellst alle benutz- oder aufnehmbaren Objekte über eine Routine und zwar erst wenn die Szene geladen worden ist, oder aber du lässt das so wie es ist, zerstörst dann aber alle Objekte sofort, die nicht mehr da sein sollen.
In beiden Fällen musst du dir irgendwie abspeichern, welche Objekte jetzt nicht mehr in der Szene sein sollen bzw. nicht mehr an dem Initialpunkt sind.
Die Routine, die die Objekte behandelt weiss dann, welches Objekt zerstört werden muss (wenn es imt der Ursprungsszene gespeichert wurde), oder welches Objekt wo instanziert werden muss (Wenn es erst nach dem Szenenstart erstellt wird).

Welche Variante du nutzt, hängt von deinem Spiel ab. Kann man bei deinem Spiel die aufgenommenen Objekte auch wieder wo anders ablegen, dann solltest du die Variante wählen, wo die Objekte nicht mit der Szene gespeichert sind, sondern erst direkt nach dem laden der Szene erstellt werden.

Wie auch immer, du brauchst eine Liste für deine Objekte und natürlich ein Organisationsscript, welches dann Objekte zerstört oder an gewisse Plätze instanziert bzw. verschiebt, wenn es die Szene nicht verlassen hat sondern nur die Position geändert wurde.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Nur eine kleine Ergänzung zu dem was @malzbie sehr gut beschrieben hat.

Ich vermute mal in der Szene 1 werden deine beiden Objekte jeweils neu geladen, d.h. wenn du in 2. Szene gehst und wieder zurück, dann werden die beiden Objekte über deine 1. Szene neu geladen. Und hier liegt auch schon das Problem, da sich diese beiden Objekte im Inventar befinden können, sollten sie nicht über die Szene (bzw. den Szenencontent) geladen werden, sondern instanziiert werden, wenn sie nicht im Inventar sind:

https://docs.unity3d.com/ScriptReference/Object.Instantiate.html

Zudem solltest du verstehen, daß die Objekte in deinem Inventar nicht den Objekten in der Szene entsprechen, je nach Inventorysystem halten die "Inventoryitems" nur  Referenzen auf die eigentlichen Szenenobjekte oder Prefabs. 
Da ich dein Inventorysystem nicht kenne, kann man an dieser Stelle nur spekulieren, aber normalerweise solltest du beim Laden der Szene 1 prüfen welche Objekte im Inventar liegen und die Objekte zu denen Referenzen in den Inventaryitems vorliegen in dieser Szene nicht instanziieren.
Ein anderer Weg wäre, die Objekte jeweils doch über die Szene zun laden und bei Start der Szene wieder zu löschen, wenn sie sich bereits in der Szene befinden. Das Problem hierbei ist allerdings, daß man dafür die Referenzen der Objekte in der Szene finden muss. Dies kann man beispielsweise lösen, indem diese Szenenobjekte eine Itemskript besitzen, welches den Typ des Items zurückliefert. Zudem kann man diese Objekte dann auch über ein solches Skript in der Szene finden. Zusammenfassend, du wirst nicht drum herum kommen, die Objekte in deinem Inventar zu prüfen und einen Abgleich gegen die Objekte in der jeweiligen Szene zu machen.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Besten Dank an "malzbie" und "Zer0Cool",

das beschriebene Problem bleibt auch, wenn die Szene gespeichert ist, bevor die beiden Objekte der Szene beigefügt wurden. Einen Szenenwechsel innerhalb des Spiels führe ich mit einem Script aus, welches einem Pfeilobjekt zugeordnet ist:

             void OnMouseDown( ) {if (aktionsManager.aktuelleAktion == "Gehe zu" && interaktivitaet.begehbar) SceneManager.LoadScene(2); }

Hier wäre meine Idee, zusätzlich die beiden Objekte in der Hierarchy zu löschen (aber wie?) oder den Renderer mit GetComponent<Renderer>.enabled =

false zu deaktivieren. Angehängt habe ich Inventory.cs und Items.cs. Sicher wird das Problem denn besser ersichtlich.

Inventory.cs

Items.cs

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ich habe mir die Skripte mal angeschaut und ich muss wiedersprechen, wenn du die Szene ohne diese beiden Objekte speicherst können sie nicht wieder auftauchen, da du nirgends (zumindest nicht in den obigen beiden Skripten) irgendwo Objekte in der Szene spawnst. Wenn du also die Szene 1 ohne diese beiden Objekte speicherst, kann das Inventory diese Objekte nicht wieder herstellen, ganz einfach, weil der entsprechende Code in deinen Skripten fehlt.

Zudem wäre es besser, du verwendest keine Tags, sondern lässt deine Klasse Items von MonoBehaviour erben und ziehst auf jedes Item in der Szene welches ins Inventar soll dieses Skript. 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hier mal die umgeschriebene Variante.
An die Items in der Szene einfach das Skript Item anhängen und Werte setzen.
Wenn du Items aufnimmst, dann tauchen sie im Inspektor des Inventars auf.
Items (GameObjekte) die du im Editor ins Inventar legst, werden beim Start der Szene deaktiviert.

Die Items werden nicht mehr gelöscht, sondern nur noch aktiviert oder deaktiviert. Die Items werden mit "DontDestroyOnLoad" markiert und bleiben daher bei einem Szenenwechsel erhalten.

Es fehlt nun noch Code um ein Item wieder zu aktivieren und aus dem Inventar herauszunehmen. Man könnte bei einem Mausclick aufs Terrain beispielsweise das Item an diese Position legen und wieder aktivieren.

using UnityEngine;

public class Item : MonoBehaviour
{
    public string itemName;
    public string itemDesc;
    public string itemID;
    public Texture2D itemImage;

    void Awake()
    {
        DontDestroyOnLoad(this.gameObject);
    }
 }
using System.Collections.Generic;
using UnityEngine;

public class Inventory : MonoBehaviour
{
    private static Inventory _instance;
    public static Inventory Instance { get { return _instance; } }

    public List<Item> itemsInInventory = new List<Item>();
    public Rect windowRect = new Rect(30, 30, 100, 500);
    public int x1, x2, y1, y2;
    private RaycastHit hit;
    private Ray ray;
    private Vector3 pos;
    private Vector2 scrollPosition = Vector2.zero;
    public Transform inventory;
    public bool showInventory = false;
               

    private void Awake()
    {
        if (_instance != null && _instance != this)
        {
            Destroy(this.gameObject);
        }
        else
        {
            _instance = this;
        }
        
    }

    void Start()
    {
        for (int i = 0; i < itemsInInventory.Count; i++)
        {
            itemsInInventory[i].gameObject.SetActive(false); // Item deaktiveren, wenn bereits im Inventar
        }
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.I))
            showInventory = !showInventory;

        ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        if (Input.GetMouseButton(0))
        {
            if (Physics.Raycast(ray, out hit, 20))
            {
                Item collectedItem = hit.collider.gameObject.GetComponent<Item>();

                if (collectedItem != null)
                {
                    itemsInInventory.Add(collectedItem); // Item ins Inventar legen
                    hit.collider.gameObject.SetActive(false); // Item nur deaktiveren
                }
            }
        }
        pos = transform.position;
        pos.x += 20;
    }


    private void OnGUI()
    {
        if (showInventory)
        windowRect = GUI.Window(0, windowRect, DoMyWindow, "Inventar");
        
    }

    void DoMyWindow(int windowID)                         
    {
        int x = 0;
        int y = 0;
        GUI.DragWindow(new Rect(0, 0, 200, 400));
        //scrollPosition = GUI.BeginScrollView(new Rect(10, 10, x1, y1), scrollPosition, new Rect(0, 0, x2, y2));

        for (int i = 0; i < itemsInInventory.Count; i++)
        {
            GUI.Label(new Rect(x+52, y+10, 85, 50), itemsInInventory[i].itemName);
            GUI.Button(new Rect(x,y+1, 48, 48), itemsInInventory[i].itemImage);
            
            y += 50;
        }

        //GUI.EndScrollView();

    }
}

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Tausend Dank!! Habe diesen Vorschlag umgesetzt, und es klappt alles wie gehabt, jetzt aber ohne Tag und  mit MonoBehaviour (könnte man mit Singleton etwas erreichen?). Ein erneuter Test (Scene1 ohne Objekte gespeichert, Objekte eingefügt, Play) bringt dasselbe Ergebnis: Objekte "fliegen" ins Inventar, verschwinden aus der Szene, werden im Inventar mit in die Szene2 mitgenommen und auch beim Zurückgehen in Szene1 bleiben sie im Inventar.....aber leider liegen sie auch wieder in der Szene1 auf dem Boden. Wie auch immer ?

Ich habe versucht, im o.g. Script nach "SceneManager.LoadScene(2)"  folgendes einzufügen, um die Objekte aus der Szene(Hierarchy) herauszubekommen : itemInInventory.gameObject.SetActive(false);" ...leider kein Effekt !

Dennoch, besten Dank für die Code-Verbesserung.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ja, stimmt, sie werden in Szene 1 halt wieder neu geladen, sind aber bereits im Inventar vorhanden und verdoppeln sich dann.

Hier eine mögliche Lösung, beim Laden einer Szene werden Objekte die sich "aktiviert" in der Szene und auch im Inventar befinden entfernt. Die Item-ID dient dabei als Verknüpfungspunkt. Dies funktioniert, da Objekte die sich im Inventar befinden,  deaktiviert sind. Die Methode "FindObjectsOfType" liefert nur Objekte die aktiviert sind und damit nur Objekte die nicht im Inventar sind. Du musst nur aufpassen, wenn du Objekte im Editor ins Inventar ziehst (Initialladung des Inventars), daß du diese in der Szene dann auch deaktivierst.

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class Inventory : MonoBehaviour
{
    private static Inventory _instance;
    public static Inventory Instance { get { return _instance; } }

    public List<Item> itemsInInventory = new List<Item>();
    public Rect windowRect = new Rect(30, 30, 100, 500);
    public int x1, x2, y1, y2;
    private RaycastHit hit;
    private Ray ray;
    private Vector3 pos;
    private Vector2 scrollPosition = Vector2.zero;
    public Transform inventory;
    public bool showInventory = false;

    void OnEnable()
    {
        //Tell our 'OnLevelFinishedLoading' function to start listening for a scene change as soon as this script is enabled.
        SceneManager.sceneLoaded += OnLevelFinishedLoading;
    }

    void OnDisable()
    {
        //Tell our 'OnLevelFinishedLoading' function to stop listening for a scene change as soon as this script is disabled. Remember to always have an unsubscription for every delegate you subscribe to!
        SceneManager.sceneLoaded -= OnLevelFinishedLoading;
    }

    void OnLevelFinishedLoading(Scene scene, LoadSceneMode mode)
    {
        Debug.Log("Level Loaded: " + scene.name);

        RemoveDuplicates();
    }

    private void RemoveDuplicates()
    {
        Item[] allItems = FindObjectsOfType<Item>();

        foreach (Item itemInventory in itemsInInventory)
        {
            foreach (Item itemScene in allItems)
            {
                if (itemScene.itemID == itemInventory.itemID)
                {
                    print("Doppeltes Item '" + itemScene.gameObject + "' zerstört");
                    Destroy(itemScene.gameObject);
                }
            }
        }
    }

    private void Awake()
    {
        if (_instance != null && _instance != this)
        {
            Destroy(this.gameObject);
        }
        else
        {
            _instance = this;
        }
        
    }

    void Start()
    {
        for (int i = 0; i < itemsInInventory.Count; i++)
        {
            itemsInInventory[i].gameObject.SetActive(false); // Item deaktiveren, wenn bereits im Inventar
        }
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.I))
            showInventory = !showInventory;

        ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        if (Input.GetMouseButton(0))
        {
            if (Physics.Raycast(ray, out hit, 20))
            {
                Item collectedItem = hit.collider.gameObject.GetComponent<Item>();

                if (collectedItem != null)
                {
                    itemsInInventory.Add(collectedItem); // Item ins Inventar legen
                    hit.collider.gameObject.SetActive(false); // Item nur deaktiveren
                }
            }
        }
        pos = transform.position;
        pos.x += 20;
    }


    private void OnGUI()
    {
        if (showInventory)
        windowRect = GUI.Window(0, windowRect, DoMyWindow, "Inventar");
        
    }

    void DoMyWindow(int windowID)                         
    {
        int x = 0;
        int y = 0;
        GUI.DragWindow(new Rect(0, 0, 200, 400));
        //scrollPosition = GUI.BeginScrollView(new Rect(10, 10, x1, y1), scrollPosition, new Rect(0, 0, x2, y2));

        for (int i = 0; i < itemsInInventory.Count; i++)
        {
            GUI.Label(new Rect(x+52, y+10, 85, 50), itemsInInventory[i].itemName);
            GUI.Button(new Rect(x,y+1, 48, 48), itemsInInventory[i].itemImage);
            
            y += 50;
        }

        //GUI.EndScrollView();

    }
}

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Moin, moin,

leider ist es ja oft so....aus der Lösung von Problemen ergeben sich neue Probleme.

Daher muss ich (darf ich) nochmals nachhaken. Im o.g. Script wird ein Objekt "itemInInventory" ins Inventar gehievt, wird es auch noch in einer Liste gespeichert (List<Item> itemInInventory) ? Meine Frage ergibt sich aus der Fehlermeldung (ArgumentNullException) bei folgendem Code:

                               if (zimmer == Room.Tuerspalt)
            {
                if (itemInInventory.Any<Item>(item => item.itemName.Equals("Papier", StringComparison.CurrentCultureIgnoreCase)))       //Zeile 40 mit Fehlermeldung
                {
                    interaktivitaet.aufnehmbar = true;
                    aktionsManager.Infotext.text = "Jetzt könnte eigentlich der Schlüssel auf das Papier fallen! Also ran an das Türschloss.";
                }

Im Inventar ist ein Zettel aus Szene1, also eigentlich nicht leer ? Oder suche ich in der falschen Liste?

ObjektKombinieren.cs

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ich kenne mich mit der Linq-Syntax nicht gut aus, daher kann sich hier leicht ein Fehler einschleichen und ich erkenne ihn nicht.

In reinem C# geht es jedenfalls so:

if (zimmer == Room.Tuerspalt)
{
   Item searchedItem = itemInInventory.Find(x => x.itemName.Contains("Papier"));
   if (searchedItem!=null)
   {
      interaktivitaet.aufnehmbar = true;
      aktionsManager.Infotext.text = "Jetzt könnte eigentlich der Schlüssel auf das Papier fallen! Also ran an das Türschloss.";
   }
}




 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Sehr gute Idee, eine Instanz für "itemInInventory" zu erzeugen. Leider ist "searchedItem" aber null, da  meines Erachtens  in itemInInventory nicht "itemName" gefunden wird, sondern lediglich Size. Wenn ich z.B. in der Klasse Inventory nach Erzeugen von collectedItem mit Debug.Log(itemInInventory) nachfrage, erhalte ich lediglich 1 und nicht den itemName. (itemID habe ich auskommentiert und  in RemoveDuplicates mit itemName ausgetauscht). 

Daher  erfolgt auch in Debug.Log(searchedItem) nach erzeugen von searchedItem die Ausgabe "null" und die If-Abfrage landet bei else mit dem entsprechenden Infotext.

Link zu diesem Kommentar
Auf anderen Seiten teilen

"Find" sucht in der Liste "itemInInventory" (hier muss ein aufgenommenes Item vorhanden sein) nach einem Item mit dem Namen "Papier" und gibt dieses Item zurück. Wenn searchedItem null ist, dann bedeutet das, es wurde kein Item mit diesem Namen gefunden (ItemName = "Papier").

Deine Klasse "ObjektKombinieren" hat ein generelles Problem, die List-Variable "itemInInventory" (und damit dein Inventory) wird nirgends gesetzt oder initialisiert und ist damit immer null!

Es fehlt vermutlich folgendes:

    void Start()
    {
        aktionsManager = GameObject.Find("Aktionsmanager").GetComponent<Aktionsmanager>();
        interaktivitaet = GetComponent<InteraktivesElement>();
        
        Inventory m_Inventory = FindObjectOfType<Inventory>(); // Inventory in der Szene suchen
        itemInInventory  = m_Inventory.itemsInInventory; // Inhalt des Inventory auslesen
    }

    

 

Zitat

 (itemID habe ich auskommentiert und  in RemoveDuplicates mit itemName ausgetauscht). 

Kann man machen, aber dann darfst du keine Items in deinem Spiel haben, die den gleichen Namen haben. Die ID identifiziert deine Items im Game eindeutig.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Die Klasse ObjektKombinieren.cs braucht eine Referenz auf das gefüllte Inventar (siehe oben).

Folgender Code sollte den Inhalt des Inventars (der Liste) in der Console ausgeben, wenn hier nix rauskommt, dann stimmt etwas nicht:

if (zimmer == Room.Tuerspalt)
{
   
   // Hier muss in der Console erscheinen: Item 0: Name 'Papier'    
   DumpInventar(itemInInventory); 
   
   Item searchedItem = itemInInventory.Find(x => x.itemName.Contains("Papier"));
   if (searchedItem!=null)
   {
      interaktivitaet.aufnehmbar = true;
      aktionsManager.Infotext.text = "Jetzt könnte eigentlich der Schlüssel auf das Papier fallen! Also ran an das Türschloss.";
   }
}

private void DumpInventar(List<Item> itemsInInventory)
{
   print("Alle Items im Inventar:");
   int i = 0;
   foreach (Item item in itemsInInventory)
   {
     print("Item " + i + ": Name '" + item.name+ "'");
     i++;
   }
}

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Besten Dank für Deine Geduld, Zer0Cool.        Leider :

1.  Debug.Log(collectedItem) in der Inventory-Klasse zwischen den Zeilen "itemInInventory.Add(collectedItem) und  hit.collider.gameObject.SetActive(false)                                           =>    Papier(Item)

2.  Debug.Log(itemInInventory)  direkt darunter                =>  System.Collections.Generic.List ' 1[Item]

3.  Debug.Log(   "Level Loaded:              in "OnLevelFinishedLoading"    =>     Level Loaded: Tuerspalte

4.  print("Alle...)     =>    Alle Items im Inventory:

5.  in der ObjektKombinieren-Klasse   Debug.Log(searchedItem)  unter der Zeile Item searchedItem = itemInInventory.Find....    => Null

6. das 2. print wird nicht ausgeführt

Link zu diesem Kommentar
Auf anderen Seiten teilen

Dann ist das Inventory in deiner ObjektKombinieren-Klasse vermutlich leer oder nicht gesetzt!

Das hier sollte in deine ObjektKombinieren-Klasse:

void Start()
    {
        aktionsManager = GameObject.Find("Aktionsmanager").GetComponent<Aktionsmanager>();
        interaktivitaet = GetComponent<InteraktivesElement>();
        
        Inventory m_Inventory = FindObjectOfType<Inventory>(); // Inventory in der Szene suchen
        itemInInventory  = m_Inventory.itemsInInventory; // Inhalt des Inventory auslesen
          
        DumpInventar(itemInInventory);
        // Hier muss in der Console erscheinen: Item 0: Name 'Papier' 
        // Sollte das Item hier nicht erscheinen, dann liegt es nicht in deinem Inventar!

        // Test für spätere Methode:
        Item searchedItem = itemInInventory.Find(x => x.itemName.Contains("Papier"));
        if (searchedItem!=null) print("Item gefunden!");
    }
          
private void DumpInventar(List<Item> itemsInInventory)
{
   print("Alle Items im Inventar:");
   int i = 0;
   foreach (Item item in itemsInInventory)
   {
     print("Item " + i + ": Name '" + item.name+ "'");
     i++;
   }
}          

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

okay, läuft soweit, ein Hindernis überwunden, danke, Zer0Cool. Gerne würde ich aber auch die Codeänderungen v e r s t e h e n.

1. Mein logisches Denken als Mensch: man betritt einen Raum, sieht ein Objekt, nimmt es in den Rucksack und geht in den nächsten Raum. Umherschauen, okay, geht wieder in den ersten Raum, Rucksack mit Objekt auf dem Rücken, auf dem Boden nichts. Das Programm kann das nicht, es hat nur das Ausgangsbild der Szene gespeichert und das wird beim zurückgehen in Raum1 wieder aufgerufen. D.h. eigentlich müsste man vor jedem Szenenwechsel speichern und dann beim Zurückgehen in diesen Raum (egal zu welchem Zeitpunkt im Spiel) nicht die Ursprungsszene1 aufrufen sondern ihre geänderte und gespeicherte Version? Wie aber speichern, PlayerPrefs erlauben meines Wissens keine Images ? Diese Situation ergibt sich doch bei jedem Szenenwechsel, vor dem im Raum irgendwelche "Aktivitäten" waren.

Das Problem mit doppelten Objekten hat Zer0Cool mit RemoveDuplicates gelöst. Ginge das auch mit Singleton ? Müsste man dann jedes einzelne Objekt (Collectable) mit einer eigenen Klasse (Singleton) mit static Konstruktor versorgen ? 

2.Die Abfrage einer Objektliste über den Namen eines Objektes scheint denn doch nicht so einfach, wie ich das aus dem Studium zahlreicher Bücher zu verstehen glaubte. Am schwierigsten ist für mich das Verständnis, aus einer Liste mit Items(itemsInInventory) ein Objekt  an Hand seines Namens in einer Liste der Klasse "Inventory" zu finden (ich bin kein Programmierer, kein gelernter IT-Mensch, nur Rentner, der am Metier Freude hat, ich will kein Spiel veröffentlichen, kein Geld verdienen, lediglich ...Lebensfreude): 

    "Item searchedItem = itemInInventory.Find(x => x.itemName.Contains("Papier") ... bringt zunächst keine Lösung.

Erst die Verknüpfung von Inventory  (Inventory m_Inventory) mit List<Item> itemsInInventory  (das ist doch den Name der Liste mit Item-Objekten?)

führt die beiden zusammen und erlaubt, mit Find(..Contains..) das gesuchte Objekt zu finden. Verstehe ich das so richtig? Denn diese Frage stellt sich in diesem Spiel permanent, wo ein Objekt mit einem anderen Objekt benutzt werden soll, um ein Hindernis zu überwinden.

Danke jedenfalls für die große Hilfe aus diesem Forum. Ohne Unterstützung von außen bleibt doch schnell die Freude aus, vor allem, wenn man auf der Stelle tritt. Da helfen auch keine 1000 Bücher, wenn einem die Praxis und der Kollege am Nachbarschreibtisch fehlen.

Gruß von der Ostseeküste

Peter

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ich habe beide Klassen noch einmal modifiziert.

In der Singleton-Klasse "Inventory" war ein Fehler. Dieser führte dazu, daß die Items nicht in eine neue Szene übernommen wurden.
Zudem werden nun nur noch Items in eine neue Szene mitgenommen, die auch im Inventar sind, "DontDestroyOnLoad" wurde aus der Klasse Item herausgenommen und in die Klasse "Inventory" verschoben.

Ich habe zusätzlich eine Methode eingebaut zum Zurücklegen des Items an seine Originalposition. Diese kann über die Inventory-UI aufgerufen werden!

Bau die Änderungen mal ein und schau dir die Ergebnisse an, vor allem im Sceneview das Inventory nach dem Laden einer neuen Szene:

using UnityEngine;

public class Item : MonoBehaviour
{
    public string itemName;
    public string itemDesc;
    public string itemID;
    public Texture2D itemImage;
 }
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class Inventory : MonoBehaviour
{
    private static Inventory m_Instance;
    public static Inventory Instance { get { return m_Instance; } }

    public List<Item> itemsInInventory = new List<Item>();
    public Rect windowRect = new Rect(30, 30, 140, 500);
    public int windowScrollWidth = 140 - 20 ;
    public int windowScrollHeight = 500 - 30;
    public int windowScrollSize = 50;
    private RaycastHit hit;
    private Ray ray;
    private Vector2 scrollPosition = Vector2.zero;
    public bool showInventory = false;

    private void Awake()
    {
        if (m_Instance != null && m_Instance != this)
        {
            Destroy(this.gameObject);
        }
        else
        {
            m_Instance = this;
        }

        //Sets this to not be destroyed when reloading scene
        DontDestroyOnLoad(gameObject);
    }

    void OnEnable()
    {
        //Tell our 'OnLevelFinishedLoading' function to start listening for a scene change as soon as this script is enabled.
        SceneManager.sceneLoaded += OnLevelFinishedLoading;
    }

    void OnDisable()
    {
        //Tell our 'OnLevelFinishedLoading' function to stop listening for a scene change as soon as this script is disabled. Remember to always have an unsubscription for every delegate you subscribe to!
        SceneManager.sceneLoaded -= OnLevelFinishedLoading;
    }

    void OnLevelFinishedLoading(Scene scene, LoadSceneMode mode)
    {
        Debug.Log("Level Loaded: " + scene.name);

        RemoveDuplicates();
    }

    private void RemoveDuplicates()
    {
        Item[] allItems = FindObjectsOfType<Item>();

        foreach (Item itemInventory in itemsInInventory)
        {
            foreach (Item itemScene in allItems)
            {
                if (itemScene.itemID == itemInventory.itemID)
                {
                    print("Doppeltes Item '" + itemScene.gameObject + "' zerstört");
                    Destroy(itemScene.gameObject);
                }
            }
        }
    }

    void Start()
    {
        for (int i = 0; i < itemsInInventory.Count; i++)
        {
            itemsInInventory[i].gameObject.SetActive(false); // Item deaktiveren, wenn bereits im Inventar
        }

        Item find = itemsInInventory.Find(x => x.itemName.Contains("Cube 2"));
        if (find != null) print("gefunden");

        DumpInventar();
    }


    public void DumpInventar()
    {
        print("Alle Items im Inventar:");
        int i = 0;
        foreach (Item item in itemsInInventory)
        {
            print("Item " + i + ": Name '" + item.name+ "'");
            i++;
        }
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.I))
            showInventory = !showInventory;

        ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        if (Input.GetMouseButton(0))
        {
            if (Physics.Raycast(ray, out hit, 20))
            {
                PlaceItemToInventar(hit.collider.gameObject); // Try to place gameObject to Inventar
            }
        }
    }

    private void PlaceItemToInventar(GameObject obj)
    {
        Item collectedItem = obj.GetComponent<Item>();

        if (collectedItem != null)
        {
            itemsInInventory.Add(collectedItem); // Item ins Inventar legen
            DontDestroyOnLoad(obj); // Item GameObjekt als Szenenübergreifend markieren
            obj.SetActive(false); // Item GameObjekt nur deaktiveren
        }
    }

    private void RestoreItemFromInventar(int index)
    {
        if (index >= itemsInInventory.Count) return; // Falscher Index
        Item item = itemsInInventory[index];
        item.gameObject.SetActive(true); // Item GameObjekt aktiveren
        string name = item.gameObject.name; // Namen zwischenspeichern
        GameObject newItemGO = Instantiate(item.gameObject); // Item duplizieren, damit DontDestroyOnLoad verschwindet
        DestroyImmediate(item.gameObject);
        newItemGO.name = name; // Namen zurücksetzen
        itemsInInventory.RemoveAt(index); // Item aus dem Inventar nehmen        
    }

    private void OnGUI()
    {
        if (showInventory)
        windowRect = GUI.Window(0, windowRect, DoMyWindow, "Inventar");
        
    }

    void DoMyWindow(int windowID)                         
    {
        int x = 10;
        int y = 20;

        scrollPosition = GUI.BeginScrollView(new Rect(10, 20, windowScrollWidth, windowScrollHeight), scrollPosition, new Rect(10, 10, windowScrollWidth + windowScrollSize, windowScrollHeight + windowScrollSize));

        for (int i = 0; i < itemsInInventory.Count; i++)
        {
            GUI.Label(new Rect(x+52, y+10, 85, 50), itemsInInventory[i].itemName);
            if (GUI.Button(new Rect(x,y+1, 48, 48), itemsInInventory[i].itemImage))
            {
                // Button Listener
                print("Item '" + itemsInInventory[i].name + "' zurückgelegt");
                RestoreItemFromInventar(i);
            }
            
            y += 50;
        }

        GUI.EndScrollView();
        GUI.DragWindow();
    }
}


 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Zitat

D.h. eigentlich müsste man vor jedem Szenenwechsel speichern und dann beim Zurückgehen in diesen Raum (egal zu welchem Zeitpunkt im Spiel) nicht die Ursprungsszene1 aufrufen sondern ihre geänderte und gespeicherte Version? 

Aktuell übernimmt das Inventar diese Speicherung, es speichert Items die in der Szene aufgenommen wurden in der internen "ItemListe" (itemsInInventory) und markiert die aufgenommenen GameObjekte in der Szene als "szenenübergereifend" (siehe DontDestroyOnLoad). Wird nun eine neue Szene geladen, verbleiben die Items im Inventar UND in der Szene als (deaktivierte) Objekte erhalten.

Wird nun in die Szene zurückgesprungen, in der die Items ehemals aufgenommen wurden, wird dies über das Inventar "erkannt" und die Objekte die sich bereits im Inventar befinden werden aus der neu geladenen Szene entfernt (siehe RemoveDuplicates).

Link zu diesem Kommentar
Auf anderen Seiten teilen

Moin, moin

den neuen Code muss ich erst noch "verdauen". Die Szenen übergreifende Lösung für die Collectables finde ich gut. In diesem Consens bleibt meine Frage bezüglich "Speichern" für andere, nicht seltene Ereignisse: als Beispiel: eine Tür ist verschlossen, der Spieler kann nur dann diese Tür öffnen, wenn er den Schlüssel gefunden hat und sich im Inventar befindet. Dabei werden auch Eigenschaften der Tür verändert (zu öffnen = true, begehbar = true  oder ähnliches). Der Spieler betritt diesen Raum durch die jetzt geöffnete Tür, sucht dort nach weiteren Objekten und verlässt wieder den Raum. Will er jetzt nochmals hinein, muss er wieder die komplette Prozedur durchlaufen, wie beim ersten Mal, da ja die Szene als "Originalszene" geladen wird. Aber das scheint ja dann doch ein anderes Thema. Also erstmals besten Dank. 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ja ist quasi das Thema speichern der Zustände innerhalb einer Szene bzw. eines Spiels, da gibt es aber wieder unendliche Möglichkeiten.
Prinzipiell wird wieder eine Klasse angelegt, welche sämtliche Daten aufnimmt, die festgehalten werden sollen, aber dann gibt es wieder diverse Varianten:

  • einfache Monobehavior-Datenklasse
    - Daten bleiben nicht szenenübergreifend erhalten, hier muss man vor dem Laden einer neuen Szene die Daten speichern (und beim Beenden der Anwendung)
    - die Instanz der Klasse muss in einer neuen Szene vorhanden sein und ihre Daten neu laden
  • einfache Monobehavior-Datenklasse mit DontDestroyOnLoad
    - Daten bleiben szenenübergreifend erhalten
    - eine die Instanz der Klasse sollte nur in einer Szene vorhanden sein (Inititalszene)
    - andere Szenen erben die Instanz aus der "Initialszene"
    - Speichern und Laden muss man beim Beenden der Anwendung
  • statische Klasse
    - Daten bleiben szenenübergreifend erhalten, da die statische Klasse ihre statischen Daten nicht verliert, wenn eine neue Szene geladen wird
    - Speichern und Laden muss man beim Beenden der Anwendung
  • Singleton Klasse
    wie bei der Inventory-Klasse, hier werden Daten szenenübergreifend transportiert, da DontDestroyOnLoad angewendet wird und somit immer eine Instanz der Klasse erhalten bleibt
    - Speichern und Laden muss man beim Beenden der Anwendung
  • PlayerPrefs
    Speichern und Laden von Daten aus oben genannten Klassen
  • Daten speichern in eine Datei 
    Speichern und Laden von Daten aus oben genannten Klassen
  • Kombination der oben genannten Möglichkeiten
     
Link zu diesem Kommentar
Auf anderen Seiten teilen

ein weites Feld, das Speichern in Unity. Habe schon einiges mit mehr oder weniger Erfolg ausprobiert. Werde mich bei Gelegenheit wieder damit befassen. Wenn ich dann wieder in einer Sackgasse lande, melde ich mich im Forum. Dank Deiner großen Hilfe kann ich ja nun das Coden aller Objektaktivitäten in meinem Spiel  angehen. Die Vorteile der neuen Codierung werden dann sicher schnell sichtbar.

Einen schönen Tag noch und Gruß von einer nass-kalten Ostseeküste mit Schneematsch

Peter

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ja, gern und viel Erfolg. Hier war Schnee, der ist nun wieder weg und nun scheint die Sonne ;D

Du kannst ja auch mal schauen, ob du die deprecated UI (OnGUI-Methode) rauswirfst und gegen die neue UI ersetzt (Canvas + UI-Label + UI-Button):
- man baut dabei ein Prefab aus UI-Label + UI-Button
- dann nimmt man eine  VLG
https://docs.unity3d.com/Manual/script-VerticalLayoutGroup.html
und fügt Instanzen dieser Prefab als Childs ein

Link zu diesem Kommentar
Auf anderen Seiten teilen

Archiviert

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

×
×
  • Neu erstellen...