1编辑器中运行游戏启动时就获取鼠标控制

设置游戏启动时就获取鼠标控制(不需要再点击一次运行窗口)

常常我们在UE编辑器当中运行的时候会发现游戏开始运行后鼠标鼠标点击一下窗口才可以进行操作,这是因为我们默认没有获取游戏鼠标控制的问题,因此我们打开编辑器偏好设置-播放-游戏获取鼠标控制就可以解决

UE5 鼠标控制设置_UE5 鼠标控制设置

UE5 鼠标控制设置_UE5 鼠标控制设置_02

或者点击Change Play Mode and Play Setting按钮,下拉列表选择Advanced Setting

2发行版运行游戏启动时就获取鼠标控制

需要先点击一下游戏画面,然后才能获取到鼠标控制


配置文件实现

DefaultViewportMouseCaptureMode=CapturePermanently_IncludingInitialMouseDown

DefaultViewportMouseLockMode=LockOnCapture

DefaultViewportMouseLockMode=LockAlways

UGameViewportClient::SetMouseLockMode | Unreal Engine Documentation

在 UE 中学习设计模式:策略模式-SetInputMode - 知乎 (zhihu.com)

UE 模拟点击 - 知乎 (zhihu.com)

【UE·底层篇】Slate源码分析——点击事件的触发流程梳理 - 知乎 (zhihu.com)

蓝图实现

获取focus,然后C++代码实现SendInput鼠标函数


修改源码

关注这两行

  if (0)

 //if (SlateUser->HasCapture(MouseEvent.GetPointerIndex()))

bool FSlateApplication::ProcessMouseButtonDownEvent( const TSharedPtr< FGenericWindow >& PlatformWindow, const FPointerEvent& MouseEvent )
{
	SCOPE_CYCLE_COUNTER(STAT_ProcessMouseButtonDown);
	
	TScopeCounter<int32> BeginInput(ProcessingInput);

#if WITH_SLATE_DEBUGGING
	FSlateDebugging::FScopeProcessInputEvent Scope(ESlateDebuggingInputEvent::MouseButtonDown, MouseEvent);
#endif

#if WITH_EDITOR
	//Send the key input to all pre input key down listener function
	if (OnApplicationMousePreInputButtonDownListenerEvent.IsBound())
	{
		OnApplicationMousePreInputButtonDownListenerEvent.Broadcast(MouseEvent);
	}
#endif //WITH_EDITOR

	SetLastUserInteractionTime(this->GetCurrentTime());
	LastUserInteractionTimeForThrottling = LastUserInteractionTime;
	
	if (PlatformWindow.IsValid())
	{
		PlatformApplication->SetCapture(PlatformWindow);
	}

	if (MouseEvent.GetUserIndex() == CursorUserIndex)
	{
		PressedMouseButtons.Add(MouseEvent.GetEffectingButton());
	}

	// Input preprocessors get the first chance at the input
	if (InputPreProcessors.HandleMouseButtonDownEvent(*this, MouseEvent))
	{
		return true;
	}

	bool bInGame = false;

	// Only process mouse down messages if we are not drag/dropping
	TSharedRef<FSlateUser> SlateUser = GetOrCreateUser(MouseEvent);
	if (!SlateUser->IsDragDropping())
	{
		FReply Reply = FReply::Unhandled();
		if (0)
		//if (SlateUser->HasCapture(MouseEvent.GetPointerIndex()))
		{
			FWidgetPath MouseCaptorPath = SlateUser->GetCaptorPath(MouseEvent.GetPointerIndex(), FWeakWidgetPath::EInterruptedPathHandling::Truncate, &MouseEvent);
			FArrangedWidget& MouseCaptorWidget = MouseCaptorPath.Widgets.Last();

			// Switch worlds widgets in the current path
			FScopedSwitchWorldHack SwitchWorld(MouseCaptorPath);
			bInGame = FApp::IsGame();

			FPointerEvent TransformedPointerEvent = TransformPointerEvent(MouseEvent, MouseCaptorPath.GetWindow());

			Reply = FEventRouter::Route<FReply>(this, FEventRouter::FToLeafmostPolicy(MouseCaptorPath), TransformedPointerEvent, [] (const FArrangedWidget& InMouseCaptorWidget, const FPointerEvent& Event)
			{
				const FReply TempReply = InMouseCaptorWidget.Widget->OnPreviewMouseButtonDown(InMouseCaptorWidget.Geometry, Event);
#if WITH_SLATE_DEBUGGING
				FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::PreviewMouseButtonDown, &Event, TempReply, InMouseCaptorWidget.Widget);
#endif
				return TempReply;
			}, ESlateDebuggingInputEvent::PreviewMouseButtonDown);

			if ( !Reply.IsEventHandled() )
			{
				Reply = FEventRouter::Route<FReply>(this, FEventRouter::FToLeafmostPolicy(MouseCaptorPath), TransformedPointerEvent,
					[this] (const FArrangedWidget& InMouseCaptorWidget, const FPointerEvent& Event)
				{
					FReply TempReply = FReply::Unhandled();
					if ( Event.IsTouchEvent() )
					{
						TempReply = InMouseCaptorWidget.Widget->OnTouchStarted(InMouseCaptorWidget.Geometry, Event);
#if WITH_SLATE_DEBUGGING
						FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::TouchStart, &Event, TempReply, InMouseCaptorWidget.Widget);
#endif
					}
					if ( !Event.IsTouchEvent() || ( !TempReply.IsEventHandled() && this->bTouchFallbackToMouse ) )
					{
						TempReply = InMouseCaptorWidget.Widget->OnMouseButtonDown(InMouseCaptorWidget.Geometry, Event);
#if WITH_SLATE_DEBUGGING
						FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::MouseButtonDown, &Event, TempReply, InMouseCaptorWidget.Widget);
#endif
					}
					return TempReply;
				}, ESlateDebuggingInputEvent::MouseButtonDown);
			}
		}
		else
		{
			FWidgetPath WidgetsUnderCursor = LocateWindowUnderMouse( MouseEvent.GetScreenSpacePosition(), GetInteractiveTopLevelWindows(), false, SlateUser->GetUserIndex());

			PopupSupport.SendNotifications( WidgetsUnderCursor );

			// Switch worlds widgets in the current path
			FScopedSwitchWorldHack SwitchWorld(WidgetsUnderCursor);
			bInGame = FApp::IsGame();

			//@todo NickD: Route API should be private; update Process methods to accept an FWidgetPath
			Reply = RoutePointerDownEvent(WidgetsUnderCursor, MouseEvent);
		}

		// See if expensive tasks should be throttled.  By default on mouse down expensive tasks are throttled
		// to ensure Slate responsiveness in low FPS situations
		if (Reply.IsEventHandled() && !bInGame && !MouseEvent.IsTouchEvent())
		{
			// Enter responsive mode if throttling should occur and its not already happening
			if( Reply.ShouldThrottle() && !MouseButtonDownResponsivnessThrottle.IsValid() )
			{
				MouseButtonDownResponsivnessThrottle = FSlateThrottleManager::Get().EnterResponsiveMode();
			}
			else if( !Reply.ShouldThrottle() && MouseButtonDownResponsivnessThrottle.IsValid() )
			{
				// Leave responsive mode if a widget chose not to throttle
				FSlateThrottleManager::Get().LeaveResponsiveMode( MouseButtonDownResponsivnessThrottle );
			}
		}
	}

	return true;
}

函数调用堆栈

>	XiHuan-Win64-DebugGame.exe!SButton::OnMouseButtonDown(const FGeometry & MyGeometry, const FPointerEvent & MouseEvent) Line 306	C++
 	[Inline Frame] XiHuan-Win64-DebugGame.exe!FSlateApplication::RoutePointerDownEvent::__l5::<lambda_310398f9f05d1d718df7855b2cc899ec>::operator()(const FArrangedWidget) Line 4930	C++
 	XiHuan-Win64-DebugGame.exe!FEventRouter::Route<FReply,FEventRouter::FBubblePolicy,FPointerEvent,<lambda_310398f9f05d1d718df7855b2cc899ec>>(FSlateApplication * ThisApplication, FEventRouter::FBubblePolicy RoutingPolicy, FPointerEvent EventCopy, const FSlateApplication::RoutePointerDownEvent::__l5::<lambda_310398f9f05d1d718df7855b2cc899ec> & Lambda, ESlateDebuggingInputEvent DebuggingInputEvent) Line 412	C++
 	XiHuan-Win64-DebugGame.exe!FSlateApplication::RoutePointerDownEvent(const FWidgetPath & WidgetsUnderPointer, const FPointerEvent & PointerEvent) Line 4916	C++
 	XiHuan-Win64-DebugGame.exe!FSlateApplication::ProcessMouseButtonDownEvent(const TSharedPtr<FGenericWindow,1> & PlatformWindow, const FPointerEvent & MouseEvent) Line 4864	C++
 	XiHuan-Win64-DebugGame.exe!FSlateApplication::OnMouseDown(const TSharedPtr<FGenericWindow,1> & PlatformWindow, const EMouseButtons::Type Button, const UE::Math::TVector2<double> CursorPos) Line 4762	C++
 	XiHuan-Win64-DebugGame.exe!FWindowsApplication::ProcessDeferredMessage(const FDeferredWindowsMessage & DeferredMessage) Line 2227	C++
 	XiHuan-Win64-DebugGame.exe!FWindowsApplication::DeferMessage(TSharedPtr<FWindowsWindow,1> & NativeWindow, HWND__ * InHWnd, unsigned int InMessage, unsigned __int64 InWParam, __int64 InLParam, int MouseX, int MouseY, unsigned int RawInputFlags) Line 2726	C++
 	[Inline Frame] XiHuan-Win64-DebugGame.exe!SharedPointerInternals::FSharedReferencer<1>::{dtor}() Line 582	C++
 	XiHuan-Win64-DebugGame.exe!FWindowsApplication::ProcessMessage(HWND__ * hwnd, unsigned int msg, unsigned __int64 wParam, __int64 lParam) Line 1895	C++
 	[Inline Frame] XiHuan-Win64-DebugGame.exe!WindowsApplication_WndProc(HWND__ *) Line 919	C++
 	XiHuan-Win64-DebugGame.exe!FWindowsApplication::AppWndProc(HWND__ * hwnd, unsigned int msg, unsigned __int64 wParam, __int64 lParam) Line 925	C++
 	[External Code]	
 	[Inline Frame] XiHuan-Win64-DebugGame.exe!WinPumpMessages() Line 113	C++
 	XiHuan-Win64-DebugGame.exe!FWindowsPlatformApplicationMisc::PumpMessages(bool bFromMainLoop) Line 142	C++
 	XiHuan-Win64-DebugGame.exe!FEngineLoop::Tick() Line 5293	C++
 	[Inline Frame] XiHuan-Win64-DebugGame.exe!EngineTick() Line 66	C++
 	XiHuan-Win64-DebugGame.exe!GuardedMain(const wchar_t * CmdLine) Line 202	C++
 	XiHuan-Win64-DebugGame.exe!LaunchWindowsStartup(HINSTANCE__ * hInInstance, HINSTANCE__ * hPrevInstance, char * __formal, int nCmdShow, const wchar_t * CmdLine) Line 233	C++
 	XiHuan-Win64-DebugGame.exe!WinMain(HINSTANCE__ * hInInstance, HINSTANCE__ * hPrevInstance, char * pCmdLine, int nCmdShow) Line 283	C++
 	[External Code]


源码剖析


bool FSlateApplication::ProcessMouseButtonDownEvent( const TSharedPtr< FGenericWindow >& PlatformWindow, const FPointerEvent& MouseEvent )


源文件定义

Engine\Source\Runtime\Engine\Classes\Engine\EngineBaseTypes.h

UENUM()
enum class EMouseCaptureMode : uint8
{
	/** Do not capture the mouse at all */
	NoCapture,
	/** Capture the mouse permanently when the viewport is clicked, and consume the initial mouse down that caused the capture so it isn't processed by player input */
	CapturePermanently,
	/** Capture the mouse permanently when the viewport is clicked, and allow player input to process the mouse down that caused the capture */
	CapturePermanently_IncludingInitialMouseDown,
	/** Capture the mouse during a mouse down, releases on mouse up */
	CaptureDuringMouseDown,
	/** Capture only when the right mouse button is down, not any of the other mouse buttons */
	CaptureDuringRightMouseDown,
};

UENUM()
enum class EMouseLockMode : uint8
{
	/** Do not lock the mouse cursor to the viewport */
	DoNotLock,
	/** Only lock the mouse cursor to the viewport when the mouse is captured */
	LockOnCapture,
	/** Always lock the mouse cursor to the viewport */
	LockAlways,
	/** Lock the cursor if we're in fullscreen */
	LockInFullscreen,
};


鼠标输入模式

基类

/** Abstract base class for Input Mode structures */
struct ENGINE_API FInputModeDataBase
{
protected:
	virtual ~FInputModeDataBase() { }

	/** Derived classes override this function to apply the necessary settings for the desired input mode */
	virtual void ApplyInputMode(class FReply& SlateOperations, class UGameViewportClient& GameViewportClient) const = 0;

	/** Utility functions for derived classes. */
	void SetFocusAndLocking(FReply& SlateOperations, TSharedPtr<class SWidget> InWidgetToFocus, bool bLockMouseToViewport, TSharedRef<class SViewport> InViewportWidget) const;

	friend class APlayerController;
};

只控制UI

/** Data structure used to setup an input mode that allows only the UI to respond to user input. */
struct ENGINE_API FInputModeUIOnly : public FInputModeDataBase
{
	/** Widget to focus */
	FInputModeUIOnly& SetWidgetToFocus(TSharedPtr<SWidget> InWidgetToFocus);

	/** Sets the mouse locking behavior of the viewport */
	FInputModeUIOnly& SetLockMouseToViewportBehavior(EMouseLockMode InMouseLockMode);

	FInputModeUIOnly()
		: WidgetToFocus()
		, MouseLockMode(EMouseLockMode::LockInFullscreen)
	{}

protected:
	TSharedPtr<SWidget> WidgetToFocus;
	EMouseLockMode MouseLockMode;

	virtual void ApplyInputMode(FReply& SlateOperations, class UGameViewportClient& GameViewportClient) const override;
};

只控制游戏内逻辑

/** Data structure used to setup an input mode that allows only the player input / player controller to respond to user input. */
struct ENGINE_API FInputModeGameOnly : public FInputModeDataBase
{
	/** Whether the mouse down that causes capture should be consumed, and not passed to player input processing */
	FInputModeGameOnly& SetConsumeCaptureMouseDown(bool InConsumeCaptureMouseDown) { bConsumeCaptureMouseDown = InConsumeCaptureMouseDown; return *this; }

	FInputModeGameOnly()
		: bConsumeCaptureMouseDown(true)
	{}

protected:
	bool bConsumeCaptureMouseDown;

	virtual void ApplyInputMode(FReply& SlateOperations, class UGameViewportClient& GameViewportClient) const override;
};

控制游戏内逻辑和UI

 /** Data structure used to setup an input mode that allows the UI to respond to user input, and if the UI doesn't handle it player input / player controller gets a chance. */
struct ENGINE_API FInputModeGameAndUI : public FInputModeDataBase
{
	/** Widget to focus */
	FInputModeGameAndUI& SetWidgetToFocus(TSharedPtr<SWidget> InWidgetToFocus) { WidgetToFocus = InWidgetToFocus; return *this; }

	/** Sets the mouse locking behavior of the viewport */
	FInputModeGameAndUI& SetLockMouseToViewportBehavior(EMouseLockMode InMouseLockMode) { MouseLockMode = InMouseLockMode; return *this; }

	/** Whether to hide the cursor during temporary mouse capture caused by a mouse down */
	FInputModeGameAndUI& SetHideCursorDuringCapture(bool InHideCursorDuringCapture) { bHideCursorDuringCapture = InHideCursorDuringCapture; return *this; }

	FInputModeGameAndUI()
		: WidgetToFocus()
		, MouseLockMode(EMouseLockMode::DoNotLock)
		, bHideCursorDuringCapture(true)
	{}

protected:

	TSharedPtr<SWidget> WidgetToFocus;
	EMouseLockMode MouseLockMode;
	bool bHideCursorDuringCapture;

	virtual void ApplyInputMode(FReply& SlateOperations, class UGameViewportClient& GameViewportClient) const override;
};

鼠标锁定在视口内

不锁定鼠标

永远锁定鼠标

只有全屏时锁定鼠标

只有按下鼠标并拖动时锁定鼠标

参考

https://docs.unrealengine.com/4.26/zh-CN/Resources/ContentExamples/MouseInterface/MouseControlSetup/

https://docs.unrealengine.com/4.27/zh-CN/BuildingWorlds/LevelEditor/InEditorTesting/Settings/

https://docs.unrealengine.com/4.27/en-US/Resources/ContentExamples/MouseInterface/MouseControlSetup/#:~:text=Click%20the%20Settings%20button%20located%20in%20the%20toolbar.,the%20map%2C%20you%20should%20now%20see%20the%20cursor.