Reaktive & dynamische Dialoge und ein Intro mit besserem Tempo
Hinweis: Dieser Blogpost wurde maschinell aus dem englischen Original übersetzt. Zum englischen Original.
Überblick
Der Oktober war ein schwieriger Monat. Ein Todesfall in der Familie und die Unterstützung und Fürsorge danach gingen vor. Ich konnte trotzdem an den Pacing-Problemen im Early Game arbeiten. Das waren vor allem Dialogänderungen, eine neue Reihenfolge bei den Freischaltungen und Playtesting.
Die gute Nachricht ist, dass der Einstieg in Exofactory jetzt deutlich spannender ist. Weniger Kino-Intro im Stil von "In einer Welt, in der du eine verlorene KI spielst" und mehr so ein "Ich bin eine verlorene KI und muss Sachen machen!" Vibe.
Dialoge und Freischaltbedingungen zu bearbeiten ergibt keinen suuuuuper interessanten Blog-Content. Also dachte ich mir, ich nutze die Gelegenheit und erkläre mal Exofactorys reaktives, dynamisches Dialogsystem.
Es ist ziemlich cool und komplett datengetrieben.
Wie immer: Wenn du das Spiel interessant findest oder meine Arbeit unterstützen möchtest, hilft es sehr, das Spiel auf Steam auf die Wunschliste zu setzen.
Das Dialogsystem im Überblick
In Exofactory spielst du eine KI, die ständig nachdenkt und über sich selbst reflektiert. Sie kommentiert, was beim Spielen passiert, und der Spielfortschritt ist eng mit dieser Selbstreflexion verknüpft.
Der Tutorial-Dialog ist einfacher, da liegt alles in einer riesigen RON-Datei pro Sprache. Fürs Tutorial passt das, es läuft von Anfang bis Ende in einer festen Reihenfolge ab. Nach dem Tutorial ist der Dialog aber eigentlich nur noch ein Haufen kleiner, voneinander unabhängiger Events, die in so ziemlich jeder Reihenfolge ausgelöst werden können.
Da ich, wann immer es geht, einen datenorientierten Ansatz bevorzuge, habe ich nach einer schöneren Möglichkeit gesucht, das aufzubauen, und am Ende eine Idee von Linux geklaut.
Von Linux geklaut
Unter Linux (oder den meisten anderen modernen Betriebssystemen wie den BSDs) gibt es .d-Verzeichnisse wie /etc/nginx/conf.d/ oder /etc/sysctl.d/. Im Grunde teilt man die Config in viele kleine Häppchen auf, statt alles in eine riesige Datei zu packen. Mit diesem Pattern können Linux-Admins Änderungen in kleinere, kontrollierte Stücke zerlegen. Man muss nicht über die Anzahl der nginx-Worker nachdenken, wenn man einen neuen Reverse-Proxy-Endpunkt hinzufügt.
Ich bin ein großer Fan von diesem Pattern. Events lassen sich unabhängig voneinander hinzufügen oder entfernen, und das ist sauber im Code und sauber in der Git-History.
Der ganze Dialog und die zugehörigen Metadaten sind in sich geschlossen und simpel.
assets/core/event_data/dialogue/tier1/
discover_iron_ingot.d_group.ron
make_zinc_ingot.d_group.ron
open_furnace_window.d_group.ron
unlock_screws.d_group.ron
...Wie eine d_group-Datei aussieht
Jede Datei hat zwei Teile, die Metadaten und den eigentlichen Dialog. Hier sind die Metadaten für den Moment, in dem das erste Eisenerz abgebaut wird:
metadata: (
id: "tier1.discover_iron_ingot",
set: Tier1,
watch_keys: [
MemoryGroup(Items),
],
conditions: [
(
t: MemoryAmount,
c: (
value: ItemsIronOre,
comparison: GreaterOrEqual,
threshold: 1,
),
),
],
fire_mode: Once,
before_dialogue: [
(
t: GrantRecipes,
c: (
recipes: [
HandcraftIronIngot,
SmeltIronIngot,
],
),
),
],
after_dialogue: [],
),
Oben sehen wir sowohl, wann der Dialog ausgelöst wird, als auch, was er macht. Die watch_keys sagen "schau mich nur an, wenn sich bei den Items etwas ändert". Die conditions (vorerst unterstützt das Spiel nur inklusive Bedingungen) sagen, dass der Eisenerz-Zähler mindestens bei 1 stehen muss. fire_mode: Once heißt, dass er nie mehr als einmal ausgelöst wird. Im before_dialogue- oder after_dialogue-Teil können die Freischaltungen passieren, in diesem Fall werden zwei Eisenbarren-Rezepte freigeschaltet, direkt bevor die Zeile abgespielt wird.
Ich bin eigentlich nicht begeistert von dem t: und c: überall, aber es war die sauberste Umsetzung, die ich hinbekommen habe, wenn man bedenkt, wie serde Enum-Varianten serialisiert.
Der zweite Teil ist der Dialog. Jede Dialogzeile hat einen Pfad zu ihrer Audiodatei, den Dialogtext selbst und zusätzliche Metadaten wie die Länge der Audiodatei und die Pause vor und nach dem Audio. Wird der Dialog ausgelöst, werden sie einfach in der Reihenfolge aus der Datei abgespielt.
Die Dateien laden
Hier hat das Bevy-Ökosystem den Großteil der Arbeit für mich erledigt.
Mit bevy_common_assets kann ich festlegen, dass jede Datei, die auf d_group.ron endet, in ein DialogueTriggerAsset-Struct deserialisiert wird:
app.add_plugins(RonAssetPlugin::<DialogueTriggerAsset>::new(&[
"d_group.ron",
]))
Dann lädt bevy_asset_loader einfach einen ganzen Tier / ein ganzes Verzeichnis an Dialogen als Collection:
#[derive(AssetCollection, Resource)]
pub struct Tier1TriggerAssets {
#[asset(path = "core/event_data/dialogue/tier1", collection(typed))]
pub triggers: Vec<Handle<DialogueTriggerAsset>>,
}
Und das ist so ziemlich der ganze Ladecode. Es gibt nirgendwo eine Liste von Dateien. Wenn eine Datei in diesem Verzeichnis liegt und auf d_group.ron endet, wird sie geladen.
Den Dialog auslösen
Das Spiel hält in einer Memories-Komponente fest, was du bisher gemacht hast (Gebäude, Items, Gebäudemenüs, Kamerawechsel und so weiter), zusammen mit Mood, also dem emotionalen Zustand der KI. Diese Datenstrukturen vergleiche ich mit einer Kopie vom letzten Dialog-Trigger-Check. Wenn sich zum Beispiel das Sub-Struct mit den Item-Zählern geändert hat, werden bei den Triggern, die Items beobachten, die Bedingungen geprüft.
Alles, was ausgelöst werden soll, landet in einer Queue. Wenn gerade kein anderer Dialog läuft, wird der nächste rausgeholt, die Before-Actions laufen, die Zeilen für deine Sprache werden ausgewählt, und das Ganze wird an die Untertitel- und Audiosysteme übergeben.
Das funktioniert auch im Multiplayer schön. Der Host hält einfach Mood und Memories aktuell und der Client reagiert einfach darauf.
Fazit
Ich bin froh, mal einen Grund zu haben, zu erklären, wie das Dialogsystem in Exofactory funktioniert. Die Umstände sind nicht schön, aber mit dem flotteren Intro und der anstehenden Terrain-Überarbeitung stehen spannende Zeiten bevor.
Wenn du Demo 2 spielen möchtest, sobald sie rauskommt, oder mich ganz allgemein unterstützen willst, kannst du das Spiel hier auf die Wunschliste setzen: