UE5で会話システムを作った備忘録HD2D風-その1


目的

有料アセットとかは使わずに HD2D風(3D空間上に2Dキャラがいる状態)のマップ内で会話システムを作りたかった。
とても長くなってしまいそうなので分割して備忘録として残す。

主な流れとしては以下の通り。

  • その1(今ここ)
    会話システムのベース作り
  • その2
    会話・選択肢表示用のウィジェットの作成
  • その3
    その1,その2で作ったものを使った実際の会話フローの作成+追加機能

作りたいもの

分岐しつつ次の図のような会話システムを作る。

注意点

  • その1
    この記録は次のYoutube動画を参考(というかほぼそのまま)にしている。一部英語でしかうまく機能しないところがあったので、日本語対応のために改変しているのと、動画には載っていないが自分が実装したかった機能を勝手に付け加えている。

UE5 | Ultimate Dialogue System – Tutorial – Pt1

Michael Pattison氏による全3パートにわたる解説動画。Michael氏は神。全編英語だけど画面に従って作っていけば何の問題もない。

  • その2
    UnrealEngineを触るのが初めてなので Michael氏が作ってくださった変数だの関数だのの意味がよくわかっていない。一応多少は調べたが 見当違いなことを書いている可能性の方が高い。
  • その3
    ダイアログシステムでよく使われているDT(データテーブル)を使用しない方法である。
    ノードがかなり長くなるので 台詞等をまとめて管理したい人には向かない
    具体的にはマップ上の1キャラクターにつき 会話用に1つのブループリントを必要とする感じ。
    ただ、Michael法のブループリントの中身はフローチャート式になっており、 会話の流れ(分岐等)を理解しやすい仕組みであるという大きなメリットがある。

下準備

次の動画を参考に とりあえず2Dキャラクターが3D空間上を歩けるようにする。通常のThirdPersonでもいい場合はすっとばして構わない。
当サイトUE5でPaperZDを使ってHD2D風にキャラを歩かせたかった備忘録でも一応解説している。

上記動画はCobra Code 氏により解説されたもので 無料プラグインでUE5にも対応しているPaperZDを利用したもの。
Cobra Code氏も神。日本で働いているプロのゲームクリエイターの方らしい。

補足

プラグインをどうしても使いたくないとか PaperZDが使えなくなった等であればUnreal Engine 5 – Making A 2D Platformer in UE5 – Beginner Tutorialの序盤が分かりやすくていいかも。

ここでは

  • プレイヤー
    やさぐれひよこ素材置き場さんからダウンロードした「やさぐれひよこ」
  • ねずみさん
    お手製(ここでは動かさないので一枚絵のみ)

とした。

結果的にできたのがこちら。ひよこさんだけ歩かせた。


ダイアログシステム(基礎)の作成

ここからダイアログシステムの基礎の作成に入る。


ブループリント(列挙型)の作成

コンテンツドロワーの中に適当なフォルダを作成し、
右クリック → ブループリント → 列挙型 を選択。

Enum_DialogueStateと名前を付ける。
これを開き、列挙子を追加を2回選び、列挙子を2つにする。

ひとつには「PassThrough
もうひとつには「Updated」と名付ける。

ここまでできたら保存し、このブループリントを閉じる。


ベースとなるブループリントの作成

ここで作るブループリントを様々なキャラクターに読み込んで使うことになる。
コンテンツドロワーを右クリック → ブループリント → ブループリントクラス → Actor Component を選択。
このブループリントには、AC_Dialogue_Baseと名付ける。

これを開き、イベントグラフ上にあるデフォルトのノードは削除する。

関数の作成

今後必要になる関数をここで概ね作ってしまう。
関数欄の+ボタンを選択して新しい関数を作成し、次のように名前を変更する。
たくさんあるので注意。

  • OpenConversation
    会話開始のための関数
  • CloseConversation
    会話終了のための関数
  • ProgressDialogue
    会話の進捗を管理するための関数
  • Dialogue
    会話内容そのものを制御するための関数。実際の会話内容は子ブループリントを作るが、この関数を子ブループリントでオーバーライドすることで動かす らしい。
  • AddDialogue
    会話内容追加のための関数
  • UpdateSelectedOption
    選択肢のアップデートのための関数
  • ClearDialogueProgress
    会話の進捗をクリアするための関数
  • RemoveDialogueProgress
    会話の進捗を一部だけ戻すための関数
  • SetDialogTreeindex
    Treeindex(同じ会話相手でも、条件によって会話内容が変わることがあるよね。ショップシステムの在庫の有無とか。要するに選択肢の選択による分岐とはまた別の分岐)をセットするための関数

ひとまずはこうなっていればOK。

ウィジェットの作成

変数の作成の前に、必要なブループリントを作る。
そのひとつがダイアログ表示用ウィジェットのブループリント。細かい部分はその2で作成するが、ガワだけ今作成しておく。

適当なフォルダ内で右クリック → ユーザーインターフェース →
ウィジェットブループリント → User Widget を選択。
このブループリントには、W_Dialogueと名付ける。

これを開いて右上のグラフボタンを選択。

デフォルトのノードを削除し、変数を+ボタンで作成する。
新たに作った変数の名前をOwningDialogueComponentに変更し、型にAC_Dialogue_Baseを選択。
詳細タブ内のインスタンス編集可能およびスポーン時に公開にチェックを入れる。

更に、関数も+ボタンで作成する。
新たに作った関数の名前をUpDateDialogueTextとし、詳細タブ内のインプットから4つのパラメータを次の表のように追加する。
コンテナタイプは型の横にあるドロップダウンから選ぶ。

パラメータ名 コンテナタイプ
DialogueIndex Integer 単一
DialogueProgress Text 単一
Dialogueindex Text 単一
DialogueTreeindex Text 配列

ひとまずはこうなっていればヨシ。

変数の作成

先述のAC_Dialogue_Base上で、今度は変数をいくつか作成する。
これまたたくさんあるので注意。

設定項目は次の表の通り。

変数名 コンテナタイプ 目的
DialogueWidget W_Dialogue(オブジェクト参照) ダイアログを表示する部分に使う
DialogueProgress Integer マップ 前:到達したダイアログの部分 後:選択肢
Dialogueindex Integer 単一 ダイアログのどこにあるかを追跡する
DialogueTreeindex Integer 単一 Treeindex(条件分岐)に使う

ひとまずはこうなっていればOK。


関数詳細作成

Open Conversation関数の詳細作成

AC_Dialogue_Base内のOpenConversation関数をダブルクリックし、開く。

ここでやりたいことは「ダイアログを含むウィジェットの表示」である。

  1. ブループリント枠内を右クリック → create widgetと検索し、「ウィジェットを作成」を選択する。
    「何も構成しない」ノードが出るので、「クラスを選択」プルダウンからW_Dialogueを選択。OpenConversationと繋げる。
  2. 続いて先ほど作成した変数からDialogueWidget変数をドラッグ&ドロップする。Set Dialogue Widgetで配置。
    ウィジェット作成ノードの実行とReturnValueを、セットノードに繋げる。
  3. セットノードからリンクを伸ばした先に「Add to ViewPort」ノードを繋げる。
  4. 先ほど作成した関数からProgressDialogueをドラッグ&ドロップし、Add to Viewportノードと繋げる。
  5. ウィジェットを作成ノードのOwning Dialogue Component要素からリンクを伸ばし、get a reference to selfを繋げる。


こうなっていればヨシ!


Close Conversation関数の詳細作成

ここでやりたいことは「ダイアログを含むウィジェットの表示を消すこと」である。

  1. 変数からDialogueWidgetをドラッグ&ドロップし、Getで配置。
  2. DialogueWidgetからリンクを伸ばし、Remove from Parentノードを繋げる。RemoveノードとCloseConversationノードも繋げる。


こうなっていればヨシ!

POINT

もしお好みで会話中にキャラクターの動きを止める等の特殊な処理を入れた場合は、このCloseConversation関数中にその処理を無効にするノードを入れることで会話後にもとに戻せる。

CloseConversation関数の編集は一旦ここで中断し、別の関数を作ろう。この関数はこの後編集する!


ProgressDialogue関数の詳細作成

  1. 変数からDialogueindexをドラッグ&ドロップし、Setで配置。ProgressDialogueノードと繋げる。
  2. セットノードのDialogueindexの値をー1にする。
  3. 関数からDialogue関数をドラッグ&ドロップで配置。セットノードの右側実行ピンと繋げる。


ひとまずはこうなっていればヨシ!


Add Dialogue関数の詳細作成

  1. Add Dialogueノードをクリックし、詳細タブのインプットの+ボタンからインプットピンを3つ追加する。
    それぞれSpeaker,Dialogue,Options textと名付け、ピンタイプはTextとし、Options textのみコンテナタイプを配列に変更する
  2. 変数からDialogueindexをドラッグ&ドロップし、Getで配置。これからリンクを伸ばし、increment Intを選択する。
  3. Increment IntノードとAdd Dialogueノードを繋げる。
  4. 変数からDialogueProgress変数をGetで配置。ここからリンクを伸ばし、findを選択する。
  5. もう一つDialogueindex変数をGetで配置し、find左下ピンと接続する。
  6. findノードの右下の赤いピンを伸ばし、ブランチを選択。ブランチの左上ピンと、Increment Int(++)ノードの右上ピンとを接続する。

    ここまででこんな感じ。Findノード右上ピンは後でつなぐ。
POINT

5,6あたり…この関数が呼び出されるたびにDialogueindex変数に1が加算される。初めて呼び出したときにDialogueindex変数を0としたいので、PrigressDialogue関数中でDialogueindex変数の値を-1と設定したらしい。

  1. ブランチのTrueピンを伸ばし、リターンノードを選択。
  2. 変数からDialogueWidget変数をGetで配置。ここからリンクを伸ばし、Update Dialogue Textを呼び出す。
  3. ブランチのFlaseピンと、UpdateDialogueTextノードの左上実行ピンとを接続する。
  4. もう一つDialogueindex変数をGetで呼び出し、UpDateDialogueTextノードのDialogue indexピンと接続する。
  5. Add DialogueノードのSpeaker,Dialogue,Options TextピンをUpdateDialogueTextノードのそれらとそれぞれ接続する。
    (もしくは右クリック → get Speaker等で呼び出し、それぞれUpdateDialogueTextノードと接続してもよい。画像はこちらの方法を採用した。)
  6. UpdateDialogueTextノードの右上実行ピンからリンクを伸ばし、もうひとつリターンノードを配置する。
  7. 2つのうちどちらかのリターンノードを選択し、詳細タブのアウトプットの+ボタンで要素を追加する。名前はState、ピンタイプはEnum Dialogue State、コンテナタイプは単一
  8. 各々のリターンノードにStateピンが追加されるので、ブランチのTrueと繋がっているリターンノードのStateはPassThrough、UpdateDialogueTextノードと繋がっているリターンノードのStateはUpdatedとする。
POINT

12,13,14あたり…このブランチにより、まだ会話が続くかどうか?を確認して、返す値を変えているらしい。難しい。


長くなったがこうなっていればヨシ。


Update Selected Option関数の詳細作成

  1. 変数からDialogue ProgressをGetで呼び出す。
  2. Dialogue Progressからリンクを伸ばし、青い関数アイコンが目印の マップ Addを接続。Update Selected Optionノードとも繋げる。
  3. Addの左下の2つのピンを、Update Selected Optionノードにドラッグ&ドロップで直接接続する。
  4. Update Selected Optionノードを選択し、インプットのKey,Valueの名前を変更する。それぞれProgressIndex、OptionIndexとする。
  5. 関数からProgressDialogueをドラッグ&ドロップで呼び出し、Addの実行ピンと接続する。


こうなっていればヨシ!


Clear Dialogue Progress関数の詳細作成

  1. 変数からDialogue Progress をGetで呼び出す。
  2. Dialogue Progressからリンクを伸ばし、Clearを選択して接続する。
  3. ClearノードとClear Dialogue Progressノードを接続する。


こうなっていればヨシ!


Remove Dialogue Progress関数の詳細作成

  1. Remove Dialogue Progressノードを選択し、詳細タブのインプットからパラメータを追加する。名前はProgressSteps、ピンタイプはInteger、コンテナタイプは単一
  2. Remove Dialogue Progressノードの右上ピンからリンクを伸ばし、For Loopノードを選択して接続する。For LoopノードのLast indexピンとProgress Stepsピンも接続する。
  3. 変数からDialogue Progress変数をGetで呼び出し、ここからリンクを伸ばしてRemoveを接続する。Removeの左上ピンとFor LoopノードのLoop Bodyピンも接続する。
  4. 変数からDialogueindex変数をGetで呼び出し、ここからリンクを伸ばして減算するを接続する。減算ノードの左下ピンとFor LoopノードのIndexピンも接続する。
  5. 減算ノードの右上ピンと、Removeノードの左下ピンを接続する。


こうなっていればヨシ!


Set Dialog Treeindex関数の詳細作成

  1. Set Dialog Treeindexノードを選択し、詳細タブのインプットからパラメータを追加する。名前はDialogueTreeindex、ピンタイプはInteger、コンテナタイプは単一
  2. 変数からDialogueTreeindex変数をセットで呼び出し、Set Dialog Treeindexノードとリンクをそれぞれ繋げる。
  3. 関数からClear Dialogue Progressを呼び出し、セットの右上ノードと接続する。


こうなっていればヨシ!


まとめ

ここまで頑張ったけど進捗的には1/3程度。
ここからさらに細かい部分を作りこむ。