UE4版本:4.253
1、当我们按下键盘时输入时,FEngineLoop::Tick()里的每个tick执行的PumpMessages函数输送按键消息
WindowsPlatformApplicationMisc.cpp
void FWindowsPlatformApplicationMisc::PumpMessages(bool bFromMainLoop)
{
//{…忽略代码}
WinPumpMessages();
//{…忽略代码}
}
static void WinPumpMessages()
{
{
MSG Msg;
while( PeekMessage(&Msg,NULL,0,0,PM_REMOVE) )
{
TranslateMessage( &Msg );
DispatchMessage( &Msg );
}
}
}
PeekMessage,读取消息,非阻塞式获取输入消息。
TranslateMessage,转换消息,将虚拟键消息转换成字符消息。
DispatchMessage,发送消息,该函数调度一个消息给窗口程序。
2、然后FWindowsApplication在AppWndProc处接受Windows传进的message;
LRESULT CALLBACK FWindowsApplication::AppWndProc(HWND hwnd, uint32 msg, WPARAM wParam, LPARAM lParam)
{
ensure( IsInGameThread() );
return WindowsApplication->ProcessMessage( hwnd, msg, wParam, lParam );
}
3、ProcessMessage()根据Message的类型对Message分类处理然后执行DeferMessage()。
4、DeferMessage()检查消息是否需要延迟处理;延迟处理则放入处理延迟处理列表TArray DeferredMessages;否则立即执行ProcessDeferredMessage()。
5、ProcessDeferredMessage()根据Message类型调用MessageHandler的不同方法开始处理事件,如鼠标双击OnMouseDoubleClick (),W键按下OnKeyDown(),则分别执行:
MessageHandler->OnMouseDoubleClick( CurrentNativeEventWindowPtr,EMouseButtons::Left );
MessageHandler->OnKeyDown( ActualKey, CharCode, bIsRepeat );
6、转到FSlateApplication
FSlateApplication根据不同按键的做不同的处理,如按下W键则执行OnKeyDown(),然后各自找出当前所有FWidgetPath、然后对于每个FWidgetPath里的Widget生成FReply响应,并执行Widget里面对应的处理事件的方法。就是当我们在场景中按下w是向前走,但在聊天那里按下w则是输入字符”w”。
bool FSlateApplication::OnKeyDown( const int32 KeyCode, const uint32 CharacterCode, const bool IsRepeat )
{
FKey const Key = FInputKeyManager::Get().GetKeyFromCodes( KeyCode, CharacterCode );
FKeyEvent KeyEvent(Key, PlatformApplication->GetModifierKeys(), GetUserIndexForKeyboard(), IsRepeat, CharacterCode, KeyCode);
return ProcessKeyDownEvent( KeyEvent );
}
7、当在编辑器里的场景(PIE)中输入W键时,FSceneViewport会响应输入事件。
FReply FSceneViewport::OnKeyDown( const FGeometry& InGeometry, const FKeyEvent& InKeyEvent )
{
//{…}
//省略了判断代码
if (!ViewportClient->InputKey(FInputKeyEventArgs(this, InKeyEvent.GetUserIndex(), Key, InKeyEvent.IsRepeat() ? IE_Repeat : IE_Pressed, 1.0f, false)))
{
CurrentReplyState = FReply::Unhandled();
}
}
8、本次我是在场景中输入W键(控制角色向前移动),转到FSceneViewportWidget里面对应的处理事件。
然后执行FViewportClient::InputKey(),被派生类UGameViewportClient重写。
bool UGameViewportClient::InputKey(const FInputKeyEventArgs& EventArgs)
{
//{…}
//省略代码
bResult = TargetPlayer->PlayerController->InputKey(EventArgs.Key, EventArgs.Event, EventArgs.AmountDepressed, EventArgs.IsGamepad());
}
9、把按键信息发给PlayerController,然后交给UPlayerInput处理。
bool APlayerController::InputKey(FKey Key, EInputEvent EventType, float AmountDepressed, bool bGamepad)
{
//省略代码
bResult = PlayerInput->InputKey(Key, EventType, AmountDepressed, bGamepad);
}
轴输入指的是鼠标的X轴和Y轴的滑动(PS:按键也是有轴输入的)。
1、当我们按下键盘时输入时,FEngineLoop::Tick()里的每个tick执行FSlateApplication::FinishedInputThisFrame()函数处理输入累积值
void FSlateApplication::FinishedInputThisFrame()
{
///代码省略…
///判断是否有主动捕获的Slate,有的话则遍历主动捕获的Widget处理累积值
if (User.HasAnyCapture())
{
for (const TSharedRef<SWidget>& Captor : User.GetCaptorWidgets())
{
Captor->OnFinishedPointerInput();
}
}
///遍历每个指针所在的最后一个Widget处理累积值
else
{
for (const auto& IndexPathPair :User.GetWidgetsUnderPointerLastEventByIndex())
{
for (const TWeakPtr<SWidget>& WidgetPtr : IndexPathPair.Value.Widgets)
{
if (TSharedPtr<SWidget> Widget = WidgetPtr.Pin())
{
Widget->OnFinishedPointerInput();
}
}
}
}
}
2、会有不同的Widget来响应轴输入,
本次测试环境是在游戏场景窗口中滑动鼠标。
所以会在视口SViewport响应轴输入。
void SViewport::OnFinishedPointerInput()
{
///代码省略…
///视图的呈现和I/O实现的接口,(当前环境即是SceneViewport)
TSharedPtr<ISlateViewport> PinnedInterface = ViewportInterface.Pin();
if (PinnedInterface.IsValid())
{
PinnedInterface->OnFinishedPointerInput();
}
}
3、跳转到场景视口SceneViewport
void FSceneViewport::OnFinishedPointerInput()
{
///当这个帧的输入完成时从slate调用,并且我们应该处理任何累积的鼠标数据。
ProcessAccumulatedPointerInput();
}
void FSceneViewport::ProcessAccumulatedPointerInput()
{
///代码省略…
if (NumMouseSamplesX > 0 || NumMouseSamplesY > 0)
{
const float DeltaTime = FApp::GetDeltaTime();
ViewportClient->InputAxis( this, 0, EKeys::MouseX, MouseDelta.X, DeltaTime, NumMouseSamplesX );
ViewportClient->InputAxis( this, 0, EKeys::MouseY, MouseDelta.Y, DeltaTime, NumMouseSamplesY );
}
}
其中NumMouseSamplesX 和NumMouseSamplesY 是在OnMouseMove修改的。
FReply FSceneViewport::OnMouseMove( const FGeometry& InGeometry, const FPointerEvent& InMouseEvent )
{
///代码省略…
++NumMouseSamplesX;
++NumMouseSamplesY;
}
OnMouseMove的流程和按键输入一样,结合上面的按键输入看下图
4、跳到游戏视口的接口:UGameViewportClient。
bool UGameViewportClient::InputAxis(FViewport* InViewport, int32 ControllerId, FKey Key, float Delta, float DeltaTime, int32 NumSamples, bool bGamepad)
{
///代码省略…
bResult = TargetPlayer->PlayerController->InputAxis(Key, Delta, DeltaTime, NumSamples, bGamepad);
}
5、跳到角色控制器APlayerController
bool APlayerController::InputAxis(FKey Key, float Delta, float DeltaTime, int32 NumSamples, bool bGamepad)
{
bool bResult = false;
if (PlayerInput)
{
bResult = PlayerInput->InputAxis(Key, Delta, DeltaTime, NumSamples, bGamepad);
}
return bResult;
}
6、跳到角色输入:UPlayerInput
UPlayerInput:: InputAxis
后面会对UPlayerInput做详细分析。