代码还是参考自Epic官方的塔防项目:StrategyGame
看了下C++的API,现成的API中貌似只支持单点触碰检测,用法如下:
注册:
// support touch devices InputComponent->BindTouch(EInputEvent::IE_Pressed, this, &ATD_MobilePlayerController::MoveToTouchLocation); InputComponent->BindTouch(EInputEvent::IE_Repeat, this, &ATD_MobilePlayerController::MoveToTouchLocation);
触发回调:
void ATD_MobilePlayerController::MoveToTouchLocation(const ETouchIndex::Type FingerIndex, const FVector Location) { FVector2D ScreenSpaceLocation(Location); // Trace to see what is under the touch location FHitResult HitResult; GetHitResultAtScreenPosition(ScreenSpaceLocation, CurrentClickTraceChannel, true, HitResult); if (HitResult.bBlockingHit) { // We hit something, move there SetNewMoveDestination(HitResult.ImpactPoint); } }
如果要实现多点触碰检测,比如实现两个手指捏动来缩放屏幕,StrategyGame项目自己实现了一套算法来实现多点屏幕检测,具体做法如下:
StrategyPlayerController.h
1,先重写StrategyPlayerController的ProcessPlayerInput()和SetupInputComponent()函数
protected: /** update input detection */ virtual void ProcessPlayerInput(const float DeltaTime, const bool bGamePaused) override; virtual void SetupInputComponent() override;
在SetupInputComponent()函数中绑定事件(这个事件机制也是自己写的),其中BIND_1P_ACTION和BIND_2P_ACTION是自己定义的宏:
void AStrategyPlayerController::SetupInputComponent() { Super::SetupInputComponent(); InputHandler = NewObject<UStrategyInput>(this); BIND_1P_ACTION(InputHandler, EGameKey::Tap, IE_Pressed, &AStrategyPlayerController::OnTapPressed); BIND_1P_ACTION(InputHandler, EGameKey::Hold, IE_Pressed, &AStrategyPlayerController::OnHoldPressed); BIND_1P_ACTION(InputHandler, EGameKey::Hold, IE_Released, &AStrategyPlayerController::OnHoldReleased); BIND_1P_ACTION(InputHandler, EGameKey::Swipe, IE_Pressed, &AStrategyPlayerController::OnSwipeStarted); BIND_1P_ACTION(InputHandler, EGameKey::Swipe, IE_Repeat, &AStrategyPlayerController::OnSwipeUpdate); BIND_1P_ACTION(InputHandler, EGameKey::Swipe, IE_Released, &AStrategyPlayerController::OnSwipeReleased); BIND_2P_ACTION(InputHandler, EGameKey::SwipeTwoPoints, IE_Pressed, &AStrategyPlayerController::OnSwipeTwoPointsStarted); BIND_2P_ACTION(InputHandler, EGameKey::SwipeTwoPoints, IE_Repeat, &AStrategyPlayerController::OnSwipeTwoPointsUpdate); BIND_2P_ACTION(InputHandler, EGameKey::Pinch, IE_Pressed, &AStrategyPlayerController::OnPinchStarted); BIND_2P_ACTION(InputHandler, EGameKey::Pinch, IE_Repeat, &AStrategyPlayerController::OnPinchUpdate); FInputActionBinding& ToggleInGameMenuBinding = InputComponent->BindAction("InGameMenu", IE_Pressed, this, &AStrategyPlayerController::OnToggleInGameMenu); ToggleInGameMenuBinding.bExecuteWhenPaused = true; }
ProcessPlayerInput()来检测触碰点变化,这是一个连续执行的函数,大概逻辑是:每次执行时都会把当前的屏幕触碰信息和上次的触碰信息做对比,检测是单点触碰,还是按住不放,还是多点触碰或多点按住不放等等,具体逻辑在InputHandler->UpdateDetection(DeltaTime);中。
void AStrategyPlayerController::ProcessPlayerInput(const float DeltaTime, const bool bGamePaused) { if (!bGamePaused && PlayerInput && InputHandler && !bIgnoreInput) { InputHandler->UpdateDetection(DeltaTime); } Super::ProcessPlayerInput(DeltaTime, bGamePaused); if (!bIgnoreInput ) { const ULocalPlayer* LocalPlayer = Cast<ULocalPlayer>(Player); AStrategySpectatorPawn* StrategyPawn = GetStrategySpectatorPawn(); if(( StrategyPawn != NULL ) && ( LocalPlayer != NULL )) { // Create the bounds for the minimap so we can add it as a 'no scroll' zone. AStrategyHUD* const HUD = Cast<AStrategyHUD>(GetHUD()); AStrategyGameState const* const MyGameState = GetWorld()->GetGameState<AStrategyGameState>(); if( (MyGameState != NULL ) && ( MyGameState->MiniMapCamera.IsValid() == true ) ) { if( LocalPlayer->ViewportClient != NULL ) { const FIntPoint ViewportSize = LocalPlayer->ViewportClient->Viewport->GetSizeXY(); const uint32 ViewTop = FMath::TruncToInt(LocalPlayer->Origin.Y * ViewportSize.Y); const uint32 ViewBottom = ViewTop + FMath::TruncToInt(LocalPlayer->Size.Y * ViewportSize.Y); FVector TopLeft( HUD->MiniMapMargin, ViewBottom - HUD->MiniMapMargin - MyGameState->MiniMapCamera->MiniMapHeight, 0 ); FVector BottomRight( (int32)MyGameState->MiniMapCamera->MiniMapWidth, MyGameState->MiniMapCamera->MiniMapHeight, 0 ); FBox MiniMapBounds( TopLeft, TopLeft + BottomRight ); StrategyPawn->GetStrategyCameraComponent()->AddNoScrollZone( MiniMapBounds ); StrategyPawn->GetStrategyCameraComponent()->UpdateCameraMovement( this ); } } } } }
StrategyInput.cpp
void UStrategyInput::UpdateDetection(float DeltaTime) { UpdateGameKeys(DeltaTime); ProcessKeyStates(DeltaTime); } void UStrategyInput::UpdateGameKeys(float DeltaTime) { AStrategyPlayerController* MyController = CastChecked<AStrategyPlayerController>(GetOuter()); // gather current states uint32 CurrentTouchState = 0; for (int32 i = 0; i < ARRAY_COUNT(MyController->PlayerInput->Touches); i++) { if (MyController->PlayerInput->Touches[i].Z != 0) { CurrentTouchState |= (1 << i); } } // detection FVector2D LocalPosition1 = FVector2D(MyController->PlayerInput->Touches[0]); FVector2D LocalPosition2 = FVector2D(MyController->PlayerInput->Touches[1]); DetectOnePointActions(CurrentTouchState & 1, PrevTouchState & 1, DeltaTime, LocalPosition1, TouchAnchors[0], Touch0DownTime); DetectTwoPointsActions((CurrentTouchState & 1) && (CurrentTouchState & 2), (PrevTouchState & 1) && (PrevTouchState & 2), DeltaTime, LocalPosition1, LocalPosition2); // save states PrevTouchState = CurrentTouchState; } void UStrategyInput::ProcessKeyStates(float DeltaTime) { for (const FActionBinding1P& AB : ActionBindings1P) { const FSimpleKeyState* KeyState = KeyStateMap.Find(AB.Key); if (KeyState && KeyState->Events[AB.KeyEvent] > 0) { AB.ActionDelegate.ExecuteIfBound(KeyState->Position, KeyState->DownTime); } } for (const FActionBinding2P& AB : ActionBindings2P) { const FSimpleKeyState* KeyState = KeyStateMap.Find(AB.Key); if (KeyState && KeyState->Events[AB.KeyEvent] > 0) { AB.ActionDelegate.ExecuteIfBound(KeyState->Position, KeyState->Position2, KeyState->DownTime); } } // update states for (TMap<EGameKey::Type,FSimpleKeyState>::TIterator It(KeyStateMap); It; ++It) { FSimpleKeyState* const KeyState = &It.Value(); if (KeyState->Events[IE_Pressed]) { KeyState->bDown = true; } else if (KeyState->Events[IE_Released]) { KeyState->bDown = false; } FMemory::Memzero(KeyState->Events, sizeof(KeyState->Events)); } }