同じレベル内で操作方法が変わる際、どのような手法が有効でしょうか

お世話になっております。
現在、ゲームコントローラーを使用したゲームを作る際、同じレベル内で操作方法が変わるときにはどのような手段を取るのがよいのか悩んでおります。

現在ではキャラクターの移動から、以下の操作切り替えを行いたいです。

  • メニュー画面を開いたあとのメニューウィンドウの操作
  • 会話した際の会話ウィンドウに対する操作への切り替え
  • サブレベルによるコマンドバトルへの切り替わり

歩兵から戦車に乗り換えるのような場合にはPossessによるPawnの切り替えで、同じアクションマッピングでも代用がしやすいですが、
アクションやRPGゲームなどで、ゲーム中にメニュー画面が表示されて操作する際には、キャラ操作とメニュー内で操作方法が変化し、同じアクションマッピングでは混乱の元になります。
QTEのようなのが挟まる場合にも、ゲームの種類によって同じレベル内で操作方法が変わることが多々存在します。
もしこれをPlayerControllerとキャラクターに任せてしまうと、とても複雑なコードになってしまう懸念があります。(キャラ操作・メニュー画面の操作も含めたアクションマッピングを事前にしておくなど)

そこで、操作方法が大きく切り替わるものに関しては、操作切り替え専用のPawnを作成してPossessし、特殊な操作が終わったらキャラにPossessし直すという方法を考えてみました。UnPossessすることで、キャラクターの操作を不能にすることと、違うPawnにPossessすることで操作方法や反応するアクションマッピングのデリゲートを変更するのを同時に行えると思ったからです。
例えば、キャラクターに話しかけて会話するような場面で、話しかけたらTalkPawnを作成し、TalkPawnにPossessすることで操作方法を変更するといったことを試してみました。
しかし、これも想定されているような使い方ではなさそうで、悩んでおります。
問題の1つとして、会話する際にカメラを切り替える必要はないのですがPossessをするとTalkPawnにカメラが切り替わってしまいます。
対策として、Possess直後にカメラを操作キャラに戻してみたのですが、今度はカメラの回転が考慮されずにリセットされてしまうなどの問題が出てしまいました。


他に以下の方法が考えられたのですが、UE4としては上記のようなときに想定されている、よい実装方法などあるのでしょうか?

  • PlayerControllerに処理をまとめ、他の入力処理もキャラが請け負う
  • 操作方法が変わるタイミングでPawnを切り替える(今回の例の方法)
  • PlayerControllerを切り替える(こちらはよい方法ではないとの情報を見ました)
  • 他の方法(InputComponent切り替え?など)

ご回答のほどよろしくお願いします。

お世話になっております。
返信が遅くなってしまい申し訳ございません。まずご提示された各方法について述べますと、

・PlayerControllerに処理をまとめ、他の入力処理もキャラが請け負う
例えばアクションやRPGなどのゲームを想定した際、マップを移動するキャラクターが存在する場合、操作キャラクターの処理はImputComponentにBindして実処理はCharacterクラスで行うのは正しいかと思います。ただし上記で述べられているように、PlayerControllerでメニュー画面や会話画面などのキー操作も管理するのは非常に煩雑ですので、"メニュー画面中"や"会話画面中"といった状態管理程度に留めるのが良いかと思います。この尺度はコンテンツによりますが、全ての操作をPlayerControllerで管理するのは複雑になります。

・操作方法が変わるタイミングでPawnを切り替える(今回の例の方法)
既に検証や想定されていますように、操作方法が変わるとPlayerControllerのPossess/UnPossessでカメラとそれに伴う位置同期の権利も移行します。そしてこれは仰る通り、歩兵から戦車といった操作対象のPawnが変更するような際にご利用頂くのに適しているものです。PlayerControllerやPlayerCameraManagerをoverrideすることでカメラを固定することも可能かと思いますが、この方法では所有者とViewTargetのPawnが不一致となり、プレイヤー管理の面でも複雑になります。

・PlayerControllerを切り替える(こちらはよい方法ではないとの情報を見ました)
PlayerControllerはGameModeに基づく一対の存在であるので、PersistentLevelに変更が無いケースでPlayerControllerを切り変えるのは望ましくないと思います。

・他の方法(InputComponent切り替え?など)
InputComponentで切り替える方法が管理しやすさで優れていると思います。例えば、マップ中を移動するキャラクターの操作はImputComponentAとしてBindし、メニュー画面では専用にActionをBindしたImputComponentを生成しておきます。そしてメニュー画面が開かれた時に APlayerController::PushInputComponent()で、メニュー画面のバインドに切り替えます。メニュー画面が閉じられた時には APlayerController::PopInputComponent()でメニュー操作のバインドを解除します。これでImputComponent単位での切り替えが容易で、かつPlayerControllerはそのImputComponentの制御のみを担うだけで良いです。また、ProjectSettings->Input->Bindingsではキー入力の設定をそれぞれ登録可能ですが、UPlayerInputを派生して複数のBindingsのパターンを作成しプリセットとして扱うことで、同じキー入力でも異なるActionNameを扱うことも可能です。同じActionNameを使い回しすぎることは操作として一貫性はありますが、汎用的な名前にせざるを得ないので、プロジェクトで独自にBindingsを追加してカスタマイズされるのも良いかと思います。
上記はPlayerInputを主体とした管理ですが、Widget側からも UUserWidget::ListenForImputAction()でBindされたActionから呼び出しが可能ですので、単純なWidgetであればこちらで完結することもできます。

249973-listenforinputaction.png

最後に付け加えますと、入力の制御はデバイス、プロジェクトの規模、コンテンツの内容に依存するため、最適な手法はプロジェクト側で見つけて頂くことになります。入力の制御に関しては、UInputComponent、UPlayerInput、APlayerControllerが主に担うので、まずはここをプロジェクトに併せて派生先で拡張して頂くのがよろしいかと思います。更にご質問がありましたらお知らせください。
よろしくお願いします。

ご回答ありがとう御座います。
詳しい情報をいただけたことで、各方法についての利点・欠点を理解することができました。また、InputComponentについて情報が少なく困っていたのですが、理解が深まることができました。
InputComponentでの管理が現在に合ったものとして近いと感じましたので、そちらで進めていきたいと思います。

確認させていただきたいのですが、InputComponentはスタックで蓄積していく形ですので、キャラ移動時には MyCharacterInputComponent をPushし、メニュー画面時には MyCharacterInputComponent をPop後に MyMenuInputComponent をPushする。メニュー画面を閉じたら MyMenuInputComponent をPopして MyCharacterInputComponent をPush……のような使い方を想定したのですが、そのような考えでよろしいでしょうか?
Pushした一番上のInputComponent以外の入力を無効にする方法や、スタックをクリアする方法などがありましたら、Popを意識しないで済むかもしれないのですがそのような機能はあるのでしょうか?(bBlockInputでよろしいのでしょうか?)
スタックの利点としてはキャラ移動を可能にしつつ、メニュー画面の入力も可能にしたい場合に便利な点だと思いますが、今回はキャラは停止してもらいたいので、上記のような処理を試しております。

ご確認ありがとうございます。
上記において少し説明が抜けていましたので補足しますと、

例えば、マップ中を移動するキャラクターの操作はInputComponentAとして作成しActionをBindしておきます。そしてメニュー画面の操作用にはメニュー画面用のInputComponentBを作成しておき、専用にActionをBindしておきます。そしてメニュー画面が開かれた時に APlayerController::PushInputComponent()で、メニュー画面のInputComponentBをPushします。メニュー画面が閉じられた時には APlayerController::PopInputComponent()でInputComponentBをPopします。これで特定の操作のみの処理を入れることが可能です。

ということになります。入力を切り替える毎にInputComponentを設けてそれを取り外しすることになります。以下の図のようなイメージです。

InputComponentはスタックで蓄積していく形ですので、キャラ移動時にはMyCharacterInputComponentをPushし、メニュー画面時にはMyCharacterInputComponent をPop後にMyMenuInputComponentをPushする。メニュー画面を閉じたらMyMenuInputComponent をPopしてMyCharacterInputComponentをPush……のような使い方を想定したのですが、そのような考えでよろしいでしょうか?

上記で示すようにActionNameが同じものがBindされる場合はPushしたComponentのFunctionが実行されますのでPopする必要はありません。よって状態によってInputComponentを付け替えるだけで良いはずです。

Pushした一番上のInputComponent以外の入力を無効にする方法や、スタックをクリアする方法などがありましたら、Popを意識しないで済むかもしれないのですがそのような機能はあるのでしょうか?(bBlockInputでよろしいのでしょうか?)

これも上記の図の通りですが、PushしたInputComponentに存在しないものは下位にあるInputComponentの操作を受け付けることができます。なのでメニュー画面用としてMyMenuInputにXとYの操作だけを割り当てたとしても、ブロックしていない場合は最初に登録されたキャラクター操作用のMyCharacterInputのZの操作は受け付けて実行されます。そしてこれをブロックするのはご提起されていますbBlockInputになります。これを有効にすることで、設定されたInputComponentより優先度が低い入力をブロックします。図のケースではMyMenuInputでbBlockInputを有効にしている場合はMyCharacterInputの入力を全てブロックすることになります。

Pushした一番上の入力以外を無効にする方法は一番上のInputComponentのbBlockInputをtrueにすることです。スタックをクリアする方法はCurrentInputStackから指定のInputComponentをRemoveすることです。

よろしくお願いします。

詳しいご補足ありがとう御座います。
同じバインドアクションに関してはスタックで積まれたばかりのものが優先して判定され、バインドしていないアクションは他に積まれているInputComponentが判定されるのですね。
また、bBlockInputした際にはそれ以下の優先度が低いInputComponentの入力をブロックする、といったことも理解することができました。

入力判定実装手法に関して非常に役に立つ情報になりました。ありがとうございました。