🖱️

【UE5】UMGのドラッグ&ドロップ実行中にマウスカーソルを表示させない

2024/11/23に公開

はじめに

UMG には、ドラッグ&ドロップ機能を簡単に実装する方法が用意されています。
ウィジェットのInputイベント内で DetectDrag / DetectDragIfPressed ノード を使用することで、ドラッグ&ドロップイベントを開始させることができます。

Blueprintでのドラッグ&ドロップ開始処理の実装例


C++でのドラッグ&ドロップ開始処理の実装例
virtual FReply NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override
{
    FReply Reply = Super::NativeOnMouseButtonDown(InGeometry, InMouseEvent);

    if (Reply.IsEventHandled())
    {
        return Reply;
    }

    // 左マウスボタンがクリックされたら, ドラッグ&ドロップを実行させる
    return UWidgetBlueprintLibrary::DetectDragIfPressed(InMouseEvent, this, EKeys::LeftMouseButton).NativeReply;
}

virtual void NativeOnDragDetected(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent, UDragDropOperation*& OutOperation) override
{
    // ドラッグ開始
    Super::NativeOnDragDetected(InGeometry, InMouseEvent, OutOperation);

    if (!OutOperation)
    {
        OutOperation = UWidgetBlueprintLibrary::CreateDragDropOperation(UDragDropOperation::StaticClass());
    }
}

しかし、この機能を使用してドラッグ&ドロップを実行している最中に、マウスカーソルが強制的に表示されてしまうという問題があります。
困ったことに、 PlayerControllerbShowMouseCursor フラグを変更しても、この問題を回避することができません。

画面に常にマウスカーソルを表示させているゲームでは気にならないかもしれませんが、ウィジェットに画面独自のデザインのカーソルを持たせているケースでは、この挙動は特に厄介です。
本記事では、この問題を解決する具体的な方法について共有します。


ドラッグ&ドロップの間だけマウスカーソルが表示されてしまう

環境

UE5.3

原因

ドラッグ&ドロップ中のマウスカーソル表示は、FDragDropOperation という抽象クラスの OnCursorQuery() によって制御されています。
この関数は、ドラッグ操作が行われている間に FSlateApplication から繰り返し呼び出され、表示すべきマウスカーソルの種類を返します。

デフォルトの挙動では、この関数が EMouseCursor::Default(通常の矢印カーソル)となる値を返すため、PlayerController のカーソル表示設定にかかわらず、マウスカーソルが強制的に表示される設計となっています。

DragAndDrop.cpp
DragAndDrop.cpp
FCursorReply FDragDropOperation::OnCursorQuery()
{
    if ( MouseCursorOverride.IsSet() )
    {
        return FCursorReply::Cursor( MouseCursorOverride.GetValue() );
    }

    if ( MouseCursor.IsSet() )
    {
        return FCursorReply::Cursor(MouseCursor.GetValue());
    }

    return FCursorReply::Unhandled();
}

これを回避するには、FDragDropOperation::OnCursorQuery() が返却する値を EMouseCursor::None に変更する必要があります。これにより、ドラッグ中のマウスカーソルを非表示にすることができます。
しかし、これも一筋縄ではいきません。Slate の設計上、生成される FDragDropOperation のオブジェクトクラスが固定されており、別の派生クラスに差し替える仕組みが用意されていません。このため、独自の挙動を適用することが難しくなっています。

解決に向けて

この問題の解決方法として、以下の2通りの方法が挙げられます。

  • エンジン機能に独自に変更を加え、FUMGDragDropOp::OnCursorQuery() がデフォルトで返却する値を変更する
  • FDragDropOperation::SetCursorOverride() を利用し、ウィジェット側から表示するマウスカーソルの種類を変更する

前者のエンジン改造を用いた方法はとてもシンプルかつ効果的ですが、プロジェクトの方針や実装担当者の裁量次第では難しい場合があります。
これを踏まえて、本記事では FDragDropOperation::SetCursorOverride() を利用した解決方法に焦点を当てて説明します。

解決方法

ドラッグ&ドロップ中のマウスカーソルを非表示にするには、ウィジェットの NativeOnDragOver() イベントを活用します。このイベントは、ドラッグ中にマウスカーソルがウィジェット上で移動するたびに呼び出されます。
イベントの引数から FDragDropOperation のオブジェクトを取得し、SetCursorOverride() を呼び出すことで、マウスカーソルを非表示に設定できます。

virtual bool NativeOnDragOver(const FGeometry& InGeometry, const FDragDropEvent& InDragDropEvent, UDragDropOperation* InOperation) override
{
    // ドラッグ&ドロップ中のマウスカーソルがこのウィジェットの上にある間は,
    // マウスカーソルを表示させない
    TSharedPtr<FDragDropOperation> Operation = InDragDropEvent.GetOperation();
    if (Operation.IsValid())
    {
        Operation->SetCursorOverride(EMouseCursor::None);
    }

    return Super::NativeOnDragOver(InGeometry, InDragDropEvent, InOperation);
}

注意点

マウスカーソルがどこに移動しても非表示となるようにするためには、画面を構成しているあらゆるウィジェットで NativeOnDragOver() をオーバーライドする必要があります。
これは、NativeOnDragOver() が、ウィジェット外でのマウス移動に対してイベントがトリガーされないためです。

画面規模的に無理が生じる場合は、エンジン機能の改造に踏み切る方法が結果的に低コストとなるため、検討すると良いでしょう。

実装例

以下は、ドラッグ&ドロップ可能な アイテムウィジェット と、それを配置する 画面ウィジェット の実装例です。
いずれのウィジェットも UMyUserWidget を基底クラスとして継承しており、NativeOnDragOver() でドラッグ&ドロップ中にマウスカーソルを非表示にさせる機能を持っています。

ウィジェットのC++基底クラス
MyUserWidget.h
/** ウィジェットの共通基底クラス */
UCLASS(Abstract)
class UMyUserWidget : public UUserWidget
{
    GENERATED_BODY()

protected:
    virtual bool NativeOnDragOver(const FGeometry& InGeometry, const FDragDropEvent& InDragDropEvent, UDragDropOperation* InOperation) override
    {
        // ドラッグ&ドロップ中のマウスカーソルがこのウィジェットの上にある間は, マウスカーソルを表示させない
        TSharedPtr<FDragDropOperation> Operation = InDragDropEvent.GetOperation();

        if (Operation.IsValid())
        {
            Operation->SetCursorOverride(EMouseCursor::None);
        }

        return Super::NativeOnDragOver(InGeometry, InDragDropEvent, InOperation);
    }
};
MyDraggableUserWidget.h
/** ドラッグ&ドロップ可能なウィジェット */
UCLASS(Abstract)
class UMyDraggableUserWidget : public UMyUserWidget
{
    GENERATED_BODY()

protected:
    virtual FReply NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override
    {
        FReply Reply = Super::NativeOnMouseButtonDown(InGeometry, InMouseEvent);

        if (Reply.IsEventHandled())
        {
            return Reply;
        }

        // 左マウスボタンがクリックされたら, ドラッグ&ドロップを実行させる
        return UWidgetBlueprintLibrary::DetectDragIfPressed(InMouseEvent, this, EKeys::LeftMouseButton).NativeReply;
    }

    virtual void NativeOnDragDetected(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent, UDragDropOperation*& OutOperation) override
    {
        // ドラッグ開始
        Super::NativeOnDragDetected(InGeometry, InMouseEvent, OutOperation);

        if (!OutOperation)
        {
            OutOperation = UWidgetBlueprintLibrary::CreateDragDropOperation(UDragDropOperation::StaticClass());
        }
    }
};


記事冒頭の例とは打って変わり、ドラッグ&ドロップ中でもマウスカーソルが表示されなくなりました。

おまけ: エンジン機能改造

エンジン機能の改造をしてマウスカーソルを非表示にする場合は、FUMGDragDropOp クラスのコンストラクタの処理を変更します。

FUMGDragDropOp は、Slate で生成される FDragDropOparation の派生クラスです。
このクラスの MouseCursorEMouseCursor::None をのデフォルト値として代入しておくことで、ドラッグ&ドロップ中のマウスカーソルがデフォルトで非表示となります。

UMGDragDropOp.cpp
FUMGDragDropOp::FUMGDragDropOp()
    : DragOperation(nullptr)
    , GameViewport(nullptr)
{
    StartTime = FSlateApplicationBase::Get().GetCurrentTime();
+
+#if 1 // ~Begin modified section
+   MouseCursor = EMouseCursor::None;
+#endif // ~End modified section
}

Discussion