
UE5でUI操作をゲームパッド・マウス両対応にする完全ガイド
ゲーム開発において、UIの操作対応は極めて重要です。特にコンソールゲームやマルチプラットフォーム対応を目指す場合、ゲームパッド操作は必須要件です。しかし、PCでのマウス操作も無視できません。本記事では、UE5(Unreal Engine 5)でUIをゲームパッド・マウスの両方に対応させる方法を、ブループリント・C++の両面から徹底解説します。
第1章:基礎知識とアーキテクチャ
UE5のUIシステム概要
UE5のUIシステムは、以下の3つの主要コンポーネントで構成されています。
【UMGシステム概要図】
User Interface
├─ UMG(Unreal Motion Graphics)
│ ├─ Widget Blueprint
│ ├─ Canvas Panel
│ └─ Input System
├─ Slate(低レベルUI)
│ └─ C++による直接操作
└─ Enhanced Input System(推奨、5.0+)
├─ Input Mapping Context
├─ Input Action
└─ Input Modifiers/Processors
UE5 5.1以降での推奨アプローチ
UE5.1より前は、従来のSetupPlayerInputComponent()を使用していました。しかし、UE5.1以降は「Enhanced Input System」(EIS)の使用が推奨されています。
| 機能 | 従来型 | Enhanced Input System |
|---|---|---|
| 柔軟性 | 低 | 高 |
| デバッグ性 | 困難 | 容易 |
| マップ管理 | 手動 | 自動 |
| モバイル対応 | 限定的 | 優秀 |
| 公式推奨度 | ✗ 非推奨 | ✓ 推奨 |
本記事では、Enhanced Input Systemを中心に解説します。
入力の優先度(Priority)
UE5では、以下の優先度順で入力が処理されます。
【入力処理の優先度】
優先度1:Modal Widget(ポップアップなど)
↓
優先度2:Focused Widget(フォーカス中のボタンなど)
↓
優先度3:Game Viewport(ゲーム画面)
↓
優先度4:Other Widgets(その他ウィジェット)
※モーダルウィジェットが開いていると、
それより下の優先度の入力はブロックされる
第2章:基本設定とプロジェクト構成
ステップ1:プロジェクト設定
1-1. Enhanced Input Systemの有効化
【手順】
1. Edit → Project Settings → Search "Enhanced"
2. 「Enhanced Input」プラグインが有効になっているか確認
✓ Enabled であることを確認
3. 再度 Project Settings を開く
Edit → Project Settings → Input
【確認項目】
□ Default Player Input Class:
DefaultPlayerInput → EnhancedPlayerInput に変更
□ Default Input Component Class:
DefaultInputComponent → EnhancedInputComponent に変更
□ 設定保存:Ctrl + S
1-2. フォルダ構成の推奨レイアウト
【プロジェクト構成】
Content/
├─ Input/
│ ├─ IA_Navigate.uasset(方向キー入力)
│ ├─ IA_Click.uasset(マウスクリック)
│ ├─ IA_Submit.uasset(決定ボタン)
│ ├─ IA_Cancel.uasset(キャンセルボタン)
│ └─ IMC_UIDefault.uasset(UI用マッピング)
│
├─ UI/
│ ├─ Widgets/
│ │ ├─ WBP_MainMenu.uasset
│ │ ├─ WBP_PauseMenu.uasset
│ │ └─ WBP_InGameUI.uasset
│ │
│ └─ Styles/
│ ├─ BP_UIFocusStyle.uasset
│ └─ DT_ButtonSettings.uasset(データテーブル)
│
└─ Characters/
└─ BP_PlayerCharacter.uasset
第3章:Enhanced Input Systemの詳細設定
ステップ2:Input Actionの作成
Input Actionは、「プレイヤーの意図」を表現するアセットです。キーボードのどのキーが押されたかではなく、「ナビゲート」「クリック」などの意図を定義します。
3-1. 基本的なInput Actionの作成例
【Create Blank Input Action】
【IA_Navigate】
├─ Value Type:Axis2D(2軸、方向キー用)
├─ Mapping:
│ ├─ Keyboard:Arrow Keys
│ ├─ Keyboard:WASD
│ ├─ Gamepad:Left Stick
│ └─ Gamepad:D-Pad
└─ Modifiers:
└─ Dead Zone(デッドゾーン設定:0.2)
【IA_Click】
├─ Value Type:Digital(1ボタン、オン/オフ)
├─ Mapping:
│ ├─ Mouse:Left Mouse Button
│ └─ Gamepad:Gamepad Face Button Bottom(A ボタン)
└─ Modifiers:なし
【IA_Submit】
├─ Value Type:Digital
├─ Mapping:
│ ├─ Keyboard:Enter
│ ├─ Gamepad:Gamepad Face Button Bottom
│ └─ Gamepad:Gamepad Face Button Right(X ボタン)
└─ Modifiers:なし
【IA_Cancel】
├─ Value Type:Digital
├─ Mapping:
│ ├─ Keyboard:Escape
│ ├─ Gamepad:Gamepad Face Button Right(B ボタン)
│ └─ Gamepad:Gamepad Special Right
└─ Modifiers:なし
3-2. ゲームパッド各ボタンの対応
UE5では、ゲームパッドのボタンが標準的に割り当てられています。
【ゲームパッド標準ボタン配置】
Face Buttons:
├─ Face Button Bottom = A ボタン(Xbox)/ ✕ ボタン(PlayStation)
├─ Face Button Right = B ボタン(Xbox)/ ◯ ボタン(PlayStation)
├─ Face Button Left = X ボタン(Xbox)/ □ ボタン(PlayStation)
└─ Face Button Top = Y ボタン(Xbox)/ △ ボタン(PlayStation)
Shoulder Buttons:
├─ Left Shoulder = LB/L1
├─ Right Shoulder = RB/R1
├─ Left Trigger = LT/L2
└─ Right Trigger = RT/R2
Stick Input:
├─ Left Stick = 左スティック(アナログ)
├─ Left Stick Click = 左スティック押込
├─ Right Stick = 右スティック(アナログ)
└─ Right Stick Click = 右スティック押込
Special Buttons:
├─ Special Left = Menu button(Options/Menu)
├─ Special Right = View button(Back/Select)
└─ Gamepad Center Button = スタートボタン
D-Pad:
├─ D-Pad Up
├─ D-Pad Down
├─ D-Pad Left
└─ D-Pad Right
ステップ3:Input Mapping Contextの作成
Input Mapping Context(IMC)は、Input Actionをどのキー/ボタンに割り当てるかを定義します。
【IMC_UIDefault の設定手順】
1. Create Mapping Context
Name:IMC_UIDefault
2. Mapping を追加
├─ IA_Navigate(方向移動用)
│ ├─ Mapping 1: Keyboard Arrow Keys
│ ├─ Mapping 2: Keyboard WASD
│ ├─ Mapping 3: Gamepad Left Stick
│ └─ Mapping 4: Gamepad D-Pad
│
├─ IA_Click(クリック用)
│ ├─ Mapping 1: Mouse Left Button
│ └─ Mapping 2: Gamepad Face Button Bottom
│
├─ IA_Submit(決定用)
│ ├─ Mapping 1: Keyboard Enter
│ └─ Mapping 2: Gamepad Face Button Bottom
│
└─ IA_Cancel(キャンセル用)
├─ Mapping 1: Keyboard Escape
└─ Mapping 2: Gamepad Face Button Right
3. Modifiers と Processors(フィルタリング)
ステップ4:Modifiers と Processors
Modifiersは入力値を変換し、Processorsは入力フローを制御します。
4-1. よく使用するModifiers
【推奨 Modifiers リスト】
1. Dead Zone Modifier(デッドゾーン)
用途:アナログスティック入力時にジッター・ノイズ除去
設定:
├─ Lower Threshold:0.1(値がこれ以下なら入力と見なさない)
├─ Upper Threshold:0.9(飽和ポイント)
└─ Type:Radial(XY平面上での距離)
2. Scalar Modifier(スケーリング)
用途:入力感度の調整
設定:
├─ Scalar:1.5(150%の感度)
└─ 用例:マウス感度が低すぎる場合に使用
3. Negate Modifier(反転)
用途:入力方向の反転
設定:なし
用例:Y軸反転(Invert Mouse Y)
4. Swizzle Axis Modifier(軸の交換)
用途:XYZの軸を入れ替える
設定:
├─ Order:YXZ(例:YとXを交換)
用例:スティック入力で上下・左右を反転
5. Smooth Value Modifier(スムージング)
用途:アナログ入力の滑らかさ向上
設定:
├─ Rising Speed:2.0(立ち上がり速度)
├─ Falling Speed:2.0(立ち下がり速度)
└─ 用例:キャラクター移動時にぎこちなさを軽減
4-2. Processors
【推奨 Processors リスト】
1. Input Value Debug Processor
用途:入力値の確認(デバッグ用)
2. Input Debounce Processor
用途:入力のチャタリング除去
設定:
└─ Duration:0.1秒(この時間内の重複入力を無視)
第4章:ブループリントでのUI操作実装
ステップ5:プレイヤーコントローラーの設定
【BP_PlayerController の設定】
1. Event BeginPlay で IMC を追加
├─ Add Input Mapping Context
│ ├─ Input Mapping Context:IMC_UIDefault
│ └─ Priority:0
│
└─ Enable Input(HUD/UI用)
└─ Pawn:Self
2. Input Setup Complete
├─ 入力システムの初期化完了を通知
└─ UI 初期化と同期
ステップ6:UI Widget への入力バインディング
UMG Widgetでの入力受け取りは、「Focus」の概念が重要です。
6-1. Focus システムの理解
【フォーカスシステム】
Focused Widget(フォーカス中):
├─ キーボード・ゲームパッド入力を受け取る
├─ ビジュアル的に「選択状態」を表示(ハイライト等)
└─ 通常1つのウィジェットのみフォーカス可
Focus Navigation:
├─ 方向キー/スティック でフォーカス移動
├─ Tab キー(次へ)/ Shift+Tab(前へ)
└─ マウスでクリック = フォーカス移動
6-2. ボタンウィジェットへの入力実装
以下、シンプルなポーズメニューの例です。
【WBP_PauseMenu(ブループリント)の実装】
【Event Construct】
├─ Create Widget:WBP_PauseMenuContent
├─ Add to Viewport
├─ Set Focus
│ └─ Target Widget:Button_Resume(再開ボタンにフォーカス)
└─ Set Input Mode
└─ Input Mode:Game and UI
(ゲーム入力とUI入力の両立)
【ボタンの On Clicked イベント】
Button_Resume:
├─ Remove from Parent(メニューを閉じる)
└─ Set Input Mode:Game Only
(UI入力を無効化、ゲーム入力のみ有効)
Button_Settings:
├─ Create Widget:WBP_SettingsMenu
├─ Add to Viewport
└─ Set Focus:Button_OK
Button_Exit:
├─ Open Level:MainMenu
└─ Flush Level Streaming
【マウス入力対応】
- Button On Hovered イベント:
└─ Set Focus(マウス移動でボタンハイライト)
- Button On Unhovered イベント:
└─ Clear Focus(マウスが離れたらハイライト解除)
6-3. カスタムボタンの作成(ゲームパッド対応)
デフォルトのButtonウィジェットは基本的ですが、ゲームパッド対応を強化するためにカスタマイズできます。
【WBP_CustomButton(カスタムボタン)】
【構成要素】
Visual Layer:
├─ Image(Background)
│ ├─ Normal State:Color = Gray
│ ├─ Hovered State:Color = White
│ └─ Pressed State:Color = Yellow
│
├─ Text(Label)
│ └─ 表示テキスト
│
└─ Image(Border)
└─ フォーカス時の枠線(ゲームパッド用)
Variables:
├─ bIsFocused:是否フォーカス状態
├─ bIsPressed:是否押下状態
└─ OnButtonClicked Delegate
【フォーカス対応の実装】
On Focus Received:
├─ Set Brush:Border → Visibility = Visible
├─ Play Sound:SelectSound.uasset
└─ bIsFocused = true
On Focus Lost:
├─ Set Brush:Border → Visibility = Hidden
└─ bIsFocused = false
On Mouse Enter:
├─ Set Focus
└─ On Focus Received を実行
On Mouse Leave:
├─ Clear Focus
└─ On Focus Lost を実行
ステップ7:ゲームパッド入力の直接処理
より高度なUI制御が必要な場合、Input Actionを直接受け取ることができます。
【WBP_AdvancedMenu での直接入力処理】
【プレイヤーコントローラーで実装】
Setup Input Component:
├─ Bind Action:IA_Navigate
│ ├─ Event:Triggered
│ ├─ Callback:On Navigate Input
│ │ ├─ 入力値(Axis2D)を取得
│ │ ├─ if(Y > 0.5)→ Move Up
│ │ ├─ if(Y < -0.5)→ Move Down
│ │ ├─ if(X > 0.5)→ Move Right
│ │ └─ if(X < -0.5)→ Move Left
│ │
│ ├─ Event:Completed
│ └─ Callback:On Navigate Input Complete
│ └─ フォーカス移動完了処理
│
├─ Bind Action:IA_Submit
│ ├─ Event:Triggered
│ └─ Callback:On Submit
│ ├─ Focused Widget の On Clicked を実行
│ └─ または委譲を呼び出し
│
└─ Bind Action:IA_Cancel
├─ Event:Triggered
└─ Callback:On Cancel
├─ メニューを閉じる
└─ 親メニューに戻る
第5章:C++での詳細実装
ステップ8:PlayerController C++ コード
8-1. 基本的なセットアップ
cpp
// MyPlayerController.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "InputActionValue.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "MyPlayerController.generated.h"
class UEnhancedInputComponent;
class UEnhancedInputLocalPlayerSubsystem;
class UInputMappingContext;
class UInputAction;
UCLASS()
class YOURPROJECT_API AMyPlayerController : public APlayerController
{
GENERATED_BODY()
public:
AMyPlayerController();
protected:
// Enhanced Input System
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
class UInputMappingContext* UIInputMappingContext;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
class UInputAction* NavigateAction;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
class UInputAction* ClickAction;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
class UInputAction* SubmitAction;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
class UInputAction* CancelAction;
virtual void BeginPlay() override;
virtual void SetupInput(class UEnhancedInputComponent* EnhancedInputComponent);
// Input callbacks
void OnNavigateInput(const FInputActionValue& Value);
void OnClickInput(const FInputActionValue& Value);
void OnSubmitInput(const FInputActionValue& Value);
void OnCancelInput(const FInputActionValue& Value);
public:
virtual void SetupPlayerInputComponent(
class UInputComponent* PlayerInputComponent) override;
};
cpp
// MyPlayerController.cpp
#include "MyPlayerController.h"
#include "InputActionValue.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "InputActionValue.h"
AMyPlayerController::AMyPlayerController()
{
bShowMouseCursor = true;
DefaultMouseCursor = EMouseCursor::Default;
bEnableClickEvents = true;
bEnableTouchEvents = true;
}
void AMyPlayerController::BeginPlay()
{
Super::BeginPlay();
// Enhanced Input System のサブシステムを取得
if (UEnhancedInputLocalPlayerSubsystem* Subsystem =
GetLocalPlayer()->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
{
// IMC を追加
Subsystem->AddMappingContext(UIInputMappingContext, 0);
}
// UI入力モード設定
FInputModeGameAndUI InputMode;
InputMode.SetWidgetToFocus(nullptr);
SetInputMode(InputMode);
}
void AMyPlayerController::SetupPlayerInputComponent(
UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
if (UEnhancedInputComponent* EnhancedInputComponent =
CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
{
// Navigate Action バインド
EnhancedInputComponent->BindAction(
NavigateAction,
ETriggerEvent::Triggered,
this,
&AMyPlayerController::OnNavigateInput);
// Click Action バインド
EnhancedInputComponent->BindAction(
ClickAction,
ETriggerEvent::Triggered,
this,
&AMyPlayerController::OnClickInput);
// Submit Action バインド
EnhancedInputComponent->BindAction(
SubmitAction,
ETriggerEvent::Triggered,
this,
&AMyPlayerController::OnSubmitInput);
// Cancel Action バインド
EnhancedInputComponent->BindAction(
CancelAction,
ETriggerEvent::Triggered,
this,
&AMyPlayerController::OnCancelInput);
}
}
void AMyPlayerController::OnNavigateInput(const FInputActionValue& Value)
{
// Axis2D値を取得(-1.0 ~ 1.0)
FVector2D NavValue = Value.Get<FVector2D>();
// フォーカス移動処理
if (NavValue.Y > 0.5f)
{
// 上キー / スティック上
OnNavigateUp();
}
else if (NavValue.Y < -0.5f)
{
// 下キー / スティック下
OnNavigateDown();
}
if (NavValue.X > 0.5f)
{
// 右キー / スティック右
OnNavigateRight();
}
else if (NavValue.X < -0.5f)
{
// 左キー / スティック左
OnNavigateLeft();
}
}
void AMyPlayerController::OnClickInput(const FInputActionValue& Value)
{
// マウスクリック または ゲームパッドAボタン
if (Value.Get<bool>())
{
// クリック処理
OnUIClicked();
}
}
void AMyPlayerController::OnSubmitInput(const FInputActionValue& Value)
{
if (Value.Get<bool>())
{
// 決定ボタン処理(Enterキー またはゲームパッドボタン)
OnSubmit();
}
}
void AMyPlayerController::OnCancelInput(const FInputActionValue& Value)
{
if (Value.Get<bool>())
{
// キャンセル処理(Escapeキー またはゲームパッドキャンセル)
OnCancel();
}
}
8-2. UI Widget の C++ コード
cpp
// MyUIWidget.h
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "Blueprint/WidgetNavigation.h"
#include "MyUIWidget.generated.h"
class UButton;
class UTextBlock;
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnButtonClicked);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnNavigationEvent,
int32, FromIndex, int32, ToIndex);
UCLASS()
class YOURPROJECT_API UMyUIWidget : public UUserWidget
{
GENERATED_BODY()
protected:
UPROPERTY(meta = (BindWidget))
class UButton* Button_Resume;
UPROPERTY(meta = (BindWidget))
class UButton* Button_Settings;
UPROPERTY(meta = (BindWidget))
class UButton* Button_Exit;
UPROPERTY(BlueprintReadWrite, Category = "UI")
int32 CurrentFocusIndex;
UPROPERTY(BlueprintReadWrite, Category = "UI")
TArray<UButton*> ButtonArray;
virtual void NativeConstruct() override;
virtual void NativeDestruct() override;
// ボタンイベント
UFUNCTION()
void OnResumeClicked();
UFUNCTION()
void OnSettingsClicked();
UFUNCTION()
void OnExitClicked();
// フォーカス管理
UFUNCTION(BlueprintCallable, Category = "UI")
void MoveNavigationUp();
UFUNCTION(BlueprintCallable, Category = "UI")
void MoveNavigationDown();
UFUNCTION(BlueprintCallable, Category = "UI")
void MoveNavigationLeft();
UFUNCTION(BlueprintCallable, Category = "UI")
void MoveNavigationRight();
UFUNCTION(BlueprintCallable, Category = "UI")
void SetFocusToButton(int32 ButtonIndex);
UFUNCTION(BlueprintCallable, Category = "UI")
void ActivateCurrentButton();
public:
UPROPERTY(BlueprintAssignable, Category = "UI")
FOnNavigationEvent OnNavigationEvent;
};
cpp
// MyUIWidget.cpp
#include "MyUIWidget.h"
#include "Components/Button.h"
#include "Components/TextBlock.h"
#include "Kismet/GameplayStatics.h"
void UMyUIWidget::NativeConstruct()
{
Super::NativeConstruct();
// ボタンイベントをバインド
if (Button_Resume)
{
Button_Resume->OnClicked.AddDynamic(this, &UMyUIWidget::OnResumeClicked);
}
if (Button_Settings)
{
Button_Settings->OnClicked.AddDynamic(this, &UMyUIWidget::OnSettingsClicked);
}
if (Button_Exit)
{
Button_Exit->OnClicked.AddDynamic(this, &UMyUIWidget::OnExitClicked);
}
// ボタン配列に格納
ButtonArray.Empty();
ButtonArray.Add(Button_Resume);
ButtonArray.Add(Button_Settings);
ButtonArray.Add(Button_Exit);
// 初期フォーカスを設定
CurrentFocusIndex = 0;
SetFocusToButton(0);
}
void UMyUIWidget::NativeDestruct()
{
Super::NativeDestruct();
// バインドを削除
if (Button_Resume)
{
Button_Resume->OnClicked.RemoveDynamic(this, &UMyUIWidget::OnResumeClicked);
}
if (Button_Settings)
{
Button_Settings->OnClicked.RemoveDynamic(this, &UMyUIWidget::OnSettingsClicked);
}
if (Button_Exit)
{
Button_Exit->OnClicked.RemoveDynamic(this, &UMyUIWidget::OnExitClicked);
}
}
void UMyUIWidget::OnResumeClicked()
{
// 再開処理
UGameplayStatics::SetGamePaused(GetWorld(), false);
RemoveFromParent();
}
void UMyUIWidget::OnSettingsClicked()
{
// 設定メニューを開く
UE_LOG(LogTemp, Warning, TEXT("Settings menu opened"));
}
void UMyUIWidget::OnExitClicked()
{
// メインメニューに戻る
UGameplayStatics::OpenLevel(GetWorld(), FName("MainMenu"));
}
void UMyUIWidget::MoveNavigationUp()
{
int32 NewIndex = CurrentFocusIndex - 1;
if (NewIndex < 0) NewIndex = ButtonArray.Num() - 1;
SetFocusToButton(NewIndex);
}
void UMyUIWidget::MoveNavigationDown()
{
int32 NewIndex = CurrentFocusIndex + 1;
if (NewIndex >= ButtonArray.Num()) NewIndex = 0;
SetFocusToButton(NewIndex);
}
void UMyUIWidget::SetFocusToButton(int32 ButtonIndex)
{
if (ButtonIndex < 0 || ButtonIndex >= ButtonArray.Num())
return;
int32 PrevIndex = CurrentFocusIndex;
CurrentFocusIndex = ButtonIndex;
if (ButtonArray[ButtonIndex])
{
ButtonArray[ButtonIndex]->SetKeyboardFocus();
}
OnNavigationEvent.Broadcast(PrevIndex, CurrentFocusIndex);
}
void UMyUIWidget::ActivateCurrentButton()
{
if (CurrentFocusIndex >= 0 && CurrentFocusIndex < ButtonArray.Num())
{
if (ButtonArray[CurrentFocusIndex])
{
ButtonArray[CurrentFocusIndex]->OnClicked.Broadcast();
}
}
}
第6章:マウス入力の高度なハンドリング
ステップ9:マウスカーソルと相互作用
【マウス入力の実装パターン】
パターン1:クリック・ホバー時のビジュアル反応
├─ On Mouse Enter → ボタン明るくする
├─ On Mouse Leave → ボタン暗くする
└─ On Clicked → アクティベート
パターン2:マウス位置からのヒット判定
├─ Get Mouse Position in Viewport
├─ Viewport to Screen Space
├─ Screen Space to World Space
└─ Line Trace で UI ウィジェット検出
パターン3:マウス速度に基づいた処理
├─ Get Last Mouse Position
├─ Calculate Velocity
└─ Spawn Drag Effect(ドラッグ表現)
9-1. ブループリントでの実装
【WBP_MouseAwareButton】
On Mouse Move:
├─ Get Mouse Position in Viewport
├─ Calculate Distance to Button Center
├─ If Distance < Threshold:
│ └─ Set Button Color to Highlight
└─ Else:
└─ Set Button Color to Normal
On Mouse Down:
├─ Play Press Animation
├─ Play Sound Effect
└─ Store Start Position
On Mouse Up:
├─ If Still Over Button:
│ ├─ Trigger Click Event
│ └─ Play Release Animation
└─ Else:
└─ Cancel Click
9-2. C++ での詳細実装
cpp
// MyMouseAwareButton.h
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "Components/Button.h"
#include "MyMouseAwareButton.generated.h"
UCLASS()
class YOURPROJECT_API UMyMouseAwareButton : public UButton
{
GENERATED_BODY()
protected:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mouse")
float HoverDistanceThreshold;
UPROPERTY(BlueprintReadWrite, Category = "Mouse")
FVector2D LastMousePosition;
UPROPERTY(BlueprintReadWrite, Category = "Mouse")
bool bIsMouseOver;
virtual void NativeConstruct() override;
virtual void NativeTick(const FGeometry& MyGeometry,
float InDeltaTime) override;
UFUNCTION()
void OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent);
UFUNCTION()
void OnMouseLeave(const FPointerEvent& MouseEvent);
UFUNCTION()
void OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent);
public:
UFUNCTION(BlueprintCallable, Category = "Mouse")
float GetDistanceToMouse(const FGeometry& MyGeometry) const;
};
cpp
// MyMouseAwareButton.cpp
#include "MyMouseAwareButton.h"
#include "Kismet/GameplayStatics.h"
void UMyMouseAwareButton::NativeConstruct()
{
Super::NativeConstruct();
HoverDistanceThreshold = 50.0f;
bIsMouseOver = false;
OnMouseEnterEvent.AddDynamic(this, &UMyMouseAwareButton::OnMouseEnter);
OnMouseLeaveEvent.AddDynamic(this, &UMyMouseAwareButton::OnMouseLeave);
OnMouseMoveEvent.AddDynamic(this, &UMyMouseAwareButton::OnMouseMove);
}
void UMyMouseAwareButton::NativeTick(const FGeometry& MyGeometry,
float InDeltaTime)
{
Super::NativeTick(MyGeometry, InDeltaTime);
// マウス位置の更新(フレームごと)
if (bIsMouseOver)
{
APlayerController* PC = GetOwningPlayer();
if (PC)
{
FVector2D MousePosition;
PC->GetMousePosition(MousePosition.X, MousePosition.Y);
LastMousePosition = MousePosition;
}
}
}
void UMyMouseAwareButton::OnMouseEnter(const FGeometry& MyGeometry,
const FPointerEvent& MouseEvent)
{
bIsMouseOver = true;
// フォーカス設定
if (!HasKeyboardFocus())
{
SetKeyboardFocus();
}
// ビジュアル反応(色変更など)
// SetColorAndOpacity(FLinearColor::Yellow);
}
void UMyMouseAwareButton::OnMouseLeave(const FPointerEvent& MouseEvent)
{
bIsMouseOver = false;
// フォーカス解除
if (HasKeyboardFocus())
{
ClearKeyboardFocus();
}
// ビジュアル戻す
// SetColorAndOpacity(FLinearColor::White);
}
void UMyMouseAwareButton::OnMouseMove(const FGeometry& MyGeometry,
const FPointerEvent& MouseEvent)
{
LastMousePosition = MouseEvent.GetScreenSpacePosition();
}
float UMyMouseAwareButton::GetDistanceToMouse(const FGeometry& MyGeometry) const
{
FVector2D ButtonCenter = MyGeometry.GetAbsoluteSize() / 2.0f;
FVector2D ButtonScreenPosition = MyGeometry.GetAbsolutePosition() + ButtonCenter;
return FVector2D::Distance(ButtonScreenPosition, LastMousePosition);
}
第7章:ゲームパッド入力の高度なハンドリング
ステップ10:ゲームパッド振動とフィードバック
ゲームパッド操作では、振動フィードバック(ハプティクス)がユーザーエクスペリエンスを大幅に向上させます。
cpp
// GamepadFeedback.cpp
#include "GameFramework/PlayerController.h"
#include "GenericPlatform/GenericApplication.h"
void AMyPlayerController::PlayGamepadVibration(float Intensity, float Duration)
{
if (!IsLocalController())
return;
FVector2D VibeAmount(Intensity, Intensity);
// ゲームパッド振動を再生
FSlateApplication::Get().OnControllerAnalogValueChanged(
FGamepadKeyNames::LeftMotor,
VibeAmount.X,
0
);
// 振動を一定時間後に停止
GetWorld()->GetTimerManager().SetTimer(
VibeTimerHandle,
[this]()
{
FVector2D StopVibe(0.0f, 0.0f);
FSlateApplication::Get().OnControllerAnalogValueChanged(
FGamepadKeyNames::LeftMotor,
0.0f,
0
);
},
Duration,
false
);
}
ステップ11:複数ゲームパッド対応
複数のプレイヤーをサポートする場合、各プレイヤーの入力を適切に管理する必要があります。
cpp
// MultiplayerController.cpp
void AMyGameMode::StartMatch()
{
// Player 1
APlayerController* PC1 = GetWorld()->GetFirstPlayerController();
if (PC1)
{
// 各プレイヤーに別々のInputMappingContextを割り当て
if (UEnhancedInputLocalPlayerSubsystem* Subsystem1 =
PC1->GetLocalPlayer()->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
{
Subsystem1->AddMappingContext(Player1IMC, 0);
}
}
// Player 2 以降も同様
}
第8章:共通のトラブルシューティング
問題1:ゲームパッドが反応しない
【原因と対処】
原因1:Input Mapping Context が追加されていない
対処:
├─ SetupPlayerInputComponent() で必ず AddMappingContext() を呼ぶ
└─ プロジェクト設定で Enhanced Input が有効か確認
原因2:Input Action が正しく割り当てられていない
対処:
├─ Mapping タブで、Gamepad mapping が存在するか確認
├─ Dead Zone が大きすぎないか確認(推奨0.1~0.3)
└─ Input Action の Type が適切か確認
原因3:ゲームパッドが認識されていない
対処:
├─ Windows: Settings → Devices → Bluetooth & other devices で確認
├─ macOS: System Preferences → Bluetooth で確認
├─ Linux: jstest-gtk などでテスト
└─ ドライバの再インストール
問題2:マウスとゲームパッドが競合する
【原因と対処】
原因:両入力が同時にUIを操作しようとしている
対処:
├─ SetInputMode() で入力モードを明確に設定
│ ├─ Game and UI:両方対応
│ ├─ Game Only:ゲーム入力のみ
│ └─ UI Only:UI入力のみ
│
├─ マウスフォーカスとゲームパッドフォーカスを分離
│ └─ デバイス種別ごとに異なるフォーカスUIを使用
│
└─ Input Consume(入力を消費)を適切に設定
└─ マウスホバー時はゲームパッド移動を無視
問題3:フォーカス移動がぎこちない
【原因と対処】
原因1:Navigation設定が不足
対処:
├─ Panel 設定で Navigation を明示的に設定
├─ Focus Navigation Rules:
│ ├─ Up Navigation
│ ├─ Down Navigation
│ ├─ Left Navigation
│ └─ Right Navigation
└─ 一方向ナビゲーションに設定
原因2:Dead Zone が小さすぎる
対処:
├─ Dead Zone Modifier のパラメータを調整
├─ Lower Threshold:0.1~0.3(推奨0.2)
└─ 感度テストを複数ゲームパッド型式で実施
原因3:ナビゲーション遅延
対処:
├─ Smooth Value Modifier を追加
├─ Rising Speed を低めに設定(1.0~2.0)
└─ Processors で Debounce を有効
問題4:マウス入力が反応しない
【原因と対処】
原因1:bShowMouseCursor が false
対処:
├─ APlayerController::BeginPlay() で設定
└─ bShowMouseCursor = true;
原因2:Input Mode が正しく設定されていない
対処:
├─ SetInputMode(FInputModeGameAndUI());
└─ または SetInputMode(FInputModeUIOnly());
原因3:UI が Canvas の背後にある
対処:
├─ Z-Order(レイヤー)順序を確認
├─ Canvas Panel の後ろに配置されていないか
└─ Add to Viewport の Priority パラメータを確認
第9章:ベストプラクティス
ガイドライン1:入力の責任分離
【良い設計パターン】
PlayerController:
├─ 入力受け取り
├─ 入力イベント発火
└─ UI への委譲
UI Widget:
├─ 入力イベント処理
├─ ビジュアル反応
└─ ゲーム状態変更要求
Game Logic:
├─ ゲーム状態管理
├─ UI への通知
└─ 入力への直接反応なし
ガイドライン2:フォーカス管理の一元化
cpp
// 推奨パターン
class AMyHUD : public AHUD
{
UPROPERTY()
UMyUIWidget* CurrentUIWidget;
public:
void SetFocusedWidget(UMyUIWidget* NewWidget)
{
if (CurrentUIWidget)
{
CurrentUIWidget->OnFocusLost();
}
CurrentUIWidget = NewWidget;
if (CurrentUIWidget)
{
CurrentUIWidget->OnFocusGained();
}
}
};
ガイドライン3:プラットフォーム別の入力設定
【推奨フォルダ構成】
Input/
├─ Common/
│ ├─ IA_Navigate
│ ├─ IA_Click
│ └─ IMC_UIDefault
│
├─ PC/
│ ├─ IA_Navigate_PC(キーボード重視)
│ └─ IMC_UIPlatformPC
│
├─ Console/
│ ├─ IA_Navigate_Console(ゲームパッド重視)
│ └─ IMC_UIPlatformConsole
│
└─ Mobile/
├─ IA_Navigate_Mobile(タッチ入力)
└─ IMC_UIPlatformMobile
ガイドライン4:デバッグ補助機能
cpp
// 推奨デバッグ実装
void AMyPlayerController::PrintInputDebugInfo()
{
if (UEnhancedInputComponent* EIC =
Cast<UEnhancedInputComponent>(InputComponent))
{
const TArray<FEnhancedInputActionValueBinding>& Bindings =
EIC->GetActionBindings();
for (const auto& Binding : Bindings)
{
UE_LOG(LogTemp, Warning,
TEXT("Action: %s, Value: %f"),
*Binding.GetAction()->GetName(),
Binding.GetValue().Get<float>()
);
}
}
}
// コンソールコマンドとして登録
FConsoleCommandWithoutArgsDelegate PrintInputDebugDelegate(
FConsoleCommandWithoutArgsDelegate::CreateUObject(
PlayerController,
&AMyPlayerController::PrintInputDebugInfo
)
);
IConsoleManager::Get().RegisterConsoleCommand(
TEXT("PrintInputDebug"),
TEXT("Print input debug information"),
PrintInputDebugDelegate
);
第10章:実装チェックリスト
ゲームパッド・マウス両対応のUI実装に向けたチェックリストです。
準備フェーズ
□ UE5.1以上のバージョンを使用
□ Enhanced Input System が有効化されている
□ DefaultPlayerInputClass が EnhancedPlayerInput に設定
□ DefaultInputComponentClass が EnhancedInputComponent に設定
Input Action 準備
□ IA_Navigate 作成(Axis2D型)
□ Keyboard: Arrow Keys
□ Keyboard: WASD
□ Gamepad: Left Stick
□ Gamepad: D-Pad
□ Dead Zone Modifier: 0.2
□ IA_Click 作成(Digital型)
□ Mouse: Left Button
□ Gamepad: Face Button Bottom
□ IA_Submit 作成(Digital型)
□ Keyboard: Enter
□ Gamepad: Face Button Bottom/Right
□ IA_Cancel 作成(Digital型)
□ Keyboard: Escape
□ Gamepad: Face Button Right
Input Mapping Context 準備
□ IMC_UIDefault 作成
□ 上記4つの Input Action をマッピング
□ 各 Action に複数の Mapping 追加
□ Modifiers と Processors を必要に応じて追加
PlayerController 実装
□ BeginPlay() で AddMappingContext() を呼び出し
□ SetupPlayerInputComponent() で全 Action をバインド
□ SetInputMode() で入力モード設定
□ bShowMouseCursor = true でマウス表示
UI Widget 実装
□ OnFocusReceived イベント実装
□ OnFocusLost イベント実装
□ OnMouseEnter/OnMouseLeave 実装
□ ゲームパッド用フォーカスビジュアル実装
□ マウス用ホバービジュアル実装
□ ナビゲーション設定(Up/Down/Left/Right)
テスト
□ マウスキーボードでの操作テスト
□ マウスクリック
□ キーボード矢印キー
□ Tab キーナビゲーション
□ ゲームパッドでの操作テスト
□ 左スティック移動
□ D-Pad移動
□ A ボタン(決定)
□ B ボタン(キャンセル)
□ 複合操作テスト
□ マウスホバー + ゲームパッド移動
□ キーボード入力 + ゲームパッド入力
□ 入力が競合しないか確認
□ パフォーマンステスト
□ フレームレート低下がないか
□ 入力レイテンシが許容値以下か
まとめ
UE5でのゲームパッド・マウス両対応UI実装は、以下の重要なポイントを押さえることで実現できます:
要点1:Enhanced Input System を採用
UE5.1以降では Enhanced Input System が標準です。従来の SetupPlayerInputComponent だけでは不十分です。Input Action と Input Mapping Context を正しく構成することが基本です。
要点2:フォーカスシステムを理解する
フォーカスはゲームパッド入力の生命線です。正確なフォーカス移動とビジュアルフィードバックが、ゲームパッド操作性を決定します。
要点3:マウス入力とゲームパッド入力の分離
両入力が競合しないよう、以下を明確にします:
- マウス入力:直接クリック、ホバー効果
- ゲームパッド入力:フォーカス移動、ボタン実行
要点4:プラットフォーム別の最適化
PC・コンソール・モバイルでは最適な入力方法が異なります。プラットフォームごとに Input Mapping Context を分けることで、各プラットフォームで最高のUXを提供できます。
要点5:継続的なテストと最適化
デッドゾーン、感度、ナビゲーション遅延など、細かな調整がユーザーエクスペリエンスに大きく影響します。複数のゲームパッド型式・マウス環境でテストすることが重要です。
参考資料・リンク集
公式ドキュメント
コミュニティリソース
関連ツール
- Enhanced Input Debugger:エディタプラグインで入力値をリアルタイム監視
- Gamepad Tester:複数ゲームパッドの入力をテスト
- Input Visualizer:UI入力の流れを可視化
このガイドが、あなたのゲーム開発における入力システム構築に役立つことを願っています。Happy Coding!






