欧美一区二区三区老妇人-欧美做爰猛烈大尺度电-99久久夜色精品国产亚洲a-亚洲福利视频一区二区

Unreal 輸入系統(tǒng) 解析

前言

  • 輸入系統(tǒng),輸入某個鍵,響應(yīng)到GamePlay層做對應(yīng)的事。例如 點(diǎn)擊鼠標(biāo),前進(jìn)還是開槍之類,是如何響應(yīng)的。這里只說應(yīng)用層邏輯,硬件層邏輯不講述。

詳解

1.問題來源

先看下面一個例子:跳躍的事件響應(yīng)堆棧

從上述堆棧我們不難發(fā)現(xiàn),疑惑點(diǎn)主要集中于 APlayerController::ProcessPlayerInput 和 UPlayerInput::ProcessInputStack.
(APlayerController::PlayerTick之前的堆棧可以忽略)

創(chuàng)新互聯(lián)電話聯(lián)系:028-86922220,為您提供成都網(wǎng)站建設(shè)網(wǎng)頁設(shè)計(jì)及定制高端網(wǎng)站建設(shè)服務(wù),創(chuàng)新互聯(lián)網(wǎng)頁制作領(lǐng)域10多年,包括塔吊租賃等多個行業(yè)擁有豐富的網(wǎng)站推廣經(jīng)驗(yàn),選擇創(chuàng)新互聯(lián),為企業(yè)錦上添花!

2.簡要分析

先查看 APlayerController::ProcessPlayerInput 源碼

void APlayerController::ProcessPlayerInput(const float DeltaTime, const bool bGamePaused)
{
	static TArray<UInputComponent*> InputStack;

	// must be called non-recursively and on the game thread
	check(IsInGameThread() && !InputStack.Num());

	// process all input components in the stack, top down
	{
		SCOPE_CYCLE_COUNTER(STAT_PC_BuildInputStack);
		BuildInputStack(InputStack);
	}

	// process the desired components
	{
		SCOPE_CYCLE_COUNTER(STAT_PC_ProcessInputStack);
		PlayerInput->ProcessInputStack(InputStack, DeltaTime, bGamePaused);
	}

	InputStack.Reset();
}

查看上述BuildInputStack的源碼也比較簡單,這里不貼了,大概的意思是把當(dāng)前PlayerPawn的InputComponent組件和當(dāng)前地圖的InputComponent和PlayerController棧上的InputComponent組件??傊?,大概意思就是把當(dāng)前世界的所有打開的InputComponent全部獲取。
傳入到PlayerInput處理。
也就是說問題,只要弄明白UPlayerInput::ProcessInputStack即可。

3.UPlayerInput::ProcessInputStack 解析

因?yàn)樵创a過大,為了不影響閱讀,下方給出的均是偽代碼,對于一些次要的的特殊邏輯也拋除了。主要是圍繞一個普通按鍵的邏輯代碼。

I.TArray<TPair<FKey, FKeyState*>> KeysWithEvents;

	ConditionalBuildKeyMappings();
	static TArray<FDelegateDispatchDetails> NonAxisDelegates;
	static TArray<FKey> KeysToConsume;
	static TArray<FDelegateDispatchDetails> FoundChords;
	static TArray<TPair<FKey, FKeyState*>> KeysWithEvents;
	static TArray<TSharedPtr<FInputActionBinding>> PotentialActions;

	// copy data from accumulators to the real values
	for (TMap<FKey,FKeyState>::TIterator It(KeyStateMap); It; ++It)
	{
		bool bKeyHasEvents = false;
		FKeyState* const KeyState = &It.Value();
		const FKey& Key = It.Key();

		for (uint8 EventIndex = 0; EventIndex < IE_MAX; ++EventIndex)
		{
			KeyState->EventCounts[EventIndex].Reset();
			Exchange(KeyState->EventCounts[EventIndex], KeyState->EventAccumulator[EventIndex]);

			if (!bKeyHasEvents && KeyState->EventCounts[EventIndex].Num() > 0)
			{
				KeysWithEvents.Emplace(Key, KeyState);
				bKeyHasEvents = true;
			}
		}
	}

從源碼最上方查看,ConditionalBuildKeyMappings,這個比較簡單,就是檢測是否需要把ProjectSetting->Engine->Input中預(yù)先綁定的值初始化到PlayerInput.
然后主要是根據(jù)KeyStateMap的數(shù)據(jù)轉(zhuǎn)換成KeysWithEvents。KeyStateMap 即會記錄當(dāng)前局內(nèi)按下的鍵位的狀態(tài),KeysWithEvents就是當(dāng)前哪些鍵需要處理。為什么KeyStateMap不是直接的一個Key的結(jié)構(gòu),而是Map,因?yàn)楹竺鏁f到,存在一個鍵按了,后面的按鍵是響應(yīng)還是不響應(yīng),出于滿足這種需求的原因。

II.核心邏輯

下述偽代碼中文是我給出的解釋,英文是源碼注釋。

	int32 StackIndex = InputComponentStack.Num()-1;
	for ( ; StackIndex >= 0; --StackIndex)
	{
		UInputComponent* const IC = InputComponentStack[StackIndex];
		if (IC)
		{
			for (const TPair<FKey,FKeyState*>& KeyWithEvent : KeysWithEvents)
			{
				if (!KeyWithEvent.Value->bConsumed)//被Consume的按鍵,不會被響應(yīng)
				{
					FGetActionsBoundToKey::Get(IC, this, KeyWithEvent.Key, PotentialActions);
					//根據(jù)Key找出當(dāng)前InputComponent中所需要響應(yīng)的事件集合 PotentialActions(就是通過BindAction綁定的那些事件)
				}
			}

			for (const TSharedPtr<FInputActionBinding>& ActionBinding : PotentialActions)
			{
				GetChordsForAction(*ActionBinding.Get(), bGamePaused, FoundChords, KeysToConsume);
				//根據(jù)KeyState 檢測該鍵是否是組合鍵,是否需要按Alt/Ctrl/Shift...,如果達(dá)成組合鍵則返回FoundChords
				//PS:這邊代碼寫的有點(diǎn)爛,寫死的組合鍵判斷
			}

			PotentialActions.Reset();

			for (int32 ChordIndex=0; ChordIndex < FoundChords.Num(); ++ChordIndex)
			{
				const FDelegateDispatchDetails& FoundChord = FoundChords[ChordIndex];
				bool bFireDelegate = true;
				// If this is a paired action (implements both pressed and released) then we ensure that only one chord is
				// handling the pairing
				if (FoundChord.SourceAction && FoundChord.SourceAction->IsPaired())
				{
					FActionKeyDetails& KeyDetails = ActionKeyMap.FindChecked(FoundChord.SourceAction->GetActionName());
					if (!KeyDetails.CapturingChord.Key.IsValid() || KeyDetails.CapturingChord == FoundChord.Chord || !IsPressed(KeyDetails.CapturingChord.Key))
					{
						if (FoundChord.SourceAction->KeyEvent == IE_Pressed)
						{
							KeyDetails.CapturingChord = FoundChord.Chord;
						}
						else
						{
							KeyDetails.CapturingChord.Key = EKeys::Invalid;
						}
					}
					else
					{
						bFireDelegate = false;
					}
				}

				if (bFireDelegate && FoundChords[ChordIndex].ActionDelegate.IsBound())
				{
					FoundChords[ChordIndex].FoundIndex = NonAxisDelegates.Num();
					NonAxisDelegates.Add(FoundChords[ChordIndex]);
				}
			}
			//上述這段,就是判斷是否是成對出現(xiàn)的事件,如果是成對出現(xiàn)的,只會被添加一條進(jìn)NonAxisDelegates.
			if (IC->bBlockInput)
			{
				// stop traversing the stack, all input has been consumed by this InputComponent
				--StackIndex;
				KeysToConsume.Reset();
				FoundChords.Reset();
				break;
			}
			//上述這段,是判斷是否bBlockInput,如果這個為true,則這個之后的InputComponent都會被吃掉,就是不會執(zhí)行。
			
			// we do this after finishing the whole component, so we don't consume a key while there might be more bindings to it
			for (int32 KeyIndex=0; KeyIndex<KeysToConsume.Num(); ++KeyIndex)
			{
				ConsumeKey(KeysToConsume[KeyIndex]);
			}
			//上述這段,最為重要,根據(jù)當(dāng)前InputComponent中的KeysToConsume,對KeyStateMap中的鍵Consume掉,這樣在之后的InputComponent的鍵,可以被吃掉,不會被執(zhí)行。
			KeysToConsume.Reset();
			FoundChords.Reset();
		}
	}

III.補(bǔ)充

正常鼠標(biāo)點(diǎn)擊的Click是直接走Slate的,如果想鼠標(biāo)點(diǎn)擊Slate也走PlayerInput的分發(fā),可在 
FReply FSlateApplication::RoutePointerUpEvent(const FWidgetPath& WidgetsUnderPointer, const FPointerEvent& PointerEvent)
{
	// 略

	if (SlateUser->HasCapture(PointerEvent.GetPointerIndex()))
	{
		// 略

					if (!Event.IsTouchEvent() || (!TempReply.IsEventHandled() && this->bTouchFallbackToMouse))
					{
						TempReply = TargetWidget.Widget->OnMouseButtonUp( TargetWidget.Geometry, Event );

						//++[Add]  [10/27/2022 xxx]
						if (TempReply.IsEventHandled())
						{
							GetGameViewport()->OnMouseButtonUp(TargetWidget.Geometry, Event);
						}
						//--[Add]

					}
	}
					

上述 GetGameViewport()->OnMouseButtonUp(TargetWidget.Geometry, Event); GameViewPort的調(diào)用來模擬PlayerInput的分發(fā)。

總結(jié)


一個PlayerInput在Tick中不斷執(zhí)行,這個PlayerInput中存了一個包含當(dāng)前世界所擁的InputComponent的棧。根據(jù)傳來的當(dāng)前響應(yīng)的鍵,在這個棧中依次進(jìn)行計(jì)算。根據(jù)Consume這個字段來判斷之后的InputComonent中的相同的鍵是否被吃掉。每個InputComponent根據(jù)bBlockInput 這個字段來決定之后的InputComponent所有鍵被吃掉。這個一般應(yīng)用搭配層級,低于這個層級的InputComponent被吃掉。

  • 如果想實(shí)現(xiàn)只在某個UI中響應(yīng)輸入,其他界面,或者PlayerController中的都不響應(yīng),可以使用bBlockInput搭配Priority實(shí)現(xiàn)。也就是對應(yīng)UserWidget中的常見的

缺陷

  • 不能自定義組合鍵。
  • 對同一個Action注冊了多個事件,順序不能自定義。
  • 同一個InputComponent的多個相同的鍵注冊的Action不能被吃掉。
  • Unreal 中 ListenForInputAction 接口,每個UserWidget生成一個新的InputComponent,而玩家的PlayerController用的是一個InputComponent。有些浪費(fèi)。

網(wǎng)站題目:Unreal 輸入系統(tǒng) 解析
網(wǎng)站鏈接:http://chinadenli.net/article22/dsoipjc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站設(shè)計(jì)、移動網(wǎng)站建設(shè)外貿(mào)建站、網(wǎng)站設(shè)計(jì)公司ChatGPT、定制開發(fā)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

手機(jī)網(wǎng)站建設(shè)