Jump to content
Unity Insider Forum

Soda - ScriptableObject Dependency Architecture


Sascha
 Share

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 to comment
Share on other sites

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 to comment
Share on other sites

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 to comment
Share on other sites

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 to comment
Share on other sites

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 to comment
Share on other sites

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 to comment
Share on other sites

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 to comment
Share on other sites

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 to comment
Share on other sites

  • 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.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

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

Loading...
 Share

×
×
  • Create New...