找回密码
 立即注册
查看: 456|回复: 0

Unreal源码学习(二)用户输入

[复制链接]
发表于 2020-11-25 09:08 | 显示全部楼层 |阅读模式
本篇文章主要明确从用户触发输入到引擎触发指定事件的主要逻辑。


为了跨平台,在虚幻中定义了一个通用的事件处理接口:
  1. class FGenericApplicationMessageHandler
  2. {
  3. public:
  4.         ...
  5.         
  6.         virtual bool OnKeyChar( const TCHAR Character, const bool IsRepeat )
  7.         virtual bool OnKeyDown( const int32 KeyCode, const uint32 CharacterCode, const bool IsRepeat )
  8.         virtual bool OnKeyUp( const int32 KeyCode, const uint32 CharacterCode, const bool IsRepeat )
  9.         virtual void OnInputLanguageChanged()
  10.         virtual bool OnMouseDown( const TSharedPtr< FGenericWindow >& Window, const EMouseButtons::Type Button )
  11.         ...
  12. };
复制代码
篇幅原因就不全贴出来了。
在这当中定义了键盘,鼠标,屏幕触摸,Gamepad等一系列的事件。
我们以键盘输入为例:
在引擎初始化时会为我们创建 FSceneViewport 和 UGameViewportClient:
  1. ..void UGameEngine::Init(IEngineLoop* InEngineLoop)
  2. {
  3.         ...
  4.         // Initialize the viewport client.
  5.         UGameViewportClient* ViewportClient = NULL;
  6.         if(GIsClient)
  7.         {
  8.                 ViewportClient = NewObject<UGameViewportClient>(this, GameViewportClientClass);
  9.                 ViewportClient->Init(*GameInstance->GetWorldContext(), GameInstance);
  10.                 GameViewport = ViewportClient;
  11.                 GameInstance->GetWorldContext()->GameViewport = ViewportClient;
  12.         }
  13.         LastTimeLogsFlushed = FPlatformTime::Seconds();
  14.         // Attach the viewport client to a new viewport.
  15.         if(ViewportClient)
  16.         {
  17.                 // This must be created before any gameplay code adds widgets
  18.                 bool bWindowAlreadyExists = GameViewportWindow.IsValid();
  19.                 if (!bWindowAlreadyExists)
  20.                 {
  21.                         UE_LOG(LogEngine, Log, TEXT("GameWindow did not exist.  Was created"));
  22.                         GameViewportWindow = CreateGameWindow();
  23.                 }
  24.                 CreateGameViewport( ViewportClient );
  25.                 if( !bWindowAlreadyExists )
  26.                 {
  27.                         SwitchGameWindowToUseGameViewport();
  28.                 }
  29.                 FString Error;
  30.                 if(ViewportClient->SetupInitialLocalPlayer(Error) == NULL)
  31.                 {
  32.                         UE_LOG(LogEngine, Fatal,TEXT("%s"),*Error);
  33.                 }
  34.                 UGameViewportClient::OnViewportCreated().Broadcast();
  35.         }
  36.         UE_LOG(LogInit, Display, TEXT("Game Engine Initialized.") );
  37.         // for IsInitialized()
  38.         bIsInitialized = true;
  39. }
复制代码
当我们按下键盘的某个按键,会触发 FSceneViewport 的 OnKeyDown 的代码,这里面会把我们输入事件经由 UGameViewportClient 和 PlayerController 的 InputKey 方法最后传递到  UplayerInput::InputKey 方法中:
  1. bool UPlayerInput::InputKey(FKey Key, EInputEvent Event, float AmountDepressed, bool bGamepad)
  2. {
  3.         // first event associated with this key, add it to the map
  4.         FKeyState& KeyState = KeyStateMap.FindOrAdd(Key);
  5.         UWorld* World = GetWorld();
  6.         check(World);
  7.         switch(Event)
  8.         {
  9.         case IE_Pressed:
  10.         case IE_Repeat:
  11.                 KeyState.RawValueAccumulator.X = AmountDepressed;
  12.                 KeyState.EventAccumulator[Event].Add(++EventCount);
  13.                 if (KeyState.bDownPrevious == false)
  14.                 {
  15.                         // check for doubleclick
  16.                         // note, a tripleclick will currently count as a 2nd double click.
  17.                         const float WorldRealTimeSeconds = World->GetRealTimeSeconds();
  18.                         if ((WorldRealTimeSeconds - KeyState.LastUpDownTransitionTime) < GetDefault<UInputSettings>()->DoubleClickTime)
  19.                         {
  20.                                 KeyState.EventAccumulator[IE_DoubleClick].Add(++EventCount);
  21.                         }
  22.                         // just went down
  23.                         KeyState.LastUpDownTransitionTime = WorldRealTimeSeconds;
  24.                 }
  25.                 break;
  26.         case IE_Released:
  27.                 KeyState.RawValueAccumulator.X = 0.f;
  28.                 KeyState.EventAccumulator[IE_Released].Add(++EventCount);
  29.                 break;
  30.         case IE_DoubleClick:
  31.                 KeyState.RawValueAccumulator.X = AmountDepressed;
  32.                 KeyState.EventAccumulator[IE_Pressed].Add(++EventCount);
  33.                 KeyState.EventAccumulator[IE_DoubleClick].Add(++EventCount);
  34.                 break;
  35.         }
  36.         ...
  37.         return true;
  38. }
复制代码
可以看到,这里面并不会直接调用具体事件,而是维护了一个 KeyStateMap 的状态集。
具体的触发事件的逻辑是在 PlayerInput::ProcessInputStack 中每帧处理:
  1. void UPlayerInput::ProcessInputStack(const TArray<UInputComponent*>& InputComponentStack, const float DeltaTime, const bool bGamePaused)
  2. {
  3.         ...
  4.         static TArray<FAxisDelegateDetails> AxisDelegates;
  5.         static TArray<FVectorAxisDelegateDetails> VectorAxisDelegates;
  6.         static TArray<FDelegateDispatchDetails> NonAxisDelegates;
  7.         static TArray<FKey> KeysToConsume;
  8.         static TArray<FDelegateDispatchDetails> FoundChords;
  9.         static TArray<TPair<FKey, FKeyState*>> KeysWithEvents;
  10.         static TArray<TSharedPtr<FInputActionBinding>> PotentialActions;
  11.         // must be called non-recursively and on the game thread
  12.         check(IsInGameThread() && !AxisDelegates.Num() && !VectorAxisDelegates.Num() && !NonAxisDelegates.Num() && !KeysToConsume.Num() && !FoundChords.Num() && !EventIndices.Num() && !KeysWithEvents.Num() && !PotentialActions.Num());
  13.         // @todo, if input is coming in asynchronously, we should buffer anything that comes in during playerinput() and not include it
  14.         // in this processing batch
  15.         APlayerController* PlayerController = GetOuterAPlayerController();
  16.         PlayerController->PreProcessInput(DeltaTime, bGamePaused);
  17.         // copy data from accumulators to the real values
  18.         for (TMap<FKey,FKeyState>::TIterator It(KeyStateMap); It; ++It)
  19.         {
  20.                 bool bKeyHasEvents = false;
  21.                 FKeyState* const KeyState = &It.Value();
  22.                 const FKey& Key = It.Key();
  23.                 for (uint8 EventIndex = 0; EventIndex < IE_MAX; ++EventIndex)
  24.                 {
  25.                         KeyState->EventCounts[EventIndex].Reset();
  26.                         Exchange(KeyState->EventCounts[EventIndex], KeyState->EventAccumulator[EventIndex]);
  27.                         if (!bKeyHasEvents && KeyState->EventCounts[EventIndex].Num() > 0)
  28.                         {
  29.                                 KeysWithEvents.Emplace(Key, KeyState);
  30.                                 bKeyHasEvents = true;
  31.                         }
  32.                 }
  33.                 if ( (KeyState->SampleCountAccumulator > 0) || Key.ShouldUpdateAxisWithoutSamples() )
  34.                 {
  35.                         if (KeyState->PairSampledAxes)
  36.                         {
  37.                                 // Paired keys sample only the axes that have changed, leaving unaltered axes in their previous state
  38.                                 for (int32 Axis = 0; Axis < 3; ++Axis)
  39.                                 {
  40.                                         if (KeyState->PairSampledAxes & (1 << Axis))
  41.                                         {
  42.                                                 KeyState->RawValue[Axis] = KeyState->RawValueAccumulator[Axis];
  43.                                         }
  44.                                 }
  45.                         }
  46.                         else
  47.                         {
  48.                                 // Unpaired keys just take the whole accumulator
  49.                                 KeyState->RawValue = KeyState->RawValueAccumulator;
  50.                         }
  51.                         // if we had no samples, we'll assume the state hasn't changed
  52.                         // except for some axes, where no samples means the mouse stopped moving
  53.                         if (KeyState->SampleCountAccumulator == 0)
  54.                         {
  55.                                 KeyState->EventCounts[IE_Released].Add(++EventCount);
  56.                                 if (!bKeyHasEvents)
  57.                                 {
  58.                                         KeysWithEvents.Emplace(Key, KeyState);
  59.                                         bKeyHasEvents = true;
  60.                                 }
  61.                         }
  62.                 }
  63.                 ...
  64.         }
  65.         EventCount = 0;
  66.         struct FDelegateDispatchDetailsSorter
  67.         {
  68.                 bool operator()( const FDelegateDispatchDetails& A, const FDelegateDispatchDetails& B ) const
  69.                 {
  70.                         return (A.EventIndex == B.EventIndex ? A.FoundIndex < B.FoundIndex : A.EventIndex < B.EventIndex);
  71.                 }
  72.         };
  73.         int32 StackIndex = InputComponentStack.Num()-1;
  74.         // Walk the stack, top to bottom
  75.         for ( ; StackIndex >= 0; --StackIndex)
  76.         {
  77.                 UInputComponent* const IC = InputComponentStack[StackIndex];
  78.                 if (IC)
  79.                 {
  80.                         check(!KeysToConsume.Num() && !FoundChords.Num() && !EventIndices.Num() && !PotentialActions.Num());
  81.                         IC->ConditionalBuildKeyMap(this);
  82.                         for (const TPair<FKey,FKeyState*>& KeyWithEvent : KeysWithEvents)
  83.                         {
  84.                                 if (!KeyWithEvent.Value->bConsumed)
  85.                                 {
  86.                                         FGetActionsBoundToKey::Get(IC, this, KeyWithEvent.Key, PotentialActions);
  87.                                 }
  88.                         }
  89.                         for (const TSharedPtr<FInputActionBinding>& ActionBinding : PotentialActions)
  90.                         {
  91.                                 GetChordsForAction(*ActionBinding.Get(), bGamePaused, FoundChords, KeysToConsume);
  92.                         }
  93.                         PotentialActions.Reset();
  94.                         for (const FInputKeyBinding& KeyBinding : IC->KeyBindings)
  95.                         {
  96.                                 GetChordForKey(KeyBinding, bGamePaused, FoundChords, KeysToConsume);
  97.                         }
  98.                         FoundChords.Sort(FDelegateDispatchDetailsSorter());
  99.                         ...
  100.                 }
  101.         }
  102.        
  103.         // Dispatch the delegates in the order they occurred
  104.         NonAxisDelegates.Sort(FDelegateDispatchDetailsSorter());
  105.         for (const FDelegateDispatchDetails& Details : NonAxisDelegates)
  106.         {
  107.                 if (Details.ActionDelegate.IsBound())
  108.                 {
  109.                         Details.ActionDelegate.Execute(Details.Chord.Key);
  110.                 }
  111.                 else if (Details.TouchDelegate.IsBound())
  112.                 {
  113.                         Details.TouchDelegate.Execute(Details.FingerIndex, Details.TouchLocation);
  114.                 }
  115.                 else if (Details.GestureDelegate.IsBound())
  116.                 {
  117.                         Details.GestureDelegate.Execute(Details.GestureValue);
  118.                 }
  119.         }
  120.         ...
  121.         PlayerController->PostProcessInput(DeltaTime, bGamePaused);
  122.         FinishProcessingPlayerInput();
  123.         AxisDelegates.Reset();
  124.         VectorAxisDelegates.Reset();
  125.         NonAxisDelegates.Reset();
  126.         TouchEventLocations.Reset();
  127.         KeysWithEvents.Reset();
  128. }
复制代码
参与的核心对象就是前文提到的 KeyStateMap 和 InputComponent。其中 InputComponent 保存着我们具体的输入配置。经过一系列的数据过滤操作,选出要触发的具体事件,在方法的最后统一触发。
总结

引擎自己定义了一套平台无关的系统信息回调接口。我们通过输入设备触发的输入事件会由此进入引擎并将触发状态维护在 PlayerInput 的 KeyStateMap 状态集中。引擎每帧依据 InputComponent 的输入绑定信息与 KeyStateMap 状态集进行数据对比,并触发具体的输入事件。
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-9-19 11:54 , Processed in 0.144319 second(s), 23 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表