Letzte Woche ging es um DataTable vs DataAsset vs Primary DataAsset. Beim Dialog hört diese Entscheidung auf, theoretisch zu sein. Fast jedes erste Dialog-System startet gleich: ein Widget-Blueprint, ein Set-Text-Node, ein Branch. Funktioniert. Dann bekommt der NPC ein zweites Thema, dann eine Bedingung, dann eine Wahl die eine Quest freischaltet — und 40 Nodes später erkennst du nicht mehr, welcher Pin wohin führt.
Die Lösung ist kein besseres Node-Layout. Die Lösung ist, das Gespräch komplett aus dem Graph rauszuholen.
Die Regel: Dialog ist Daten, keine Logik
Dein Blueprint soll wissen, wie man ein Gespräch abspielt. Es soll nicht wissen, was irgendwer sagt. Diese eine Trennung entscheidet, ob dein System 30 Zeilen überlebt oder 3000.
Konkret: eine DataTable hält die Zeilen. Ein Blueprint liest eine Row, zeigt sie, wartet auf Input, springt zur nächsten Row-ID. Das Blueprint wächst nie. Die Tabelle schon.
Der Row-Struct
Fang mit einem Struct an — nenn ihn S_DialogueLine. Das Minimum, das wirklich skaliert:
CB0
Und der Choice-Struct:
CB1
FName für IDs ist Absicht — Vergleiche sind pointer-billig, und Tippfehler zeigen sich im Editor-Dropdown statt zur Laufzeit.
FText nehmen, niemals FString
Das ist der Fehler, der am härtesten und am spätesten zubeißt. FString lässt sich nicht lokalisieren. FText schon. Wenn du deine Dialoge in FString-Felder schreibst und 4000 Zeilen shippst, ist dein Übersetzungspfad ein Rewrite jeder einzelnen Row.
Noch besser: die eigentlichen Strings in eine String Table legen und die DataTable nur die Keys referenzieren lassen. Dann kriegen deine Übersetzer ein Asset statt fünfzehn Tabellen. Das kannst du jederzeit nachziehen, aber am ersten Tag kostet es dich nichts.
Bedingungen ohne Spaghetti-Graph
Die Versuchung heißt Branch → Branch → Branch. Lass es. Gib jeder Zeile ein optionales ConditionTag und jeder Choice ein optionales RequiredTag, und stell zur Laufzeit genau eine Frage: hat der Tag-Container des Spielers diesen Tag?
CB2
Das ist das komplette Bedingungssystem. Quest-Fortschritt, Reputation, "hat mit dem Schmied gesprochen" — alles wird zum Tag. Ein Code-Pfad, unbegrenzte Bedingungen.
Facts: die Gedächtnis-Schicht
NPCs müssen sich erinnern. Halt dafür eine simple Map in einem Game-Instance-Subsystem: Map<FName, int32>. TalkedToLeif = 1. BoughtSword = 3. Bedingungen lesen, Events schreiben, und dein Save-System serialisiert eine Map statt jeden NPC einzeln abzuklappern.
Flach halten und klein halten. Ein Fact ist eine Zahl, kein Objekt.
Portraits und Voice-Lines: nur Soft References
Jede Dialog-Row, die eine Portrait-Textur oder ein Voice-Cue hart referenziert, zieht dieses Asset in den Speicher, sobald die DataTable lädt. Eine 300-Zeilen-Tabelle mit hart referenzierten 2K-Portraits kostet dich mehrere hundert Megabyte, bevor ein einziges Wort gesprochen wurde.
Nimm TSoftObjectPtr bzw. Soft References im Struct und lade beim Anzeigen async — das Muster aus Async-Loading in Blueprints. Das Portrait erscheint einen Frame später. Merkt keiner. Dein Speicher-Graph schon.
Events: sagen, nicht greifen
Wenn eine Wahl eine Tür öffnen oder eine Quest starten soll, lass das Dialog-Blueprint nicht die Tür suchen. Feuer ein EventTag und lass einen Listener das erledigen — wieder das Event-Dispatcher-Muster. So bleibt dein Dialog-System ein Dialog-System, statt langsam zu deinem Quest-System zu werden.
Häufige Fehler
- FString statt FText. Kostet dich den kompletten Lokalisierungspfad. Ab einer gewissen Menge nicht mehr billig zu reparieren.
- Portraits und Audio hart im Row-Struct referenzieren. Lädt deine ganze Besetzung beim Start.
- Dialog-State auf dem NPC speichern. Zwei NPCs im selben Gespräch oder ein Save mitten im Dialog, und es fällt auseinander. State gehört ins Subsystem.
- Row-IDs als Text von Hand tippen. Ein Tippfehler, eine stille Sackgasse. Nimm ein
Name-Feld und lass das DataTable-Dropdown validieren. - Einen Node-Graph-Editor bauen bevor du 50 Dialogzeilen hast. Eine DataTable im Spreadsheet ist schneller zu befüllen als jeder Graph, solange du nicht wirklich hart verzweigst. CSV exportieren, in Excel editieren, re-importieren.
30-Sekunden-Recap
- Blueprint spielt Gespräche ab; die DataTable ist das Gespräch.
FTextab Tag eins, idealerweise über eine String Table.- IDs sind
FName. Sprünge sind ID-Lookups, keine Node-Verbindungen. - Bedingungen und Choices gaten über Gameplay Tags — ein Code-Pfad.
- Facts leben im Subsystem als flache
Map<FName, int32>. - Portraits und Voice sind Soft References, async beim Anzeigen geladen.
Nächsten Sonntag: Abilities und Cooldowns ohne GAS — warum das volle Gameplay Ability System oft das falsche erste Werkzeug ist, und wie eine leichtgewichtige Alternative aussieht.
— Marco
Comments
Leave a comment