Jump to content
Unity Insider Forum

Soda - ScriptableObject Dependency Architecture


Recommended Posts

Hallo

Hier die ganze Klasse:

 

using System;
using System.Collections.Generic;
using ThirteenPixels.Soda.ModuleSettings;
using UnityEngine;

namespace Chrische
{
    public class Log : ModuleSettings
    {
        #region Variables

        [SerializeField]
        private  List<string> _allUserEntries = new List<string>();
        
        [SerializeField]
        private  List<string> _absolutAllEntries = new List<string>();
        
        private readonly Dictionary<string, List<string>> _dicEntries = new Dictionary<string, List<string>>();

        protected override string title => "My Log";

        #endregion
        

        public void Write(string entry)
        {
            var wholeEntry = DateTime.Now + " " + entry;
            _allUserEntries.Add(wholeEntry);
            _absolutAllEntries.Add(wholeEntry);
        }
    
        public void Write(string tree, string entry)
        {
            var wholeEntry = DateTime.Now + " " + entry;
            _absolutAllEntries.Add(wholeEntry);
            if (tree != "Debug")
            {
                _allUserEntries.Add(wholeEntry);
            }
            if (!_dicEntries.ContainsKey(tree))
            {
                _dicEntries.Add(tree, new List<string>());
            }
            _dicEntries[tree].Add(wholeEntry);
        }
        
        public void WriteDebug(string message, string className)
        {
            Debug.Log("#" + className + "#: " + message);
            Write("Debug", message);
        }

        #region Properties

        public List<string> AllUserEntries => _allUserEntries;

        public List<string> AbsoluteAllEntries => _absolutAllEntries;

        public Dictionary<string, List<string>> DicEntries => _dicEntries;

        #endregion
    }
}

 

Christoph

Link zu diesem Kommentar
Auf anderen Seiten teilen

Dafür ist das aber nicht da xD

Wenn du Logs im Editor haben willst, nimm Debug.Log und filtere ggf. die Konsole :)

Serialisierte Felder sind dafür da, dass du Daten im Editor einstellen kannst und diese dann im Spiel vorhanden sind. Wenn du die nur zum Anzeigen von Daten haben willst, dann produzierst du damit nur Datenmüll in deinem Build. Wenn die normale Konsole dir nicht übersichtlich genug ist, schau dir Console Pro an oder mach sonst ein eigenes EditorWindow auf.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Naja, so halb.

Die Frage war ja, warum das für statische Service-Klassen gedacht ist und warum man nicht einfach das SO besorgt und für sich arbeiten lässt. ScriptableObjects sind halt grundsätzlich als stumpfe Datenhalte mit minimaler Funktionalität gedacht. Wenn du also ein SO vom ModuleSettings-System besorgst, dann kannst du damit entweder nichts sinnvolles machen außer dessen Daten auslesen, oder du hast die Klasse zu mächtig gemacht.

Die statische Service-Klasse ist außerdem ein zentraler Punkt in deinem Projekt, wie z.B. die alte Input-Klasse. Die existiert halt einmalig im Programm und kann von jedem genutzt werden. Die ModuleSettings.Get-Methode ist gleichermaßen von überall aus verwendbar. ModuleSettings.Get zu benutzen, um eine Referenz auf ein Objekt zu kriegen, das sowieso nur einmal existieren soll, ist irgendwie ein Umweg. Und es setzt in jeder verwendenden Klasse Kenntnis über das ModuleSettings-System voraus. Anstatt also Input.GetAxis oder Mathf.Clamp zu schreiben, müsstest du irgendwo UnityServices.Get<Mathf>() schreiben und dann das Ding benutzen, was der zurückgibt. Eine Referenz, die jede Code-Stelle für sich noch einmal neu holen und speichern muss. Es ist einfach ein unnötiger Extraschritt.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hallo

Ich muss noch einmal nachfragen, bitte entschuldige mich, wenn die Frage unsinnig sein sollte. Du sagst also, dass es für den Compiler ein Unterschied ist, ob ich:

GameService.GetLog().WriteDebug("no textMesh for news attached", name);

oder

ModuleSettings.Get<Log>().WriteDebug("no textMesh for news attached", name);

schreibe?

Wenn nicht, verstehe ich den "Umweg" über die statische Klasse nicht, weil das ja ein zusätzliches Element in meinem Projekt ist, welches ich gar nicht brauche.

Wäre das was anderes?

public static class GameService
    {
        private static Log _log;
        static GameService()
        {
            _log = ModuleSettings.Get<Log>();
        }
        public static Log GetLog()
        {
            return _log;
        }
    }

Hier würde ich den Unterschied verstehen, weil ich bei GetLog() ja nicht immer Get<Log> nutze.

Eventuell stehe ich komplett auf dem Schlauch...

 

Christoph

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 3 Stunden schrieb chrische5:

Ich muss noch einmal nachfragen, bitte entschuldige mich, wenn die Frage unsinnig sein sollte.

Keine Sorge :)

vor 3 Stunden schrieb chrische5:

Du sagst also, dass es für den Compiler ein Unterschied ist

Nein, es geht hier nicht um Performance oder so etwas. Hier geht es nur um simplen und nachvollziehbaren Code. Und darum, Dinge zu benutzen, die für die Situation angemessen sind. Das verringert den Spielraum für Fehler. Deswegen sind Singletons (also: richtige Singletons, nicht das, was man bei Unity als Singleton kennenlernt) ja auch so doof. Statische Klassen gibt's exakt einmal im Programm und man kann keine Instanzen davon erzeugen. Eine nicht-statische Klasse ist dafür da, um eine beliebige Anzahl an Objekten zu erzeugen. Wenn man für einen Service, denes garantiert nur einmal im Programm geben soll, eine nicht-statische Klasse benutzt, dann ist das wirklich kein Beinbruch. Aber es ist halt so ein bisschen an dem vorbei, wofür es da ist. Du kannst halt auch wunderbar mit einer Kneifzange Nägel in die Wand schlagen.

Dazu sei gesagt, dass es je nach Kontext andere Regeln gibt. Wenn du z.B. mit Zenject arbeitest, weil du irgendwie Interfaces für Services haben willst, dann benutzt du halt trotzdem Instanzen statt statischer Klassen für deine Service Provider.

vor 3 Stunden schrieb chrische5:

Wenn nicht, verstehe ich den "Umweg" über die statische Klasse nicht, weil das ja ein zusätzliches Element in meinem Projekt ist, welches ich gar nicht brauche.

Da gibt's halt verschiedene Ebenen. Ja, es ist eine Klasse mehr, die übrigens dafür da ist, technische Innereien anderer Systeme zu verpacken. Dass deine Komponenten halt nichts darüber wissen müssen, dass es das ModuleSettings-System gibt. Der Umweg, den du damit aber sparst, ist der zusätzliche Aufruf und ggf. auch das zusätzliche Feld, um die Referenz zu speichern.

Ohne statische Klasse:

public void Foo()
{
  var logger = ModuleSettings.Get<Logger>();
  logger.Log("Foo");
}

oder

private Logger logger;

private void Awake()
{
  logger = ModuleSettings.Get<Logger>();
}

public void Foo()
{
  logger.Log("Foo");
}

Mit statischer Klasse:

public void Foo()
{
  Logger.Log("Foo");
}

Stell dir halt vor, du müsstest auch bei Unity ständig Unity.Get<Mathf>() oder Unity.Get<Debug>() machen, um Mathf.Clamp oder Debug.Log aufrufen zu können. An dieser Stelle, also bei der Verwendung, steckt der unnötige Umweg.

Die statische Klasse würde dann eben auch das ScriptableObject verbergen, anstatt es rauszugeben, und den Service selber anbieten. Das ScriptableObject soll halt keine Funktionalität haben und nur Daten abspeichern und rausrücken. Du hast jetzt in deinem Beispiel eine statische Klasse, die lauter Services anbietet. Die Idee ist aber, dass du eine Klasse pro Service hast.

Also statt

public static class GameService
{
  private static Log _log;
  
  static GameService()
  {
    _log = ModuleSettings.Get<Log>();
  }
  
  public static Log GetLog()
  {
    return _log;
  }
}

gibt's

public static class Log
{  
  private static LogSettings settings;
  
  static GameService()
  {
    settings = ModuleSettings.Get<LogSettings>();
  }
  
  public static void Write(string s)
  {
    // ...
  }
}

Deswegen heißt das System auch "ModuleSettings", weil es dafür da ist, Einstellungen zu laden, nicht um die Services selbst zu verwalten.

Das ist aber halt schon pingelig sein auf sehr hohem Niveau. Ist also wirklich kein Beinbruch, wenn du das Warum nicht so ganz sehen kannst. Aber ich hoffe, die Begründung ist jetzt etwas einleuchtender?

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hallo

 

Ahh. Okay. So langsam verstehe ich. Die Idee mit dem GAmeService kam mir auch falsch vor, weil es am Ende ja nur einen anderer Name für Manager ist. Hältst du eine solche Logklasse also eher für ungeeignet für die Settings? Viel mehr als Daten halten, macht die am Ende aber auch nicht. Mit Settings hat es allerdings wenig zu tun.

 

Christoph 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Die Settings sind dafür da, Daten, die man nur im Editor einstellen kann (also Referenzen auf Assets) in seinen Code zu schleusen, ohne dafür irgendetwas in irgendeine Szene einbauen zu müssen. Alle alternativen Wege dafür bedeuten entweder ein Setup, das maintained werden muss. Entweder ein verpflichtendes Einfügen irgendwelcher System-Prefabs in jede neue Szene, eine Initialisierungs-Szene oder Assets im Resources-Ordner. Alle diese Varianten bedeuten, dass da ein im Editor sichtbarer Teil des Projekts nicht angefasst werden darf, wenn man sich im Code nicht auskennt, weil sonst der Code aufhört zu funktionieren. Ich will halt keinem Level-/Game-/UI-Designer sagen müssen "Finger weg von der Build List, dieser Szene und/oder diesem Resources-Ordner". Dagegen sind Code-Dateien und Project Settings für Leute, die keine Ahnung von Code haben, sowieso schon mehr oder weniger tabu :)

Um aber mal direkt zu antworten: Wenn dein dein Log-System aus dem Assets-Ordner nichts braucht, dann ist es in der Tat eher weniger ein Kandidat für das ModuleSettings-System. Allerdings spricht nichts dagegen, auch andere Einstellungen (Zahlenwerte oder Strings oder so) über ModuleSettings einzustellen. Wenn du davon augehst, dass sich diese Einstellungen immer mal wieder während der Entwicklung ändern, dann kannst du es damit vermeiden, den Code immer wieder neu kompilieren und warten zu müssen.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Nö, einfach nur eine statische Klasse.

Ein Pseudo-Singleton wäre ja nur dann eine Idee, wenn du einen Grund hast, das Ding in der Szene aufzubewahren. Und ein Soda-ScriptableObject ergibt nur dann Sinn, wenn du einen Vorteil davon hast, das über den Editor zu injecten. Aber ein Log-System ist etwas sehr technisches, das im Editor gar nicht sichtbar sein sollte. Du siehst ja auch von Unitys Debug-Klasse keine Spur im Editor.

Link zu diesem Kommentar
Auf anderen Seiten teilen

  • 3 months later...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Gast
Auf dieses Thema antworten...

×   Du hast formatierten Text eingefügt.   Formatierung jetzt entfernen

  Only 75 emoji are allowed.

×   Dein Link wurde automatisch eingebettet.   Einbetten rückgängig machen und als Link darstellen

×   Dein vorheriger Inhalt wurde wiederhergestellt.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Lädt...
×
×
  • Neu erstellen...