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

Godot

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

Godot 4.x ・ GDScript

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

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

Labelをルートにしたシーンを作って保存します。港の完全なエクスポートをres://story/に置きます。LabelにFirstText.gdをアタッチし、F6でシーンを実行します。

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

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

extends Label

const TEXT_ID = "fd7b61a3-8233-4ba7-8f77-b88b119a91bf"
var story: Dictionary

func _ready() -> void:
    var parser = JSON.new()
    var error = parser.parse(FileAccess.get_file_as_string("res://story/story.json"))
    if error != OK or not parser.data is Dictionary:
        text = "Cannot read story/story.json"
        return
    story = parser.data
    show_language("en")

func show_language(locale: String) -> void:
    var record: Dictionary = story["content"]["texts"][TEXT_ID]
    var translation: Dictionary = story["content"]["translations"].get(locale, {}).get(TEXT_ID, {})
    var message: Dictionary = translation.get("message", record["sourceMessage"])
    var line := ""
    for segment in message["segments"]:
        if segment["kind"] != "text":
            text = "This example needs text-only segments"
            return
        line += segment["text"]
    text = line

「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によって異なります。

名前と会話はLabel.textで表示します。回答にはButton.textとpressedシグナルを使い、選択肢IDを保持します。Endでは共通のControlをhide()で隠します。サンプルのshow_language("de")は、ノードを進めずに同じ文章の言語を変更します。

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

選択肢のデータを読む

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

# story is the Dictionary loaded above. These Harbour options have no conditions.
var choice: Dictionary = story["execution"]["nodes"]["42edfff8-b8fe-46bf-ae0d-9e2347b6e726"]
for option_id in choice["optionOrder"]:
    var option: Dictionary = choice["options"][option_id]
    print(option_id, " ", option["text"]["textRef"])
# Resolve each text and bind its option_id to a Button's pressed signal.
var branch: Dictionary = story["execution"]["nodes"]["c1c3c372-0a73-4cd0-851d-ede59d5709bb"]
var gold := 10 # Repeat with 2.
var next: Dictionary = branch["whenTrue"] if gold >= branch["condition"]["right"]["value"]["value"] else branch["whenFalse"]
print(next["nodeRef"])

所持金が10ならnextは「Pay five gold」、2なら「Not enough gold」を指します。実際のゲームでは、選択されたルートがこのノードに到達したときだけ判定してください。各回答には、それぞれの進行先があります。

4 · 自分の画像を対応付ける

この補足コードでは、現在の会話オブジェクトをnodeとし、コメントに記載したUI要素が存在することを前提とします。C++とMonoGameでは、speakerRefはすでにSpeakerIdまたはspeakerIdとして読み込まれています。キーはミラの実際のキャラクターIDです。画像の名前と配置先はゲーム側で決めます。指定箇所に各行を挿入してください。

# Put mira.png at res://portraits/mira.png and add a TextureRect named Portrait.
var portraits = {"807b2957-7bab-4a28-83a4-1f3d1589bbc2": preload("res://portraits/mira.png")}
# node is the current dialogue dictionary, not a Godot scene node.
$Portrait.texture = portraits.get(node.get("speakerRef", ""))

話者名は、会話文と同様にcontent.characters[speakerRef].nameTextRefから取得します。画像の対応がない場合は、前のポートレートを隠すか代替画像を表示します。画像は言語によって切り替わりません。

完全なエクスポートに含まれる画像参照を使う

5 · ゲームに必要なファイルを同梱する

FileAccessで読むJSONは、そのままエクスポートに含めてください。利用できる場合はKeep File、または非リソースファイル用のエクスポートフィルターを使います。画像はインポートしたTexture2Dを使うか、元のPNGを明示的に含めます。エクスポートしたゲームも実行してください。

結果を確認する

  1. 英語とドイツ語の挨拶が正しく表示され、対応していない言語では英語に戻ることを確認します。
  2. 進行処理を追加したら、両方の回答、所持金10と2、サブストーリーからの復帰、Endを試してください。
  3. ビルドしたゲームをプロジェクトフォルダーの外で実行します。JSON、フォント、ポートレートがそこでも読み込めることを確認してください。

公式ドキュメント

この実装で使用するAPIと設定の資料: