【Unreal Engine】UE5の会話システム:文字送りと選択肢で、村人との会話を作る

作成: 2026-07-20最終更新: 2026-09-06

UE5のData TableとUMGで、村人へ話しかける会話を作ります。セリフの管理、1文字ずつの表示、途中スキップ、選択肢の分岐、会話後に操作を戻すところまで順に説明します。

村人に話しかけると、セリフが少しずつ現れる。読み終えて「次へ」を押すと続きを話し、「はい」「いいえ」を選ぶと返事が変わる。会話画面は、文字を表示するだけでなく、プレイヤーが読む速さや選んだ答えに合わせて進むUIです。

今回は、依頼を持ちかける村人との短い会話を作ります。セリフはData Tableへ置き、表示を担当するWidgetは「指定された行を読み、次に読む行へ進む」という形にします。文章を増やしたときも、同じ表示処理を使い回せます。

村人の依頼を読み、はい・いいえの選択肢で返事を選ぶ会話画面の完成イメージ

この記事でわかること

  • 会話を「次に読む行」でつなぐデータの持ち方
  • 1文字ずつ表示し、途中でスキップできるようにする方法
  • 選択肢の数だけボタンを並べて分岐する手順
  • 会話中は操作を止め、終わったら戻す流れ

この記事で作るもの

  • 村人の近くで E を押すと始まる、4行の会話
  • 1文字ずつ表示し、途中なら「次へ」で全文を出す文字送り
  • 「はい」「いいえ」で返事が変わり、最後は移動操作へ戻る流れ

Third Personテンプレートの、ローカルのプレイヤー1人・NPC1体で試します。UMGの部品配置構造体、Blueprintの変数とイベントを使ったことがある方を想定しています。NPCを増やす設計は最後に触れます。

Sponsored

会話は「次に読む行」でつなぐ

Data Tableは、同じ項目を持つデータを行ごとに並べるアセットです。今回は1行を1つのセリフにして、話者・本文・次の行を持たせます。

たとえば、Start 行を読み終えたら Ask 行へ。Askで「はい」を選んだら Yes 行へ進みます。行名は、次に読むページを指定する目印のようなものです。

セリフを行ごとに持ち、NextRowやChoicesで次に読む行を指定するData Tableの考え方

図のセリフは仕組みを見るための短縮例です。実践では、次の4行を入力します。

先に、選択肢1個分の形を決める

コンテンツブラウザでBlueprints → Structureを選び、S_DialogueChoice を作ります。

変数内容
ChoiceTextTextボタンに表示する「はい」などの文字
TargetRowName選んだ後に読む行の名前

次に、セリフ1行分の S_DialogueLine を作ります。

変数内容
SpeakerText話者名
LineTextセリフ本文
NextRowName「次へ」で進む行
ChoicesS_DialogueChoiceの配列その行で見せる選択肢。なければ要素数0

配列は、同じ種類のものを複数並べて持つためのものです。Choicesへ2要素入れれば、後で2つのボタンを作れます。

Textは画面へ見せる文章、Nameは行を指定する名前として使い分けます。Textで原文を持つと、後から翻訳へ対応しやすくなります。文字送りの間だけは、切り出しに使えるStringへ変換します。

村人の会話を4行入れる

Data Tableを作り、Row StructureへS_DialogueLineを指定して、DT_Dialogue と名付けます。今回はCSVを使わず、Data Tableのエディタで直接入力します。

Row NameSpeakerLineNextRowChoices
Start村人こんにちは。旅の人だね。Ask0要素
Ask村人よかったら、この村を手伝ってくれないか?None2要素
Yes村人ありがとう!恩に着るよ。None0要素
No村人そうか…気が変わったらまた来てくれ。None0要素

AskのChoicesに2要素を追加し、1つ目を ChoiceText=はい / TargetRow=Yes、2つ目を ChoiceText=いいえ / TargetRow=No にします。

None は、Name型で名前が指定されていない状態です。今回は「次の行がなければ会話を閉じる」という目印にします。ただしAskでは、先に選択肢を選んでもらいます。NextRowがNoneという理由だけで、質問を表示した直後に閉じるわけではありません。

セリフとボタンの置き場所を作る

User Widgetを親に WBP_Dialogue を作り、Designerで次の階層にします。最初は、画面に見える「次へ」ボタンから送る形にしましょう。

Canvas Panel
└─ Size Box
   └─ Border
      └─ Vertical Box
         ├─ SpeakerText(Text)
         ├─ Size Box
         │  └─ BodyText(Text)
         ├─ ChoiceBox(Vertical Box)
         ├─ ContinueIcon(Text「▼」)
         └─ AdvanceButton(Button)
            └─ Text「次へ」

外側のSize BoxはWidth Overrideを 760 にします。CanvasスロットはAnchorsを下中央、Alignmentを 0.5 / 1、Positionを 0 / -32、Auto Sizeをオン。Borderは白、Paddingは 24 にします。1280×720程度のゲーム画面で確認します。

SpeakerTextとBodyTextは濃紺、Font Sizeは 22。BodyTextのAuto Wrap Textをオンにし、その親のSize BoxはMin Desired Heightを 96 にします。短いセリフでも本文の場所を確保し、文字が増えるたびにパネルの高さが変わるのを抑えます。

Vertical Boxの部品は横Fill、上下Padding 4。AdvanceButtonは青、内側のTextは白、Padding 10 とします。ContinueIconは右寄せ、初期VisibilityはHiddenです。「▼」は、全文が出て次へ進める合図に使います。

SpeakerText・BodyText・ChoiceBox・ContinueIcon・AdvanceButtonのIs Variableをオンにしてください。グラフから表示や状態を変える部品です。ChoiceBoxには、いまは何も入れません。

DesignerのBodyTextへ試しに長めのセリフを入れ、折り返してパネル内に収まることを確かめます。確認後はSpeakerTextとBodyTextの初期文字を空に戻します。実行中の文字は、これから組む処理で設定します。

話者��・本文・選択肢・送りの合図・次へボタンを、縦に並べた会話パネルの配置図

図の色付きの枠と点線は、部品の場所を示すものです。空のChoiceBoxは実行時には高さが縮まり、選択肢を追加するとその分だけ広がります。

選択肢ボタンは、1個をひな型にする

もう1つUser Widgetを作り、WBP_ChoiceButton と名付けます。ルートに ChoiceButton(Button)、その子に ChoiceLabel(Text)を置き、両方のIs Variableをオンにします。ボタンは青、文字は白・Font Size 22、Padding 10。ChoiceLabelのAuto Wrap Textもオンにします。

このWidgetに、次を用意します。

用意するもの型・入力役割
変数TargetRowNameName、初期値Noneこのボタンを選んだ後の行
関数ConfigureChoiceInText:Text、InTargetRowName:Name表示文字と行名を受け取る
Event Dispatcher OnChosen入力RowName:Name選ばれた行名を親へ知らせる

ConfigureChoiceは、入口から Set TargetRowName → Set Text の順です。TargetRowNameの値へInTargetRowName、Set TextのTargetへChoiceLabel、In TextへInTextをつなぎます。

ConfigureChoiceで次の行名を保存し、ChoiceLabelへ選択肢の文字を設定する接続

ChoiceButtonのOn Clickedでは Call OnChosen を呼び、RowNameへGet TargetRowNameを渡します。

選択肢をクリックしたら、ボタンが覚えているTargetRowNameをOnChosenで知らせる接続

Event Dispatcherは、出来事を聞く登録をした相手へ知らせる仕組みです。このボタンは「Yesが選ばれた」と伝え、親の会話画面がYes行へ進みます。詳しい仕組みはEvent Dispatcherの記事も参照してください。

Sponsored

1文字ずつ見せる仕組み

全文が「こんにちは」なら、最初は「こ」、次は「こん」と、先頭から見せる範囲を広げます。全文そのものは変えず、何文字目まで見せるかを増やします。

全文「こんにちは」から、Leftで先頭の1文字・3文字・5文字を取り出す文字送りの仕組み

Left は、文字列の先頭から指定した数だけ取り出すノードです。表示文字数を1ずつ増やして、その結果をBodyTextへ入れます。

一定間隔で処理を呼ぶのが Timer です。今回は0.03秒ごとに表示を更新します。通常の文章なら、文字数が多いほど最後まで出る時間も長くなります。

WBP_Dialogueに、次の変数を作ってください。

変数初期値役割
CurrentLineS_DialogueLine構造体の既定値今表示している行の情報
FullStringString切り出しに使う全文
VisibleCountInteger0先頭から何文字を見せるか
CharIntervalFloat0.031回の更新までの秒数
TypeTimerTimer Handle既定値後で止めるタイマーを指定するための値
IsTypingBooleanfalse文字送りの途中ならtrue

IsTypingは、「次へ」を押したときの意味を変えるために使います。trueなら全文表示、falseなら選択肢の有無を調べて次の行へ進めるかを判断します。

ここから先の ShowLineRevealNextCharFinishTypingBuildChoicesHandleChoiceEndDialogue は、WBP_DialogueのEvent GraphへCustom Eventとして作ります。ShowLineとHandleChoiceにはName型の入力RowNameを追加し、ほかは引数なしです。

タイマーやDispatcherから呼ぶイベントも、同じEvent Graphへ置きます。通常のFunctionの編集画面にはCustom Eventを置かないでください。

表示する行を読み、タイマーを動かす

ShowLineで、前の表示を片付ける

ShowLineは「この名前の行を表示して」という呼び出しです。先に前のタイマーと表示を片付けます。

順番ノード指定するもの
1Clear and Invalidate Timer by HandleHandle=TypeTimer
2Set IsTypingfalse
3BodyTextのSet TextIn Textは空
4ContinueIconのSet VisibilityHidden
5ChoiceBoxのClear Children古い選択肢を取り除く
6AdvanceButtonのSet Is Enabledtrue

文字数を0にするだけでは、前のセリフは画面から消えません。 BodyTextも空にします。前のタイマーを止めるのは、新しい行を出し始めた後に、古い更新処理が混ざるのを防ぐためです。

次に、RowNameがName型の None と等しいかをBranchで分けます。TrueはEndDialogueへ進みます。

Falseでは Get Data Table Row を呼びます。Data TableはDT_Dialogueを直接選び、Row NameへShowLineのRowNameを渡してください。

このノードには、Row FoundとRow Not Foundという2つの白い実行出力があります。行が見つかったかを表すBooleanを探す必要はありません。

  • Row Found:次の「行の内容をセットする」へ
  • Row Not Found:Print StringでRowNameを表示し、EndDialogueへ

見つからない行を読んだまま会話画面を残すと、プレイヤーが操作へ戻れなくなります。失敗したときにも、閉じる処理へ進めます。Get Data Table Rowの公式仕様

行が見つかった場合だけOut RowをCurrentLineへ保存し、見つからなければ会話を終了する接続

行の内容をセットする

Row Foundから、次の順に実行線をつなぎます。

  1. Out Rowを Set CurrentLine の値へ渡す。
  2. CurrentLineを Break S_DialogueLine で分け、SpeakerをSpeakerTextのSet Textへ渡す。
  3. Lineを To String (Text) で変換し、Set FullStringへ渡す。
  4. VisibleCountを 0、IsTypingを true にする。

Breakは、構造体にまとまっている項目を取り出すノードです。ここでは話者・本文を使い、NextRow・Choicesは後の送り操作に使います。

FullStringの Len が0かをBranchで調べます。Lenは文字数です。空のセリフならTrueからFinishTypingへ進み、待たずに次へ進める状態にします。

Falseでは Set Timer by Event を呼び、TimeへCharInterval、Loopingをオンにします。赤いEventピンを、先ほど用意したRevealNextCharの赤い出力へつなぎます。Return ValueはSet TypeTimerへ渡し、白い実行線もTimerからSetへつなぎます。

赤い接続は「時間が来たらRevealNextCharを呼ぶ」という登録です。RevealNextCharの白い実行出力には、次の文字更新処理をつなぎます。

タイマーのEventへRevealNextCharを登録する赤い接続。0.03秒間隔で繰り返し呼ぶ

図は呼び出すイベントの登録部分です。TimeにはCharIntervalを接続し、Timerの白い実行出力とReturn ValueからSet TypeTimerへ進めてください。返された値を覚えておくと、全文表示や終了のときに、このタイマーを指定して止められます。

RevealNextCharで、見せる範囲を増やす

  1. VisibleCountへ整数の 1 を足し、Set VisibleCountで戻す。
  2. Left のSource StringへFullString、Countへ更新後のVisibleCountを渡す。
  3. LeftのReturn Valueを To Text (String) でTextへ変換し、BodyTextのSet Textへ渡す。
  4. VisibleCountがLen(FullString)以上かをBranchで調べ、TrueならFinishTypingを呼ぶ。Falseはそのまま終える。

次の更新はTimerが呼びます。この処理から自分自身を呼び直す必要はありません。

RevealNextCharからVisibleCountを1増やし、本文更新へ進む接続

増やした文字数を保存した後、その新しい値で先頭から切り出します。Set TextのTargetには、表示先のBodyTextをつなぎます。

FullStringと更新後のVisibleCountをLeftへ渡し、取り出した文字をBodyTextへ表示する接続

全文表示と「次へ」を分ける

表示途中は全文表示、全文表示後は次の行、選択肢待ちは答えを選ぶという3つの状態

FinishTypingで、読み終わりの状態へそろえる

FinishTypingの入口でIsTypingをBranchへ渡し、True側だけを進めます。Falseは何もしません。タイマーとボタン操作の両方から呼んでも、同じ行の選択肢を重ねて作らないためです。

Trueから、次の順につなぎます。

  1. Clear and Invalidate Timer by Handle でTypeTimerを止める。
  2. IsTypingをfalseへ変える。
  3. CurrentLineのLineを、Text型のままBodyTextのSet Textへ渡す。
  4. CurrentLineのChoicesを取り出し、配列のLengthが0より大きいかをBranchで分ける。

選択肢があるTrue側は、AdvanceButtonのSet Is Enabledをfalseにして、BuildChoicesを呼びます。False側はContinueIconをVisibleにします。

これで、文字送りの途中に「次へ」を押した場合も、最後まで待った場合も、全文が表示され、選択肢か「▼」が出ている状態へそろいます。

AdvanceButtonは、今の状態を見て動く

AdvanceButtonのOn Clickedから、IsTypingをBranchへ渡します。

状態処理
IsTyping=trueFinishTypingを呼び、いまの行を全文表示する
IsTyping=false、ChoicesのLengthが0ShowLineを呼び、RowNameへCurrentLineのNextRowを渡す
IsTyping=false、ChoicesのLengthが1以上何もしない。選択肢を待つ

false側では、ChoicesのLengthが0かをもう1つのBranchで調べます。NextRowがNoneなら、ShowLineの入口からEndDialogueへ進みます。

ゆっくり読むなら、文字が流れ終わってから1回。先を読みたいなら、1回目で全文を出して、2回目で次のセリフへ。途中の1回押しだけで、読んでいない次の行まで飛ばさないのが、この分け方の狙いです。

選択肢の数だけボタンを並べる

BuildChoicesでは、CurrentLineのChoicesを For Each Loop へ渡します。For Each Loopは、配列の要素を1つずつ取り出して、同じ処理を行うノードです。

選択肢のTargetRowを使い、「はい」ならYes、「いいえ」ならNoの行を読む仕組み

次の接続を、Loop Bodyから順につなぎます。

順番ノード指定するもの
1Create WidgetClass=WBP_ChoiceButton、Owning Player=Get Owning Player
2Configure ChoiceTarget=作ったWidget。InText=Array ElementのChoiceText、InTargetRowName=TargetRow
3Bind Event to OnChosenTarget=同じWidget。Event=HandleChoice
4Add Child to Vertical BoxTarget=ChoiceBox、Content=同じWidget

Array Elementは Break S_DialogueChoice で分けます。Create WidgetのReturn Valueは、Configure Choice、Bind、Addの3か所へ使います。作っただけでは画面に並ばないので、最後にChoiceBoxへ入れます。

選択肢Widgetを作り、配列から取り出した文字と行名をConfigureChoiceへ渡す接続

Bindの赤いEventピンへHandleChoiceをつなぎ、入力RowNameがName型でそろっていることを確認してください。HandleChoiceの白い実行出力はShowLineへつなぎ、RowNameをそのまま渡します。

選択肢のOnChosenを受け取る処理としてHandleChoiceを登録する赤い接続

赤い線は「ボタンが選ばれたときに呼ぶ相手」の登録です。Bindを実行した時点でHandleChoiceが走るわけではありません。実際に選ばれたら、次の白い線からShowLineへ進みます。

HandleChoiceで受け取った行名を、そのままShowLineへ渡す接続

一方、ボタンを作っている最中の処理は、Bindの白い実行出力からAdd Child to Vertical Boxへ続けます。Targetは入れ物のChoiceBox、Contentは中へ入れる選択肢Widgetです。

Add Child to Vertical BoxのTargetへChoiceBox、Contentへ作った選択肢を指定する接続

古いボタンはShowLineの最初で消します。BuildChoicesはFinishTypingのTrue側から、その行について1回だけ呼ぶ形です。

たとえば「いいえ」を選ぶと、ボタンがNoという名前を知らせ、HandleChoiceがShowLine(No)を呼びます。文章の内容を比較して「いいえだったら……」と分岐を増やす必要はありません。

Sponsored

NPCに話しかけ、会話後に操作を戻す

文字を表示する処理ができたので、会話を始めるきっかけと、終わった後の片付けをつなぎます。

NPCへ話しかけ、会話中は移動を止め、終了後に操作へ戻る3つの場面

NPCと、話しかけられる範囲を置く

Actorを親に BP_NPC を作り、目印のStatic MeshとBox Collisionを追加します。Boxは TalkRange と名付け、Box Extentを 150 / 150 / 100 にします。

TalkRangeは、プレイヤーを押し返す壁ではなく、出入りを知るための範囲です。目印のMeshには、ひとまずCubeなどの形を指定しておけば構いません。

TalkRangeはCollision EnabledをQuery Only、すべてのチャンネルをIgnoreにしたうえでPawnだけOverlap、Generate Overlap Eventsをオンにします。プレイヤーのCapsuleもGenerate Overlap Eventsをオンにしてください。目印のMeshは、この練習ではNo Collisionで構いません。

BP_NPCには、次の変数を作ります。

変数初期値
bPlayerInRangeBooleanfalse
StartRowNameStart
DialogueRefWBP_Dialogue Object ReferenceNone

DialogueRefは、作った会話画面を後から指定するための 参照 です。初期値のNoneは、まだ画面を覚えていない状態を表します。会話を始めるときに保存し、終わったら空に戻します。

StartRowはInstance Editableをオンにします。レベルに置いたNPCのDetailsから、開始行を変えられます。

TalkRangeのBegin Overlapでは、次の2つをObject型の ==(同じ相手かを比べるノード)で調べ、AND Boolean でまとめてBranchのConditionへ渡します。

  • Other Actorが Get Player Character(Player Index=0)と同じ
  • Other Compが、そのCharacterの Get Capsule Component と同じ

ANDは「両方ともtrueか」を調べます。プレイヤーのCapsuleだけを判定に使うことで、Meshなど別の部品が出入りしたときに、範囲外と扱うのを避けます。

BranchのTrueだけ、bPlayerInRangeをtrueにして Enable Input を呼びます。TargetはSelf、Player ControllerはGet Player Controller(0)です。

End Overlapも同じ2つの比較をし、TrueだけbPlayerInRangeをfalseにして Disable Input を呼びます。同じPlayer Controllerを指定します。

Enable Inputは、このNPCにもキー入力を受け取らせる登録です。普通のActorへEキーイベントを置くだけでは、入力が届かないため追加します。

会話画面を作り、終了の知らせを受け取る

WBP_Dialogueに、引数なしのEvent Dispatcher OnDialogueEnded を追加してコンパイルします。会話が終わったことを、画面を開いたNPCへ知らせます。

BP_NPCに E のKeyboard Eventを置き、PressedからbPlayerInRangeをBranchへ渡します。Trueから、実行ピン付きのIs ValidでDialogueRefを確認します。

Is Validは、参照先が存在し、利用できるかを調べます。今回は、会話画面がすでに作られているかの確認に使います。

  • Is Valid:すでに会話画面があるので、何もしない
  • Is Not Valid:Create WidgetでWBP_Dialogueを作る。Owning Player=Get Player Controller(0)
DialogueRefが有効なら会話中として何もせず、無効な場合だけ会話画面を作る接続

Create Widgetの後で、Return ValueをSet DialogueRefへ渡します。続けて、DialogueRefをTargetに Bind Event to OnDialogueEnded を呼びます。

作った会話画面のOnDialogueEndedへ、NPC側のHandleDialogueEndedを登録する接続

BindのEventから、BP_NPCのEvent Graphに引数なしのCustom Event HandleDialogueEnded を作ります。このイベントではSet DialogueRefを置き、値を未接続のNoneに戻します。画面を閉じた後に、もう一度話しかけられるようにする処理です。

HandleDialogueEndedが呼ばれたらDialogueRefをNoneに戻す接続

移動を止めて、入力を会話画面へ渡す

Bindの白い実行出力から、次の順につなぎます。

順番ノード指定するもの
1Add to ViewportTarget=DialogueRef
2Set Ignore Move InputTarget=Get Player Controller(0)、New Move Input=true
3Stop Movement ImmediatelyTarget=Get Player Character(0)から取得したCharacter Movement
4Set Input Mode UI OnlyPlayer Controller=Get Player Controller(0)、Focusは空、Do Not Lock、Flush Input=true
5Set Show Mouse CursorTarget=同じPlayer Controller、true
6Show LineTarget=DialogueRef、RowName=StartRow

UI Onlyは、操作の入力先をUIへ切り替える設定です。キャラクターがすでに持っている速度は、それだけで0になるとは限りません。今回は移動入力を無視する設定を入れ、Movement ComponentのStop Movement Immediatelyで現在の移動も止めます。

会話中の操作は、画面の「次へ」と選択肢のクリックです。Eは会話の開始に使い、表示中の送りには使いません。ゲームをポーズにはしないので、文字送りのTimerも動き続けます。

EndDialogueで、止めたものを戻す

WBP_DialogueのEndDialogueを、次の順で組みます。ここからPlayer Controllerを取得するときは Get Owning Player を使います。Create Widgetで指定した、画面の持ち主です。

順番ノード指定するもの
1Clear and Invalidate Timer by HandleHandle=TypeTimer
2Set IsTypingfalse
3Set Ignore Move InputTarget=Get Owning Player、New Move Input=false
4Set Input Mode Game OnlyPlayer Controller=Get Owning Player、Flush Input=true
5Set Show Mouse CursorTarget=Get Owning Player、false
6Remove from ParentTarget=Self
7Call OnDialogueEnded引数なし

Remove from Parentで会話画面を外し、OnDialogueEndedでNPCのDialogueRefも空へ戻します。タイマーの停止・入力の復帰・画面の片付けを、同じ終了処理にまとめました。

Set Ignore Move Inputは、今回trueにした1回に対してfalseを1回呼びます。開く処理が重複しないようにしたのは、画面だけでなく、こうした状態の戻し忘れを防ぐためでもあります。

最後まで通して確かめる

コンパイル・保存し、BP_NPCをThird Personのレベルへ1体置きます。床の上でNPCへ近づき、E を押してください。

まずはStartの1行で、文字が少しずつ現れ、最後に「▼」が出ることを確かめます。ここまで動けば、行の読み込み・Timer・Text表示がつながっています。

流れている途中で押すと全文表示、もう一度で質問へ、選ぶと返事、最後は会話画面が消える

次に、同じ会話を次の順で試します。

操作確かめる結果
文字が流れている間に「次へ」を1回押す同じセリフが全文表示される。次の行には飛ばない
全文表示後にもう1回押すAskの質問へ進み、表示完了後に「はい」「いいえ」が並ぶ
「はい」を選ぶYesの「ありがとう!恩に着るよ。」が出る
最後のセリフを読み、「次へ」を押す会話画面とカーソルが消え、再び移動できる
同じNPCへ再び話しかけ、今度は「いいえ」を選ぶNoの返事へ分かれる。古い選択肢は残らない
歩きながらNPCへ話しかける会話中に歩き続けず、終了後に移動を再開できる

次に、DT_DialogueのStart行だけを長い文章へ変えてみます。表示のBlueprintはそのままでも、セリフと表示時間が変わります。CharIntervalを 0.08 にすると、同じ文をゆっくり読ませられます。間隔は0より大きい値にしてください。

うまくいかないとき

症状確認する場所
1文字だけ出て止まるSet Timer by EventのLoopingがオンか
文字が出ないTimerのEventがRevealNextCharへつながっているか、CharIntervalが0以下でないか
行名だけがログに出て閉じるDT_Dialogueの指定、StartRowやTargetRowの綴り。余分な文字も確認する
前のセリフが一瞬残るShowLineの最初でBodyTextを空にしているか
選択肢が増え続けるShowLineのClear Children、FinishTyping入口のIsTyping分岐
選択肢を押しても進まないConfigureChoiceでのTargetRowNameの代入、OnChosenのCall、親のBind
Eを押しても始まらないTalkRangeのOverlap、Other Actorの比較、Enable Input
一度話すと再び話せないOnDialogueEndedのBindと、DialogueRefをNoneへ戻す処理
終了後も動けない、カーソルが残るEndDialogueの移動入力・Game Only・カーソルの3つの復帰

Row Nameに使うName型は、大文字と小文字を区別せず比較します。Startstart だけの違いを原因と決めつけず、存在しない行や別のData Tableを指定していないかを見ます。

おまけ:自分のゲームへ広げるなら

NPCを増やす。 今回は、1体のNPCへ入力を渡す練習です。複数の話せる相手が近くにいるゲームでは、プレイヤー側で「今どの相手へ話しかけるか」を1体に決め、その相手へ開始を依頼すると整理できます。

選択の結果でゲームを変える。 今回の「はい」は返事を変えるところまでです。クエストの受注などを加えるなら、会話の表示処理とは別の担当へ知らせます。クエスト・目標システムが続きになります。

2回目の会話を変える。 話した事実をGame InstanceやSaveGameなどへ持たせ、StartRowを選びます。Data Tableはセリフの定義として使い、プレイヤーごとの進行は別に覚えます。

翻訳や特殊な文字。 原文はTextで保持しましたが、途中表示ではStringを切り出しています。絵文字や結合文字には、見た目の1文字が複数の単位でできているものがあります。文字送りを崩さず見せたい場合は、その文字を含む実際の文章で確認し、必要に応じて切り出し方を変えます。

CSVやJSONで編集する。 会話が増えたら外部ファイルでの管理も候補です。ただし、再インポートでデータが置き換わるため、エディタで足したChoicesと取り込み元を別々に更新する運用は避けます。元データへ選択肢も含め、取り込み後に分岐先を確認します。

会話全体をスキップする。 最後の行へ飛ばすだけでよいかは、途中に受注や報酬などの処理があるかによります。飛ばしてよい演出と、必要な進行処理を分けてから実装します。

まとめ

会話の内容はData Tableへ置き、Widgetは指定された行を表示します。Timerで見せる文字数を増やし、途中の「次へ」は全文表示、読み終えた後の「次へ」は次の行へ進む操作にしました。

選択肢も、選ばれたボタンが持つTargetRowNameをShowLineへ渡す形です。まずは同じ村人へ2回話しかけて、YesとNoの両方を通してみてください。最後に移動へ戻れれば、読む・選ぶ・遊びへ戻る、という会話の一往復ができています。

参考:Data Tableの使い方Set Timer by EventName型の比較Stop Movement Immediately

Unreal Engine このセクションのノート98