Unreal Engine
Unreal Engine 5 - C++
Начните с вывода одной фразы. Этот небольшой пример напрямую читает реальные данные экспорта гавани, но ещё не является полноценной системой диалогов. Затем подключите данные к интерфейсу своей игры.
1 · Подготовка файлов и сцены
Используйте проект C++ и собственный Actor, размещённый на открытой карте. Добавьте Json в зависимости модуля в .Build.cs. Поместите story.json в Content/Story. Добавьте приведённую ниже вспомогательную функцию в .cpp вашего Actor и вызовите UE_LOG(LogTemp, Display, TEXT("%s"), *ReadWelcome()); в BeginPlay после Super::BeginPlay(). Результат появится в Output Log.
story.json · Назначение файлов экспорта
2 · Выведите первый текст
// 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;
}Ожидаемый текст: «Welcome to the harbour. I am Mira.» При locale = "de" или параметре языка de: «Willkommen im Hafen. Ich bin Mira.» Если язык отсутствует, используется оригинал. Этот пример поддерживает текстовые сегменты и не отбрасывает подстановочные поля без предупреждения.
3 · От одной фразы к разговору
Фиксированный ID текста выше нужен только для первого теста. В игре выберите flowRef/entryRef из story-index.json, прочитайте execution.entries[entryRef].nodeRef, затем execution.nodes[nodeRef]. Текущий диалог содержит text.textRef и speakerRef. Дальнейший путь зависит от типа узла.
Для панели диалога используйте UMG: UTextBlock::SetText(FText::FromString(Line)), UButton::OnClicked и UImage. Виджету нужна зависимость от модуля UMG и привязанные поля виджетов. При завершении вызовите RemoveFromParent() либо измените видимость и восстановите режим игрового ввода. Приведённому выше коду чтения JSON модуль UMG не нужен.
- Continue и End: переходы по связям и закрытие окна диалога
- Выбор: ID ответа, порядок и выбранное продолжение
- Условия и переменные: десять или две золотые монеты
- Язык: те же ID, заново полученный текст
- Command: выдать предмет или открыть магазин
- Call/Return: перейти в подысторию и вернуться
Разберите ответы и проверку золота на этом языке программирования
Прочитайте данные узла выбора
Вставьте фрагмент там, где после загрузки доступен story (для RPG Maker: const story = $gameTemp.vnleExample.story;). Он выводит ID для проверки; замените этот вывод кнопками ответов. Фрагмент охватывает два ответа примера и следующую за ними проверку золота, а не реализует универсальный интерпретатор.
// 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")));При 10 золотых next указывает на Pay five gold, при 2 — на Not enough gold. В игре выполняйте проверку только тогда, когда выбранный путь дошёл до этого узла. Каждый ответ имеет собственное продолжение.
4 · Настройте соответствия для своих изображений
Этот дополнительный фрагмент ожидает текущий объект диалога с именем node и элементы интерфейса, указанные в комментариях. В C++ и MonoGame speakerRef уже прочитан как SpeakerId или speakerId. Ключ — реальный ID персонажа Миры, а имя и расположение изображения определяются вашей игрой. Вставьте строки в указанные места.
// 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);Получайте имена персонажей через content.characters[speakerRef].nameTextRef так же, как текст диалога. Если соответствие изображения отсутствует, скройте предыдущий портрет или покажите заглушку. Изображения не выбираются по языку.
Используйте ссылки на изображения из полного экспорта
5 · Включите файлы в сборку игры
Добавьте Story в Project Settings → Packaging → Additional Non-Asset Directories to Package. FFileHelper использует файловый доступ Unreal. Не импортируйте этот JSON как DataTable: вложенные таблицы ID не являются строками DataTable. Руководство использует C++; предоставлять ли результаты в Blueprint, решаете вы сами.
Проверьте свой результат
- Приветствия на английском и немецком отображаются правильно; при выборе недоступного языка используется английский.
- Добавив выполнение сюжета, проверьте оба ответа, 10 и 2 золотых, возврат из подыстории и End.
- Запустите собранную игру за пределами папки проекта. JSON, шрифты и портреты должны быть доступны и там.
Официальная документация
Документация по API и настройке для этого подхода: