Die Entwicklung von Spielen mit HTML5 hat sich in den letzten Jahren rasant weiterentwickelt. Gerade für Entwickler, die auf Performance und Flexibilität angewiesen sind, stellt sich häufig die Frage nach der besten Architektur für ihre Spiel-Engine. Eine zentrale Diskussion dabei dreht sich um das Konzept von Vererbung versus Komposition. Während traditionelle objektorientierte Programmierung oft auf Klassenvererbung setzt, rückt die Komposition als Designprinzip zunehmend in den Fokus. Gerade beim Umgang mit leistungsintensiven Anwendungen wie Spielen kann der Wechsel zu Komposition zu erheblichen Verbesserungen führen.
Der Ursprung vieler HTML5-Spiel-Engines – beispielsweise impact.js – basiert traditionell auf Vererbung. Diese ermöglicht es Entwicklern, eine Basisklasse für Spielobjekte zu definieren, aus der alle anderen Entitäten abgeleitet werden. Die Intention ist klar: Wiederverwendbarkeit und Strukturierung des Codes. Doch mit zunehmender Komplexität des Spiels wächst auch der Umfang der einzelnen Klassen, die immer mehr Eigenschaften und Methoden aufnehmen.
In Folge dessen entstehen überladene Objekte, die in JavaScript erhebliche Performanceprobleme verursachen können. Diese Herausforderungen ergeben sich vor allem aus der Art und Weise, wie JavaScript Engines, insbesondere die V8 Engine von Google, Code optimieren. Die Optimierungen funktionieren am besten, wenn Objekte eine einheitliche Struktur aufweisen und Funktionaufrufe polymorph, also mit wenigen Varianten von Objekten, erfolgen. Vererbung führt in einem dynamischen, prototypbasierten System jedoch oft dazu, dass Objekte eine Vielzahl unterschiedlicher Eigenschaften mit sich tragen. Dies erschwert die Optimierung, weil Zugriffe auf die zahlreichen Eigenschaften und polymorphe Aufrufe für die Engine komplex und ineffizient sind.
Ein praxisnahes Beispiel zeigt das Team hinter dem Spiel CrossCode. Die Entwickler hatten ihre Engine ursprünglich auf Vererbung aufgebaut und stellten fest, dass trotz vergleichbarer Entitäten und Spielwelten die Performance mit der Zeit immer weiter abnahm. Dies lag nicht an mehr Inhalt, sondern an der steigenden Komplexität der Klassenhierarchie und der zunehmenden Polymorphie innerhalb der Entities. Besonders Savegame-Durchläufe mit vielen unterschiedlich strukturierten Spielfiguren verlangsamten die Engine deutlich. Doch statt die komplette Architektur wegzuwerfen, entschieden sich die Entwickler für eine hybride Strategie.
Wesentliche, performancekritische Aspekte wie Rendering und Kollisionsabfrage wurden aus den eigentlichen Entities ausgelagert und in eigene Komponenten ausgelagert. Dadurch konnten die großen und komplexen Entitätsklassen schlanker gestaltet werden. Die Komposition dieser Komponenten mit den Entities verbesserte die Speichereffizienz und unterstützte die JavaScript Engine dabei, die Eigenschaften der Objekte einheitlicher zu halten. Das Aufteilen in sogenannte Sprite-Komponenten für das Rendering und CollEntry-Komponenten für die Kollisionslogik ermöglichte eine fokussierte Optimierung. Da es für jede Komponente nur eine Klasse gab, die für alle Entities genutzt wurde, verringerte sich die Vielfalt der Objektstrukturen drastisch.
Gleichzeitig wurden die komplexen Entities zu schlanken Containern, die lediglich auf ihre Komponenten referenzierten. Die Hauptzuständigkeit für das Zeichnen und die Kollision lag nun auf einer Ebene, die für die JavaScript Engine leichter zu optimieren war. Natürlich bedeutete diese Umstrukturierung einen erheblichen Refactoring-Aufwand. Die Umstellung erforderte viele Anpassungen im Code, da Zugriffe nun beispielsweise von this.pos.
x auf this.coll.pos.x geändert werden mussten. Trotzdem konnten die Entwickler diese Anpassungen innerhalb weniger Tage umsetzen und beobachteten anschließend messbare Verbesserungen in der Performance, insbesondere in der Ausführungsgeschwindigkeit der Kollisionsalgorithmen und der GUI-Renderings.
Die Vorteile eines solchen Kompositionsansatzes liegen auf der Hand: Er kann bestehende Engines und Systeme optimieren, ohne sie komplett neu schreiben zu müssen. Entwickler profitieren von flacheren Objektstrukturen und einer effizienteren Nutzung von Speicherressourcen. Komposition ermöglicht zudem, einzelne Bereiche der Engine gezielt zu verbessern, ohne die gesamte Vererbungshierarchie infrage zu stellen. Allerdings ist Komposition nicht zwangsläufig der „Allheilmittel“-Ansatz. Gerade in kleineren Spielen oder einfacheren Engines kann Vererbung ausreichend performant und besser überschaubar sein.
Die Leserlichkeit und Wartbarkeit von Code stellen weiterhin wichtige Kriterien dar, die Vererbung unterstützt. Somit empfiehlt sich vor allem eine bewusste Abwägung zwischen den beiden Prinzipien, wobei bei steigender Komplexität die Komposition tendenziell geeigneter ist. Ein weiterer wichtiger Aspekt ist, dass Komposition die Flexibilität erhöht. Das Hinzufügen, Entfernen oder Ersetzen von Komponenten erlaubt eine viel granularere Steuerung der Entity-Funktionalitäten. So können Entwickler gezielt einzelne Fähigkeiten oder Verhaltensweisen konfigurieren, ohne eine neue Klasse erstellen zu müssen.
Dies passt sehr gut zu modernen, datengetriebenen Designs und führt zudem zu besser skalierbaren Architekturen. Auch wenn die Diskussion um Komposition versus Vererbung oft emotional geführt wird, zeigt die Praxis, dass beide Ansätze ihre Berechtigung haben. Es geht weniger darum, eines komplett zu vermeiden, sondern vielmehr darum, ihre Stärken entsprechend sinnvoll einzusetzen. Im Kontext von JavaScript-Engines besteht die Herausforderung darin, die internen Limitierungen der Sprache und ihres Laufzeitumfelds zu verstehen und die Architektur entsprechend auszulegen. Die Entwickler von CrossCode haben darüber hinaus erkannt, dass weitere Optimierungen auch über die Komponentenebene hinaus möglich sind.
Beispielsweise könnten kritische Engine-Kerne, wie etwa die Kollisionsprüfung oder das Rendering, in niedrigeren Sprachen geschrieben und mittels Technologien wie asm.js oder WebAssembly eingebunden werden. Dadurch würden sich die Performancevorteile noch weiter steigern lassen, während das eigentliche JavaScript zur Steuerung der Engine dient. Abschließend lässt sich sagen, dass der Wechsel von starker Vererbung hin zu einer Mischung aus Komposition und Vererbung ein pragmatischer und effektiver Weg ist, um die Performance von HTML5-Spiel-Engines zu verbessern. Es eröffnet neue Möglichkeiten, komplexe Spiellogik performant umzusetzen, ohne die Vorteile einer klaren objektorientierten Struktur komplett aufgeben zu müssen.