Jump to content
Unity Insider Forum

Allgemeine Hilfe, bzw. Tipps erfragen


Kurumi-chan

Recommended Posts

Hey, ich habe kürzlich angefangen Unity zu benutzen. Dort möchte ich gerne ein keines 2D Spiel, wie beispielsweise die alten SNES Spiele, erstellen. Ich bin aber generell auch noch Programmieranfänger. Meine jetzige Frage bezieht sich auf mein Ingame Menü. Dies funktioniert nun endlich so, wie ich es mir vorgestellt habe, aber ich fürchte, dass die Programmierung eher grausam ist. Daher würde ich euch gerne bitte mir evtl. ein paar Tipps zugeben, wie ich das besser lösen kann.

Hierbei handelt es sich exemplarisch um das Menü welches die Items anzeigt (aber das gesamte Menü sieht ziemlich ähnlich aus). Diese werden als Scriptable Object erstellt und im Editor im GameManager Script in eine Liste eingefügt und in die Variable Inventory überführt. Weiterhin sind die Items aber auch noch nicht fertig. Es fehlen noch Dinge wie Zustände usw. und ich denke, dass es spätestens damit noch schlimmer wird.

Hoffe ihr könnt damit was anfangen :-)

Aber hier erstmal der Code:

 

using TMPro;
using System;
using UnityEngine.UI;

public class Item : MonoBehaviour
{
    private List<(ItemSO, int)> inventory;
    private int amount;
    private ItemSO item;

    private GameObject itemInfoPanel;

    private GameObject itemBtn;
    public GameObject statsPanel;
    
    private int itemIndex;

    private TMP_Text itemBtnText;
    private TMP_Text itemBtnAmountText;
    private Image itemBtnIcon;
    private TMP_Text itemText;
    private TMP_Text itemAmountText;
    private TMP_Text itemDescriptionText;
    private Image itemIcon;

    private GameObject[] p1;
    private GameObject[] p2;
    private TMP_Text itemTargetText;
    private TMP_Text itemStatsText;
    private TMP_Text itemStatsValue;
    private TMP_Text itemSpecialStatsText;
    private TMP_Text itemSpecialStatsValue;


    void Awake()
    {
        RefreshCurrentItem();
        
    }
    // Start is called before the first frame update
    void Start()
    {
        p1 = new GameObject[4];
        p2 = new GameObject[4];
        Refresh();
        itemInfoPanel.SetActive(false);
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    public void OnCancel()
    {
        GameMenu.instance.OnSubCancel();
        itemInfoPanel.SetActive(false);
    }

    public void PlayButtonSound(int sfx)
    {
        AudioManager.instance.PlaySFX(sfx);
    }

    public void LoadItemContent()
    {
        RefreshCurrentItem();
        Refresh();
    }

    private void SetTextFieldsMain()
    {
        itemBtn = this.gameObject;
        itemInfoPanel = itemBtn.transform.parent.parent.GetChild(2).gameObject;

        itemBtnText = itemBtn.transform.GetChild(2).GetComponent<TMP_Text>();
        itemBtnAmountText = itemBtn.transform.GetChild(3).GetComponent<TMP_Text>();
        itemBtnIcon = itemBtn.transform.GetChild(1).GetComponent<Image>();

        itemText = itemInfoPanel.transform.GetChild(1).GetChild(0).GetComponent<TMP_Text>();
        itemAmountText = itemInfoPanel.transform.GetChild(1).GetChild(2).GetComponent<TMP_Text>();
        itemDescriptionText = itemInfoPanel.transform.GetChild(1).GetChild(1).GetComponent<TMP_Text>();
        itemIcon = itemInfoPanel.transform.GetChild(0).GetComponent<Image>();
    }

    private void SetTextFieldsItem()
    {
        SetTextFieldsMain();
        itemTargetText = itemInfoPanel.transform.GetChild(1).GetChild(3).GetChild(1).GetComponent<TMP_Text>();
        itemStatsText = null;
        itemStatsValue = null;
        itemSpecialStatsText = null;
        itemSpecialStatsValue = null;
    }

    private void SetTextFieldsEquip()
    {
        
        SetTextFieldsMain();
        test();
        for (int i = 0; i < item.stats.Count; i++) 
        {
            itemStatsText = p1[i].transform.GetChild(0).GetComponent<TMP_Text>();
            itemStatsValue = p1[i].transform.GetChild(1).GetComponent<TMP_Text>();
            itemStatsText.text = item.stats[i].paramName;
            itemStatsValue.text = item.stats[i].paramValue.ToString();


        }
        for (int i = 0; i < item.specialStats.Count; i++)
        {
            itemSpecialStatsText = p2[i].transform.GetChild(0).GetComponent<TMP_Text>();
            itemSpecialStatsValue = p2[i].transform.GetChild(1).GetComponent<TMP_Text>();
            itemSpecialStatsText.text = item.specialStats[i].paramName;
            itemSpecialStatsValue.text = item.specialStats[i].paramValue.ToString();
        }
       

        itemTargetText = null;
    }

    private void test()
    {
        if (item.stats.Count > 0)
        {
            for (int i = 0; i < item.stats.Count; i++)
            {
                p1[i] = Instantiate(statsPanel, itemInfoPanel.transform.GetChild(1).GetChild(3).GetChild(1));
                p1[i].name = "Stat" + i.ToString();
            }
        }
        if (item.specialStats.Count > 0)
        {
            for (int i = 0; i < item.specialStats.Count; i++)
            {
                p2[i] = Instantiate(statsPanel, itemInfoPanel.transform.GetChild(1).GetChild(3).GetChild(2));
                p2[i].name = "Stat" + i.ToString();
            }
        }
    }

    private void LoadMainData()
    {
        itemBtnText.text = item.itemName;
        itemBtnAmountText.text = amount.ToString();
        itemText.text = item.itemName;
        itemAmountText.text = amount.ToString();
        itemDescriptionText.text = item.itemDescription;
        itemIcon.sprite = item.icon;
        itemBtnIcon.sprite = item.icon;
    }

    private void LoadItemData()
    {
        LoadMainData();
        itemTargetText.text = item.targetAll ? "Alle" : "Einzeln";
    }

    private void LoadWeaponData(int i)
    {
        if (i == 0) LoadMainData();
        
    }

    private void LoadArmorData()
    {
        LoadMainData();
        
    }

    private void RefreshCurrentItem()
    {
        int i = Convert.ToInt32(this.gameObject.name.Substring(4));
        inventory = GameManager.instance.GetInventory();
        item = inventory[i].Item1;
        amount = inventory[i].Item2;
    }

    private void Refresh()
    {
        Clear();
        switch (GameMenu.instance.GetSubBtnIndex())
        {
            case (int)ItemType.Items:
                SetTextFieldsItem();
                LoadItemData();
                itemInfoPanel.SetActive(true);
                break;
            case (int)ItemType.Weapons:
                SetTextFieldsEquip();
                LoadMainData();
                itemInfoPanel.SetActive(true);
                break;
            case (int)ItemType.Armors:
                //SetTextFieldsEquip();
                LoadArmorData();
                itemInfoPanel.SetActive(true);
                break;
        }
        
    }

    private void Clear()
    {
        if (p1.Length != 0) 
        {
            for (int i = 0; i < p1.Length; i++)
            {
                Destroy(p1[i]);
            }
        }

        if (p2.Length != 0)
        {
            for (int i = 0; i < p2.Length; i++)
            {
                Destroy(p2[i]);
            }
        }
    }
}

Und ein Bild dazu, wie es aktuell aussieht:

Screenshot.png

Link zu diesem Kommentar
Auf anderen Seiten teilen

Moin!

Was ich da auf jeden Fall sehe, ist fehlende Aufteilung deiner Logik auf mehrere Klassen und GameObjects. So Sachen wie GetChild kommen bei mir im Code quasi nie vor. Statt lauter gleichartige Objekte zu haben, die du im Code auf dieselbe Weide durchforstest, solltest du ein Prefab haben, das du mehrere Male instanziierst. Das Prefab hat im Root-Objekt eine Komponente, die Eigenschaften hat, denen du die relevanten Objekte (also deine Text-Komponenten z.B.) zuweisen kannst. Dann lagerst du noch Logik, die mit diesen Objekten arbeitet, in diese Komponente aus.

So als Beispiel: Statt

public void SetupAllThings()
{
  foreach (var thing in things)
  {
    thing.GetChild(0).GetComponent<Text>().text = "wat";
    thing.GetChild(1).GetComponent<Foo>().number = 1;
  }
}

machst du eine Komponente auf ein Prefab

public class Thing : MonoBehaviour
{
  [SerializeField]
  private Text someText;
  [SerializeField]
  private Foo someFoo;
  
  public void SetUp(string text, int fooNumber)
  {
    someText.text = text;
    someFoo.number = fooNumber;
  }
}

und machst in der Schleife nur noch

public void SetupAllThings()
{
  foreach (var thing in things)
  {
    thing.SetUp("wat", 1);
  }
}

Wenn sich jetzt die Struktur deines Objekts, also des Prefabs, ändert... dann geht dein Code davon nicht kaputt. Du musst nur ggf. die Referenzen in der Komponente auf dem Prefab fixen.

Was mir dann noch auffällt ist, dass du Felder, also Objektvariablen benutzt, um innerhalb von Methoden Werte darin abzulegen. Sowas wie itemSpecialStatsText. Dafür benutzt man eher lokale Variablen.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hey Sascha,

vielen dank für deine Antwort 😄

Die Objektvariablen waren vorher noch notwendig, aber da alles nicht funktioniert hat, ist dann der neue Code daraus geworden und ich habe nicht mehr daran gedacht, dass man dies jetzt beim StatsPanel ändern könnte. Prefabs benutze ich aber in dem Fall schon. Das wird jeweils für p1 und p2 so oft instanziiert, wie es Werte gibt.

Ja, die GetChild Sachen würde ich sehr gerne los werden, da dachte ich mir schon, dass es sicher einen besseren Weg gibt, aber ich selbst könnte keinen finden, bzw der Weg über z.B. Find() funktioniert aus welchem Grund auch immer nicht immer. Manche Panels könnte er nicht finden, obwohl es nur einmal existiert und auch richtig geschrieben wird. Aber ich denke, auch Find() ist nicht der beste Weg :-D

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 3 Stunden schrieb Kurumi-chan:

Aber ich denke, auch Find() ist nicht der beste Weg :-D

In der Tat :)

Eine der Sachen, an die ich mich immer halte (und das sage ich extra so, weil das nicht alle genauso machen), ist, dass der Code nicht kaputt gehen darf, wenn man im Editor etwas ändert. Zum einen, weil jede Einschränkung, die der Code einem UI- oder Leveldesigner im Team gibt, ihm die Arbeit schwieriger macht. Zum anderen kannst du aber auch selber, wenn du alleine arbeitest, leichter in eine Falle tappen. Da vergisst du für eine Minute, dass ein zwei Monate alter Code darauf angewiesen ist, dass dein Prefab in dieser und jener Struktur aufgebaut ist, änderst etwas, weil es mehr Sinn ergibt und bumm! Fehler, die erst zur Laufzeit passieren, und die schwer zu debuggen sind.

Daher eine Sache, die ich sehr empfehlen kann: Wenn etwas im Editor eingerichtet wird, dann sollte es auch nur im Editor etwas kaputt machen. Wenn z.B. ein Knopf die falsche Tür öffnet, dann ist das ja dem Code egal. Aber wenn dein Code anfängt zu weinen, weil dein Prefab sich ändert, dann ist das nicht gut.

Klar kann man das nicht zu 100% durchziehen. Man kann zwar Code sehr tolerant machen (wenn die Kamera kein Objekt zum Hinterherlaufen zugewiesen hat, dann bleibt sie einfach stehen anstatt Exceptions zu werfen), aber letzten Endes braucht der Code immer noch hin und wieder einige Dinge vom Editor. Gerade, wenn du GetChild und Find und so weiter vermeiden willst, brauchst du als Ersatz halt Referenzen, die im Editor gesetzt werden müssen. Aber "Referenzen sind gesetzt" ist schwieriger zu verhunzen als "Prefab hat genau die richtige Struktur, die der Code erwartet".

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hallo 

 

Ich klinke mich da mal kurz ein, weil ich diene Texte immer sehr spannend finde und ich denke, dass mein "Problem" hier gut passt. 

Wie entscheidest du dich zwischen GetComponent (zusammen requirecomponent) und Konsorten auf der einen und Zuweisung im Editor auf der anderen Seite. 

Wenn ich den Gedanken verfolge, dass möglichst wenig im Editor eingestellt werden soll, müsste ich das ja immer im Code machen. Das erscheint mir nicht immer elegant, aber das ist nur ein Gefühl. 

Ich weiß also nicht richtig, welche Variante ich wann bevorzugen sollte. 

 

Christoph 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ich habe es jetzt eher andersherum, das man eher vieles im Editor referenzieren sollte, damit man im Code keine Probleme bekommt.

Aber allgemein ist es eine gute Frage,  vor allem, wann und warum lässt es sich nicht immer so machen?

Ich hatte mich tatsächlich extra entschieden den Editor nicht zu verwenden,  weil ich dachte 100e Referenzen im Editor seinen ein wenig viel, vor allem, weil es sich ja hier nur um das Itemmenü handelt. Da kommen ja dann noch zig andere Menüs dazu.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Alsooo... Wenn RequireComponent funktioniert, nehme ich das sofort. Eine Komponente, die eine andere zwingend braucht, darf diese oft auch auf demselben GameObject haben - Problem gelöst. Die Zuweisung im Editor dagegen sollte dagegen immer mindestens ein Stück weit eine Design-Entscheidung sein. Welchem Objekt folgt die Kamera, welche Tür öffnet der Knopf, aber auch "welches GameObject ist die Hand, an die das Schwert angeschweißt wird" oder eben "welches Textfeld zeigt den Spezial-Angriffswert an". Das sind alles Fragen, deren Antworten rein von der Logik her aus dem Editor kommen, also dürfen die Antworten auch dort eingetragen werden.

Sobald wir diese zwei Fälle aber verlassen, wird es etwas komplizierter. Da wäre zum einen der Fall "Der Code benötigt eine andere Komponente zwingend, aber diese Komponente sollte eher nicht auf demselben GameObject sein". Das ist ein Fall, für den es keine Lösung von Unity gibt. Man bräuchte quasi [RequireComponentInChildren] oder so. Ich habe dafür dieses Paket programmiert: https://gitlab.com/13pixels/tools/Validation. Damit kannst du deiner Komponente beibringen, was sie so alles braucht, und ein Test, den du im Test Runner laufen lassen kannst, meldet dir, wenn ein Prefab die gegebenen Eigenschaften nicht erfüllt. Das Paket soll noch irgendwann Szenen unterstützen, aber da bin ich immer noch nicht zu gekommen. Da man die Requirements als Code implementiert, deckt das Paket übrigens auch Fälle ab, dass bestimmte Eigenschaften gebraucht werden. Zum Beispiel "Ich brauche einen Collider, der auf IsTrigger gestellt ist".

Generell ist meine Faustregel: Wenn etwas im Code definiert ist, dann soll auch ausschließlich Code dafür verantwortlich sein, mit dieser Entscheidung zu "leben". Wenn eine Entscheidung im Editor getroffen wird (z.B. wie die GO-Hierarchie eines Prefabs aussieht), dann soll auch im Editor damit gearbeitet werden. Der Code öffnet dann (z.B. mit einem serialisierten Feld) die Hände und lässt sich vom Editor bedienen, um selber nichts über die Einzelheiten wissen zu müssen.

Fälle wie "ich brauche eine bestimmte Komponente irgendwo in den Children" bilden da natürlich direkt eine Ausnahme. Aber man will einfach nicht unbedingt 20 Komponenten auf einem GO haben. Ein klassisches Beispiel dafür wäre ein Controller für eine Figur, der die Bewegung steuert. Irgendwo untergeordnet ist der Animator, dem wiederum ein SkinnedMeshRenderer untergeordnet ist. Ich will die Komponente, die die Parameter in den Animator schmeißt, auf demselben GameObject haben. Die Werte, wie sich die Figur bewegt, kommen ja aber aus dem Controller weiter oben. Ich schwäche das Problem mit weiteren Faustregeln ab:

  1. Komponente im Parent brauchen ist besser als Komponente im Child brauchen.
  2. Nur eine Komponente auf dem Root-GameObject des Prefabs sollte Komponenten in Kindern brauchen, und Kinder-Komponenten sollten nur Komponenten im Root-GameObject brauchen.
  3. Komponenten sollten in der Hierarchie einmalig (oder allesamt gleichwertig) sein, oder wird wieder eine Design-Entscheidung welche die richtige ist, und dann gibt's einfach wieder ein serialisiertes Feld.

@Kurumi-chan Ich weiß nicht, ob du die kennst, aber GetComponentInParent und GetComponentInChildren suchen dir im der ganzen Hierarchie des jeweiligen GameObjects bestimmte Komponenten heraus. Wenn du also wie im Beispiel eine "PlayerAnimatorController"-Komponente hast, kann sie den übergeordneten "PlayerMovementController" mit GetComponentInParent finden.

Edit, Nachtrag: Deswegen ja Prefabs. Da setzt du einmal eine kleine Handvoll Referenzen, und das Script, das die benutzt, arbeitet dann damit. Das sind dann bei vielen Prefans auch viele Referenzen, aber die kosten erstmal quasi nichts. Wichtig ist da nur, wie viel Handarbeit du hast. Und die sollte überschaubar sein.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Wow, danke dir Sascha, für deinen ausführlichen Beitrag. Und danke, das du das Tool zur Verfügung stellst. Werde mir das auch mal ansehen um von deinem Code zu lernen, wenn das ok ist 🙂

Prefabs benutze ich derzeit 2 Stück, einmal das, welches für die stats verantwortlich ist und einmal der Itembutton, da diese über Schleifen erstellt werden. Gäbe es in diesem Beispiel noch mehr, was du mit Prefabs lösen würdest? Ich möchte halt immer genau wissen, welche Button ausgewählt wurden, um mein zurückgegebn, diesen selektieren zu können, bzw. um beim moven schon den neuen Inhalt zu laden und erst beim drücken dann auch das entsprechende Menü anzuzeigen. Denke, dass dies dann nicht mehr so leicht möglich ist? Vor allem, weil ich das alles in einem Skript habe, quasi der MenuManger.

Also ich kenne Sie dem Namen nach, da VS sie ja regelmäßig anzeigt, aber deren genauen nutzen kenne ich nicht, bzw. ich habe mich generell nicht damit auseinandergesetzt. Bei Component habe ich halt das Problem gesehen, dass es zu viele davon gibt und ich nicht sicher weiß, welche er jetzt gefunden hat. Ich arbeite im Moment wie oben erwähnt nur mit 2 Skripten. Einmal halt Items und einmal Manager. Und wenn ich von Manager, der ganz oben liegt, jetzt nach einer Komponente suchen würde, bekomme ich ja ohne ende angeboten 😄 Und ich habe irgendwo mal gelesen/gehört, dass sowas auch gerne mal zufällig ausgesucht wird.

Aber ich werde jetzt auf jeden fall mal versuchen, deine Tipps umzusetzen und meinen Code umzustrukturieren. Du hast sehr geholfen, herzlichen dank dafür ☺️ 

Wird wieder viel Arbeit, da ja auch schon einiges gemacht wurde, aber was muss, dass muss 🙂 

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 13 Minuten schrieb Kurumi-chan:

Wow, danke dir Sascha, für deinen ausführlichen Beitrag. Und danke, das du das Tool zur Verfügung stellst. Werde mir das auch mal ansehen um von deinem Code zu lernen, wenn das ok ist 🙂

Freut mich! Und natürlich darfst du den Code studieren ;)

vor 14 Minuten schrieb Kurumi-chan:

Gäbe es in diesem Beispiel noch mehr, was du mit Prefabs lösen würdest?

Hmm nö, klingt gut. Prefabs sollte man halt immer dann in Erwägung ziehen, wenn eine Art GameObject bzw. eine gleichartige GO-Hierarchie mehrere Male vorkommen wird. Entweder gleichzeitig (Münzen in einem Mario-Level), oder nacheinander (ein UI-Popup), oder einfach nur in mehreren Szenen (die Spielfigur). Einfach, damit man etwas daran ändern kann und diese Änderung überall gilt, wo man das Ding benutzt.

vor 50 Minuten schrieb Kurumi-chan:

ch möchte halt immer genau wissen, welche Button ausgewählt wurden, um mein zurückgegebn, diesen selektieren zu können, bzw. um beim moven schon den neuen Inhalt zu laden und erst beim drücken dann auch das entsprechende Menü anzuzeigen.

Sorry, irgendwie komme ich da nicht so ganz mit 😅

vor 50 Minuten schrieb Kurumi-chan:

Vor allem, weil ich das alles in einem Skript habe, quasi der MenuManger.

Joa... da rate ich halt auch grundsätzlich von ab :)

vor 1 Stunde schrieb Kurumi-chan:

dass sowas auch gerne mal zufällig ausgesucht wird.

Nee, zufällig ist das nicht. Aber undefiniert. Unity wird dir vermutlich immer dasselbe in derselben Situation geben, aber du hast halt keine Garantie dafür, dass das immer so bleibt. Vielleicht kriegst du im Build etwas anderes als im Editor, oder das Ergebnis ändert nach einem Update der Unity-Version. Da du dich darauf also nicht verlassen kannst, solltest du es meiden.

vor 2 Stunden schrieb Kurumi-chan:

Einmal halt Items und einmal Manager. Und wenn ich von Manager, der ganz oben liegt, jetzt nach einer Komponente suchen würde, bekomme ich ja ohne ende angeboten

Naja, du behandelst sie ja alle gleich, oder? In dem Fall kannst du mit GetComponentsInChildren alle Komponenten eines Typs in allen Kind-Objekten finden und kriegst ein Array zurück, über das du iterieren kannst. Wenn dein Script ansonsten die Prefab-Instanzen selber spawnt, hast du ja auch schon alle Referenzen zur Hand.

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 7 Stunden schrieb Sascha:
vor 10 Stunden schrieb Kurumi-chan:

Ich möchte halt immer genau wissen, welche Button ausgewählt wurden, um mein zurückgegebn, diesen selektieren zu können, bzw. um beim moven schon den neuen Inhalt zu laden und erst beim drücken dann auch das entsprechende Menü anzuzeigen.

Sorry, irgendwie komme ich da nicht so ganz mit 😅

Also im Bild ist ja zu sehen, dass das Untermenü Waffen ausgewählt ist und der Button dazu eben auch. Wenn ich nun von den einzelnen Waffen zurück ins Submenü wechsle, dann wird auch wieder Waffen ausgewählt, das gleich bei allen anderen Items. Wenn ich dort jetzt hoch zu den Konsumgütern move (ist reine Tastatursteuerung), dann werden schon dir Konsumgüter geladen und angezeigt, ohne das ich diesen Button drücke. Wenn ich nun zurück ins Hauptmenü wechseln würde, dann ist erstmal Items ausgewählt. Würde ich dort jetzt das moven, dann würde auch dort der neue Inhalt geladen werden, z.B. die Gruppe, oder der Status, ohne das ich den Button drücken muss. Das Drücken des Buttons läd in dem Fall lediglich das entsprechende Menü des Untermenüs.

 

vor 7 Stunden schrieb Sascha:
vor 10 Stunden schrieb Kurumi-chan:

Vor allem, weil ich das alles in einem Skript habe, quasi der MenuManger.

Joa... da rate ich halt auch grundsätzlich von ab :)

Boah, da hab ich gar keine Ahnung, wie ich das anders machen sollte 😕 

 

vor 7 Stunden schrieb Sascha:
vor 10 Stunden schrieb Kurumi-chan:

dass sowas auch gerne mal zufällig ausgesucht wird.

Nee, zufällig ist das nicht. Aber undefiniert. Unity wird dir vermutlich immer dasselbe in derselben Situation geben, aber du hast halt keine Garantie dafür, dass das immer so bleibt. Vielleicht kriegst du im Build etwas anderes als im Editor, oder das Ergebnis ändert nach einem Update der Unity-Version. Da du dich darauf also nicht verlassen kannst, solltest du es meiden.

Hmm, ok. tatsächlich nicht ganz, Main- und Submenü werden leicht anders behandelt und im Main die Gruppe, aber ich denke ich könnte da trotzdem noch ein wenig optimieren

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hey, ich habe das Menü jetzt umgeschrieben und wollte kurz das Ergebnis zeigen.

Habe es jetzt in 2 Scripts aufgeteilt und zwar ein ItemMenu, welches auf der obersten Ebene des Itemmenüs liegt und in Item, welches auf dem jeweiligen Item Button liegt. Ebenso habe ich jetzt alle Itemkategorien in einem Panel. Vorher waren das jedes mal eigenständige Panels.

Ich hoffe, dass es so jetzt schon um einiges besser ist. Persönlich muss ich sagen, dass ich es so auf jeden fall wesentlich übersichtlicher finde.

Hier mal die Scripts

using TMPro;
using System;
using UnityEngine.UI;
using UnityEngine;

public class Item : MenuBase
{
    private ItemMenu itemMenu;
    private int amount;
    private ItemSO item;
    private GameObject[] p1;
    private GameObject[] p2;

    void Awake()
    {
        itemMenu = ItemMenu.instance;
    }

    void Start()
    {
        p1 = new GameObject[4];
        p2 = new GameObject[4];
        RefreshCurrentItem();
    }

    public void LoadItemContent()
    {
        Clear();
        itemMenu.itemText.transform.GetComponent<TMP_Text>().text = item.itemName;
        itemMenu.itemAmountText.transform.GetComponent<TMP_Text>().text = amount.ToString();
        itemMenu.itemDescriptionText.transform.GetComponent<TMP_Text>().text = item.itemDescription;
        itemMenu.itemIcon.transform.GetComponent<Image>().sprite = item.icon;
        itemMenu.targetPanel.transform.GetChild(1).GetComponent<TMP_Text>().text = item.targetAll ? "Alle" : "Einzeln";
        LoadStatsPanel();
    }

    public void OnCancel()
    {
        GameMenu.instance.OnSubCancel();
        itemMenu.infoPanel.SetActive(false);
    }

    private void LoadStatsPanel()
    {
        if (item.stats.Count > 0)
        {
            for (int i = 0; i < item.stats.Count; i++)
            {
                p1[i] = Instantiate(itemMenu.statsPrefab, itemMenu.paramPanel.transform);
                p1[i].name = "Stat" + i.ToString();
                p1[i].transform.GetChild(0).GetComponent<TMP_Text>().text = item.stats[i].paramName;
                p1[i].transform.GetChild(1).GetComponent<TMP_Text>().text = item.stats[i].paramValue.ToString();
            }
        }
        if (item.specialStats.Count > 0)
        {
            for (int i = 0; i < item.specialStats.Count; i++)
            {
                p2[i] = Instantiate(itemMenu.statsPrefab, itemMenu.sParamPanel.transform);
                p2[i].name = "Stat" + i.ToString();
                p2[i].transform.GetChild(0).GetComponent<TMP_Text>().text = item.stats[i].paramName;
                p2[i].transform.GetChild(1).GetComponent<TMP_Text>().text = item.stats[i].paramValue.ToString();
            }
        }
    }

    private void RefreshCurrentItem()
    {
        int i = Convert.ToInt32(this.gameObject.name.Substring(4));
        item = GameManager.instance.GetInventory()[i].Item1;
        amount = GameManager.instance.GetInventory()[i].Item2;
    }

    private void Clear()
    {
        if (p1.Length != 0)
        {
            for (int i = 0; i < p1.Length; i++)
            {
                Destroy(p1[i]);
            }
        }

        if (p2.Length != 0)
        {
            for (int i = 0; i < p2.Length; i++)
            {
                Destroy(p2[i]);
            }
        }
    }
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using System;
using UnityEngine.UI;

public class ItemMenu : MonoBehaviour
{
    public GameObject header;
    public GameObject itemList;
    public GameObject itemBtn;
    public GameObject infoPanel;

    [Header("Info Texts")]
    public GameObject itemText;
    public GameObject itemAmountText;
    public GameObject itemDescriptionText;
    public GameObject itemIcon;

    [Header("Stats Panels")]
    public GameObject statsPanel;
    public GameObject paramPanel;
    public GameObject sParamPanel;
    [Space(5)]
    public GameObject targetPanel;
    [Space(5)]
    public GameObject statsPrefab;

    private List<(ItemSO, int)> inventory;
   
    private int itemType;
    private int lastItemType;
    public static ItemMenu instance;
    
    void Start()
    {
        instance = this;
        itemType = 0;
        lastItemType = 0;
        inventory = GameManager.instance.GetInventory();
        CreateItemButtons();
    }

    void Update()
    {
        if(itemType != lastItemType)
        {
            lastItemType = itemType;
            Refresh();
        }
    }

    public void SetCurrentItem(GameObject item)
    {
        itemBtn = item;
    }

     public void OnButtonSelected(int i)
    {
        itemType = i;
        GameMenu.instance.SetSubBtnIndex(i);
    }

    private void Refresh()
    {
        Clear();
        CreateItemButtons();
    }

    public void CreateItemButtons()
    {
        int items = inventory.Count;
        switch (itemType)
        {
            case (int)ItemType.Items:
                for (int i = 0; i < items; i++)
                {
                    if ((int)inventory[i].Item1.itemType == (int)ItemType.Items)
                    {
                        LoadItemData(i);
                        targetPanel.SetActive(true);
                        statsPanel.SetActive(false);
                    }
                }
                break;
            case (int)ItemType.Weapons:
                for (int i = 0; i < items; i++)
                {
                    if ((int)inventory[i].Item1.itemType == (int)ItemType.Weapons)
                    {
                        LoadItemData(i);
                        targetPanel.SetActive(false);
                        statsPanel.SetActive(true);
                    }
                }
                break;
            case (int)ItemType.Armors:
                for (int i = 0; i < items; i++)
                {
                    if ((int)inventory[i].Item1.itemType == (int)ItemType.Armors)
                    {
                        LoadItemData(i);
                        targetPanel.SetActive(false);
                        statsPanel.SetActive(true);
                    }
                }
                break;
            case (int)ItemType.Accessory:
                for (int i = 0; i < items; i++)
                {
                    if ((int)inventory[i].Item1.itemType == (int)ItemType.Accessory)
                    {
                        LoadItemData(i);
                        targetPanel.SetActive(false);
                        statsPanel.SetActive(true);
                    }
                }
                break;
            case (int)ItemType.KeyItems:
                for (int i = 0; i < items; i++)
                {
                    if ((int)inventory[i].Item1.itemType == (int)ItemType.KeyItems)
                    {
                        LoadItemData(i);
                        targetPanel.SetActive(false);
                        statsPanel.SetActive(false);
                    }
                }
                break;
        }
    }

    private void LoadItemData(int index)
    {
        var btn = Instantiate(itemBtn, itemList.transform);
        btn.name = "Item" + index.ToString();
        btn.transform.GetChild(1).GetComponent<Image>().sprite = inventory[index].Item1.icon;
        btn.transform.GetChild(2).GetComponent<TMP_Text>().text = inventory[index].Item1.itemName;
        btn.transform.GetChild(3).GetComponent<TMP_Text>().text = inventory[index].Item2.ToString();
    }
    private void Clear()
    {
        if (itemList.transform.childCount > 0)
        {
            for (int i = 0; i < itemList.transform.childCount; i++)
            {
                Destroy(itemList.transform.GetChild(i).gameObject);
            }
        }
    }
}

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Archiviert

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

×
×
  • Neu erstellen...