1 Punkte von GN⁺ 2023-09-18 | 1 Kommentare | Auf WhatsApp teilen
  • „Caves of Qud“ hat eine technische Machbarkeitsprüfung durchgeführt, bei der die langjährige Unity-Abhängigkeit entfernt und der Spielkern in Godot gebaut und gebootet wurde; das erste Ziel ist, es ohne moderne VFX oder UI im ASCII- plus Tile-Modus lauffähig zu machen
  • Die Arbeit gliederte sich in den Import von Tile-Assets, die Portierung zentraler C#-Assemblies und den Aufbau einer Rendering- und Input-Rig; alte BMP-Tile-Dateien wurden in PNG konvertiert und in Godot geladen
  • Die 5.641 Fehler des ersten Builds wurden reduziert, indem GeneratedCode, ConsoleLib, Genkit, Language, HistoryKit, Newtonsoft JSON, CodeDom usw. übernommen wurden, während die Abhängigkeitsoberfläche zu UnityEngine mit Stubs schrittweise verkleinert wurde
  • Einsatzstellen von Color, GameObject, AudioSource, Debug.Log und Screen aus Unity wurden mit UnityEngineReplacer und Ersatzklassen behandelt; chargen- und presentation-Schichten nahe an PlayFab bzw. Unity UI wurden vorübergehend entfernt oder als Kandidaten für ausgelagerte Glue-Module belassen
  • Im Ergebnis ist der rund 500.000 Zeilen umfassende C#-Spielkern nun in Godot so weit bootfähig, dass er Frames liefert und auf Eingaben wartet, aber Arbeiten an Renderer, Input-Harness sowie VFX-, Sound- und UI-Rigging stehen noch aus

Umfang der Portierung von Unity nach Godot

  • Die Portierung teilt sich in drei Bereiche
    • Asset-Import: Tile-Assets in das Godot-Projekt übernehmen
    • Portierung der Kern-Assemblies: die nicht zu Unity gehörende Haupt-Spiel-Engine XRL Application und den Engine-seitigen GameManager übertragen
    • Aufbau des Rendering-Rigs: nach dem Booten des Kerns Bildausgabe und Eingaben anbinden
  • Es wurde ein Godot-„mobile“-Projekt erstellt und Quds Textur-Content in den Projekt-Root-Ordner kopiert
  • Da Godot alte .bmp-Dateien nicht verarbeiten konnte, wurden Dateien einschließlich der ASCII-Tiles gesammelt in PNG konvertiert
  • Nach der Konvertierung wurden die Assets geladen, allerdings dauerte es etwa 30 Sekunden, bis überhaupt ein Import-Fortschrittsbalken erschien

Kern-Assemblies übertragen und Build-Fehler reduzieren

  • Nach dem Kopieren von XRL Application wurden in Godot ein C#-Projekt und eine Solution erzeugt; im Ausgangszustand traten 5.641 Fehler auf
  • Universelle Bibliotheken wie ConsoleLib und Genkit wurden vollständig übernommen, während bei Kobold, einer älteren Sprite- und Atlasing-Lösung, dateiweise geprüft wurde, was wirklich benötigt wird
  • KoboldJSON wurde als einfache JSON-Serialisierungsbibliothek unverändert übernommen
  • Durch globales Ersetzen der 85 Einsatzstellen von Color sank die Fehlerzahl auf 4.700
  • Nachdem der fehlende Ordner GeneratedCode ergänzt wurde, kamen die generierten Klassen für einzelne Events hinzu und die Fehlerzahl sank auf 1.557
    • Caves of Qud nutzt eine Struktur, in der eventbezogene Klassen mit viel Boilerplate codegeneriert werden, um Performance und eine stark typisierte Event-Oberfläche zu erhalten

Unity-Abhängigkeiten und die Glue-Schicht aufräumen

  • Embark Builder und die Charaktererstellung chargen enthalten viel Unity-UI-Code und wurden für den ersten ASCII-Techniknachweis entfernt
    • Eine Konsolenversion existiert noch nicht, aber der frühe Nachweis lässt sich durch Laden von Spielständen oder einen zufälligen Start testen
  • Der Ordner Game ist größtenteils Glue zwischen Unity und dem Spiel, enthält aber auch nicht zur Glue-Schicht gehörende Ordner wie CodeGeneration; deshalb wurde entschieden, diese in ein separates Root oder einen Ordner Platform zu verschieben
  • Durch die Übernahme der Bibliotheken Language und HistoryKit sank die Fehlerzahl auf 454; hilfreich war, dass Spiel- und Glue-Schicht trotz Unvollkommenheiten bereits teilweise getrennt waren
  • Die verbleibenden großen Fehlerkategorien waren CodeDom/Roslyn, PlayFab, Harmony, einige compilerbezogene Probleme und eine in die Spielebene durchgesickerte UnityEngine-Oberfläche
  • Mit PlayFab zusammenhängender Code wurde vorübergehend auskommentiert und als Kandidat für ein Glue-Modul belassen

Ersatz-Stubs für Unity-APIs und Unterschiede in Godot C#

  • Color32 ist ein bytebasierter Farbtyp, daher wurde schnell eine Ersatzimplementierung geschrieben
  • Es wurde ein Ordner UnityEngineReplacer angelegt, und ohne Unity-Dokumentation oder Code-Referenz wurde anhand der Compilerfehler nur die tatsächlich benötigte Oberfläche implementiert
  • Sobald ein GameObject-Stub vorhanden war, wandelten sich Fehler wie „GameObject ist unbekannt“ in Fehler für tatsächlich benötigte Felder und Methoden, wodurch sichtbar wurde, welche Interface-Oberfläche das Spiel wirklich nutzt
  • Auch bei AudioSource wurden anhand der Fehlerliste die angesprochenen Member einzeln ergänzt, bis die vom Spiel tatsächlich verwendete vollständige AudioSource-Port-Schnittstelle vorhanden war
  • Weitere behandelte Oberflächen waren unter anderem
    • Ersatz-Shim für Debug.Log
    • Ersatzimplementierung für Screen
    • Portierung einer Bibliothek mit der Erweiterungsmethode IsNullOrEmpty
    • Behandlung von Math-Referenzen und Unterschieden zwischen PI und Pi
    • Behandlung der unterschiedlichen Großschreibung von Koordinatennamen X und Y in Godot
    • Bereinigung falsch von der IDE importierter System.Drawing.Color und System.Numerics.Vector3
  • Newtonsoft JSON wurde durch Hinzufügen des NuGet-Pakets in Visual Studio gelöst, CodeDom über das NuGet-Paket CodeDom

Bis zum vollständigen Boot in Godot

  • Als die Fehlerzahl auf 11 gesunken war, betraf das verbleibende Problem Code zur dynamischen C#-Kompilierung im Mod-Management; nicht zwingend notwendig, aber komplex genug, um bis zuletzt offen zu bleiben
  • Danach wurden noch Link-Schritt-Probleme und Stub-Feldfehler behoben, sodass rund 500.000 Zeilen C# gebaut werden konnten
  • Der nächste Schritt war, einen kleinen Renderer und ein Input-Harness zu erstellen, um die Kern-Assembly zu booten; Ziel war, das Spiel im ASCII- plus Tile-Modus ausführbar zu machen
  • In Godot wurde eine leere Scene erstellt und GameManager.cs an den Basis-Node gehängt; GameManager erbte von Node und benötigte partial Code
  • Godots _Ready wurde entsprechend Unitys Awake verwendet, _Process entsprechend Unitys Update
  • Im Initialisierungsprozess wurden Load-Pfade und das Mod-Management portiert, und das Problem, dass der Type-Resolver Typen nicht anhand ihrer Namen fand, wurde per printf-artigem Tracing untersucht
  • Die Ursache lag in der Behandlung dynamischer Assemblies
    • Da Mod-Assemblies dynamisch waren, wurden dynamische Assemblies bei der Prüfung der Main-Assembly ausgeschlossen
    • Im Godot-Editor ist aber auch die Main-Game-Assembly dynamisch und fiel deshalb aus der Typensuche heraus
  • Nach der Korrektur gelang der vollständige Boot, und der 500-kloc-Spielkern liefert nun Frames und wartet auf Eingaben
  • Die verbleibende Arbeit wurde zwar als „just work“ beschrieben, tatsächlich steht aber noch erheblicher Aufwand für VFX, Sound und UI-Rigging aus

1 Kommentare

 
GN⁺ 2023-09-18
Hacker-News-Kommentare
  • Dieses Spiel nutzt offenbar fast vollständig eine eigene Engine, und Unity dient nur als Hardware-Abstraktionsschicht und als Gerüst fürs Portieren; in Sachen Portierungsaufwand wirkt das daher fast wie ein Best-Case-Szenario.
    Es gibt überraschend viele Spiele dieser Art, aber natürlich ist das nicht repräsentativ für die meisten Unity-Titel.

    • Genau. In irgendeinem Interview wurde einmal gesagt, dass Qud damals innerhalb von Unity wie eine Konsolen-App lief.
      Ich weiß nicht, ob das nach der UI-Überarbeitung immer noch so ist, aber falls ja, wirkte es auf mich immer seltsam, dass sie nicht auf MonoGame portiert und damit Kosten gespart haben.
    • Ich sehe das als Ergebnis davon, dass die Standardbestandteile von Unity langfristig fast alle nutzlos werden.
      Bei Android ist es ähnlich: Das Betriebssystem wird nur noch dafür benutzt, Libraries zu laden, die Dinge wie Kameraerkennung oder Verschlüsselung ersetzen, bis man eines Tages merkt, dass das Betriebssystem im Grunde nur noch eine dünne Metallschicht und ein Bootloader ist.
  • https://nitter.net/unormal/status/1703163364229161236

  • Wow, wirklich großartig. Es ist schön, diesen Portierungsprozess Schritt für Schritt sehen zu können, und ich bin überrascht, dass der Zeitaufwand ziemlich vernünftig war.

    • Godot hat deutlich weniger Funktionen, ist aber meiner Meinung nach wirklich leicht zu lernen.
    • Ich würde gern sehen, wie sie die für Caves of Qud typische visuelle Atmosphäre in Godot umgesetzt haben.
  • Wenn es so viel Custom-Code gibt und die Editor-Funktionen weniger genutzt werden, könnte man statt einer Engine auch eine Rendering-Library verwenden.

    • Wenn man ein Spiel gleichzeitig für mehrere Plattformen veröffentlichen will, wird es schnell lästig, sich selbst um Rendering-, Sound- und Eingabe-Code pro Plattform zu kümmern.
      Mit einer Engine als Basis bekommt man diese Dinge gratis dazu. Bei Unity könnten allerdings Kosten anfallen.
    • Sie verwenden bereits eine Rendering-Library. Sie heißt „Unity“.
  • Wenn man wissen will, was Brian darüber denkt, sollte man sich dieses Video ansehen: https://www.youtube.com/watch?v=U03XXzcThGU
    Ich habe früher viel daraus gelernt.

  • Das erinnert mich an die jüngste TypeScript-Kontroverse um DHH.
    Wenn man sich vorstellt, so eine Portierung ohne statische Typen zu machen, müsste man nach jeder Änderung bauen, ausführen und nach Abstürzen suchen. In solchen Momenten ist man wirklich dankbar, wenn etwas in einer refactoring-freundlichen Sprache und mit guter Architektur gebaut wurde.

  • Ich habe keinen Twitter-Account, was ist los?