InputSystem基础框架
Key
flowchart LR
Input(PlayerController::UpdateInput)
Input
-->InputProcesssor::Tick
Input
-->InputHandle::Tick
Input
-->InputCollector::Tick
对于 InputProcessor
,进行按键的原始输入处理,调用 InputKey
将将数据传递到 InputCollector
,继续后续的操作;
通过监听引擎原始的按键数据 APlayerController::IsInputKeyDown(const FKey Key)
;
允许外部调用输入,执行 InputKey
(比如某个 UI
点击触发输入);
特别地,在 Tick
中进行原始数据处理;
由于一般按键输入支持 Ctrl / Shit / Alt / Cmd
+ Key
,需要对这种情况适配,同时防止按键冲突:对于KeySetting(KeyType - 具体按键配置 映射)
额外维护 bCtrl (需要与 ctrl 组合)
、MustNoCtrl (需要不按下 Ctrl)
;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 void UInputProcessor::ProcessInputKey () { bool bShift = PC->IsInputKeyDown (EKeys::LeftShift) || PC->IsInputKeyDown (EKeys::RightShift); bool bCtrl = PC->IsInputKeyDown (EKeys::LeftControl) || PC->IsInputKeyDown (EKeys::RightControl); bool bAlt = PC->IsInputKeyDown (EKeys::LeftAlt) || PC->IsInputKeyDown (EKeys::RightAlt); bool bCmd = PC->IsInputKeyDown (EKeys::LeftCommand) || PC->IsInputKeyDown (EKeys::RightCommand); auto IsKeyDown = [&](EInputKeyType KeyType) -> bool { for (auto KeySetting : KeySettings[KeyType]) { bool bKeyDown = PC->IsInputKeyDown (FKey (FName (KeySetting.KeyName ()))); if ((KeySetting.Shift () && !bShift) || (KeySetting.MustNoShift () && bShift)) bKeyDown = false ; else if ((KeySetting.Ctrl () && !bCtrl) || (KeySetting.MustNoCtrl () && bCtrl)) bKeyDown = false ; else if ((KeySetting.Alt () && !bAlt) || (KeySetting.MustNoAlt () && bAlt)) bKeyDown = false ; else if ((KeySetting.Cmd () && !bCmd) || (KeySetting.MustNoCmd () && bCmd)) bKeyDown = false ; if (bKeyDown) return true ; } return false ; }; for (auto KeyType : KeyTypes) { if (IsKeyDown (KeyType)) { InputKey (KeyType); } } } void UInputProcessor::InputKey (EInputKeyType KeyType) { if (!IsValid (InputCollector)) return ; InputCollector->SetKey (KeyType, true ); }
classDiagram
direction LR
InputCollector --> FInputKeyInfo
class InputCollector {
KeyInfoMap: TMap~KeyType, FInputKeyInfo~
}
FInputKeyInfo *-- EKeyState
class FInputKeyInfo {
bEnable : bool
bPressed : bool
KeyState : EKeyState
}
class EKeyState {
IKS_Idle : 未按下
IKS_Press : 当帧按下
IKS_Release : 当帧释放
IKS_Holding : 持续按下
}
对于 InputCollector
,维护 Key - Press
相关的原始输入数据,将数据从 Press
加工到 KeyState
,并每帧检测是否未按下,这样外部只需要进行 Press
,而不需要 Release
;有几个主要的功能:
提供 SetKey
进行按键设置,提供 GetKey
查询按键状态;
维护每个 Key
的 KeyInfo
,数据用于后续处理;
提供 EnableKey
,设置按键是否可以输入(Disable
时需要取消输入);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 USTRUCT (BlueprintType)struct FInputKeyInfo { GENERATED_BODY () UPROPERTY () bool bPressed = false ; UPROPERTY () EInputKeyState KeyState = IKS_Idle; UPROPERTY () bool bEnable = true ; bool bProcessedThisFrame = false ; };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 void UInputCollector::SetKey (EInputKeyType KeyType, bool bPressed, bool bForce) { if (bForce || IsEnableKey (KeyType)) { SetKey_Internal (KeyType, bPressed); } } FInputKeyInfo UInputCollector::GetKeyInfo (EInputKeyType KeyType) { return KeyInfoMap.Contains (KeyType) ? KeyInfoMap[KeyType] : {}; } void UInputCollector::Tick () { for (auto KeyType : KeyTypes) { FInputKeyInfo& KeyInfo = KeyInfoMap[KeyType]; if (!KeyInfo.bProcessedThisFrame) { SetKey_Internal (KeyType, false ); KeyInfo.bProcessedThisFrame = false ; } } } void UInputCollector::SetKey_Internal (EInputKeyType KeyType, bool bPressed) { FInputKeyInfo& KeyInfo = KeyInfoMap[KeyType]; if (bPressed == KeyInfo.bPressed) { KeyInfo.KeyState = bPressed ? IKS_Holding : IKS_Idle; } else { KeyInfo.KeyState = bPressed ? IKS_Press : IKS_Release; } KeyInfo.bPressed = bPressed; KeyInfo.bProcessedThisFrame = true ; }
classDiagram
class InputHandleBase {
KeyInfo : TArray~EInputKeyType~
SetInput(KeyType, bPressed)
CollectInput()
ProcessInput()
NotifyInput()
}
InputHandleBase <|-- InputHandld_Default
InputHandleBase <|-- InputHandle_Character
InputHandleBase <|-- InputHandle_Vehicle
业务自定义逻辑的位置,一般仅对业务开放这部分的代码修改;
根据不同的输入模式,创建不同的 InputHandle
,在这里对输入进行业务级别的加工,同时处理基本的输入操作回调(内部基本的业务逻辑,以及允许其它模块向内部注册的事件),并将输入操作通知给其它模块;
业务自定义其输入逻辑,比如:
该输入模式下,当输入某个操作时,系统自动取消其它操作的输入等;
处理按键是否需要自动抬起;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 void UInputHandleBase::Tick () { RecordInput (); CollectInput (); ProcessInput (); DispatchInput (); } void UInputHandleBase::ProcessInput () { for (auto Key : ActiveKeyTypes) { bool bLastKeyDown = RecordKeyInfoMap[Key].bPressed; bool bCurrentKeyDown = KeyInfoMap[Key].bPressed; if (bCurrentKeyDown != bLastKeyDown) { bCurrentKeyDown ? OnGameKeyPress (Key) : OnGameKeyRelease (Key); } else { bCurrentKeyDown ? OnGameKeyHolding (Key) : OnGameKeyIdle (Key); } } } DECLARE_MULTICAST_DELEGATE_TwoParams (FInputEvent, KeyType, KeyState);using FInputEventCallbackParamTypes = FInputEventCallbackType::FDelegate::TFuncType;TMap <KeyType, FInputEvent> ProcessInputEvents; template < typename UserClass, typename FuncType = TMemFunPtrType<TIsConst<UserClass>::Value, UserClass, FInputEventCallbackParamTypes>::Type > bool UInputHandleBase::RegisterInputEvent (EInputKeyType KeyType, UserClass* InUserObject, FuncType InFunc){ if (!ProcessInputEvents.Contains (KeyType)) ProcessInputEvents.Add (KeyType, {}); auto & Event = Processvents[KeyType]; if (Event.IsBoundToObject (InUserObject)) { return false ; } Event.AddUObject (InObject, InFunc); return true ; } template <typename UserClass>bool UnregisterInputEvent (EInputKeyType Type, UserClass* InUserObject) { if (!ProcessInputEvents.Contains (Type)) return false ; auto & Event = ProcessInputEvents[Type]; Event.RemoveAll (InUserObject); return true ; } void UInputHandleBase::DispatchInput () { for (auto KeyType : ActiveKeyTypes) { if (auto EventPtr = ProcessInputEvents.Find (KeyType)) { EventPtr->Broadcast (KeyType, GetKeyState (KeyType)); } } } void UInputHandleBase::SetInput (EKeyType KeyType) { KeyInfo[KeyType].bPressed = true ; } void UInputHandleBase::UpdateKeyFromCollector (EInputKeyType KeyType) { SetInput (KeyType, InputCollector->GetKeyInfo (KeyType).bPressed); }
Mouse
Interface : IInputProcessor
由 FSlateApplication
驱动,也有 Key
、Mouse
相关操作的回调:
flowchart LR
FSlateApplication::ProcessMouseButtonDownEvent
-->FSlateApplication::InputPreProcessorsHelper::HandleMouseButtonDownEvent
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class SLATE_API IInputProcessor { public : IInputProcessor (){}; virtual ~IInputProcessor (){} virtual void Tick (const float DeltaTime, FSlateApplication& SlateApp, TSharedRef<ICursor> Cursor) = 0 ; virtual bool HandleKeyDownEvent (FSlateApplication& SlateApp, const FKeyEvent& InKeyEvent) { return false ; } virtual bool HandleKeyUpEvent (FSlateApplication& SlateApp, const FKeyEvent& InKeyEvent) { return false ; } virtual bool HandleAnalogInputEvent (FSlateApplication& SlateApp, const FAnalogInputEvent& InAnalogInputEvent) { return false ; } virtual bool HandleMouseMoveEvent (FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) { return false ; } virtual bool HandleMouseButtonDownEvent ( FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) { return false ; } virtual bool HandleMouseButtonUpEvent ( FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) { return false ; } virtual bool HandleMouseButtonDoubleClickEvent (FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) { return false ; } virtual bool HandleMouseWheelOrGestureEvent (FSlateApplication& SlateApp, const FPointerEvent& InWheelEvent, const FPointerEvent* InGestureEvent) { return false ; } virtual bool HandleMotionDetectedEvent (FSlateApplication& SlateApp, const FMotionEvent& MotionEvent) { return false ; }; virtual const TCHAR* GetDebugName () const { return TEXT ("" ); } };
可以在合适的地方(比如 InputSubSystem / InputManager
)进行创建:
1 2 InputProcessor = MakeShareable (new FInputProcessor ()); FSlateApplication::Get ().RegisterInputPreProcessor (InputProcessor);
其次是与 UI
的交互, UserWidget
中也提供了 Mouse
的回调:
1 2 3 4 virtual FReply NativeOnTouchStarted (const FGeometry& InGeometry, const FPointerEvent& InGestureEvent) override ;virtual FReply NativeOnTouchMoved (const FGeometry& InGeometry, const FPointerEvent& InGestureEvent) override ;virtual FReply NativeOnTouchEnded (const FGeometry& InGeometry, const FPointerEvent& InGestureEvent) override ;virtual FReply NativeOnMouseWheel (const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override ;
可以将接口收束在一个 InputManager
中,向外暴露相关事件;
DragDrop
DragArea
用于维护可拖动的范围,以及封装引擎基本接口、提供注册方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 function DragAreaView:OnMouseButtonDown (Geometry, MouseEvent) return UE.UWidgetBlueprintLibrary.DetectDragIfPressed(MouseEvent, self , "LeftMouseButton" ) end function DragAreaView:OnDragDetected (Geometry, PointerEvent) local OperationClass = self .DragDropOperationClass local Operation = NewObject(OperationClass) if self .DragDetectedDelegate ~= nil then for Object, Callback in pairs (self .DragDetectedDelegate) do xpcall (Callback, error_handler, Object, Geometry, PointerEvent, Operation) end end return Operation end function DragAreaView:OnDragCancelled (Geometry, PointerEvent, Operation) if self .DragCancelledDelegate ~= nil then for Object, Callback in pairs (self .DragCancelledDelegate) do xpcall (Event, error_handler, Object, Geometry, PointerEvent, Operation) end end end function DragAreaView:BindEvent (EventName, Object, Callback) if self [EventName] == nil then self [EventName] = {} end if self [EventName][Object] == nil then self [EventName][Object] = Callback end end function DragAreaView:UnbindEvent (EventName, Object) if self [EventName] == nil then self [EventName] = {} end if self [EventName][Object] ~= nil then self [EventName][Object] = nil end end function DragAreaView:BindDragDetected (Object,Func) self :BindEvent("DragDetectedDelegate" , Object, Func) end function DragAreaView:UnbindDragDetected (Object) self :UnbindEvent("DragDetectedDelegate" , Object) end function DragAreaView:BindDragCancelled (Object,Func) self :BindEvent("DragCancelledDelegate" , Object, Func) end function DragAreaView:UnbindDragCancelled (Object) self :UnbindEvent("DragCancelledDelegate" , Object) end
DragItem
用于维护拖动的实际 Item
;
PreviewItem
用于维护拖动时的 Preview
(也就是拖动时显示的 Item
预览);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 function DragItemView:Construct () self .DragArea:BindDragDetected(self , self .OnDragDetectedCallback) self .DragArea:BindDragCancelled(self , self .OnDragCancelledCallback) end function DragItemView:Destruct () self .DragArea:UnbindDragDetected(self ) self .DragArea:UnbindDragCancelled(self ) end function DragItemView:OnDragDetectedCallback (Geometry,PointerEvent,Operation) local BPClass = UE.UGameplayStatics.GetObjectClass(self ) Operation.PreviewItem = UE.UCommonWidgetUtils.CreateWidget(BPClass) Operation.DefaultDragVisual = Operation.PreviewItem if Operation.PreviewItem and Operation.PreviewItem.OnDragDetected then Operation.PreviewItem:OnDragDetected() end Operation.Pivot = UE.EDragPivot.TopLeft local LocalPosition = UE.USlateBlueprintLibrary.AbsoluteToLocal(self :GetCachedGeometry(), UE.UKismetInputLibrary.PointerEvent_GetScreenSpacePosition(PointerEvent)) local Offset = UE.FVector2D() local Size = UE.USlateBlueprintLibrary.GetLocalSize(self :GetCachedGeometry()) Offset.X = LocalPosition.X / Size.X * -1 Offset.Y = LocalPosition.Y / Size.Y * -1 Operation.Offset = Offset Operation.UseInAnimation = false end function DragItemView:OnDragCancelledCallback (Geometry, PointerEvent, Operation) if Operation.PreviewItem and Operation.PreviewItem.OnDragCancelled then Operation.PreviewItem:OnDragCancelled() end UE.UCommonWidgetUtils.DestroyWidget(Operation.PreviewItem) end
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 UUserWidget* UCommonWidgetUtils::CreateWidget (UWorld* InWorld, TSubclassOf<UUserWidget> WidgetType) { UUserWidget* Widget = UWidgetBlueprintLibrary::Create (InWorld, WidgetType, nullptr ); AddObjectRef (Widget); return Widget; } void UCommonWidgetUtils::DestroyWidget (UUserWidget* Widget) { RemoveObjectRef (Widget); } void UCommonWidgetUtils::AddObjectRef (UObject* Object) { if (Object && IsUObjectValid (Object)) { const auto L = UnLua::GetState (); auto & Env = UnLua::FLuaEnv::FindEnvChecked (L); Env.AddManualObjectReference (Object); } } void UCommonWidgetUtils::RemoveObjectRef (UObject* Object) { if (Object && IsUObjectValid (Object)) { const auto L = UnLua::GetState (); auto & Env = UnLua::FLuaEnv::FindEnvChecked (L); Env.RemoveManualObjectReference (Object); } }
特别地:
通过 Operation
的 DefaultDragVisual
来设置拖动时的预览 Widget
,在 FUMGDragDropOp::New
时,将其设置到 Operation->DecoratorWidget
,并在 FUMGDragDropOp::OnDragged
时更新 Position
;
CreateWidget
/ DestroyWidget
针对 PreviewItem
,这里可以额外维护一个 Pool
进行 Widget
复用;
在合适的地方增加额外 Callback
;
Touch
引擎对于 Touch
的相关实现主要在:UPlayerInput::InputTouch(uint32 Handle, ETouchType::Type Type, const FVector2D& TouchLocation, float Force, FDateTime DeviceTimestamp, uint32 TouchpadIndex)
中;
实际上,可以根据 IInputProcessor
提供的接口,在 InputManager
中维护 TArray <FInputTouchData> InputTouchDatas
以及相关信息,统一管理并分发给各个业务模块,同时方便自定义与扩展。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 struct FInputTouchData { ETouchIndex::Type TouchIndex; FVector2D StartLocation; FVector2D Location; FVector2D DeltaMove; float BeginTime; float UpdateTime; float DeltaTime; float ForceValue; bool bPendingKill; }
需要和引擎一样维护所有的 TouchData
,这样才可以通过 TouchIndex
与其它的接口关联,对应到其它回调的 PointerEvent
中的 (ETouchIndex::Type)PointerEvent.GetPointerIndex()
;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 class FInputProcessor { TArray <FInputTouchData> InputTouchDatas; } FInputTouchData FInputProcessor::GetInputTouchData (ETouchIndex::Type TouchIndex) { FInputTouchData* DataPtr = InputTouchDatas.FindByKey (TouchIndex); return DataPtr != nullptr ? *DataPtr : {}; } void FInputProcessor::ProcessMouseEvent (ETouchType TouchType, const FPointerEvent& MouseEvent) { FVector2D OutPixelPosition = FVector2D::ZeroVector; FVector2D OutViewportPosition = FVector2D::ZeroVector; USlateBlueprintLibrary::AbsoluteToViewport (GetWorld (), MouseEvent.GetScreenSpacePosition (), OutPixelPosition, OutViewportPosition); InputTouch (MouseEvent.GetPointerIndex (), TouchType, OutPixelPosition, MouseEvent.GetTouchForce ()); } bool FInputProcessor::HandleMouseButtonDownEvent (FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) { ProcessMouseEvent (ETouchType::Began, MouseEvent); return true ; } bool FInputProcessor::HandleMouseMoveEvent (FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) { ProcessMouseEvent (ETouchType::Moved, MouseEvent); return true ; } bool FInputProcessor::HandleMouseButtonUpEvent (FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) { ProcessMouseEvent (ETouchType::Ended, MouseEvent); for (int Index = InputTouchDatas.Num () - 1 ; Index >= 0 ; Index--) { if (InputTouchDatas[Index].bPendingKill == true ) { InputTouchDatas.RemoveAt (Index); } } return true ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 void FInputProcessor::InputTouch (uint32 Handle, ETouchType::Type Type, const FVector2D& TouchLocation, float Force) { FVector Location (TouchLocation, Force) ; switch (Type) { case ETouchType::Began: BeginTouch ((ETouchIndex::Type)Handle, Location); break ; case ETouchType::Ended: EndTouch ((ETouchIndex::Type)Handle, Location); break ; default : MoveTouch ((ETouchIndex::Type)Handle, Location); break ; } } void FInputProcessor::BeginTouch (const ETouchIndex::Type TouchIndex, const FVector Location) { FInputTouchData* DataPtr = InputTouchDatas.FindByKey (TouchIndex); if (DataPtr == nullptr ) { InputTouchDatas.AddDefaulted (1 ); DataPtr = &InputTouchDatas.Last (); } FInputTouchData& InputTouchData = *DataPtr; InputTouchData.TouchIndex = TouchIndex; InputTouchData.StartLocation = InputTouchData.Location = FVector2D (Location.X, Location.Y); InputTouchData.ForceValue = Location.Z; InputTouchData.BeginTime = InputTouchData.UpdateTime = GetWorld ()->GetTimeSeconds (); } void FInputProcessor::MoveTouch (const ETouchIndex::Type TouchIndex, const FVector Location) { FInputTouchData* DataPtr = InputTouchDatas.FindByKey (TouchIndex); if (DataPtr == nullptr ) return ; FInputTouchData& InputTouchData = *DataPtr; FVector2D ScreenLocation = FVector2D (Location.X, Location.Y); InputTouchData.DeltaMove = ScreenLocation - InputTouchData.Location; InputTouchData.Location = ScreenLocation; InputTouchData.ForceValue = Location.Z; InputTouchData.DeltaTime = GetWorld ()->GetDeltaSeconds (); InputTouchData.UpdateTime = GetWorld ()->GetTimeSeconds (); } void FInputProcessor::EndTouch (const ETouchIndex::Type TouchIndex, const FVector Location) { for (auto & InputTouchData : InputTouchDatas) { if (InputTouchData.TouchIndex == TouchIndex) { InputTouchData.bPendingKill = true ; } } }
这样就在 InputProcessor
中维护了所有的 TouchDatas
,可以分发事件给外部监听,以及提供数据查询。
Gesture
根据 Touch
的信息,通过 FGestureRecognizer
维护 Gesture_Pinch
、Gesture_Flick
、Gesture_Rotate
等;
在 UPlayerInput::Tick
中会进行 GestureRecognizer.DetectGestures(Touches, this, DeltaTime)
:
对于 Pinch
:如果 float* CurrentAlpha = CurrentGestureValues.Find(EKeys::Gesture_Pinch)
即 Pinch
还未开启,并且本次 TouchCount > 2
,则认为 Pinch
刚刚开始,此时根据 CurrentPinchPoint_Start
、CurrentPinchPoint_End
计算出 AnchorDistanceSq
并存储,将 1.0f
作为基础缩放值存储在 GestureValue
;在下一次根据新的 NewDistanceSq / AnchorDistanceSq
作为缩放值记录在 CurrentGestureValues[EKeys::Gesture_Pinch]
; TouchCount
减少到 < 2
时, Pinch
结束;
对于 Flick
:要求 FlickTime < 0.25f && (FlickCurrent - AnchorPoints[0]).SizeSquared() > 10000.f
,经过的时间在 0.25s 内,并且滑动一定的距离;计算和存储 Angle
于 CurrentGestureValues[EKeys::Gesture_Flick]
;
对于 Rotate
:如果 float* CurrentAngle = CurrentGestureValues.Find(EKeys::Gesture_Rotate)
即 Rotate
还未开启,当 TouchCount > 2
时,则认为 Rotate
刚刚开始,此时计算出 StartAngle
记录,将 0.0f
作为基础旋转值存储在 GestureValue
;在后续更新 GestureValue
为 NewAngle - StartAngle
,表示相对角度; TouchCount
减少到 < 2
时, Rotate
结束;
最后:
Gesture
CurrentGestureValue : Type
Pinch
Scale (相对开始的比例缩放)
Flick
Angle(本次滑动的角度)
Rotate
Angle (相对开始的角度)
1 2 3 4 5 6 7 8 9 bool UPlayerInput::InputGesture (const FKey Gesture, const EInputEvent Event, const float Value) { FKeyState& KeyState = KeyStateMap.FindOrAdd (Gesture); KeyState.Value.X = KeyState.RawValue.X = KeyState.RawValueAccumulator.X = Value; KeyState.EventAccumulator[Event].Add (++EventCount); return true ; }
这些信息会存储在 KeyState
里,通过 UPlayerInput::GetKeyState(FKey InKey)
获取;
当然,在 Mouse
的回调中:
1 2 3 virtual FReply NativeOnTouchStarted (const FGeometry& InGeometry, const FPointerEvent& InGestureEvent) override ;virtual FReply NativeOnTouchMoved (const FGeometry& InGeometry, const FPointerEvent& InGestureEvent) override ;virtual FReply NativeOnTouchEnded (const FGeometry& InGeometry, const FPointerEvent& InGestureEvent) override ;
可以看到这里有 GestureEvent
,也可以获取对应主要信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 FVector2D ScreenSpacePosition; FVector2D LastScreenSpacePosition; FVector2D CursorDelta; const TSet<FKey>* PressedButtons;FKey EffectingButton; uint32 PointerIndex; uint32 TouchpadIndex; float Force;bool bIsTouchEvent;EGestureEvent GestureType; FVector2D WheelOrGestureDelta; bool bIsDirectionInvertedFromDevice;bool bIsTouchForceChanged;bool bIsTouchFirstMove;
实际上,可以将这里的 GestureRecognizer
移到统一维护的 InputManager
中,将相关信息提供给各个业务查询,方便自定义与扩展:比如增加 Tag
限定提供给外部设置,当且仅当 Tag
一致时,进行对应 Gesture
更新等。
参考
UE 5.4 源码