Unreal Engine
Unreal Engine 5 · C++
Beginne mit einem sichtbaren Satz. Dieses kleine Beispiel liest direkt den echten Harbour-Export; es ist kein vollständiger Dialogplayer. Anschließend verbindest du die Daten mit deiner Spieloberfläche.
1 · Dateien und Szene vorbereiten
Nutze ein C++-Projekt und einen eigenen Actor in der geöffneten Map. Ergänze Json in den Modulabhängigkeiten deiner .Build.cs. Lege story.json unter Content/Story ab. Füge den folgenden Helfer in die .cpp des Actors ein und rufe in BeginPlay nach Super::BeginPlay() auf: UE_LOG(LogTemp, Display, TEXT("%s"), *ReadWelcome()); Das Ergebnis erscheint im Output Log.
story.json · Exportdateien erklärt
2 · Den ersten Text anzeigen
// Add these includes at the top of your existing Actor .cpp file.
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "Dom/JsonObject.h"
#include "Serialization/JsonReader.h"
#include "Serialization/JsonSerializer.h"
// File-local helper; call from your Actor's BeginPlay.
static FString ReadWelcome()
{
FString Raw;
if (!FFileHelper::LoadFileToString(Raw, *(FPaths::ProjectContentDir() / TEXT("Story/story.json"))))
return TEXT("Cannot load story.json");
TSharedPtr<FJsonObject> Story;
const auto Reader = TJsonReaderFactory<>::Create(Raw);
if (!FJsonSerializer::Deserialize(Reader, Story) || !Story.IsValid())
return TEXT("Invalid JSON");
// Following accesses expect the supplied, validated Harbour export.
const auto Content = Story->GetObjectField(TEXT("content"));
const FString Id = TEXT("fd7b61a3-8233-4ba7-8f77-b88b119a91bf");
auto Message = Content->GetObjectField(TEXT("texts"))->GetObjectField(Id)->GetObjectField(TEXT("sourceMessage"));
const FString Locale = TEXT("en");
const TSharedPtr<FJsonObject>* Table = nullptr;
const TSharedPtr<FJsonObject>* Translation = nullptr;
if (Content->GetObjectField(TEXT("translations"))->TryGetObjectField(Locale, Table)
&& (*Table)->TryGetObjectField(Id, Translation))
Message = (*Translation)->GetObjectField(TEXT("message"));
FString Line;
for (const auto& Segment : Message->GetArrayField(TEXT("segments")))
{
const auto Part = Segment->AsObject();
if (Part->GetStringField(TEXT("kind")) != TEXT("text"))
return TEXT("Text-only example");
Line += Part->GetStringField(TEXT("text"));
}
return Line;
}Erwartet: „Welcome to the harbour. I am Mira.“ Mit locale = "de" beziehungsweise dem Sprachparameter de: „Willkommen im Hafen. Ich bin Mira.“ Fehlt die Sprache, wird der Quelltext verwendet. Das Beispiel unterstützt Textsegmente; Platzhalter werden absichtlich nicht stillschweigend übersprungen.
3 · Von einem Satz zum Gespräch
Die feste Text-ID oben dient dem ersten Test. Im Spiel wählst du flowRef/entryRef aus story-index.json, liest execution.entries[entryRef].nodeRef und dann execution.nodes[nodeRef]. Der aktuelle Dialog liefert text.textRef und speakerRef. Fortsetzungen hängen vom Knotentyp ab.
Für die Dialogleiste UMG verwenden: UTextBlock::SetText(FText::FromString(Line)), UButton::OnClicked und UImage. Das Widget benötigt UMG als Modulabhängigkeit und gebundene Widget-Felder. Ende: RemoveFromParent() oder Sichtbarkeit ändern und den Input-Modus des Spiels wiederherstellen. Das JSON-Lesebeispiel oben benötigt noch kein UMG.
- Weiter und Ende: Verbindungen verfolgen, Textbox schließen
- Choices: Antwort-ID, Reihenfolge und ausgewählter Ausgang
- Conditions und Variablen: zehn oder zwei Goldstücke
- Sprache: dieselben IDs, neu aufgelöste Inhalte
- Command: Gegenstand geben oder einen Shop öffnen
- Call/Return: Untergeschichte aufrufen und zurückkehren
Antworten und Goldprüfung in dieser Sprache nachvollziehen
Die Daten einer Entscheidung lesen
Füge diesen Ausschnitt dort ein, wo story nach erfolgreichem Laden verfügbar ist (bei RPG Maker: const story = $gameTemp.vnleExample.story;). Er gibt IDs zum Nachvollziehen aus. Die Anzeige ersetzt du durch deine Antwortbuttons. Er ist auf die zwei Harbour-Antworten und deren nachfolgende Goldprüfung begrenzt, kein allgemeiner Interpreter.
// Inside ReadWelcome, after Story has been parsed successfully.
const auto Nodes = Story->GetObjectField(TEXT("execution"))->GetObjectField(TEXT("nodes"));
const auto Choice = Nodes->GetObjectField(TEXT("42edfff8-b8fe-46bf-ae0d-9e2347b6e726"));
for (const auto& IdValue : Choice->GetArrayField(TEXT("optionOrder")))
{
const FString OptionId = IdValue->AsString();
const auto Option = Choice->GetObjectField(TEXT("options"))->GetObjectField(OptionId);
UE_LOG(LogTemp, Display, TEXT("%s: %s"), *OptionId, *Option->GetObjectField(TEXT("text"))->GetStringField(TEXT("textRef")));
}
const auto Branch = Nodes->GetObjectField(TEXT("c1c3c372-0a73-4cd0-851d-ede59d5709bb"));
const double Minimum = Branch->GetObjectField(TEXT("condition"))->GetObjectField(TEXT("right"))->GetObjectField(TEXT("value"))->GetNumberField(TEXT("value"));
const int32 Gold = 10; // Repeat with 2.
const auto Next = Branch->GetObjectField(Gold >= Minimum ? TEXT("whenTrue") : TEXT("whenFalse"));
UE_LOG(LogTemp, Display, TEXT("%s"), *Next->GetStringField(TEXT("nodeRef")));Mit 10 Gold verweist next auf „Pay five gold“, mit 2 auf „Not enough gold“. Beim tatsächlichen Spielen führst du die Prüfung erst aus, wenn der gewählte Pfad diesen Knoten erreicht. Jede ausgewählte Antwort liefert dafür ihre eigene continuation.
4 · Eigene Bilder zuordnen
Dieses Ergänzungsstück setzt einen aktuellen dialogue-Knoten namens node und die im Kommentar genannten UI-Elemente voraus. Bei C++ und MonoGame ist speakerRef bereits als SpeakerId beziehungsweise speakerId ausgelesen. Der Schlüssel ist Miras tatsächliche Charakter-ID; der Bildname und sein Ablageort gehören deinem Spiel. Füge die Zeilen an den angegebenen Stellen ein.
// In your UUserWidget header; assign the imported texture in its Blueprint defaults.
UPROPERTY(EditDefaultsOnly, Category="Dialogue")
TMap<FString, TObjectPtr<UTexture2D>> Portraits;
// Set the map key to: 807b2957-7bab-4a28-83a4-1f3d1589bbc2
// In widget code, PortraitImage is your bound UImage; SpeakerId comes from speakerRef.
auto* Texture = Portraits.Find(SpeakerId);
PortraitImage->SetBrushFromTexture(Texture ? Texture->Get() : nullptr);
PortraitImage->SetVisibility(Texture && Texture->Get()
? ESlateVisibility::Visible : ESlateVisibility::Hidden);Sprechernamen über content.characters[speakerRef].nameTextRef genauso wie Dialogtexte auflösen. Eine fehlende Zuordnung soll das alte Porträt ausblenden oder ein Ersatzbild zeigen. Bilder werden nicht nach Sprache gewählt.
Bildverweise aus dem vollständigen Export verwenden
5 · Dateien mit dem Spiel ausliefern
Unter Project Settings → Packaging → Additional Non-Asset Directories to Package den Ordner Story aufnehmen. FFileHelper nutzt Unreals Dateizugriff. Die JSON nicht als DataTable importieren: ihre verschachtelten ID-Tabellen sind keine DataTable-Zeilen. Dieser Einstieg ist C++; eine Blueprint-Anbindung ist eine Entscheidung deines Projekts.
Dein Ergebnis prüfen
- Der englische und deutsche Begrüßungstext erscheinen korrekt; eine nicht vorhandene Sprache fällt auf Englisch zurück.
- Beim Ergänzen des Ablaufs: beide Antworten, Gold 10/2, Rückkehr aus der Untergeschichte und Ende ausprobieren.
- Den gebauten Spielordner außerhalb deines Projekts starten. JSON, Schriften und Porträts müssen dort ebenfalls vorhanden sein.
Offizielle Dokumentation
API- und Einrichtungsreferenzen für diesen Weg: