Technische Erläuterung Sommer 2024: Hytales Entity Component System
-
Noah -
September 17, 2024 at 5:39 PM -
0 Comments -
64 Views - New
Hallo zusammen! Heute werfen wir einen kurzen Blick hinter die Kulissen und sehen uns die Technologie an, die der Hytale-Engine zugrunde liegt. Dabei konzentrieren wir uns insbesondere auf eines der Frameworks, die wir kürzlich angekündigt haben: Flecs – ein leichtes und leistungsstarkes Entity Component System (ECS).
Vor einiger Zeit haben wir über unsere Entscheidung gesprochen, die Hytale-Engine neu zu starten und von einem Java-Server und einem C#-Client zu C++ zu wechseln. Es gab viele Gründe für diese Änderung: Wir wollten sicherstellen, dass wir das Spiel auf mehreren Plattformen veröffentlichen können; wir wollten unsere Leistung verbessern, wenn wir Geräte mit niedrigeren Spezifikationen ansprechen; und wir wollten eine Kern-Engine erstellen, die robust genug ist, damit wir das Spiel auch in Zukunft patchen und warten können.
ECS ist eines der vielen Tools, auf die wir uns verlassen, um diese Ziele zu erreichen. In diesem Beitrag besprechen wir, warum wir Flecs als ECS-Grundlage für Hytales neue Engine gewählt haben und wie es uns genau dabei hilft, diese Ziele zu erreichen.
Doch bevor wir uns zu sehr mit Flecs selbst befassen, müssen wir einen Blick auf ECS werfen – das Entity Component System Pattern.
VON EINEM DREI-BUCHSTABEN-AKRONYM ZUM ANDEREN
ECS ist kein besonders neues Konzept und auch in der Spieleentwicklung nicht mehr so ungewöhnlich wie noch vor ein paar Jahren. Trotzdem kann es bei der ersten Begegnung immer noch komplex und ungewohnt sein. Um richtig darüber sprechen zu können, müssen wir das ECS-Konzept im Rahmen der Spieleentwicklung kontextualisieren.
Ein Großteil der traditionellen Spieleentwicklung basiert auf dem uralten objektorientierten Programmiermodell (OOP) oder auf Entity-Component- (oder Actor-Component-!) Architekturen. Objektorientierte Programmierung ist in der Softwareentwicklung allgemein weit verbreitet und zerlegt Probleme in vertraute Strukturen, die als Objekte betrachtet werden können. Sie könnten einen übergreifenden Charakterobjekttyp haben, der eine allen Charakteren gemeinsame Spiellogik bereitstellt, die dann von einem Player-Objekt, verschiedenen NPC-Objekten und allen anderen Typen, die als Charaktere betrachtet werden könnten, übernommen oder spezialisiert wird.
Dieser Baum kann sehr breit werden.
Entity-Component bringt uns einen Schritt näher an ECS und ist die primäre Architektur, die von vielen beliebten Spiele-Engines wie Unreal und Unity verwendet wird. In diesem Paradigma haben wir jetzt Entitäten – einzelne Einheiten wie einen „Spieler“, einen „NPC“ oder einen „Stuhl“ – und Komponenten – eine Kombination aus Daten und Funktionen, die an diese Entitäten angehängt werden können. Jede Entität besteht aus einer Reihe von Komponenten. Wenn Sie mit OOP vertraut sind, basiert das Entity-Component-Paradigma stark auf dem Prinzip „ Komposition statt Vererbung “. Beispiel: Ein Spieler könnte eine Position in der Welt haben, die Fähigkeit, Controller-Eingaben zu lesen, und ein Inventar; ein NPC hat ebenfalls eine Position, könnte ein Inventar haben und verfügt über eine Art Verhaltenslogik; unser Stuhl hat leider nur eine Position.
Bis Sie Verhaltenslogik hinzufügen und es zu einer Kreatur wird [ Haftungsausschluss: Dies dient Demonstrationszwecken! ]
Es sollte sofort klar werden, wie befreiend eine solche Struktur für das Modding sein kann . Sie ermöglicht nicht nur stark datengesteuerte Funktionalität durch Asset-Konfiguration, bei der das einfache Ändern der an einen NPC oder ein Objekt angehängten Komponenten zu deutlich anderem Verhalten führt, sondern ermöglicht theoretisch auch die Erstellung völlig neuer Funktionalität, ohne dass man am vorhandenen Code herumbasteln muss. Mit einer Skriptsprache können Sie Ihre eigene Komponente erstellen und hinzufügen und sie an die Entitäten anhängen, die dieses Verhalten ausführen sollen. Es eröffnen sich eine Vielzahl von Möglichkeiten.
Eine Entität kann auf viele verschiedene Arten zusammengesetzt sein!
Aber das ist noch kein ECS. ECS – Entity Component System – übernimmt diese Konzepte und entwickelt sie weiter. Während im Entity-Component-Modell die Funktionalität (z. B. Methoden und Funktionen) innerhalb der Komponente selbst liegt, entkoppelt ECS diese Funktionalität von den Daten und dem Status, den sie verarbeitet. Anstatt jeder Komponente ihre eigene interne Aktualisierungslogik zu geben, haben wir Systeme, die Entitäten mit definierten Komponentensätzen abgleichen und auf sie reagieren. Das bedeutet, dass wir mit ECS immer noch die gleiche Fähigkeit freisetzen, Entitäten aus verschiedenen Komponenten zusammenzusetzen, aber die Entkopplung führt zu einer Daten- und Logikarchitektur, die für die Hardware deutlich effizienter und damit leistungsfähiger ist. Viele der Details dazu, wie ECS diese Leistungsvorteile erreicht, sind hochtechnisch, aber es genügt zu sagen, dass es darum geht, die CPU-Architektur auszunutzen, Daten dicht gepackt zu strukturieren, um von ihrer Lokalität in Zugriffsmustern zu profitieren, und diese Zugriffsmuster zu verwenden, um so viel Logik wie möglich zu parallelisieren.
Systeme können mit jeder beliebigen Kombination von Komponenten übereinstimmen.
ECS IN DER LEGACY-ENGINE
Schon während der Entwicklung der Legacy-Engine wussten wir, dass wir auf eine ECS-Architektur umsteigen wollten, da wir damit eine Leistungssteigerung und Skalierbarkeit erzielen konnten und dies auch unserem datengesteuerten Ansatz zur Konstruktion und Konfiguration von Spieleinheiten und -akteuren entsprach. Daher entwickelten wir unsere eigene Java-Implementierung des Konzepts und begannen, es in den gesamten Legacy-Server zu integrieren. Zu diesem Zeitpunkt hatten wir kein Äquivalent für den C#-Client, sodass unsere Implementierung streng serverbasiert war.
Ein Teil dieser Arbeit bestand darin, Aspekte der bestehenden Logik so umzugestalten, dass sie dem ECS-Muster folgten, selbst als wir begannen, parallel dazu neue Funktionen zu entwickeln. Wir haben in dieser Zeit viel gelernt, vor allem, dass die Implementierung eines robusten und leistungsfähigen ECS-Frameworks von Grund auf ein unglaublich anspruchsvolles und zeitaufwändiges Unterfangen ist. Es gibt unzählige verschiedene Varianten von ECS, jede mit ihren eigenen Vor- und Nachteilen, aber alle erfordern ein tiefes Verständnis und technische Spezialisierung, um sie auf hohem Niveau auszuführen. Java ist außerdem nicht immer die leistungsstärkste Programmiersprache und wir haben aufgrund seiner einzigartigen Eigenheiten viele Zugeständnisse und Designentscheidungen getroffen.
Schon damals bot unsere noch junge ECS-Implementierung spürbare Leistungsvorteile sowie einen neuen Ansatz für die Systemarchitektur, der die Prinzipien des datengesteuerten Designs verkörperte, die wir erreichen wollten. Wenn wir es richtig machten, konnten wir es Moddern leicht machen, Daten bereitzustellen, die das Verhalten des Spiels beeinflussen würden, und das praktisch ohne technische Kenntnisse.
Eine zuverlässige Basis für einen neuen Motor
Als wir die Engine neu starteten, wussten wir, dass wir ECS weiterhin verwenden wollten, dies aber auch auf den Client ausweiten wollten, um sicherzustellen, dass wir in jeder Hinsicht von den Vorteilen profitieren würden. Wir wussten auch, dass unsere Umstellung auf C++ bedeuten würde, dass es andere Frameworks geben könnte – solche, die wir nicht selbst erstellen und pflegen müssten, mit hochmodernen Funktionen, die die Grenzen des Paradigmas erweitern würden.
Nachdem wir alle verfügbaren Optionen ausgewertet hatten, entschieden wir uns für Flecs – ein hochentwickeltes ECS-Framework, das von einem ECS-Experten geschrieben und gepflegt wird: Sander Mertens .
Es bietet uns sofort Zugriff auf eine Vielzahl von Funktionen, die bei den meisten ECS-Implementierungen üblich sind, sowie hervorragende Leistung und plattformübergreifende Kompatibilität. Da es vollständig in C mit einer C++-API geschrieben ist, ist es deutlich schneller, als jede Implementierung in C# oder Java es sich erträumen könnte, sodass wir die intelligente Implementierung von Parallelisierung und Multithreading voll ausnutzen können. Ein weiterer offensichtlicher Vorteil ist, dass wir es nicht selbst warten müssen – Flecs ist praxiserprobt, erhält häufige Updates und Bugfixes und mit seiner umfassenden Testsuite können wir uns seiner Stabilität relativ sicher sein.
Aber der vielleicht verlockendste Aspekt war die breite Palette an Funktionen, die über das hinausgehen, was ein traditionelles ECS-Framework bietet, und eine Flexibilität bieten, die ECS auf die nächste Ebene bringt. Ein Beispiel dafür ist das Konzept der „Beziehungen“. Wie Komponenten sind Beziehungen Daten, die Sie an eine Entität anhängen können, aber diese Daten werden verwendet, um eine Entität mit einer anderen zu verbinden. Eine Eltern-Kind-Beziehung ist ein gutes Beispiel dafür, bei der eine Spielerentität eine Kameraentität als Kind haben könnte, die ihr folgt. Ein anderes Beispiel könnte noch wörtlicher sein, wenn Entität A Entität B mag. Durch die Verwendung dieser Struktur können wir problemlos Abfragen wie „Finde mir alle Entitäten, die Entität B mögen“ oder „Finde mir alle Entitäten, die Entität B mag“ ausführen.
In vielerlei Hinsicht ähnelt ein ECS einer Datenbank, und Flecs nutzt diese Tatsache voll aus. Die zugrunde liegende Regel-Engine ist ein leistungsstarkes Tool, das Datenabfragen auf verschiedene Arten unterstützt, angefangen von einfachen Matching-Verfahren für Systeme (z. B. ein System, das das Pflanzenwachstum basierend auf einer Anzahl angeschlossener Komponenten aktualisiert) bis hin zu komplexen Nachschlagevorgängen für Spiellogik oder Debugging (z. B. finde alle NPCs mit Schwertern, die den Spieler angreifen). Darüber hinaus ermöglicht uns der Mechanismus zur Komponentenfreigabe, einen Basis-Akteurtyp wie einen Charakter zu erstellen und zu sagen, dass ein NPC oder ein Spieler ein Charakter ist, wodurch wir Zugriff auf OOP-artige Vererbung erhalten, die jedoch so aufgebaut ist, dass sie von ECS-Optimierungen profitiert.
Manchmal kann es schwierig sein, über einige dieser Funktionen nachzudenken, wie es oft der Fall ist, wenn man eine völlig neue Architektur lernt. Wenn man sie jedoch einmal verstanden und gemeistert hat, bieten sie äußerst leistungsstarke Tools für die Spieleentwicklung.
SCHNELLER DENKEN
Zum Abschluss untersuchen wir ein solches Beispiel. In der Vergangenheit haben wir eine Verbesserung für Hytales NPCs eingeführt, die den Combat Action Evaluator genannt wird . Dabei handelt es sich um ein Framework, das es NPCs ermöglicht, intelligentere und „unschärfere“ Entscheidungen darüber zu treffen, welcher Angriff verwendet und auf welches Ziel er angewendet werden soll, basierend auf einer Reihe hochgradig konfigurierbarer Eingaben. Obwohl es ursprünglich in der Legacy-Engine implementiert wurde, wurde es von Anfang an datengesteuert konzipiert, wobei jede einzelne Eingabe auf eine Weise verbunden wird, die den Komponenten in einem ECS ähnelt.
Obwohl es seinen Zweck in der alten Engine bewundernswert erfüllte und kämpfende NPCs lieferte, die manchmal mit menschlichen Spielern verwechselt werden konnten, mussten ihm aufgrund seiner möglichen Auswirkungen auf die allgemeine Leistung des Spiels Einschränkungen auferlegt werden. Schließlich bedeutet es eine erhebliche Verarbeitungslast, wenn man NPCs „unscharfe“ Entscheidungen auf der Grundlage einer enorm großen Menge an Eingabedaten in einer OOP-Umgebung treffen lässt – eine, für die unsere Java-basierte Engine vor ECS nicht gerüstet war. Daher ließen wir den Kampfaktionsauswerter nur in unregelmäßigen Abständen laufen. Dies könnte bedeuten, dass wir eine mögliche Serververlangsamung durch eine große Anzahl von NPCs im aktiven Kampf vermeiden würden, aber es bedeutete auch, dass sie insgesamt langsamere Entscheidungen treffen würden – langsam genug, um für den Spieler wahrnehmbar zu sein.
Mit Flecs sind unsere Möglichkeiten zur Spielgestaltung nicht annähernd so stark durch Leistungsbedenken eingeschränkt. Durch die Neugestaltung des internen Frameworks nach ECS-Mustern und die umfassende Nutzung der Flecs-Funktionen erhalten wir ein Äquivalent, das diese Daten nicht mehr so ineffizient verarbeiten muss. Stattdessen können wir alle Abfragen, die bestimmte Informationen prüfen, intelligent gruppieren und parallelisieren, was zu viel schnelleren Verarbeitungszeiten führt und die künstlichen Beschränkungen der Auswertungshäufigkeit überflüssig macht. Während wir in der alten Engine unsere Prüfungen nacheinander ausführten (wobei wir die teuren zuerst priorisierten, damit wir früher aufhören konnten, wenn sie fehlschlugen!), können diese jetzt alle gleichzeitig ausgeführt werden, wobei die Abfrage-Engine von Flecs die Schwerstarbeit übernimmt.
Im Wesentlichen bedeutet dies, dass NPCs schneller denken können – und auf Veränderungen der Umwelt und ihrer Umgebung viel rascher reagieren können, als dies mit der alten Engine jemals möglich war.
IN DIE ZUKUNFT
Letztendlich ist dies nur ein kleiner Ausschnitt dessen, was mit Flecs und ECS möglich ist. Viele andere Teile der Engine bieten interessante Optimierungsmöglichkeiten rund um ECS, von der Asset-Datenbank bis zur stufenweisen Weltgenerierung, und da sich sowohl Flecs als auch die Hytale-Engine weiterentwickeln, erwarten wir, dass die Möglichkeiten nur noch weiter wachsen.