VNLEWIKIСоздавайте истории. Соединяйте миры.
Русский
Руководство VNLE

MonoGame

← Интеграция контента в игру

MonoGame 3.8 · DesktopGL · C#/.NET

Начните с вывода одной фразы. Этот небольшой пример напрямую читает реальные данные экспорта гавани, но ещё не является полноценной системой диалогов. Затем подключите данные к интерфейсу своей игры.

1 · Подготовка файлов и сцены

Начните с проекта MonoGame DesktopGL. Поместите story.json в Content/ и настройте копирование исходного файла в выходную папку (см. конфигурацию проекта ниже). Добавьте ReadWelcome в Game1. В Content Pipeline Tool создайте SpriteFont с именем DialogueFont, соберите контент и загрузите его через Content.Load("DialogueFont").

story.json · Назначение файлов экспорта

2 · Выведите первый текст

<ItemGroup>
  <None Update="Content/story.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

Добавьте поля SpriteFont _dialogueFont; и string _dialogueLine = ""; в Game1. В LoadContent используйте _dialogueFont = Content.Load("DialogueFont"); _dialogueLine = ReadWelcome("en");. В Draw, внутри существующего блока SpriteBatch, используйте _spriteBatch.DrawString(_dialogueFont, _dialogueLine, new Vector2(24, 24), Color.White);. Если SpriteBatch ещё нет, создайте его в LoadContent через new SpriteBatch(GraphicsDevice) и используйте Begin()/End() в Draw.

// Game1.cs: add using System.IO; using System.Text.Json; using System.Text;
// Place this method inside the existing Game1 class.
private string ReadWelcome(string locale)
{
    using var stream = TitleContainer.OpenStream("Content/story.json");
    using var document = JsonDocument.Parse(stream);
    var content = document.RootElement.GetProperty("content");
    const string id = "fd7b61a3-8233-4ba7-8f77-b88b119a91bf";
    var message = content.GetProperty("texts").GetProperty(id).GetProperty("sourceMessage");
    if (content.GetProperty("translations").TryGetProperty(locale, out var table)
        && table.TryGetProperty(id, out var translation))
        message = translation.GetProperty("message");
    var line = new StringBuilder();
    foreach (var segment in message.GetProperty("segments").EnumerateArray())
    {
        if (segment.GetProperty("kind").GetString() != "text")
            throw new InvalidDataException("Text-only example");
        line.Append(segment.GetProperty("text").GetString());
    }
    return line.ToString();
}

Ожидаемый текст: «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. Дальнейший путь зависит от типа узла.

Имя и диалог: SpriteBatch.DrawString внутри Begin/End. Обработайте перенос строк и наличие нужных символов в шрифте для длинного текста. Области ответов и ввод мышью или клавиатурой реализует игра; храните ID, а не переведённый текст. End: отключите вывод и ввод через dialogueOpen = false, а не Game.Exit().

  1. Continue и End: переходы по связям и закрытие окна диалога
  2. Выбор: ID ответа, порядок и выбранное продолжение
  3. Условия и переменные: десять или две золотые монеты
  4. Язык: те же ID, заново полученный текст
  5. Command: выдать предмет или открыть магазин
  6. Call/Return: перейти в подысторию и вернуться
Разберите ответы и проверку золота на этом языке программирования

Прочитайте данные узла выбора

Вставьте фрагмент там, где после загрузки доступен story (для RPG Maker: const story = $gameTemp.vnleExample.story;). Он выводит ID для проверки; замените этот вывод кнопками ответов. Фрагмент охватывает два ответа примера и следующую за ними проверку золота, а не реализует универсальный интерпретатор.

// Inside ReadWelcome while document is alive; inspect data before returning.
var nodes = document.RootElement.GetProperty("execution").GetProperty("nodes");
var choice = nodes.GetProperty("42edfff8-b8fe-46bf-ae0d-9e2347b6e726");
foreach (var idToken in choice.GetProperty("optionOrder").EnumerateArray())
{
    string optionId = idToken.GetString();
    var option = choice.GetProperty("options").GetProperty(optionId);
    System.Console.WriteLine(optionId + " " + option.GetProperty("text").GetProperty("textRef").GetString());
}
var branch = nodes.GetProperty("c1c3c372-0a73-4cd0-851d-ede59d5709bb");
int gold = 10; // Repeat with 2.
int minimum = branch.GetProperty("condition").GetProperty("right").GetProperty("value").GetProperty("value").GetInt32();
var next = branch.GetProperty(gold >= minimum ? "whenTrue" : "whenFalse");
System.Console.WriteLine(next.GetProperty("nodeRef").GetString());
// Copy strings/values you need later before disposing JsonDocument.

При 10 золотых next указывает на Pay five gold, при 2 — на Not enough gold. В игре выполняйте проверку только тогда, когда выбранный путь дошёл до этого узла. Каждый ответ имеет собственное продолжение.

4 · Настройте соответствия для своих изображений

Этот дополнительный фрагмент ожидает текущий объект диалога с именем node и элементы интерфейса, указанные в комментариях. В C++ и MonoGame speakerRef уже прочитан как SpeakerId или speakerId. Ключ — реальный ID персонажа Миры, а имя и расположение изображения определяются вашей игрой. Вставьте строки в указанные места.

// Game1 class field, so both LoadContent and Draw can access it:
private readonly System.Collections.Generic.Dictionary<string, Texture2D> portraits = new();
// Inside LoadContent(): import/build mira.png as portraits/mira.
portraits["807b2957-7bab-4a28-83a4-1f3d1589bbc2"] = Content.Load<Texture2D>("portraits/mira");
// Inside a SpriteBatch Begin/End in Draw; speakerId comes from speakerRef:
if (portraits.TryGetValue(speakerId, out var portrait))
    _spriteBatch.Draw(portrait, new Vector2(24, 120), Color.White);

Получайте имена персонажей через content.characters[speakerRef].nameTextRef так же, как текст диалога. Если соответствие изображения отсутствует, скройте предыдущий портрет или покажите заглушку. Изображения не выбираются по языку.

Используйте ссылки на изображения из полного экспорта

5 · Включите файлы в сборку игры

Не загружайте JSON через Content.Load в этом примере. TitleContainer открывает исходный файл, csproj копирует его. Соберите SpriteFont и текстуры через Content Pipeline. Сначала проверьте DesktopGL, остальные платформы — отдельно.

Проверьте свой результат

  1. Приветствия на английском и немецком отображаются правильно; при выборе недоступного языка используется английский.
  2. Добавив выполнение сюжета, проверьте оба ответа, 10 и 2 золотых, возврат из подыстории и End.
  3. Запустите собранную игру за пределами папки проекта. JSON, шрифты и портреты должны быть доступны и там.

Официальная документация

Документация по API и настройке для этого подхода: