【Unreal Engine】クエスト入門:3体倒して報告し、報酬を一度だけ受け取る

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

UE5のBlueprintで、受注・討伐数の更新・報告・報酬までつながるクエストを作ります。定義と進捗を分け、達成と完了を4状態で整理。Mapへの書き戻しや通知の登録も確認し、キー操作で0/3から報酬100Gまで試します。

スライムを3体倒した。依頼主へ報告すると100Gもらえた。ところが、もう一度話しかけると、また100Gもらえてしまう。クエストでは「何体倒したか」に加えて、「いまどの段階か」も覚えておく必要があります。

今回は、受注→討伐→報告→報酬 という一周を作ります。討伐数は0/3から進み、3体目で「報告しよう」へ変化。報告後は、同じ操作を繰り返しても報酬が増えない形にします。

クエストの0/3表示を見上げ、目標を確かめるイメージ

この記事でわかること

  • クエストの定義と、プレイヤーの進捗を分ける
  • 4つの状態で、受注・達成・報告の可否を決める
  • 対象の敵を倒した通知で、進行中のクエストだけ数える
  • 早すぎる報告と二重報酬を、キー操作で確かめる

Blueprintの変数・関数・Branchを使える人向けの実践です。まずは キー入力を撃破の代わりにし、Print Textの文字で進捗を確認 します。戦闘や会話、画面隅のクエスト表示は、この仕組みが動いてからつなげます。

Sponsored

「達成した」と「報告した」を分ける

今回のクエストは、次の4段階で考えます。状態 は「いまどの段階にいるか」を表す値です。

未受注から進行中、達成、完了へ進む。達成前の報告と二重完了は止める
状態意味次へ進むきっかけ
NotStarted:未受注まだ引き受けていない受注する
InProgress:進行中受注済みで、まだ3体に届かない3体目を倒す
ReachedGoal:達成討伐は済んだが、報酬は未受取報告する
Completed:完了報告し、報酬を受け取った今回はここで終了

Enum は、用意した選択肢から1つを選ぶための型です。「受注済み」「達成済み」「報酬受取済み」を別々のBooleanで持つ代わりに、現在の状態を1つ持ちます。Enumの記事と同じ考え方です。

報告を受け付けるのはReachedGoalだけ。InProgressなら「まだ討伐中」、Completedなら「もう受け取り済み」です。この区別が、二重報酬を防ぐ判断になります。

準備:定義・進捗・通知を用意する

Third PersonのBlueprintプロジェクトを使います。Variantを選べる版ではNone、操作対象は標準のBP_ThirdPersonCharacterです。次の順番でアセットを作ると、構造体を作る段階で必要な型がそろいます。

1. 状態のEnumと、クエストの定義

コンテンツブラウザの「Blueprints → Enumeration」で E_QuestState を作り、先ほどの4項目をNotStarted、InProgress、ReachedGoal、Completedの順に追加します。

続いて「Blueprint Class → All Classes」からPrimary Data Assetを親に、BPDA_QuestDefinition を作ります。次の変数はすべてInstance Editableを有効、Privateを無効にします。

名前型を作るときの初期値
QuestIDNameNone
TitleText空欄
TargetEnemyIDNameNone
RequiredCountInteger1
RewardGoldInteger0

コンパイル・保存したら、「Miscellaneous → Data Asset」でこの型を選び、DA_Quest_SlimeHunt を作ります。

項目入れる値
QuestIDSlimeHunt
Titleスライム退治
TargetEnemyIDSlime
RequiredCount3
RewardGold100

QuestIDは どのクエストかを識別する名前、TargetEnemyIDは 数える敵の種類を識別する名前 です。SlimeHuntとSlimeは役割が違います。画面に出す「スライム退治」はTitleへ入れます。

このアセットが 定義 です。「何を何体倒すと、いくらもらえるか」を持ちます。Data Assetの記事と同様、型のBlueprintと、値を入れるアセットを分けます。

2. いまの進捗を入れる構造体

「Blueprints → Structure」で S_QuestProgress を作ります。構造体 は、関連する値をひとまとめにする型です。

名前初期値
DefinitionBPDA_QuestDefinitionのObject ReferenceNone
StateE_QuestStateNotStarted
CurrentCountInteger0

Definitionは、どの定義を使うかを指す参照です。Class Referenceではなく、DA_Quest_SlimeHuntのようなデータを指せるObject Referenceを選びます。

定義の必要数3・報酬100と、進捗の現在数1・進行中を別に持つ

目標の3体と、現在の1体は別の値です。必要数や報酬は定義から読み、プレイヤーごとに変わる現在数と状態は構造体へ置きます。インベントリの「定義と所持数を分ける」と同じ構成です。

3. GameInstanceへ進捗と通知を置く

GameInstanceを親に BP_GameInstance を作り、次を追加します。

用意するもの名前設定
変数ActiveQuestsキーがName、値がS_QuestProgressのMap。空で開始。Privateを有効
変数TotalGoldInteger、初期値0
Event DispatcherOnEnemyKilledInputsにEnemyID(Name)を1つ追加
Event DispatcherOnQuestUpdatedInputsにQuestID(Name)を1つ追加

Map は、キーと値の組を持つ入れ物です。今回はSlimeHuntをキーにすると、そのクエストの進捗が見つかります。未受注のクエストはまだMapへ入れません。完了した項目は残し、再受注も防ぎます。ActiveQuestsという名前ですが、今回は完了記録も含む一覧です。

Event Dispatcher は、登録した相手へ出来事を知らせる仕組みです。「My Blueprint」のEvent Dispatchersから作り、DetailsのInputsで引数を追加します。OnEnemyKilledは「Slimeが倒された」、OnQuestUpdatedは「SlimeHuntの進捗が変わった」を伝えます。

作成後、「Project Settings → Maps & Modes → Game Instance Class」をBP_GameInstanceに設定し、保存します。

町で受注し、別のレベルで3体倒し�、町へ戻って報告する間も進捗を保持する

通常のレベル切り替えでは、Characterなどが作り直されます。そこで今回は、同じゲーム実行中に残るGameInstanceへ進捗を預けます。ゲームを終了しても残す保存は、後半のおまけで分けて考えます。

現在の進捗を読み、文字で表示する

後の処理が同じ値を読めるよう、先に取得と表示の関数を用意します。以後、特記がない関数はBP_GameInstanceの中へ作り、Pureを無効にします。

GetQuestProgress:見つかったかも返す

関数 GetQuestProgress に入力QuestID(Name)、出力Progress(S_QuestProgress)とFound(Boolean)を作り、この関数だけPureを有効にします。

ActiveQuestsのGetからMap用のFindを作ります。KeyへQuestID、ValueからReturn NodeのProgress、BooleanのReturn ValueからFoundへつなぎます。関数の内側では、入口の白い線をReturn Nodeへ直接つなぎます。Findへ白い線はつなぎません。

Findから進捗と発見の有無を返す。未受注ならFoundはfalse

Found は「Mapにそのクエストがあったか」です。未受注ならfalseなので、存在しない進捗の定義を読まずに止められます。GetQuestProgressとFindは値を読むだけのPure関数で、呼び出す側に白い実行ピンはありません。

ShowQuestStatus:状態に合った文字を出す

関数 ShowQuestStatus は入力QuestID(Name)、出力なしで作ります。ローカル変数Progress(S_QuestProgress)を追加します。ローカル変数は、この関数の中だけで使う作業用の値です。

入口からBranchへつなぎ、ConditionをGetQuestProgress(QuestIDを渡す)のFoundにします。Falseは「未受注」とPrint Textして終了。Trueは、取得したProgressをSet Progressでローカル変数へ代入します。

ProgressのGetからBreak S_QuestProgressを作ると、Definition・State・CurrentCountを取り出せます。Definitionを実行ピン付きIs Validへ渡し、有効側だけSwitch on E_QuestStateへ進めます。SelectionはProgressのStateです。無効側は「定義が未指定です」と表示して終了します。

Is Validは、参照先のデータを使えるか確かめるノードです。Definitionが未指定のままTitleなどを読まないよう、先に確認しています。

各状態の白い出力にはPrint Textをつなぎます。表示する文字はFormat Textで作り、値の線でPrint TextのIn Textへ渡します。Format Textには白い実行線をつなぎません。

状態Format Textへ入れる文字
NotStarted未受注
InProgress{Title}: {Count}/{Required} / 所持金: {Gold}G
ReachedGoal{Title}: {Count}/{Required} 達成・報告しよう / 所持金: {Gold}G
Completed{Title}: 完了 / 所持金: {Gold}G

Format TextのFormat欄に上の文字を入れると、{Title} などに対応する入力ピンが現れます。ここへ渡した値が、表示するときに文字へ埋め込まれます。

TitleとRequiredはDefinitionのTitle・RequiredCount、CountはProgressのCurrentCount、GoldはTotalGoldから取ります。Definitionの青いピンからドラッグし、Get TitleやGet Required Countを選べます。

現在の状態に応じて、進行中・報告待ち・完了の表示を切り替える

この記事のPrint TextはPrint to ScreenとPrint to Logを有効、Duration=8、Key=Noneにそろえます。数値の変化は画面かOutput Logで確認できます。

受注する:0から始めるのは最初だけ

関数 AcceptQuest を作ります。入力Definition(BPDA_QuestDefinitionのObject Reference)、出力Success(Boolean)です。

まず実行ピン付きIs ValidでDefinitionを確かめます。無効なら「クエストが未指定です」と表示し、Return NodeでSuccess=falseにします。Return Nodeは、その関数を終了して結果を返すノードです。以後、失敗する枝もfalseを返して終えます。

有効側からBranchへ進み、次のどれかに当てはまるかを調べます。比較結果をBooleanのORでつなぎ、Conditionへ渡します。

  • QuestIDがNone
  • TargetEnemyIDがNone
  • RequiredCountが0以下
  • RewardGoldが0未満

Trueは「クエスト定義を確認してください」と表示してfalse。Falseから、ActiveQuestsのContains(Key=DefinitionのQuestID)をConditionにした次のBranchへ進みます。

ContainsがTrueなら、すでに受けたクエストです。「受注済みです」と表示してfalseで終えます。受注ボタンを押し直しても、1/3を0/3へ戻さない ための確認です。

ContainsがFalseの枝でMake S_QuestProgressを作り、Definition=入力Definition、State=InProgress、CurrentCount=0にします。

白い線はMap用のAdd→Call OnQuestUpdated→Return Nodeの順につなぎ、最後のSuccessをtrueにします。AddはTarget Map=ActiveQuests、Key=DefinitionのQuestID、Value=Makeの出力です。通知にも同じQuestIDを渡します。

定義・進行中・現在数0をMakeでまとめ、Mapへ入れる進捗を作る

これで、クエストの定義を読むところから、0/3の進捗を保存するところまでつながりました。表示を自動で呼ぶための通知先は、後で登録します。

Sponsored

撃破を数える:1件ずつ更新して書き戻す

スライムを倒しても、すべてのクエストが進むわけではありません。進行中で、対象がSlimeのクエストだけ を増やします。

Slimeの撃破通知を受け、進行中かつ対象の敵ならカウントを増やす

複数のクエストを順に調べる処理と、1件の進捗を更新する処理を分けます。先に1件分を作ると、「対象が違ったら、この1件だけ終える」という流れを追いやすくなります。

TryAdvanceQuest:この1件を進められるか

関数 TryAdvanceQuest は入力QuestID(Name)とEnemyID(Name)、出力なしです。ローカル変数Progress(S_QuestProgress)とNewCount(Integer)を作ります。

入口から次の順に確認します。ここで「終了」は、この関数のReturn Nodeへつなぐ意味です。

順番確認・操作通らないとき
1GetQuestProgressのFoundをBranchへ。Trueで出力ProgressをローカルProgressへSetFalseは終了
2BreakしたDefinitionをIs ValidへIs Not Validは終了
3State == InProgressをBranchへFalseは終了
4DefinitionのTargetEnemyID == 入力EnemyIDをBranchへFalseは終了
5DefinitionのRequiredCount > 0をBranchへFalseは終了

通ったら、CurrentCount + 1 とRequiredCountを整数用のMinへ渡し、出力をNewCountへSetします。Minは小さい方を選ぶので、目標3なら表示は3/3までに収まります。

次にMake S_QuestProgressで、書き戻す進捗を作ります。DefinitionはProgressのDefinition、CurrentCountはNewCountです。StateにはSelectを使い、次の2択を渡します。

Selectの入力設定
E_QuestState
IndexNewCount >= RequiredCount のBoolean
FalseInProgress
TrueReachedGoal

MakeのStateピンからSelectを作り、IndexへBooleanの比較結果をつなぐと、TrueとFalseで状態を選べます。2/3なら進行中、3/3なら達成 です。

NewCountと達成判定から、新しい状態を含む進捗をMakeで作る

Set NewCountの後の白い線を、Map Add→Call OnQuestUpdatedへつなぎます。AddはTarget Map=ActiveQuests、Key=入力QuestID、Value=Makeの出力。通知のQuestIDも同じ入力です。

新しい進捗を同じQuestIDへ書き戻してから、変更を通知する

MapのFindで取り出した進捗を変えるだけでは、Mapの値は更新されません。新しい構造体を、同じキーへAddで入れ直す のが書き戻しです。この手順なら、達成したときも、まだ途中のときも、更新した個数を保存できます。

HandleEnemyKilled:受注したクエストを順番に調べる

関数 HandleEnemyKilled は入力EnemyID(Name)、出力なしです。

入口からMap用のKeysへつなぎ、Target MapへActiveQuestsを渡します。Keysはキーの一覧を配列で取り出すノードで、白い実行ピンがあります。白い出力をFor Each Loopの実行入力へ、Keysの配列をArrayへつなぎます。

KeysでQuestIDの一覧を取り、Loop Bodyから1件ずつTryAdvanceQuestを呼ぶ

Loop BodyからTryAdvanceQuest(Target=Self)を呼びます。QuestIDへArray Element、EnemyIDへこの関数の入力EnemyIDを渡します。Completedは終了で構いません。

TryAdvanceQuestが途中でReturnしても、呼び出し元のループは次のクエストへ進みます。スライム討伐とゴブリン討伐を同時に受けていても、それぞれの定義と状態で判断できます。

報告する:完了を記録してから報酬を渡す

関数 CompleteQuest を作り、入力QuestID(Name)、出力Success(Boolean)、ローカル変数Progress(S_QuestProgress)を用意します。

GetQuestProgressのFoundで分岐し、FalseならSuccess=falseで終了。TrueならProgressをローカル変数へSetし、DefinitionをIs Validで確認します。無効ならfalseで終了します。

有効側で ProgressのState == ReachedGoal をBranchへ渡します。FalseはSuccess=false。Trueだけ、次の処理へ進みます。

Make S_QuestProgressを作り、DefinitionとCurrentCountはProgressから渡し、StateだけCompletedへ変えます。これをMap AddでActiveQuestsへ書き戻します。Keyは入力QuestIDです。

Addの後に TotalGold + DefinitionのRewardGold をSet TotalGoldへ渡します。その後、Call OnQuestUpdated(入力QuestID)→Return Node(Success=true)とつなぎます。

CompletedをMapへ記録し、その後に報酬を足して通知し、成功を返す

順序は 完了を記録→報酬を加算→通知 です。2回目の報告では、すでにCompletedなので状態の確認を通りません。未受注はFoundで、達成前と完了後は状態で止めています。

通知先を登録し、キー操作につなぐ

1. GameInstanceの開始時に、2つの通知先を登録する

ここまでの関数をコンパイル・保存したら、BP_GameInstanceのイベントグラフへEvent Initを追加します。InitはGameInstanceが初期化されるときのイベントです。

白い線を、Init→Bind Event to OnEnemyKilled→Bind Event to OnQuestUpdatedの順につなぎます。両方のTargetはSelfです。Selfは「このBlueprint自身」を指し、ここでは通知を持つBP_GameInstanceです。

それぞれの赤いEventピンからCreate Eventを作り、Object=Selfとして、呼ぶ関数を選びます。

登録する通知Create Eventで選ぶ関数受け取る入力
OnEnemyKilledHandleEnemyKilledEnemyID(Name)
OnQuestUpdatedShowQuestStatusQuestID(Name)
撃破通知にはカウント処理、進捗通知には表示処理を登録する

Bindは登録、Callは発信 です。Create Eventは、登録する「相手と関数」を指定します。ここでは両関数ともNameを1つ受け取り、出力はありません。候補に出ない場合は入力の型・数とコンパイルを確認します。

GameInstanceが自分の通知を聞くのは、ここを共通の通知窓口にしているからです。敵は「Slimeが倒された」と伝え、クエスト処理がそれを受け取ります。後から実績の聞き手を足す場合も、敵に実績の処理を追加する必要がありません。

ただし、敵とBP_GameInstanceの関係は残ります。今回切り離しているのは、敵から個別の討伐クエストや報酬処理を呼ぶ部分です。

2. CharacterからGameInstanceを取得する

BP_ThirdPersonCharacterのイベントグラフへキーボードのJを追加します。PressedをCast To BP_GameInstanceの白い入力へ、Get Game InstanceのReturn ValueをCastのObjectへつなぎます。

キーの白い実行線はCastへ、Get Game Instanceの青い出力はObjectへつなぐ

Get Game Instanceには白い実行ピンがありません。Castが成功した白い出力から、As BP Game InstanceをTargetにした関数を呼びます。Cast Failedには「Game Instance Classを確認してください」のPrint Textをつなぎます。

この形を使って、次のキーを作ります。引数のアセットは欄から選び、NameはSlimeなどの文字を入力します。

キーCast成功後に呼ぶ処理引数
JAcceptQuestDefinition=DA_Quest_SlimeHunt
KCall OnEnemyKilledEnemyID=Slime
HCall OnEnemyKilledEnemyID=Goblin
LCompleteQuestQuestID=SlimeHunt
PShowQuestStatusQuestID=SlimeHunt

すべてTargetはAs BP Game Instanceです。JのSuccessは接続不要です。成功時は変更通知で進捗が表示され、失敗時はAcceptQuest自身が理由を表示します。

As BP Game InstanceをTargetへ、クエスト定義をDefinitionへ指定して受注する

LはCompleteQuestの白い出力からBranchへ進み、ConditionへSuccessをつなぎます。Trueは終了。Falseだけ「まだ報告できないか、完了済みです」とPrint Textします。成功時の所持金はOnQuestUpdated経由で表示されます。

KとHは、撃破通知を発信する仮操作です。敵Actorを消しているわけではありません。Hを用意したのは、対象ではない敵を倒しても進まないこと を確認するためです。

Sponsored

確認:0/3から、報酬100Gまで

すべてコンパイル・保存し、Playします。ゲーム画面をクリックしてキー入力を受け取れる状態にしてから、次の順で試します。

操作確認する結果
P未受注
Kの後にPまだ未受注。受注前の撃破は数えない
L報告できない
Jスライム退治: 0/3、所持金0G
Hの後にP0/3のまま。Goblinは対象外
K1/3、所持金0G
Jの後にP受注済み。1/3のままでリセットされない
L報告できない。進捗はそのまま
Kをあと2回2/3→3/3。達成・報告しよう、所持金0G
Kの後にP3/3のまま。達成後は増えない
L完了、所持金100G
Lをもう1回、その後P報告は断られ、所持金100Gのまま
JやKの後にP完了のまま。再受注も追加カウントもしない
0/3から3/3へ進み、報告で100G。二重報告でも100Gのまま

「3体倒せた」だけでなく、受けていない、まだ足りない、もう終わった という場面も通してみると、4つの状態を分けた意味が見えてきます。

必要数を変えてみる

Playを止め、DA_Quest_SlimeHuntのRequiredCountを5へ変えて、Playし直します。Jで受注すると0/5から始まり、3回のKではまだ報告できず、5回で達成になります。表示の必要数も定義から読んでいるので、Blueprintの数値を書き直す必要はありません。

RequiredCountを0にすると、Jで「クエスト定義を確認してください」と表示され、受注しません。確認後は3へ戻してください。今回の討伐クエストは、1体以上の目標を前提にしています。

うまく動かないとき

症状確認するところ
どのキーでもCast FailedProject SettingsのGame Instance Class
Jで受注できないDefinitionの指定、IDがNoneでないか、必要数と報酬の値
J後も何も表示されないInitのOnQuestUpdatedへのBindと、ShowQuestStatusの引数
Kで進まないOnEnemyKilledのBind、EnemyIDとTargetEnemyIDが一致するか
毎回1/3になるTryAdvanceQuestの最後にMap Addで書き戻したか
3/3でも報告できないSelectのTrueがReachedGoalか。その出力をMakeのStateへ渡したか
2回目の報告で200GになるReachedGoalの確認と、報酬より前のCompleted書き戻し
表示がすぐ消えるPrint TextのDuration。Pで現在値を再表示できる

UIや実際の敵へつなげる

ここまでの結果を、画面隅のクエスト表示や実際の戦闘へつなげられます。進捗を変える関数は、そのまま使います。

文字表示をクエストUIへ置き換える

画面隅の「スライム退治 1/3」は、進捗を追う トラッカー と呼ばれるUIです。表示側はGetQuestProgressで現在値を読み、OnQuestUpdatedで変更を知ります。

進捗を変更するGameInstanceと、通知を受けて現在値を読むWidgetを分ける

UMGの記事のWidgetへ、ShowQuestStatusの「状態を見て文字を作る」部分を移し、Print Textの代わりにTextのSetTextへ渡すと表示を作れます。参照は次のようにつなぎます。

  1. WidgetのConstructでGet Game Instance→Castを行い、As BP Game InstanceをGIRefへ保存する。
  2. OnQuestUpdated(Target=GIRef)へ、QuestID(Name)を受け取る更新関数をCreate EventでBindする。
  3. 更新関数では追跡中のQuestIDかを確かめ、GIRefのGetQuestProgressから現在値を読む。見つからなければ未受注表示にする。
  4. Bindの直後にも、追跡中のQuestIDを渡して更新関数を1回呼ぶ。
  5. DestructでGIRefが有効なら、同じ相手・同じ関数を指定してUnbind Eventする。

通知は過去の受注を再送しません。初回に現在値を読むのはそのためです。Completedの表示を残すか、トラッカーを隠すかはUI側で決められます。Event Dispatcherの記事と、インベントリの記事の表示更新も参考になります。

撃破通知を、敵の死亡処理から出す

実際の敵へつなぐときは、Kの通知部分を敵の死亡処理へ移します。敵にEnemyID(Name、Slimeなど)を持たせ、死亡を確定した後、Get Game Instance→Cast→Call OnEnemyKilled(Target=As BP Game Instance、EnemyID=自分のEnemyID)を実行します。

通知は敵をDestroy Actorで消す前に出します。同じ敵の死亡処理が2回走らないよう、死亡済みの確認も必要です。クエストの状態確認は、別々の撃破通知が同じ敵から来たかまでは判別しません。

会話へつなぐ場合は、JのAcceptQuestとLのCompleteQuestを会話の選択肢から呼びます。会話側で討伐数や所持金を直接書き換える必要はありません。

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

クエストをもう1件受ける

定義を複製し、QuestID=GoblinHunt、TargetEnemyID=Goblin、Title=ゴブリン退治などへ変えると、別の討伐クエストを作れます。新しい定義でAcceptQuestを呼べば、同じループが両方を調べます。

ただし、アセットを増やすだけでは受注されません。受注する入口と、報告時に渡すQuestIDも用意します。QuestIDが同じなら同じクエストとして扱うため、複製後はIDも変更してください。

薬草集めは「いま持つ数」を読む

討伐数は過去の出来事なので、通知のたびに1を足しました。一方、「薬草を3個持ってきて」は、現在の所持数を読む方が合います。インベントリの変更通知を受け、GetCountで数え直します。

3個集めた後に1個使えば、2個へ減ります。「報告時にも3個必要」というルールなら、達成から進行中へ戻す処理と、報告時の所持数の再確認が必要です。納品する場合は、必要数を取り除けたことも確かめてから完了・報酬へ進みます。

次回の起動にも進捗を残す

GameInstanceはゲーム実行中の入れ物です。Save Gameへ、QuestID・State・CurrentCount・TotalGoldを保存し、ロード時にQuestIDから定義を探して進捗を組み直せば、次の起動にも引き継げます。IDと定義の対応表を用意する手順も必要です。

マップに置いた敵やWidgetは、レベル切り替えで作り直されることがあります。GameInstanceに残すのはその参照を使い続ける前提の処理ではなく、クエストの進捗です。再開後のUIは復元した現在値から表示を作ります。

今回の中心は、いまの状態を見て、進めてよい操作だけを通す ことです。討伐数、報告の可否、報酬が同じ進捗につながると、会話や表示を足しても、判断する場所をそろえられます。

まとめ

  • クエストの「定義」と、プレイヤーの「進捗」は別に持つ
  • 「達成した」と「報告した」を分けると、二重報酬を防げる
  • 撃破の通知は、進行中のクエストだけが数える
  • 報酬は、完了を記録してから渡す

作るときの問いかけは、「この状態は、次に何ができるかを決めているか」 です。決めているなら、状態として持ちます。

データの持ち方は Data Table、通知の配り方は Event Dispatcher にあります。

参考:MapのFindMapのKeysEvent DispatchersGameInstance

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