Unreal 輸入系統 解析

来源:https://www.cnblogs.com/u3ddjw/archive/2022/05/03/16149880.html
-Advertisement-
Play Games

前言 輸入系統,輸入某個鍵,響應到GamePlay層做對應的事。例如 點擊滑鼠,前進還是開槍之類,是如何響應的。這裡只說應用層邏輯,硬體層邏輯不講述。 詳解 1.問題來源 先看下麵一個例子:跳躍的事件響應堆棧 從上述堆棧我們不難發現,疑惑點主要集中於 APlayerControllerProcess ...


前言

  • 輸入系統,輸入某個鍵,響應到GamePlay層做對應的事。例如 點擊滑鼠,前進還是開槍之類,是如何響應的。這裡只說應用層邏輯,硬體層邏輯不講述。

詳解

1.問題來源

先看下麵一個例子:跳躍的事件響應堆棧
節點
從上述堆棧我們不難發現,疑惑點主要集中於 APlayerController::ProcessPlayerInput 和 UPlayerInput::ProcessInputStack.
(APlayerController::PlayerTick之前的堆棧可以忽略)

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的源碼也比較簡單,這裡不貼了,大概的意思是把當前PlayerPawn的InputComponent組件和當前地圖的InputComponent和PlayerController棧上的InputComponent組件。總之,大概意思就是把當前世界的所有打開的InputComponent全部獲取。
傳入到PlayerInput處理。
也就是說問題,只要弄明白UPlayerInput::ProcessInputStack即可。

3.UPlayerInput::ProcessInputStack 解析

因為源碼過大,為了不影響閱讀,下方給出的均是偽代碼,對於一些次要的的特殊邏輯也拋除了。主要是圍繞一個普通按鍵的邏輯代碼。

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中預先綁定的值初始化到PlayerInput.
然後主要是根據KeyStateMap的數據轉換成KeysWithEvents。KeyStateMap 即會記錄當前局內按下的鍵位的狀態,KeysWithEvents就是當前哪些鍵需要處理。為什麼KeyStateMap不是直接的一個Key的結構,而是Map,因為後面會說到,存在一個鍵按了,後面的按鍵是響應還是不響應,出於滿足這種需求的原因。

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的按鍵,不會被響應
				{
					FGetActionsBoundToKey::Get(IC, this, KeyWithEvent.Key, PotentialActions);
					//根據Key找出當前InputComponent中所需要響應的事件集合 PotentialActions(就是通過BindAction綁定的那些事件)
				}
			}

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

			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]);
				}
			}
			//上述這段,就是判斷是否是成對出現的事件,如果是成對出現的,只會被添加一條進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都會被吃掉,就是不會執行。
			
			// 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]);
			}
			//上述這段,最為重要,根據當前InputComponent中的KeysToConsume,對KeyStateMap中的鍵Consume掉,這樣在之後的InputComponent的鍵,可以被吃掉,不會被執行。
			KeysToConsume.Reset();
			FoundChords.Reset();
		}
	}

總結

節點
一個PlayerInput在Tick中不斷執行,這個PlayerInput中存了一個包含當前世界所擁的InputComponent的棧。根據傳來的當前響應的鍵,在這個棧中依次進行計算。根據Consume這個欄位來判斷之後的InputComonent中的相同的鍵是否被吃掉。每個InputComponent根據bBlockInput 這個欄位來決定之後的InputComponent所有鍵被吃掉。這個一般應用搭配層級,低於這個層級的InputComponent被吃掉。

  • 如果想實現只在某個UI中響應輸入,其他界面,或者PlayerController中的都不響應,可以使用bBlockInput搭配Priority實現。也就是對應UserWidget中的常見的
    節點

缺陷

  • 不能自定義組合鍵。
  • 對同一個Action註冊了多個事件,順序不能自定義。
  • 同一個InputComponent的多個相同的鍵註冊的Action不能被吃掉。
  • Unreal 中 ListenForInputAction 介面,每個UserWidget生成一個新的InputComponent,而玩家的PlayerController用的是一個InputComponent。有些浪費。
學以致用,不致用,何學?
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.1 MySQL安裝 1.1.1 下載wget命令 yum -y install wget 1.1.2 線上下載mysql安裝包 wget https://dev.mysql.com/get/mysql57-community-release-el7-8.noarch.rpm 1.1.3 安裝My ...
  • MySQL伺服器上 存儲引擎 負責對錶中數據的讀取和寫入工作,不同存儲引擎中 存放的格式 一般是不同的,甚至有的存儲引擎(Memory)不用磁碟來存儲數據。 頁 (Page) 是磁碟和記憶體之間交互的基本單位 ,也就是說資料庫管理存儲空間的基本單位是頁,資料庫I/O操作的最小單位是頁 (InnoDB頁 ...
  • **導讀:**聯友科技是一家旨在提供在汽車行業全價值鏈解決方案的科技公司。公司以數字化、智能零部件以及智能網聯為三大核心業務領域,涵蓋研發/製造/營銷等領域的信息化產品、系統運行維護服務、雲服務、大數據分析服務、智能網聯及數字化運營服務、車載智能部件及汽車設計等業務。本次分享會圍繞以下四點展開: 車 ...
  • 1. 啟動並下載一個clickhouse-server, By default, starting above server instance will be run as default user without password. docker run -d --name ch-server - ...
  • 前言 有段時間沒寫技術文章了,一是因為工作太忙,再者因為本人文筆實在一般。最近終於閑下來,本著分享的目的將一些組件設計上的心得與大家分享。 本篇文章是基於原有一篇關於支付文章的進一步優化設計,所以在閱讀本篇文章前還是建議先移步到那篇文章。 文章地址: 微信、支付寶、銀聯、Paypal 支付組件封裝 ...
  • js中關於原型和原型鏈有 __proto__ 、prototype、constructor 頻頻出現在面試題中,但是記得多了反而容易記混。 這裡簡單總結下每個屬性的使用場景,方便記憶。 對象和函數都有 __proto__,對象的 __proto__指向構造函數的prototype,構造函數的__pr ...
  • 今天是vue基礎、vue核心內容第三天,也是最後一天,後面開始進入組件化學習,整個基礎內容以生命周期的結束而結束,不得不說,張天禹把這節課講活了,開始覺得vue是一個有生命的東西,包括前面所說的很多臟活累活都給他做,我們只管調用,說的我都於心不忍如此對待vue了,所以思來想去,我絕對對待它最好的辦法 ...
  • https://www.cnblogs.com/yeungchie/ PyQt5 from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * PySide2 from PySide2.QtWid ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...