お世話になっております。
InputPreprocessorを使用した再現プロジェクトのご提供をありがとうございます。
新たに①エディタUIへの入力混入と②BindKeyの二重発火という問題が出ましたが、それぞれ、
・①InputPreProcessorがFSlateApplication::ProcessKeyDownEventの冒頭(アプリ全体のキー処理をするタイミング)で呼び出され、PlayerController(以下PC)に入力を横流しするために発生
・②Slateに流した入力はUIで消費されなかった場合にViewport経由でPCに渡されるため、InputPreProcessorの入力横流しとあわせて2回発火
という原因があり、
・①については、エディタビルド時にPIEにフォーカスが当たっているかどうかをチェックし、PCへの入力横流しを実行する条件を絞ることで、
・②については、PC入力への横流しを行った際は、SlateからViewport経由でPCに入力を流す処理を止めることで、
解決が可能と考えております。
一部エンジン改造が必要ですが、以下の対策をお願いいたします。
■①
CustomInputPreprocessor.cpp
// 冒頭に以下のinclude追加
+ #include "Engine/GameViewportClient.h"
+ #include "Engine/LocalPlayer.h"
+ #include "Engine/World.h"
+ #include "Framework/Application/SlateApplication.h"
+ #include "GameFramework/PlayerController.h"
+ #include "Widgets/SViewport.h"
+ #include "Widgets/SWindow.h"
// ※.h にこの private 関数の宣言も追加すること
+ bool ICustomInputPreprocessor::ShouldRouteToPlayerInput(FSlateApplication& SlateApp) const
+ {
+ if (!World || !World->IsGameWorld())
+ {
+ return false;
+ }
+
+ UGameViewportClient* GameViewportClient = World->GetGameViewport();
+
+ if (!GameViewportClient)
+ {
+ return false;
+ }
+
+ TSharedPtr<SViewport> ViewportWidget = GameViewportClient->GetGameViewportWidget();
+ if (!ViewportWidget.IsValid())
+ {
+ return false;
+ }
+
+ #if WITH_EDITOR
+ if (World->IsPlayInEditor())
+ {
+ // Viewport が PIE 用 Viewport なのか確認
+ if (FViewport* GameViewport = GameViewportClient->Viewport)
+ {
+ if (!GameViewport->IsPlayInEditorViewport())
+ {
+ return false;
+ }
+ }
+
+ // PIE Viewport へのフォーカス確認
+ if (!ViewportWidget->HasMouseCapture())
+ {
+ return false;
+ }
+ }
#endif
return true;
}
bool ICustomInputPreprocessor::HandleKeyDownEvent(FSlateApplication& SlateApp, const FKeyEvent& InKeyEvent)
{
IInputProcessor::HandleKeyDownEvent(SlateApp, InKeyEvent);
+ if (!ShouldRouteToPlayerInput(SlateApp))
+ {
+ return false;
+ }
// 略
bool ICustomInputPreprocessor::HandleKeyUpEvent(FSlateApplication& SlateApp, const FKeyEvent& InKeyEvent)
{
IInputProcessor::HandleKeyUpEvent(SlateApp, InKeyEvent);
+ if (!ShouldRouteToPlayerInput(SlateApp))
+ {
+ return false;
+ }
■②
SlateApplication.h
/** Sets the handler for otherwise unhandled key down events. This is used by the editor to provide a global action list, if the key was not consumed by any widget. */
SLATE_API void SetUnhandledKeyUpEventHandler(const FOnKeyEvent& NewHandler);
+ bool ShouldViewportSkipInputKeyForCurrentEvent() const { return bSuppressViewportInputKeyForCurrentEvent; }
+ void RequestSuppressViewportInputKeyForCurrentEvent() { bSuppressViewportInputKeyForCurrentEvent = true; }
+ private:
+ bool bSuppressViewportInputKeyForCurrentEvent = false;
+ public:
/** [Content removed] or controller */
double GetLastUserInteractionTime() const { return LastUserInteractionTime; }
SlateApplication.cpp
bool FSlateApplication::ProcessKeyDownEvent( const FKeyEvent& InKeyEvent )
{
SCOPE_CYCLE_COUNTER(STAT_ProcessKeyDown);
#if WITH_SLATE_DEBUGGING
FSlateDebugging::FScopeProcessInputEvent Scope(ESlateDebuggingInputEvent::KeyDown, InKeyEvent);
#endif
TScopeCounter<int32> BeginInput(ProcessingInput);
TSharedRef<FSlateUser> SlateUser = GetOrCreateUser(InKeyEvent);
#if WITH_EDITOR
//Send the key input to all pre input key down listener function
if (OnApplicationPreInputKeyDownListenerEvent.IsBound())
{
OnApplicationPreInputKeyDownListenerEvent.Broadcast(InKeyEvent);
}
#endif //WITH_EDITOR
+ bSuppressViewportInputKeyForCurrentEvent = false;
// 略
bool FSlateApplication::ProcessKeyUpEvent( const FKeyEvent& InKeyEvent )
{
SCOPE_CYCLE_COUNTER(STAT_ProcessKeyUp);
#if WITH_SLATE_DEBUGGING
FSlateDebugging::FScopeProcessInputEvent Scope(ESlateDebuggingInputEvent::KeyUp, InKeyEvent);
#endif
TScopeCounter<int32> BeginInput(ProcessingInput);
+ bSuppressViewportInputKeyForCurrentEvent = false;
SceneViewport.cpp
// ほぼ同じ構造のFReply FSceneViewport::OnKeyDownも改造すること
FReply FSceneViewport::OnKeyDown( const FGeometry& InGeometry, const FKeyEvent& InKeyEvent )
{
// Start a new reply state
CurrentReplyState = FReply::Handled();
FKey Key = InKeyEvent.GetKey();
if (Key.IsValid())
{
KeyStateMap.Add(Key, true);
//@todo Slate Viewports: FWindowsViewport checks for Alt+Enter or F11 and toggles fullscreen. Unknown if fullscreen via this method will be needed for slate viewports.
if (ViewportClient && GetSizeXY() != FIntPoint::ZeroValue)
{
// Switch to the viewport clients world before processing input
FScopedConditionalWorldSwitcher WorldSwitcher(ViewportClient);
+ const bool bSkipViewportInputKey = FSlateApplication::IsInitialized()
+ && FSlateApplication::Get().ShouldViewportSkipInputKeyForCurrentEvent();
+ if (bSkipViewportInputKey)
+ {
+ CurrentReplyState = FReply::Unhandled();
+ }
+ else
{
if (!ViewportClient->InputKey(FInputKeyEventArgs(this, InKeyEvent.GetInputDeviceId(), Key, InKeyEvent.IsRepeat() ? IE_Repeat : IE_Pressed, 1.0f, false, InKeyEvent.GetEventTimestamp())))
{
CurrentReplyState = FReply::Unhandled();
}
}
あとは、ICustomInputPreprocessor::HandleKeyDownEvent()、HandleKeyUpEvent() それぞれの関数内で、PC->InputKey(Args); の直後にこの新しい Slate 関数の呼び出しを仕込みます。
// ICustomInputPreprocessor::HandleKeyDownEvent() 内
if (Viewport)
{
FInputKeyEventArgs Args(
Viewport,
InKeyEvent.GetInputDeviceId(),
InKeyEvent.GetKey(),
InKeyEvent.IsRepeat() ? IE_Repeat : IE_Pressed
);
PC->InputKey(Args);
+ SlateApp.RequestSuppressViewportInputKeyForCurrentEvent();
// ICustomInputPreprocessor::HandleKeyUpEvent() でも同様
一度お試しください。
以上、よろしくお願いします。
[Attachment Removed]