VNLEWIKI物語をつくる。世界をつなぐ。
日本語
VNLEガイド

MonoGame

← コンテンツをゲームに組み込む

MonoGame 3.8 · DesktopGL · C# / .NET

まず、一文を画面に表示してみましょう。この小さなサンプルは「港の鍵」の実際のエクスポートを直接読み込みます。会話をすべて実行する仕組みではありません。表示できたら、データをゲームのUIにつなげていきます。

1 ・ファイルやシーンの準備

MonoGame DesktopGLプロジェクトを用意します。story.jsonをContent/へ置き、元のファイルとして出力先にコピーする設定にします(下のプロジェクト設定を参照)。Game1にReadWelcomeを追加します。Content Pipeline ToolでDialogueFontというSpriteFontを作り、ビルドしてContent.Loadで読み込みます("DialogueFont")。

story.json · エクスポートファイルの説明

2 · 最初のテキストを表示

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

Game1にSpriteFont _dialogueFont;とstring _dialogueLine = "";を追加します。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)を使って作り、DrawでBegin()/End()を呼びます。

// 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は、最初の動作確認用です。実際のゲームでは、story-index.jsonからflowRef/entryRefを選び、execution.entries[entryRef].nodeRef、続いてexecution.nodes[nodeRef]を読みます。現在の会話ノードからtext.textRefとspeakerRefを取得します。次へ進む方法はノードのkindによって異なります。

名前と会話はBegin/End内のSpriteBatch.DrawStringで描画します。長文の折り返しとフォントの対応文字を確認してください。回答の領域とマウス・キーボード入力はゲーム側で用意し、翻訳文ではなくIDを保持します。EndではdialogueOpen = falseで描画と入力を止めます。Game.Exit()は使いません。

  1. ContinueとEnd:接続をたどり、会話ボックスを閉じる
  2. 選択肢:回答のID、表示順、選択後の進行先
  3. 条件と変数:10ゴールドと2ゴールドで試す
  4. 言語:同じIDから別の言語の内容を読み出す
  5. コマンド: アイテムを与えるか、ショップを開く
  6. Call/Return:サブストーリーを呼び出して戻る
この言語で回答と所持金の判定を確認する

選択肢のデータを読む

読み込み後にstoryを使える場所へ、このコード片を挿入します。RPG Makerではconst story = $gameTemp.vnleExample.story;で取得します。確認用にIDを出力するので、その出力処理を回答ボタンに置き換えてください。対象は港の2つの回答と、その後の所持金判定です。あらゆるノードを実行できる汎用処理ではありません。

// 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とし、コメントに記載したUI要素が存在することを前提とします。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と設定の資料: