Hey Garzec,
ja eine Trennung von Logik und Daten macht Sinn. Ich habe hierfür ein paar praktische Faustregeln, die mir als Entwickler schon bei vielen Projekten deutlich geholfen haben:
1. Klassen die von MonoBehaviour ableiten sind lediglich für Referenzen zu Objekten in der Szene (GameObject, Transform, Button, Camera etc. pp.) zuständig
2. Einstellbare Daten gehören in ein ScriptableObject und werden im Projekt-Folder verwaltet und NICHT in der Szene (MaxHealth, Armor, MoveSpeed etc. pp.)
3. Logik sowie Daten die zur Laufzeit benötigt werden, gehören weder in ein MonoBehaviour noch in ein ScriptableObject
4. Um die Connection zu den Unity-Magic-Methods zu erhalten (Start/Update etc. pp.) lohnt sich eine reguläre Main/Controller-Klasse (die als Einzige neben den Editorreferenzen ein MonoBehaviour ist und in der Szene liegen muss)
Hier mal die Trennung anhand deines Beispiels:
|
C#-Quelltext
|
1
2
3
4
5
6
|
public class EditorReferences : MonoBehaviour {
public Image healthBar;
public Text healthText;
public GameObject popupText;
}
|
|
C#-Quelltext
|
1
2
3
4
5
|
[CreateAssetMenu]
public class GameSettings : ScriptableObject {
public float maxHealth = 100.0f;
}
|
|
C#-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
|
public class Main : MonoBehaviour {
public EditorReferences editorReferences;
public GameSettings gameSettings;
private PlayerHealth playerHealth;
private void Start() {
playerHealth = new PlayerHealth(editorReferences, gameSettings);
//You have a well constructed object now. No hidden init or start method...
}
}
|
|
C#-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class PlayerHealth {
private float currentHealth = 100.0f;
public PlayerHealth(EditorReferences editorReferences, GameSettings gameSettings) {
editorReferences.healthBar.rectTransform.localScale = new Vector3(currentHealth / gameSettings.maxHealth, 1, 1);
editorReferences.healthText.text = currentHealth + " / " + gameSettings.maxHealth;
//or save the specific references/settings you need....
}
/*
....
*/
}
|
Sieht am Anfang erstmal umständlich aus, aber das Ganze hat einige Vorteile:
1. Du hast einen sauberen Konstruktionsprozess und kannst Konstruktoren verwenden
2. Daten die nur einmalig vorhanden sein müssen (wie maxHealth), reservieren nun nicht mehr auf JEDEN PlayerHealth-MonoBehaviour Speicher obwohl der Wert überall gleich ist
3. Daten die global eingestellt werden sollen (über ScriptableObject) können vom Designer sehr easy durch Testsettings ausgetauscht werden, oder durch die Coder gesonder serialisiert etc. pp.
4. Magic-Methods wie z.B. die Update können deutlich Performanceeinbußen bringen, wenn sie auf einer Vielzahl von GameObjects aufgerufen werden, im Gegensatz zu einer Schleife über ein Array (
10 Update() calls)
5. Die Logik kann, wenn man Unity-Methoden und Datenstrukturen wrappt auch völlig ohne Unity funktionieren (Stichwort Portabilität und Simulation)
6. Es ist deutlich übersichtlicher einen Haupteinstiegspunkt (e.g. Main, anstatt dutzenden von MonoBehaviours mit Awakes und Starts) (...außerdem auch deutlich wartbarer imo) zu haben
7. Um 1. nochmal zu wiederholen: Man kann Konstruktoren nutzen! In Kombination mit Properties und event Callbacks ein Segen für saubere Datenkapselung!
Es gibt noch viel mehr zum Thema, aber das soll's erstmal sein
PS: Ich bin kein großer Freund von MVC für Spiele. Ich empfehle ein ECS - zumindest wenn es das Spielkonzept her gibt.
PPS: Zum Thema Methode in Basis-Klasse schreiben und mehrfach nutzen: Composition > Inheritance! Es macht natürlich super Sinn, die Popup-Logik von der Health-Logik zu trennen. Ggf. möchte man Popups auch für andere Dinge nutzen? Kleine spezialisierte Klassen sind hier der Schlüssel und deutlich wart- und erweiterbarer als große, (möglicherweise) aufgeblähte Ableitungsklassen.