Jump to content
Unity Insider Forum

Was ist ein Singleton


ollimolli

Recommended Posts

Servus miteinander!

Ich hätte eine Frage, die ich mir leider selber nicht ganz beantworten kann. Und zwar, was bitte ist ein Singleton? Ist das eine Globale Schnittstelle für Variablen zum Verwenden, oder ein Speicher? Oder was macht das? 

Ein Kollege von mir meinte, ich sollte diesen ja verwenden, da es einfach ist, Variablen in anderen Scripts verwenden zu können, OHNE großen Murks zu machen.

Ich danke im Vorraus für jegliche Antworten.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hallo und Herzlich willkommen,

Ein Singleton wird normalerweise so, oder so ähnlich eingeleitet:

public class Singleton : MonoBehaviour
{
    public static Singleton instance;
    private void Awake()
    {
        if(instance != null && instance != this)
        {
            Destroy(this);
        }
        else
        {
            instance = this;
        }
    }
}

Damit hast du das Grundgerüst. Das kannst du auf ein GameObject in deiner Scene ziehen auf das andere Objecte zugreifen können. Hat dein Singleton also beispielsweise eine Methode wie:

    public void DoSomething()
    {
        //Ich tue Dinge
    }

können deine Objecte so darauf zugreifen:

Singleton.instance.DoSomething();

 

Damit kannst du eine Schnittstelle erstellen ohne dass du großartig dieses eine Object suchen musst oder erst über den Inspector zugreifen musst.

Hat dein Singleton beispielsweise einen int points, könnten in einem Tower Defence Game die Gegner die getötet werden einfach das Singleton aufrufen und eine Methode auf dem Singleton ausführen um die Punkt (,Gold etc.) zu erhöhen.

Alternativ könntest du eine Static Class erstellen die letzlich das gleiche ist, aber das kann nicht von MonoBehaviour erben wordurch einzelne Methoden nicht ausgeführt werden können.

Was du damit auch machen kannst, Also mit einem Singleton, du kannst diesem Objekt andere Objekte zuweisen. Zum Beispiel den Spielern, um immer zugriff auf ihn zu haben. Also, um im Beispiel eines Tower Defence zu bleiben, ein Gegner läuft ins Ziel und der Spieler soll einen Lebenspunkt verlieren (der Sich auch auf der Karte herum bewegen kann) Dann muss du nur die Zuweisung machen, dass das Singleton den Spieler kennt aber der Gegner muss nicht erst den Spieler suchen um ihm dann einen Schaden zuzufügen.
(Ja das letzte beispiel wäre warscheinlich sowieso besser über das EventSystem aber, you got the point)

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hey, da hab ich doch mal einen Blogeintrag zu geschrieben :)

http://blog.13pixels.de/2019/unity-and-the-mysterious-singleton/

Aber ich erzähl trotzdem mal.

vor 5 Stunden schrieb ollimolli:

Ein Kollege von mir meinte, ich sollte diesen ja verwenden, da es einfach ist, Variablen in anderen Scripts verwenden zu können, OHNE großen Murks zu machen.

Das klingt furchtbar. Ich hoffe für deinen Kollegen, dass du seine Aussage nur nicht gut verstanden hast :D

Ein Singleton (bzw. das, was unter Unity-Entwicklern als solches bezeichnet wird) ist dazu da, dass eine Referenz auf ein einzigartig gemeintes Objekt global bekannt gemacht wird. Einzigartig könnte z.B. die Spielerfigur, die Ziellinie oder die Lebensanzeige des Spielers im UI sein. Mit diesem Muster kannst du Objekten der jeweiligen Klasse sagen, dass sie sich im Code als "DAS" Objekt dieser Sorte anmelden sollen. Jeglicher anderer Code kann dann auf dieses Objekt direkt zugreifen, ohne es erst heraussuchen zu müssen. Hast du im Vergleich dazu eine beliebige Menge von Objekten (z.B. Zombies), dann kannst du schlecht einfach so auf eines dazu zugreifen, weil der Computer natürlich nicht raten will, welchen der im Zweifelsfall vielen Zombies du gerade meinen könntest.

Nun stellt sich aber, wenn man lang genug über das Thema nachdenkt, eben jenes heraus:

vor 5 Stunden schrieb Singular:

Alternativ könntest du eine Static Class erstellen die letzlich das gleiche ist

und genau das ist auch die Hauptkritik am originalen (nicht-Unity-bezogenen) Singleton-Pattern. Wenn man verstanden hat, was eine statische Klasse ist, dann ist es nicht schwer zu erkennen, dass eine Objekt, von dem immer nur eines existieren soll, im Prinzip dasselbe macht wie statische Klasse (was ein Ding ist, das Daten speichern oder Methoden haben kann, und es kann immer nur eine davon pro Sorte im Programm geben).

Im Unity-Kontext gibt es ein paar mehr Argumente für dieses Pattern. Zum einen können nur Component-Instanzen MonoBehaviour-Events (wie z.B. Update) empfangen und zum anderen kann man nur auf Objekten in der Szene, Prefabs oder ScriptableObjects Werte serialisieren (bzw. anders gesagt: im Editor eingeben). Wenn du z.B. eine Textur aus den Assets im Editor zuweisen willst, geht das nicht in eine statische Klasse hinein.

Wenn man versucht, sich zu diesem Pattern zu informieren, dann hört man gefühlt sehr viele Stimmen die entweder aus ihrer begrenzten Erfahrung heraus voll dafür sind, oder andere die immer mal wieder gehört haben dass das schlecht sein soll und das deswegen verteufeln.

Meiner Meinung nach gibt es Situationen, in denen die Anwendung sinnvoll ist, aber auch sehr oft Leute, die das Ding voll toll finden, es viel zu oft benutzen und sich dann wundern warum ihr Projekt so schlecht voran geht. Besonders übel sind die "Manager"-Klassen. Das sind dann oft so Klassen, die gar nicht großartig Teil der Szene sind, sondern einfach nur da sind, um irgendeine globale Aufgabe zu erfüllen. Zum Beispiel der "LevelManager". Der hat dann Methoden zum Laden verschiedener Level. Und da wird's dann halt echt lächerlich, weil eine statische Klasse genau dasselbe machen kann, ohne dass man deine Szenen-Hierarchie zumüllt. Wenn dich das weiter interessiert, empfehle ich dir dafür das Video meinem verlinkten Artikel.

Warum ich erstmal das Gesicht verzogen hab, als ich das Zitat von deinem Kollegen gelesen habe: Wir hatten hier früher mal so Anfängertutorials, von denen eines genau dieses Pattern vorgeschlagen hat, damit ein Knopf eine Tür öffnen kann. Wir hatten dann alle Nase lang die Frage, wie man das denn machen soll, wenn man zwei Türen und zwei Knöpfe hat. An dieser Stelle war das Pattern nämlich total fehl am Platz. Darum: Static und das Singleton-Pattern sind nicht dazu da, dir das Leben einfacher zu machen, wenn es generell darum geht, auf andere Objekte zuzugreifen. Sie erlauben es dir, "das eine" Objekt einer Sorte (den einen Spieler, die eine Health Bar) global zugreifbar zu machen, sodass man da nicht unnötig Referenzen definieren und Objekte suchen muss. Und wenn du ein Objekt hast, das sowieso Teil deiner Szene sein soll, weil es dort hineingehört, und du dir sicher bist, dass du davon immer nur eines zur Zeit haben wirst - dann kannst du dieses Pattern guten Gewissens verwenden. Der einzige Fehler, den du nicht begehen solltest, ist, das Ding ständig zu benutzen ohne jedes Mal kurz darüber nachzudenken, ob das an dieser Stelle nicht totaler Quark ist.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Singleton kurz und knapp bedeutet, dass man SICHER stellt, dass ein Objekt nur EINMAL existiert. Dabei ist egal, ob du das selber erstellt hast oder direkt eine Funktion aufrufst.

In Unity mit GameObject bedeutet das, dass dein Monobehaviour überprüft, ob überhaupt eine Instanz existiert. Wenn nicht, wird dann ein GameObject erstellt, dann Component hinzugefügt und nun ist es verfügbar.

Die Frage ist, wie kannst du das überprüfen? Dazu wird static variable verwendet. Der übliche (simple) Weg wurde schon durch @Singular gezeigt. 

Ein Singleton kann, aber muss nicht von außen erreichbar sein. Beispiel Playerlist.Instance.Players wäre von außen. Nun kann man das anders machen. Playerlist.Players. Jetzt greifst du auf etwas zu, aber lässt die Instance raus, sonst könnte man auch auf Member zugreifen. Vllt will man ja damit nur intern arbeiten.

Meine Empfehlung bei sowas ist, dass man den selbst initialisieren sollte. Beispiel AudioPlayer.Init(). Nun AudioPlayer.Clips. Allerdings ist das bei Unity natürlich problematisch. Was wenn man eine Scene testen möchte, aber AudioPlayer.Init() erst im Hauptmenu aufgerufen wird? Schon kommen die erste Problem (aber dafür gibt es einige Lösungen.. Gott sei Dank :D)

WANN sollte man das verwenden?
Ein Beispiel wo es sinn macht. AudioPlayer.Play("pfad/zu/mein/sound.wav", volume, pitch, isLoop). Das ist ein gutes Beispiel. Mein ParticleSpawn System funktioniert so ähnlich.
Ein besseres Beispiel wäre, wenn es überhaupt kein GameObject oder so erstellen muss. Beispiel Steamworks von Facepunch funktioniert ohne GameObject und ist von überall zugreifbar.

WANN sollte man das NICHT verwenden?
Ein Beispiel Player Klassen um da auf Variable zuzugreifen.
PlayerHealthComponent.instance.Health.

Ich benutze selbst sehr selten Singletons, aber ich habe auch dafür eine Technik entworfen, welches mir gewährleistet, dass es überall, egal in welcher Scene ich bin auch funktioniert. Leider brauche ich dafür ScriptableObject (geht auch ohne, aber finde ich unschön).

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ich glaube zunehmend, dass ich meinen Singleton-Artikel mal überarbeiten muss.

Ich bleibe dabei, dass es wichtig ist, das im Gang of Four-Buch vorgestellte Pattern klar von dem zu trennen, was die Leute so in Unity machen. Einfach schon, weil es im Unity-Kontext Serialisierung und MonoBehaviour nur für Instanzen gibt.

Was im Artikel noch fehlt ist, dass ich mir angewöhnt habe, dass Code möglicht wenig Restriktionen im Editor erzeugen soll. Deshalb finde ich das Standardpattern für Unity- Singletons auch so doof - der Code tut nur was er soll, wenn man auch ja nicht vergisst, das Ding in die Szene zu schmeißen. Wenn man wie @MaZy ein anderes Pattern benutzt, mit dem der Code tut, egal was mit im Editor anstellt... dann sieht das schon gleich ganz anders aus.

Ein Beispiel für so ein Pattern wäre dieses:

public static class MusicPlayer
{
  private static AudioSource audioSource;
  
  [RuntimeInitializeOnLoadMethod]
  private static void Initialize()
  {
    var go = new GameObject("Music Player");
    go.AddComponent<AudioSource>();
    go.hideFlags = HideFlags.HideAndDontSave;
    Object.DontDestroyOnLoad(go);
  }
  
  public static void PlayMusic(AudioClip clip)
  {
    audioSource.Stop();
    audioSource.clip = clip;
    audioSource.Play();
  }
}

Das ist halt im Prinzip auch ein Singleton, und es ist auch sinnvoll, weil eine statische Klasse halt keine AudioSource haben kann - dazu braucht man eine GameObject-Instanz. Allerdings erzeugt das Ding sich sein GameObject automatisch bei Spielstart, macht es unsterblich und versteckt es. Im Editor kriegst du nichts davon mit. Und vor allem muss keiner daran denken, das Ding auch in eine Szene einzufügen, damit der Code tut was er soll.

Es bleibt nur noch Serialisierung als Problem, das ich für mich über SettingsProvider gelöst habe - ich kann meinen statischen (oder beliebigen anderen) Klassen damit in den Project Settings Referenzen und andere Werte zuweisen. Hab das in Soda eingebaut *hust* :D

Lange Rede, kurzer Sinn... ich muss mal den Artikel updaten.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Archiviert

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

×
×
  • Neu erstellen...