Tagebuch: Tower Defense

Hab das Ursprüngliche Problem mit meinem Kontext-Menü beheben können. Das Hauptding war, dass wenn ich Time.timeScale=0 aufrufe (was das Spiel pausiert) - dann pausiert das alle Animationen. Leider auch die von dem UI. Was bedeutet: das Kontextmenü verschwindet nicht, auch wenn ich es gerne hätte.

Da sich das auch auf das neue Menü auswirken würde, musste also eine Lösung her. Die war zum Glück recht einfach. Ich hab im Internet die Info gefunden, dass man den Animator einfach einstellen kann - was er verwenden soll. Und ich möchte, dass die UI-Animationen gegen Time.unscaledTime laufen sollen:

Was gut ist. Denn bisher hab ich das neue Menu innerhalb der Story für das PauseMenü entwickelt gehabt. Nun kann ich das ganze in einer eigenen Story entwickeln und das PauseMenü zu einem Abschluss bringen :slight_smile:

Eben hab ich 3 Sachen in den 0.5.1-Branch gemergt: Das Pausemenü (endlich fertig) und 2 Bugfixes :slight_smile:.

Wie schon angekündigt möchte ich ja auch das Ingame-Contextmenu updaten. Dafür gibt es auch eine eigene Story. Um das nächste release aber nicht zu sehr aufzublähen hab ich das in eine neue Version gepackt. Nämlich 0.5.2 - ob es dabei bleibt weiß ich aber jetzt noch nicht. Den eigentlich ist es nicht notwendig (da nur Verschönerung) und andere Sachen wären vermutlich wichtiger. Mal schauen, ob ich es da lasse oder später bringen werde.

Ansonsten fehlt für das nächste Release nur noch eine Story (laufendes Spiel beenden) - dann kann ich mich ans release machen. Mal schauen, ob ich heute für die Kleinigkeit noch Zeit finde.

Hab btw am Sonntag schon mit der letzten Story angefangen. Leider ist die doch etwas größer als erwartet.

Es reicht leider nicht, einfach das Spiel zu beenden - nein ich muss auch noch das Spielfeld aufräumen. Was ich aber erst bei einer neuen Runde machen möchte. Denn ich finde es ganz nett, wenn das Spiel im Hauptmenü verschwommen im Hintergrund “weiter läuft”. Allerdings dürfen dann natürlich auch keine Werte mehr gezählt werden.

Das sind dann im Prinzip noch meine offenen Sub-Tasks:

  • Spiel aufräumen
  • Werte nur zählen, wenn aktive Runde

Ich glaub ich dreh grad am Rad. Ich wollte meine neue Version veröffentlichen, funktioniert aber nicht. Es fehlt angeblich eine 64bit-Version. Kann nicht sein - wie erwähnt hab ich mich darum ja schon vor einiger Zeit gekümmert.

Ausgehend davon, dass mir ein Fehler unterlaufen sein könnte, schaue ich nach was mir Google empfielt. Gibt sogar eine Unity3D-Sektion. Und jetzt kommts: genau das hab ich gemacht! Schaue ich in mein bundle rein (über die Entwickler-Console vom PlayStore!), dann bekomme ich dort sogar angezeigt, dass 64bit vorhanden ist:

image

Und mein Fehler den ich angezeigt bekomme ist der hier:

image

Und das hier ist meine Build-Config:

Was dem entspricht was hier beschrieben wird:

https://developer.android.com/distribute/best-practices/develop/64-bit#unity-developers

… Ich verstehs grad echt nicht…


Der hier ( https://stackoverflow.com/questions/57582157/why-cant-i-publish-the-game ) scheint das gleiche Problem zu haben wie ich.

So. Ich konnte das Update einreichen. Wie es ausschaut mag die Play-Console die x86 Architektur nicht.

Nachdem ich die rausgeworfen hab ging es. Hatte aber auch schwerwiegende Folgen. Denn alle Smartphones mit x86 werden nicht mehr unterstützt - und das sind in zahlen 2 (ZWEI!!).


Btw bin ich sehr gespannt, wann dieses Update online sein wird. Denn laut Golem hat google seinen Reviewprozess verschärft. Dementsprechend könnte es ab sofort ein paar Tage dauern, bis das Update tatsächlich durch ist.


Ok, scheint das Beta-programm nicht zu betreffen. Den laut Play-Store ist es schon online (nur in der Console hab ich noch keine Benachrichtigung)

Oh und btw. Die neuen Sachen in dem Release sind folgende:

+ Statistik
+ Statistik: Getötete Minions
+ Statistik: Gespielte Spiele
+ Statistik: Gebaute Türme
+ Statistik: Beste Runde

+ Features: Pausemenü
+ Feature: Laufendes Spiel beenden

+ Bugfix: Hautmenü konnte während des Spiels eingeblendet werden
+ Bugfix: Navigation in Menü war fehleranfällig

Ich bin mal wieder an den Punkt gekommen, wo ich meine aktuelle Architektur verfluche und der Meinung bin „ich könnte es besser!“.

Aber neu schreiben möchte ich nicht - das wäre mir zu viel Aufwand. Ich hab mich entschieden den Leidensweg weiter zu gehen und es beim nächsten Spiel besser zu machen.

Mein Problem ist, dass ich die Core-Logik (nach jetzigem Stand) besser auf herkömmlichem Weg programmiert hätte. Denn momentan ist sehr vieles wirklich auf Komponenten verteilt — und wirkt auf mich gerade eher wie Spagetthi-Code. Ich hab auch mehr Integrations-Tests als Unit-Tests. Das hätte vermutlich auch schon ein Hinweis sein sollen.

The Unity-Way :crazy_face:

Wenn man es mal von einer anderen Seite betrachtet, ist jede Komponente ein eigener Thread. Da ist nur definiert, wann welche Methode aufgerufen wird. In welcher Reihenfolge zwischen den Komponenten - völlig unklar/undefiniert.

Bei mir sieht es genau so aus.

Nun ja, aber ich glaub nicht, dass wirklich ALLES als Komponente realisiert werden soll. Letztendlich sind es ja eigentlich Behaviors.

Oder anders ausgedrückt: früher oder später hat glaub ich jeder Manager-Komponenten die eigentlich nur an einem GameObject hängen, damit diese in der Scene verfügbar sind. Aber eigentlich ist es „backend“-Code.

Für mein nächstes Spiel will ich deswegen versuchen die Trennung klarer zu halten. Komponenten (also Monobehaviors) sollen nur Dinge sein die mit der Präsentationsschicht zu tun haben. Aber die eigentlich SpielLogik soll in ein separates Assembly. Es wird dann natürlich auch ein GameManager-Objekt geben - aber dieses delegiert dann die Calls weiter.

Im Idealfall hab ich dann auch mehr Unit- als Integrationstests.

Ich glaub ich hab in letzter Zeit immer mal wieder zu lange Pausen zwischen meinen Programmiersessions.

Gerade gestern hab ich nämlich nochmal einiges wieder umgestrickt - denn ich hatte meine eigene Zielarchitektur nicht mehr im Kopf.

Es gibt nämlich schon eine mehr oder weniger sinnvolle Aufsplittung vom Code. Ich hab Services die Daten manipulieren können und DatenModel-Klassen auf die das Game-Assembly nur Lese-Zugriff hat.

Nach dem ich meinen aktuelles Feature umgeschrieben sohingehend dann auch umgeschrieben hab, macht mich das ganze etwas glücklicher. Nur hab ich trotzdem irgendwie das Gefühl, dass die es eine sinnvollere Architektur geben könnte.

Je mehr ich recherchiere, desto mehr macht es den Eindruck auf mich, dass diese Aussage falsch ist.

Schon vor 3 Jahren hat ein Unity-Typ empfohlen, mehr auf ScriptableObjects als auf MonoBehaviors zu setzen:

Ich möchte jetzt mal ein simples Game-of-Life basteln mit so wenig Monobehaviors wie möglich.

Das hat schon angefangen beim GameManager - welchen ich nie mochte. Er lebt in der Scene als GameObjekt - ist es aber eigentlich nicht. Weswegen ich den in meinem TowerDefender-Spiel erst sehr spät eingeführt habe.

Beim GOL-Beispiel hab ich jetzt aber direkt mit dem GameManager angefangen. Er besteht im wesentlichen aus 3 Dateien:

  • GolState: Soll den Zustand vom Spiel halten. Da dieser global von interesse ist, ist es ein ScriptableObject. Da es nur Daten hält brauche ich dafür auch keine tests
  • GolGame: Ist eine einfache Service-Klasse die später aber die Hauptlogik enthält und alle Entscheidungen trifft. Wichtig bei der klasse: Sie sollte gut zu mocken sein (was bei SO/Monobehaviors halt überhaupt nicht der Fall ist). Wichtig: GolGame ist Stateless, da der State in GolState liegt.
  • GolController: Ist das ScriptableObject was bisher immer als Monobehavior realisiert wurde. Es hält eine Referenz zum GolState-Asset und erstellt selbst eine Instanz von GolGame.

Das ganze stellt einen dann vor neue Herausforderungen. Z.b. dass ich keine Objekte aus der Szene an einem ScriptableObject als listener für events verwenden kann. Um die Verlinkung hin zu bekommen, muss ich selber einen entsprechenden Layer schreiben.

Ich will damit aber nicht all zu viel Zeit verbringen - deswegen ist das Projekt-Setup auch eher auf Prototyp-niveau. Es gibt eine Assembly und fertig. Auch werde ich keine Tests schreiben - aber mir wenigstens im Kopf vorstellen, wie diese aussehen würden.

Ok, ich hab glaube ich was ganz nettes hinbekommen. Ich erweitere damit ein bewertes Grundprinzip was ich schon aus einem Talk mitgenommen hab. Nämlich das ich Events in ScriptableObjects auslagere.

Mein GolController kann nämlich einfach diese Events feuern. Und die Events sind in Form von ScriptableObjects vorhanden (welche ich einfach Monobehaviors und anderen ScriptableObjects als Abhänigkeit mitgeben kann).

Somit hab ich also schonmal keine Probleme mit einem GameManager der kein Monobehavior mehr ist!

Um noch weitere Monobehaviors unnötig zu machen, hab ich Aktionen eingeführt. Diese können ein Ziel (in Form von Generics) definieren und darauf eine bestimme Sache ausführen. Z.B. wenn man Buttons basierend auf dem Zustand vom Spiel ändern möchte:

[CreateAssetMenu(menuName = "Actions/Interactable On Running")]
public class InteractableOnRunning : GameAction<Button> {
    [Header("Options")]
    [SerializeField] private bool invert;
    
    public override void Execute(Button target) {
        target.interactable = invert ? !State.Running : State.Running;
    }
}

Verwende ich so in der Form z.B. für Start/Stop-Buttons. Letztendlich haben die ja dieselbe Logik - nur umgedreht (dafür das invert-flag):

Ich glaube die Architektur macht durchaus Sinn. Ich erhoffe mir davon kleinere und überschaubarer Klassen zu haben. Außerdem sollte das alles besser zu testen sein und ich kann mehr mit Unit-Tests arbeiten (ScriptableObjects brauchen keine Szene um zu funktionieren).

Wer sich meinen bisherigen Stand mal genauer ansehen möchte, für den hänge ich hier mal die Dateien an:

Arch01.zip (31,6 KB)

Wer es ausprobieren möchte: einfach die Datei entpacken und die den Arch01-Ordner in den Asset-Folder kopieren.

Hab jetzt ein einfachs GOL umgesetzt:

Und ich glaube, ich hab es sogar ziemlich genauso hinbekommen wie ich es mir erhofft hatte. Nämlich mit einer klarer Trennung.

Hätte ich assemblies, dann würden da 2 Stück rauskommen:

  • Core.dll
    Enthält die gesamte Spiellogik - also im Groben: Die Datenhaltung und die Berechnung neuer Generationen. Hier sollte es nach Möglichkeit geben. Ich hab zwei Stück drin die für das EventSystem notwendig sind.

  • Game.dll
    Hier liegt alles was mit der Scene zu tun hat. Dazu gehören Prefabs, Event-Assets, GolController/GolState-Assets und Monobehaviors.
    Das tolle daran ist jetzt aber, dass die Monobehaviors wirklich nur Scenen-relevanten Code haben. So gibt es z.B. einen MapController der auf GolGame (aus der Core.dll) zugreift und anhand von dessen Informationen die Map neu aufbaut oder updated.
    Klicks auf die Buttons Restart und Next Generation werden z.B auch direkt an Core.dll weitergeleitet.

An und für sich find ich das gerade extrem elegant. Denn Game.dll ist wirklich komplett Datengetrieben und in der Core.dll muss ich mir absolut keine Gedanken darum machen, wie ich es dar stellen möchte.


Fall jemand einen genaueren Blick drauf werfen möchte, so kann er das gerne tun. Hier ist der komplette Source. Einfach ins Asset-Folder entpacken:

Arch01.zip (32,3 KB)

So. Ich hab jetzt noch einen Autoclicker eingebaut und die Farbe des Shaders animiert. Das ganze schaut dann so aus:

Und irgendwie könnte ich bei solchen GoL-Spielen ewig lang zuschauen ^^.

Was ich nicht genutzt habe sind btw die Aktionen von denen ich geredet hab. Muss mir jetzt als nächstes Überlegen, in wie weit es sinnvoll ist meine Architektur von Arena Defender anzupassen.

So. Ich glaub ich lege das Tower Defense erstmal wieder auf Eis.

Warum? Aus diversen Gründen:

  1. Meine Motivation für das Spiel lässt spürbar nach. Man sieht es ja an den Updates, ich mache nur noch Kleinigkeiten
  2. Ich bin selber kein großer Fan mehr von TD-Spielen. Ergo würde ich es vermutlich nie spielen (schlägt bei Punkt 1 zu).
  3. Es ist eigentlich in einem spielbaren Zustand. Macht zwar keinen wirklich Spaß - weil Abwechslung fehlt. Aber im Prinzip funktioniert es
  4. Es ist mein zweites jemals programmiertes Spiel: von daher auch als Lernprojekt anzusehen.

Und wenn ich mal auf Punkt 4 zurück blicke, dann hat es doch einiges gebracht. Was mir auf anhieb einfallen würde:

  1. Pathfinding: Ich weiß wie ich damit arbeiten kann.
  2. LWRP: Ich hab viel über die LightwightRenderingPipeline und eigene Shader gelernt. Unter anderem auch, dass ich kaum Lichteffekte und Co einbauen kann.
  3. PostProcessing: Das ist das erste Spiel, wo ich eben PostProcessing eingesetzt hab und ich fand das wirklich cool
  4. 3D-Zeug: Ich hab (denke ich) ein besseres Gefühl für meine 3D-Modelle und wie ich diese speichere/hinterlege. Vor allem, dass ich mich sehr verschätzt habe was die Polygonanzahl angeht. Das wird schneller teuer als erwartet.
  5. SVG-Grafiken: Ich weiß wie ich diese verwenden kann (auch wenn das Package noch als experimentell eingestuft ist, so scheint es funktional zu sein)
  6. Architektur: Es hat mir einiges gezeigt, was ich gleich oder anders machen möchte.

Von dem her will ich mit einem anderen Projekt weiter machen. Was das sein wird ist noch nicht klar. Aber ich arbeite schon seit einigen Stunden (yay!) an einem Prototypen und hab glaub eine recht nette Idee :slight_smile:.