任意無人機手柄鏈接Unity-100元的鳳凰SM600手柄接入Unity Input System?

網上教程真少!奮發圖強自力更生!2025.5.1

目前有用的鏈接:

unity如何添加自定義HID設備,自己開發的手柄如何支持unity。 - 嗶哩嗶哩

HID Support | Input System | 1.0.2?官方教程

https://zhuanlan.zhihu.com/p/503209742

分步詳解:鳳凰6000模擬器接入Unity Input System?

前提條件:

  1. 安裝 Unity Input System:?確保你的 Unity 項目已經通過 Package Manager 安裝了?Input System?包。如果沒有,請前往?Window -> Package Manager,選擇?Unity Registry,搜索?Input System?并安裝。

  2. 啟用 Input System:?在?Edit -> Project Settings -> Player -> Other Settings?中,找到?Active Input Handling?選項,將其設置為?Input System Package (New)?或者?Both。Unity 會提示重啟編輯器。

步驟一:找到鳳凰SM600手柄的 VID 和 PID

這是識別你設備的“身份證號”。

  1. 連接手柄:?將鳳凰SM600手柄通過 USB 連接到你的 Windows 電腦。

  2. 打開設備管理器:

    • 在 Windows 搜索欄搜索“設備管理器”并打開。

    • 或者右鍵點擊“此電腦” -> “管理” -> “設備管理器”。

  3. 找到你的手柄:?在設備列表中查找,它可能在“人體學輸入設備 (HID)”、"通用串行總線控制器" 下,或者顯示為設備名稱(如 "Phoenix SM600" 或類似名稱)。仔細查找,可能顯示為 "USB 輸入設備" 或 "HID-compliant game controller"。

    • 提示:?如果不確定是哪個設備,可以嘗試拔掉手柄再插上,觀察設備列表的變化。

  4. 查看屬性:?找到設備后,右鍵點擊它,選擇“屬性”。

  5. 查找 VID 和 PID:

    • 切換到“詳細信息”選項卡。

    • 在“屬性”下拉菜單中,選擇“硬件 ID”。

    • 你會看到類似?HID\VID_xxxx&PID_xxxx?或?USB\VID_xxxx&PID_xxxx?的值。這里的?xxxx?就是你需要記下的?Vendor ID (VID)?和?Product ID (PID)。它們通常是 4 位的十六進制數(例如?054C?或?09CC)。?請記下你找到的實際 VID 和 PID 值。


?步驟1:獲取設備VID/PID.

?

?操作流程?

  1. 連接鳳凰6000模擬器到電腦
  2. 打開設備管理器?→ 右鍵設備 → ?屬性 → 詳細信息 → 硬件ID?
  3. 記錄VID_XXXXPID_XXXX(例如:VID_1234&PID_5678

步驟2:?撰寫腳本讓Unity支持設備哦

下面的腳本是自己創建一個Unity 可識別的搖桿!實現檢測硬件,并且被Unity的新輸入系統支持!

using UnityEngine; // 使用Unity引擎的基本功能(比如畫圖、控制游戲)
using UnityEngine.InputSystem; // 使用Unity的輸入系統(控制鍵盤、鼠標、手柄)
using UnityEngine.InputSystem.Layouts; // 定義輸入設備的布局(比如手柄的按鈕和搖桿位置)
using UnityEngine.InputSystem.HID; // 處理USB設備(如飛行器遙控器)
using UnityEngine.InputSystem.Utilities; // 工具類幫助處理輸入
using UnityEngine.InputSystem.LowLevel; // 低級輸入處理(直接讀取設備數據)
using System.Runtime.InteropServices; // 處理不同系統的兼容性(比如Windows和Mac)
using System.Diagnostics; // 調試工具(查看程序運行信息)
#if UNITY_EDITOR // 如果在Unity編輯器中運行
using UnityEditor; // Unity編輯器的工具(如創建菜單)
#endif// 1. 定義設備布局結構體 (簡化版,只映射原始字節)
[StructLayout(LayoutKind.Explicit, Size = 9)] // 報告大小為 9 字節
public struct PhoenixSM600HIDInputReport : IInputStateTypeInfo
{// Report ID (偏移 0)[FieldOffset(0)] public byte reportId;// 將所有 8 個數據字節映射為原始 Byte 控件// 這樣可以在 Input Debugger 中看到每個字節的原始值[InputControl(name = "byte1Raw", layout = "Byte", offset = 1, displayName = "數據字節 1 (右搖桿左右?)")][FieldOffset(1)] public byte byte1_raw;[InputControl(name = "byte2Raw", layout = "Byte", offset = 2, displayName = "數據字節 2 (未知?)")][FieldOffset(2)] public byte byte2_raw;[InputControl(name = "byte3Raw", layout = "Byte", offset = 3, displayName = "數據字節 3 (右搖桿上下?)")][FieldOffset(3)] public byte byte3_raw;[InputControl(name = "byte4Raw", layout = "Byte", offset = 4, displayName = "數據字節 4 (左搖桿上下?)")][FieldOffset(4)] public byte byte4_raw;[InputControl(name = "byte5Raw", layout = "Byte", offset = 5, displayName = "數據字節 5 (左搖桿左右?)")][FieldOffset(5)] public byte byte5_raw;[InputControl(name = "byte6Raw", layout = "Byte", offset = 6, displayName = "數據字節 6 (右上角開關?)")][FieldOffset(6)] public byte byte6_raw;[InputControl(name = "byte7Raw", layout = "Byte", offset = 7, displayName = "數據字節 7 (左上角旋鈕?)")][FieldOffset(7)] public byte byte7_raw;[InputControl(name = "byte8Raw", layout = "Byte", offset = 8, displayName = "數據字節 8 (按鈕?)")][FieldOffset(8)] public byte byte8_raw;// 實現 IInputStateTypeInfo 接口public FourCC format => new FourCC('H', 'I', 'D');
}// 2. 注冊設備布局 (保持不變,確保 VID/PID 正確)
#if UNITY_EDITOR
[InitializeOnLoad]
#endif
// 注意這里可以繼續繼承 Gamepad,即使當前沒有映射所有 Gamepad 控件
[InputControlLayout(stateType = typeof(PhoenixSM600HIDInputReport), displayName = "Phoenix SM600 Drone Controller (Raw)")] // 修改顯示名稱以便區分
public class PhoenixSM600ControllerSupport : Gamepad
{static PhoenixSM600ControllerSupport(){// 使用 VID 和 PID 注冊設備// 確保這里的 VID/PID (0x1781, 0x0898) 是正確的InputSystem.RegisterLayout<PhoenixSM600ControllerSupport>(matches: new InputDeviceMatcher().WithInterface("HID").WithCapability("vendorId", 0x1781).WithCapability("productId", 0x0898));Debug.Log("Phoenix SM600 Controller (Raw) layout registered.");}[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]static void InitializeInPlayer(){// Triggers the static constructor}// 不需要在這里定義屬性或 FinishSetup(),因為只映射了原始字節
}

這個腳本就像一個設備驅動程序!不需要掛載游戲物體上被運行!

這個腳本的作用:?想象一下,你買了一個新的、很特別的玩具遙控器(Phoenix SM600),但你的電腦(Unity)還不認識它。這個腳本就像是給電腦安裝一個“驅動程序”或者“說明書”。它告訴 Unity:“嘿,如果你看到一個 USB 設備,它的身份證號(VID)是?0x1781,型號(PID)是?0x0898,那它就是這個鳳凰遙控器,它發過來的信號(數據)是這樣排列的(就是我們定義的那個?struct?結構)。”

它是如何工作的:

代碼里的?[InitializeOnLoad]?(編輯器里用) 和?[RuntimeInitializeOnLoadMethod]?(游戲運行時用) 這兩個“魔法標記”,會確保 Unity 在啟動時或者游戲開始運行時,自動去執行這個腳本里的注冊代碼(InputSystem.RegisterLayout?那部分)。

這個注冊過程是全局性的,它直接修改了 Unity 輸入系統本身對設備的認知,而不是只針對某一個游戲物體。

它不是什么:?這個腳本不是用來?讀取?遙控器輸入的(比如檢查哪個按鈕被按下了)。它只是負責?讓 Unity 能夠理解?這個遙控器。

誰來讀取輸入:?你需要另外寫一個腳本(或者使用 Unity 的 Input Action Assets),那個腳本才需要掛載到游戲物體上。那個腳本會去問 Unity 的輸入系統:“嘿,鳳凰遙控器的左搖桿現在在哪里?”或者“按鈕 A 按下了嗎?” 因為有了我們這個“說明書”腳本的注冊,輸入系統才能正確地回答那個掛載在物體上的腳本的問題。

總結:

這個腳本是定義和注冊設備布局的,它在后臺自動工作,讓 Unity 認識新設備。

不需要掛載到游戲物體上。你需要另外的腳本(掛載到物體上)來實際使用這個遙控器的輸入。

所以,你只需要把這個 C# 文件放到你的 Unity 項目的?Assets?文件夾(或者任何子文件夾)下,確保沒有編譯錯誤,它就會自動生效了。

不用擔心,沒有被掛載到場景就不被打包出去生效的問題!它不需要掛載也會在打包后生效:
?

這個腳本是用來“教”Unity認識你的手柄的,而不是直接讀取輸入的,所以它不需要掛載。

現在,關于打包成 APK 安裝到 VR 眼鏡(如 Meta Quest、Pico 等獨立 VR 設備)后,如何確保手柄能工作,你的思路是正確的,關鍵在于確保那個“教學”過程在 VR 眼鏡上也能順利進行

以下是確保它工作的關鍵點和步驟:

  1. [RuntimeInitializeOnLoadMethod]?是關鍵:

    • 你的代碼里使用了?[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]?這個標記。這非常重要

    • 它的作用就是告訴 Unity:“當這個游戲(打包后的 APK)在任何設備上啟動時,在加載第一個場景之前,請自動運行?InitializeInPlayer()?這個函數。”

    • 而?InitializeInPlayer()?函數雖然是空的,但它的存在會觸發?PhoenixSM600ControllerSupport?類的靜態構造函數(static PhoenixSM600ControllerSupport())運行。

    • 靜態構造函數里的?InputSystem.RegisterLayout(...)?就是注冊手柄布局的核心代碼。

    • 結論:?只要你的腳本包含在最終的 APK 包里,并且有?[RuntimeInitializeOnLoadMethod],那么每次游戲在 VR 眼鏡上啟動時,注冊過程理論上會自動執行

  2. VR 眼鏡的操作系統 (Android) 需要識別手柄:

    • 大多數現代獨立 VR 眼鏡(如 Quest 2/3, Pico 4)運行的是定制版的 Android 系統。

    • 它們通常支持?USB OTG (On-The-Go),這意味著它們的 USB-C 接口可以作為“主機”來識別和使用外部 USB 設備,比如鍵盤、鼠標、還有游戲手柄

    • 當你的 USB 手柄通過合適的轉接頭(如果需要的話,比如 USB-A 轉 USB-C)插入 VR 眼鏡的 USB 口時,VR 眼鏡的 Android 系統首先需要能夠識別它是一個標準的?HID (Human Interface Device)?游戲手柄。

    • 對于絕大多數標準 USB 游戲手柄,這是自動的,Android 系統有內置的驅動來處理它們。你的 Phoenix SM600 很可能也屬于這一類。

  3. Unity 輸入系統在 Android 上的工作:

    • Unity 的 Input System 包在 Android 平臺上會與 Android 底層的輸入管理系統交互。

    • 當 Android 系統識別出你的手柄后,它會把這個設備信息告訴正在運行的 Unity 應用。

    • 此時,因為你的腳本已經在游戲啟動時通過?RegisterLayout?注冊了你的手柄的特定 VID (Vendor ID - 廠家 ID) 和 PID (Product ID - 產品 ID),Unity 的 Input System 就會檢查新連接的設備列表。

    • 如果它發現一個 HID 設備的 VID 和 PID?完全匹配你在代碼中設置的?0x1781?和?0x0898,它就會應用你定義的?PhoenixSM600ControllerSupport?布局來處理這個手柄的輸入信號。

  4. 需要檢查和確認的事項:

    • VR 眼鏡的 USB OTG 支持:?確認你的目標 VR 眼鏡型號確實支持通過 USB 連接外部游戲手柄。對于主流設備(Quest, Pico)通常是支持的,但最好查一下官方文檔或社區確認。

    • 正確的 VID/PID:?再次確認你在代碼中使用的?0x1781?和?0x0898?絕對準確。差一個數字或字母,匹配就會失敗。你可以在 PC 的設備管理器里查找手柄的硬件 ID 來確認。

    • Input System 包已安裝:?確保你的 Unity 項目中,Input System 包已經通過 Package Manager 正確安裝,并且包含在最終的 Build 設置里。

    • 腳本包含在 Build 中:?確保你的?PhoenixSM600ControllerSupport.cs?文件位于?Assets?文件夾下,并且沒有被設置排除在 Build 之外(通常默認是包含的)。

    • 物理連接:?確保 USB 線纜和任何需要的轉接頭工作正常。

    • 供電:?極少數情況下,如果手柄耗電量很大,VR 眼鏡的 USB 口可能供電不足,但這對于普通手柄不太常見。

  5. 如何測試和調試 (如果遇到問題):

    • 先在 PC 上測試 Build:?打包一個 Windows/Mac 的可執行文件,確認手柄在這個獨立 Build 中能正常工作。這可以排除腳本本身邏輯或 VID/PID 錯誤的問題。

    • 基礎 Android 測試:?嘗試將手柄連接到 VR 眼鏡后,看看 VR 眼鏡本身的系統菜單或者其他支持手柄的應用是否能識別到手柄(即使按鍵映射可能不對)。這能確認基礎的 OTG 和 HID 識別是否正常。

    • Android Logcat:?在 VR 眼鏡上啟用開發者模式和 USB 調試,連接到電腦,使用?adb logcat?命令。啟動你的游戲,并插拔手柄,觀察日志中是否有與 Input System、HID 或你的設備 VID/PID 相關的錯誤或信息。這是查找底層問題的強大工具。

    • Unity Profiler/Input Debugger (遠程連接):?如果可能,嘗試將 Unity 編輯器通過網絡連接到運行在 VR 眼鏡上的游戲 (Build and Run with Profiler Connection)。然后可以在編輯器中使用 Input Debugger 查看設備是否被識別,以及應用了哪個布局。

總結:

你的 C# 腳本設計是正確的,利用?[RuntimeInitializeOnLoadMethod]?可以在 APK 運行時自動注冊布局。只要 VR 眼鏡的 Android 系統能識別你的 USB 手柄作為標準 HID 設備,并且你的 VID/PID 完全正確,那么 Unity 的 Input System 就應該能夠匹配并使用你定義的布局,讓手柄在 VR 游戲中正常工作。關鍵在于確保硬件連接無誤、操作系統支持,以及代碼中的設備標識符準確無誤。

詳細注釋版本:

// -----------------------------------------------------------------------------
// 想象一下,我們在用積木搭一個可以和電腦溝通的玩具遙控器 (Phoenix SM600)
// 這些 'using' 語句就像是告訴電腦:“我們要用這些工具箱里的工具哦!”
// 每個工具箱里都有別人已經寫好的代碼,可以幫我們做一些事情。
// -----------------------------------------------------------------------------// 這個工具箱(UnityEngine)是 Unity 游戲引擎自帶的,有很多基礎功能。
using UnityEngine;
// 這個工具箱(UnityEngine.InputSystem)是專門用來處理玩家輸入(比如按按鈕、動搖桿)的。
using UnityEngine.InputSystem;
// 這個工具箱(UnityEngine.InputSystem.Layouts)幫助我們定義輸入設備(比如遙控器)的樣子和功能。
using UnityEngine.InputSystem.Layouts;
// 這個工具箱(UnityEngine.InputSystem.HID)是專門處理一種叫做 HID 的設備,很多鍵盤、鼠標、游戲手柄都用這種方式和電腦溝通。我們的遙控器也是。
using UnityEngine.InputSystem.HID;
// 這個工具箱(UnityEngine.InputSystem.Utilities)提供了一些方便的小工具。
using UnityEngine.InputSystem.Utilities;
// 這個工具箱(UnityEngine.InputSystem.LowLevel)處理更底層、更接近硬件的輸入信息。
using UnityEngine.InputSystem.LowLevel;
// 這個工具箱(System.Runtime.InteropServices)能幫助我們的 C# 代碼和電腦底層或者其他語言寫的代碼更好地“合作”。這里用它來精確控制數據在內存里的排列方式。
using System.Runtime.InteropServices;
// 這個工具箱(System.Diagnostics)里面有一些工具,比如可以用來在控制臺打印信息,幫助我們檢查代碼有沒有問題。
using System.Diagnostics;// -----------------------------------------------------------------------------
// 下面這幾行是特殊指令,只在 Unity 編輯器(我們制作游戲的地方)里才有用。
// 就好像說:“這段說明書只給正在搭積木的人看,玩積木的人不用看。”
// -----------------------------------------------------------------------------
#if UNITY_EDITOR // 如果我們正在使用 Unity 編輯器...
// 這個工具箱(UnityEditor)包含了很多只在 Unity 編輯器里才能使用的工具。
using UnityEditor;
#endif // ...那么就包含上面那個工具箱,否則就跳過。// -----------------------------------------------------------------------------
// 1. 定義設備布局結構體 (簡化版,只映射原始字節)
//    我們要告訴電腦,遙控器每次發過來的信息(數據包)長什么樣。
//    就像畫一張圖紙,標明這個數據包里每個位置放的是什么信息。
//    我們這里用一個叫做 "struct" 的東西來畫這張圖紙,它就像一個小盒子,專門存放遙控器發來的數據。
// -----------------------------------------------------------------------------// 這個 [StructLayout(...)] 就像是給小盒子(struct)定規矩。
// LayoutKind.Explicit 的意思是:“我們要非常明確地告訴電腦,盒子里每個東西放在哪個精確的位置(偏移量)。”
// Size = 9 的意思是:“這個小盒子總共能裝 9 個字節(byte)那么大的信息。” (一個字節就像一個小小的數字格子,可以放 0 到 255 之間的數字)
[StructLayout(LayoutKind.Explicit, Size = 9)]
// public struct PhoenixSM600HIDInputReport : IInputStateTypeInfo
// public: 表示這個圖紙(結構體)大家都可以看和用。
// struct: 告訴電腦,這是一個“結構體”,一個小數據容器的藍圖。
// PhoenixSM600HIDInputReport: 這是我們給這個小盒子圖紙起的名字,意思是“鳳凰SM600遙控器通過HID方式發送的輸入報告(數據)”。
// : IInputStateTypeInfo: 這表示我們的小盒子圖紙還遵守了一個“協議”(接口),保證它能告訴別人自己里面裝的數據是什么類型的。
public struct PhoenixSM600HIDInputReport : IInputStateTypeInfo
{// -------------------------------------------------------------------------// 在小盒子里(結構體內部)定義每個數據存放的位置和名字// -------------------------------------------------------------------------// Report ID (偏移 0)// [FieldOffset(0)] 告訴電腦:“這個數據要放在小盒子的第 0 個位置(也就是最開始的位置)。”[FieldOffset(0)]// public byte reportId; 定義了一個叫做 reportId 的小格子(byte類型),它是公開的(public)。// 這個 reportId 通常用來表示這份數據報告是關于什么的,但在這個簡單版本里我們可能不會直接用它。public byte reportId;// -------------------------------------------------------------------------// 將所有 8 個數據字節映射為原始 Byte 控件// 遙控器除了第一個字節的 reportId,后面還有 8 個字節是真正包含按鈕、搖桿信息的數據。// 我們現在先把這 8 個字節都當作“原始數字”來看,不做任何轉換。// 這樣可以在 Unity 的調試工具(Input Debugger)里直接看到遙控器發來的最原始的數字是什么。// -------------------------------------------------------------------------// [InputControl(...)] 這個標記很重要!它告訴 Unity 的輸入系統:// “嘿,這個小格子里的數據是一個‘輸入控件’!玩家可以通過它來控制游戲。”// name = "byte1Raw": 給這個控件起一個內部名字叫 "byte1Raw"。// layout = "Byte": 告訴 Unity 這個控件的數據類型就是一個原始的字節(數字 0-255)。// offset = 1: 告訴 Unity 這個數據在小盒子里的第 1 個位置(緊跟在 reportId 后面)。// displayName = "數據字節 1 (右搖桿左右?)": 這是在 Unity 調試工具里顯示給開發者看的名字,方便我們猜這個字節可能是干嘛的(比如控制右邊搖桿的左右移動?)。[InputControl(name = "byte1Raw", layout = "Byte", offset = 1, displayName = "數據字節 1 (右搖桿左右?)")]// [FieldOffset(1)] 再次確認這個數據放在小盒子的第 1 個位置。[FieldOffset(1)]// public byte byte1_raw; 定義一個公開的(public)小格子(byte類型),名字叫 byte1_raw,用來存放第 1 個數據字節的原始值。public byte byte1_raw;// 下面的定義和 byte1_raw 非常類似,只是位置(offset)和名字不一樣,對應遙控器發來的第 2 到第 8 個數據字節。// 告訴 Unity 這是第 2 個數據字節,也是一個原始字節控件。放在小盒子的第 2 個位置。[InputControl(name = "byte2Raw", layout = "Byte", offset = 2, displayName = "數據字節 2 (未知?)")]// 確認放在小盒子的第 2 個位置。[FieldOffset(2)]// 定義存放第 2 個數據字節原始值的小格子。public byte byte2_raw;// 告訴 Unity 這是第 3 個數據字節,也是一個原始字節控件。放在小盒子的第 3 個位置。(可能跟右搖桿上下有關?)[InputControl(name = "byte3Raw", layout = "Byte", offset = 3, displayName = "數據字節 3 (右搖桿上下?)")]// 確認放在小盒子的第 3 個位置。[FieldOffset(3)]// 定義存放第 3 個數據字節原始值的小格子。public byte byte3_raw;// 告訴 Unity 這是第 4 個數據字節,也是一個原始字節控件。放在小盒子的第 4 個位置。(可能跟左搖桿上下有關?)[InputControl(name = "byte4Raw", layout = "Byte", offset = 4, displayName = "數據字節 4 (左搖桿上下?)")]// 確認放在小盒子的第 4 個位置。[FieldOffset(4)]// 定義存放第 4 個數據字節原始值的小格子。public byte byte4_raw;// 告訴 Unity 這是第 5 個數據字節,也是一個原始字節控件。放在小盒子的第 5 個位置。(可能跟左搖桿左右有關?)[InputControl(name = "byte5Raw", layout = "Byte", offset = 5, displayName = "數據字節 5 (左搖桿左右?)")]// 確認放在小盒子的第 5 個位置。[FieldOffset(5)]// 定義存放第 5 個數據字節原始值的小格子。public byte byte5_raw;// 告訴 Unity 這是第 6 個數據字節,也是一個原始字節控件。放在小盒子的第 6 個位置。(可能跟右上角的某個開關有關?)[InputControl(name = "byte6Raw", layout = "Byte", offset = 6, displayName = "數據字節 6 (右上角開關?)")]// 確認放在小盒子的第 6 個位置。[FieldOffset(6)]// 定義存放第 6 個數據字節原始值的小格子。public byte byte6_raw;// 告訴 Unity 這是第 7 個數據字節,也是一個原始字節控件。放在小盒子的第 7 個位置。(可能跟左上角的某個旋鈕有關?)[InputControl(name = "byte7Raw", layout = "Byte", offset = 7, displayName = "數據字節 7 (左上角旋鈕?)")]// 確認放在小盒子的第 7 個位置。[FieldOffset(7)]// 定義存放第 7 個數據字節原始值的小格子。public byte byte7_raw;// 告訴 Unity 這是第 8 個數據字節,也是一個原始字節控件。放在小盒子的第 8 個位置。(可能跟某些按鈕有關?)[InputControl(name = "byte8Raw", layout = "Byte", offset = 8, displayName = "數據字節 8 (按鈕?)")]// 確認放在小盒子的第 8 個位置。[FieldOffset(8)]// 定義存放第 8 個數據字節原始值的小格子。public byte byte8_raw;// -------------------------------------------------------------------------// 實現 IInputStateTypeInfo 接口// 我們之前承諾了要遵守 IInputStateTypeInfo 這個協議,現在要兌現承諾。// 這個協議要求我們提供一個“格式代碼”,告訴別人我們這個小盒子里裝的是什么類型的數據。// -------------------------------------------------------------------------// public FourCC format => new FourCC('H', 'I', 'D');// public: 表示這個信息是公開的。// FourCC format: 定義了一個叫做 format 的屬性,它的類型是 FourCC(一種特殊的四個字母代碼)。// =>: 是一個簡寫方式,表示“這個屬性的值是...”// new FourCC('H', 'I', 'D'): 創建一個新的 FourCC 代碼,由字母 H, I, D 組成。這三個字母合起來就是 "HID",告訴 Unity 輸入系統:“我這個小盒子里裝的是 HID 類型的數據哦!”public FourCC format => new FourCC('H', 'I', 'D');
} // 小盒子(結構體)的定義到這里結束。// -----------------------------------------------------------------------------
// 2. 注冊設備布局 (保持不變,確保 VID/PID 正確)
//    光有數據圖紙(結構體)還不夠,我們還需要告訴 Unity:
//    “當你看到一個長得像‘鳳凰SM600’遙控器的設備插到電腦上時,就用我們剛才畫的那個圖紙來理解它發來的數據。”
//    這個過程叫做“注冊布局”。
// -----------------------------------------------------------------------------#if UNITY_EDITOR // 這段代碼同樣只在 Unity 編輯器里運行
// [InitializeOnLoad] 這個標記告訴 Unity 編輯器:“當你啟動或者重新加載代碼的時候,請自動運行下面這個類(Class)里面一個特殊的靜態構造函數(后面會看到)。”
// 這樣可以確保我們的遙控器布局在編輯器一打開時就被注冊好。
[InitializeOnLoad]
#endif // 結束只在編輯器運行的部分// [InputControlLayout(...)] 這個標記是給下面定義的“類”(Class)貼標簽的。
// stateType = typeof(PhoenixSM600HIDInputReport): 告訴 Unity:“當處理這個設備時,請使用我們上面定義的 PhoenixSM600HIDInputReport 這個小盒子圖紙(結構體)來存放和理解它的狀態數據。”
// displayName = "Phoenix SM600 Drone Controller (Raw)": 這是在 Unity 編輯器的輸入設備列表里顯示的名字,方便我們找到它。(Raw 表示我們看的是原始數據)
[InputControlLayout(stateType = typeof(PhoenixSM600HIDInputReport), displayName = "Phoenix SM600 Drone Controller (Raw)")]
// public class PhoenixSM600ControllerSupport : Gamepad
// public: 表示這個“類”大家都可以用。
// class: 告訴電腦,這是一個“類”,是用來創建對象的藍圖。這個藍圖比“結構體”更復雜,可以包含數據和操作數據的方法。這里它代表了我們的遙控器在 Unity 里的“身份”。
// PhoenixSM600ControllerSupport: 我們給這個遙控器身份藍圖起的名字,意思是“對鳳凰SM600遙控器的支持”。
// : Gamepad: 這表示我們的遙控器“繼承”自 Unity 已知的 Gamepad(游戲手柄)類型。
//           意思是:“雖然我們的遙控器可能有點特殊,但你可以把它當作一種游戲手柄來對待。”
//           這樣做的好處是,即使我們現在只看了原始字節,以后想讓它表現得更像標準手柄(比如有左右搖桿、按鈕)會更容易些。
public class PhoenixSM600ControllerSupport : Gamepad
{// -------------------------------------------------------------------------// 靜態構造函數 (Static Constructor)// 這個函數很特別,它前面有 static 關鍵字,并且名字和類名完全一樣。// 它會在這個類第一次被“需要”的時候自動運行一次,而且只會運行一次。// 因為我們前面用了 [InitializeOnLoad] 標記,所以在編輯器啟動時,這個函數就會被自動運行。// 它的作用就是執行注冊布局的核心代碼。// -------------------------------------------------------------------------static PhoenixSM600ControllerSupport() // 注意這里沒有返回值,名字和類名一樣{// 使用 VID 和 PID 注冊設備// VID (Vendor ID) 就像是廠家的身份證號。// PID (Product ID) 就像是這個廠家生產的這款產品的型號。// 這兩個號碼組合起來,通常能唯一確定一個 USB 設備。// 確保這里的 VID/PID (0x1781, 0x0898) 是你的鳳凰SM600遙控器實際的號碼!如果號碼不對,Unity 就認不出你的遙控器了。// 0x 開頭表示這是一個十六進制數,是電腦喜歡用的一種數字表示方法。// InputSystem.RegisterLayout<PhoenixSM600ControllerSupport>(...)// InputSystem: 我們之前引入的輸入系統工具箱里的主要負責人。// RegisterLayout: 調用它的“注冊布局”功能。// <PhoenixSM600ControllerSupport>: 告訴它我們要注冊的是我們自己定義的這個 PhoenixSM600ControllerSupport 藍圖。InputSystem.RegisterLayout<PhoenixSM600ControllerSupport>(// matches: new InputDeviceMatcher() ...// matches: 參數告訴注冊功能:“只有滿足以下條件的設備,才使用我們這個藍圖。”// new InputDeviceMatcher(): 創建一個“設備匹配器”,用來設定匹配條件。matches: new InputDeviceMatcher()// .WithInterface("HID"): 第一個條件是,設備的接口類型必須是 "HID"。我們的遙控器是 HID 設備。.WithInterface("HID")// .WithCapability("vendorId", 0x1781): 第二個條件是,設備的“能力”(Capability)中,廠家ID(vendorId)必須是 0x1781。.WithCapability("vendorId", 0x1781)// .WithCapability("productId", 0x0898): 第三個條件是,設備的產品ID(productId)必須是 0x0898。.WithCapability("productId", 0x0898)// 只有同時滿足這三個條件的設備,才會被 Unity 識別為 PhoenixSM600ControllerSupport,并使用我們定義的布局來處理數據。); // RegisterLayout 函數調用結束// Debug.Log(...) 是向 Unity 的控制臺窗口打印一條消息。// 這就像是代碼在說:“報告!我已經成功注冊了鳳凰SM600遙控器(原始數據版)的布局!”// 這可以幫助我們確認注冊過程確實運行了。Debug.Log("Phoenix SM600 Controller (Raw) layout registered.");} // 靜態構造函數結束// -------------------------------------------------------------------------// 在游戲運行時初始化// 有時候,在編輯器里注冊好了還不夠,我們還需要確保在真正玩游戲的時候,這個注冊也能生效。// 下面這個方法就是用來做這個的。// -------------------------------------------------------------------------// [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]// 這個標記告訴 Unity:“當游戲開始運行后,在加載第一個游戲場景(畫面)之前,請自動運行下面這個叫做 InitializeInPlayer 的函數。”[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]// static void InitializeInPlayer()// static: 表示這個函數也屬于類本身,而不是某個具體的遙控器對象。// void: 表示這個函數運行完不返回任何結果。// InitializeInPlayer: 函數的名字,意思是“在玩家玩的時候進行初始化”。static void InitializeInPlayer(){// 這個函數里面是空的!但它有一個重要的作用。// // Triggers the static constructor (觸發靜態構造函數)// 僅僅是因為這個帶有 [RuntimeInitializeOnLoadMethod] 標記的函數存在,// Unity 在游戲開始運行時就會“注意到” PhoenixSM600ControllerSupport 這個類,// 如果這個時候靜態構造函數(就是上面那個注冊布局的函數)還沒運行過,// 它就會被自動觸發運行。// 這樣就保證了即使是在發布的游戲里,我們的遙控器布局也能被正確注冊。// 這是一個小技巧,確保靜態構造函數在需要時一定會被執行。}// -------------------------------------------------------------------------// 不需要在這里定義屬性或 FinishSetup(),因為只映射了原始字節// 一般來說,如果我們想讓遙控器表現得像一個標準游戲手柄,// 我們會在這里定義一些屬性,比如 leftStick (左搖桿), rightStick (右搖桿), buttonSouth (南方向按鈕,通常是 A 鍵) 等等。// 并且還會有一個叫做 FinishSetup() 的函數,在里面把從原始字節里解析出來的數據,賦值給這些標準屬性。// 但是,因為我們這個版本非常簡單,我們只關心看到原始的 8 個字節數據是什么,// 所以我們不需要定義這些額外的屬性,也不需要 FinishSetup() 函數來做轉換。// 我們在上面的結構體里用 [InputControl] 定義的 byte1Raw 到 byte8Raw 就是我們目前關心的所有輸入控件了。// -------------------------------------------------------------------------} // 遙控器身份藍圖(類)的定義到這里結束。

步驟3:轉換輸入數據(從搖桿到軸數據)?

這是最關鍵也是最困難的一步,因為你需要知道手柄發送數據的確切格式。?上面代碼中的?PhoenixSM600HIDInputReport?結構體只是一個?完全假設?的例子,你必須用實際的數據結構替換它!

現在我們已經教會了 Unity 如何識別和理解我們的自定義 HID 設備(如 Phoenix SM600 手柄)發送的原始數據,接下來需要設置 Input Actions 并編寫一個腳本來實際讀取這些數據,并將其用于游戲邏輯。

  1. 創建 Input Actions Asset:

    • 在 Unity 項目的?Assets?窗口中,右鍵點擊?Create > Input Actions。

    • 給這個新資源文件命名,例如?CustomControllerActions。

    • 雙擊打開該資源文件,進入 Input Actions 編輯器。

  2. 定義 Action Map 和 Actions:

    • Action Maps:?在左側面板點擊 "+" 號添加一個新的 Action Map,命名為例如?Gameplay。Action Map 用于組織一組相關的操作(比如所有玩家控制的動作)。

    • Actions:?在中間面板為?Gameplay?Action Map 添加 Actions。根據你的需求定義動作。基于你提供的腳本,我們可能需要讀取至少兩個軸的輸入。**重要:**由于我們在第 2 步中只映射了原始字節,我們需要創建 Actions 來讀取這些原始字節值。

      • 點擊 "+" 添加一個 Action,命名為例如?LeftStickVerticalRaw。

        • 設置?Action Type?為?Value。

        • 設置?Control Type?為?Axis?(或者?Integer?如果你只想讀取原始 0-255 值,但?Axis?通常更靈活用于后續處理)。

      • 添加另一個 Action,命名為例如?LeftStickHorizontalRaw。

        • 同樣設置?Action Type?為?Value?和?Control Type?為?Axis。

      • (根據需要添加更多 Actions,例如對應右搖桿的原始字節?RightStickVerticalRaw,?RightStickHorizontalRaw?等)

  3. 綁定 Actions 到自定義設備控件:?這是將抽象動作連接到具體設備輸入的關鍵步驟。

    • 選中?LeftStickVerticalRaw?Action。

    • 在右側的?Properties?面板中,點擊?Path?屬性旁邊的 "+" 號,選擇?Add Binding。

    • 在彈出的綁定窗口 (Listen / Path) 中,展開?HID?或你設備繼承的類型(如?Gamepad)。

    • 找到你的自定義設備布局名稱(在第 3 步中?InputControlLayout?的?displayName?定義的,例如 "Custom USB HID Device (Raw)" 或 "Phoenix SM600 Drone Controller (Raw)")。

    • 展開該設備,找到你在第 2 步中定義的對應搖桿垂直方向的原始字節控件(例如?Data Byte 4?或你在?displayName?里標記的 "左搖桿上下?" 對應的?byte4Raw)。選擇這個原始字節控件

    • 對?LeftStickHorizontalRaw?Action 重復此過程,將其綁定到代表左搖桿水平方向的原始字節控件(例如?Data Byte 5?或?byte5Raw)。

    • (為其他需要讀取的原始字節 Action(如右搖桿)重復綁定過程)

    • 完成后,點擊 Input Actions 編輯器窗口頂部的?Save Asset?按鈕。

  4. 編寫或調整輸入讀取腳本:?現在我們使用一個腳本來引用并讀取這些配置好的 Actions。以下是你提供的腳本的一個修正和解釋版本,假設我們讀取上面定義的?LeftStickVerticalRaw?和?LeftStickHorizontalRaw。

using UnityEngine;
using UnityEngine.InputSystem; // 引入 Input System 命名空間public class LeftStickUpRightValueReader : MonoBehaviour // 腳本名稱
{// 使用 [SerializeField] 在 Inspector 中關聯 Action// 這些引用變量將關聯到 Input Actions Asset 中名為 "left" 和 "right" 的 Action[SerializeField]private InputActionReference leftActionForLeftStickUp; // 用于關聯 Input Actions Asset 中名為 "left" 的 Action[SerializeField]private InputActionReference leftActionForLeftStickRight; // 用于關聯 Input Actions Asset 中名為 "left" 的 Action[SerializeField]private InputActionReference rightActionForLeftStickUp; // 用于關聯 Input Actions Asset 中名為 "right" 的 Action[SerializeField]private InputActionReference rightActionForLeftStickRight; // 用于關聯 Input Actions Asset 中名為 "right" 的 Action// Awake 在腳本對象被加載時調用void Awake(){// 檢查引用是否設置if (leftActionForLeftStickUp == null || leftActionForLeftStickUp.action == null) Debug.LogError("Left Action Reference (for LeftStickUp) not set in LeftStickUpRightValueReader.");if (leftActionForLeftStickRight == null || leftActionForLeftStickRight.action == null) Debug.LogError("Left Action Reference (for LeftStickUp) not set in LeftStickUpRightValueReader.");if (rightActionForLeftStickUp == null || rightActionForLeftStickUp.action == null) Debug.LogError("Left Action Reference (for LeftStickUp) not set in LeftStickUpRightValueReader.");if (rightActionForLeftStickRight == null || rightActionForLeftStickRight.action == null) Debug.LogError("Right Action Reference (for LeftStickRight) not set in LeftStickUpRightValueReader.");}// OnEnable 在對象啟用時調用void OnEnable(){// 啟用關聯的 Actionif (leftActionForLeftStickUp != null && leftActionForLeftStickUp.action != null) leftActionForLeftStickUp.action.Enable();if (leftActionForLeftStickRight != null && leftActionForLeftStickRight.action != null) leftActionForLeftStickRight.action.Enable();if (rightActionForLeftStickUp != null && rightActionForLeftStickUp.action != null) rightActionForLeftStickUp.action.Enable();if (rightActionForLeftStickRight != null && rightActionForLeftStickRight.action != null) rightActionForLeftStickRight.action.Enable();}// OnDisable 在對象禁用時調用void OnDisable(){// 禁用關聯的 Actionif (leftActionForLeftStickUp != null && leftActionForLeftStickUp.action != null) leftActionForLeftStickUp.action.Disable();if (rightActionForLeftStickRight != null && rightActionForLeftStickRight.action != null) rightActionForLeftStickRight.action.Disable();}// Update 每幀調用一次void Update(){// 讀取 Action 的當前值 (Float 類型)// "left" Action 現在對應 leftStick/up 的值 (0 ~ ~1)float leftStickUpValue = 0f;if (leftActionForLeftStickUp != null && leftActionForLeftStickUp.action != null){leftStickUpValue = leftActionForLeftStickUp.action.ReadValue<float>();}float leftStickRightValue = 0f;if (leftActionForLeftStickRight != null && leftActionForLeftStickRight.action != null){leftStickRightValue = leftActionForLeftStickRight.action.ReadValue<float>();}float RightStickUpValue = 0f;if (rightActionForLeftStickUp != null && rightActionForLeftStickUp.action != null){RightStickUpValue = rightActionForLeftStickUp.action.ReadValue<float>();}// "right" Action 現在對應 leftStick/right 的值 (0 ~ ~1)float RightStickRightValue = 0f;if (rightActionForLeftStickRight != null && rightActionForLeftStickRight.action != null){RightStickRightValue = rightActionForLeftStickRight.action.ReadValue<float>();}Debug.Log($"Action \"left\" (左搖桿Up): {RightStickUpValue:F2} | Action \"right\" (左搖桿Right): {leftStickRightValue:F2}" );// --- 每幀輸出這兩個值到 Console ---Debug.Log($"Action \"left\" (左搖桿Up): {leftStickUpValue:F2} | Action \"right\" (左搖桿Right): {RightStickRightValue:F2}");// {值:F2} 用于格式化輸出,保留兩位小數// leftStickUpValue 的范圍是 0 到 ~1:// 0 代表沒有向上推,~1 代表完全向上推。s// leftStickRightValue 的范圍是 0 到 ~1:// 0 代表沒有向右推,~1 代表完全向右推。// 當左搖桿回中時,這兩個值都應該是 0。// 當你推向左上角時,leftStickUpValue 和 leftStickRightValue 都會大于 0。// 在這里你可以根據 leftStickUpValue 和 leftStickRightValue 的值來控制你的無人機行為。// 例如:你可以結合這兩個值來判斷左搖桿的推向方向和力度,用于無人機的水平移動等。}// 注意: 使用 InputActionReference 時,通常不需要手動 Dispose
}

將腳本添加到場景并配置:

  • 在 Unity 場景中創建一個空的游戲對象(GameObject),或者選擇一個你想用來處理輸入的現有對象。

  • 將上面編寫的?CustomDeviceInputReader.cs?腳本拖拽到這個游戲對象的 Inspector 面板上。

  • 你會看到腳本組件上有?Left Stick Vertical Raw Action?和?Left Stick Horizontal Raw Action?兩個字段(以及你可能添加的其他字段)。

  • 點擊每個字段旁邊的圓形圖標,或者直接將你在?CustomControllerActions?資源文件中定義的相應 Action(例如?Gameplay/LeftStickVerticalRaw)拖拽到對應的字段上。

  • 確保這個掛載了腳本的游戲對象在場景中是激活(Active)的。

運行與測試:

  • 連接你的自定義 HID 設備。

  • 運行 Unity 場景。

  • 觀察 Console 窗口的輸出。當你移動手柄的左搖桿時,你應該能看到?Raw Left Stick?的字節值 (0-255) 和?Processed Left Stick?的標準化值 (-1 to +1) 相應地變化。

  • 根據輸出調整?NormalizeByteAxis?函數中的邏輯(特別是中心值 128 和除數 127/128),以確保靜止時軸值接近 0,推到極限時接近 -1 或 +1。


下一步/優化:

  • 直接映射標準控件:?如果你確定了哪些原始字節對應標準的游戲手柄控件(如左搖桿、右搖桿、按鈕),可以回到第 2 步和第 3 步,修改設備布局 (struct?和?class)。使用 Input System 提供的更高級的?InputControl?布局(如?StickControl,?ButtonControl),并在?FinishSetup()?方法中將原始字節數據處理后映射到這些標準控件上。這樣做的好處是,你的 Input Actions 可以直接綁定到標準的?leftStick,?rightStick,?buttonSouth?等路徑,使輸入配置更通用,讀取腳本也更簡單(可以直接?ReadValue<Vector2>()?獲取搖桿值)。但這需要對設備的數據格式有更深入的理解。

  • 處理按鈕:?按鈕通常隱藏在某個字節的特定位(bit)中。需要使用位運算(如?&?按位與)來檢查特定位是否為 1,以判斷按鈕是否按下。同樣可以在設備布局中定義?ButtonControl?并進行映射。


現在,你應該擁有一個完整的流程:從識別未知 HID 設備、定義其數據布局、在 Unity Input System 中注冊它,到最后通過 Input Actions 讀取其(目前是原始的)輸入值并在游戲中使用。

?

    本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
    如若轉載,請注明出處:http://www.pswp.cn/news/903705.shtml
    繁體地址,請注明出處:http://hk.pswp.cn/news/903705.shtml
    英文地址,請注明出處:http://en.pswp.cn/news/903705.shtml

    如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

    相關文章

    2024睿抗CAIP-編程技能賽-本科組(省賽)題解

    藍橋杯拿了個省三&#xff0c;天梯沒進1隊&#xff0c;睿抗是我最后的機會 RC-u4 章魚圖的判斷 題目描述 對于無向圖 G ( V , E ) G(V,E) G(V,E)&#xff0c;我們定義章魚圖為&#xff1a; 有且僅有一個簡單環&#xff08;即沒有重復頂點的環&#xff09;&#xff0c;且所…

    Java 泛型參數問題:‘ResponseData.this‘ cannot be referenced from a static contex

    問題與處理策略 問題描述 Data AllArgsConstructor NoArgsConstructor public class ResponseData<T> {private Integer code;private String msg;private T data;public static final int CODE_SUCCESS 2001;public static final int CODE_FAIL 3001;public static …

    用TCP實現服務器與客戶端的交互

    目錄 一、TCP的特點 二、API介紹 1.ServerSocket 2.Socket 三、實現服務器 四、實現客戶端 五、測試解決bug 1.客戶端發送了數據之后&#xff0c;并沒有響應 2.clientSocket沒有執行close()操作 3.嘗試使用多個客戶端同時連接服務器 六、優化 1.短時間有大量客戶端訪…

    鳥籠效應——AI與思維模型【84】

    一、定義 鳥籠效應思維模型指的是人們在偶然獲得一件原本不需要的物品后,會為了這件物品的配套或使用需求,進而繼續添加更多與之相關但自己原本可能并不需要的東西,仿佛被這個“鳥籠”牽著走,最終陷入一種慣性消費或行為模式的現象。簡單來說,就是人們在心理上會有一種自…

    加密解密記錄

    一、RSA 加密解密 密鑰對生成 1.前端加密解密 &#xff08;1&#xff09;.vue頁面引入 npm install jsencrypt&#xff08;2&#xff09;工具 jsencrypt.js import JSEncrypt from jsencrypt/bin/jsencrypt.min// 密鑰對生成 http://web.chacuo.net/netrsakeypairconst p…

    淺析 MegEngine 對 DTR 的實現與改進

    分享筆者在學習 MegEngine 對 DTR 的實現時的筆記。關于 DTR 可以參考&#xff1a;【翻譯】DTR_ICLR 2021 文章目錄 MegEngine 架構設計MegEngine 的動態圖部分Imperative RuntimeImperative 與 MegDNN / MegBrain 的關系靜態圖運行時管家 —— MegBrain動態圖接口 —— Impera…

    micro-app前端微服務原理解析

    一、核心設計思想 基于 WebComponents 的組件化渲染 micro-app 借鑒 WebComponents 的 CustomElement 和 ShadowDom 特性&#xff0c;將子應用封裝為類似 WebComponent 的自定義標簽&#xff08;如 <micro-app>&#xff09;。通過 ShadowDom 的天然隔離機制&#xff0c;實…

    CMake中強制啟用option定義變量的方法

    在CMake中&#xff0c;若要在另一個CMake文件中強制啟用由option()定義的變量&#xff0c;可使用set(... FORCE)覆蓋緩存變量。具體步驟如下&#xff1a; 使用set命令強制覆蓋緩存&#xff1a; 在需要強制啟用選項的CMake文件中&#xff0c;使用set命令并指定CACHE和FORCE參數。…

    C++漫溯鍵值的長河:map set

    文章目錄 1.關聯式容器2.set2.1 find2.2 lower_bound、upper_bound 3.multiset3.1 count3.2 equal_range 4.map4.1 insert4.2 operate->4.3 operate[ ]4.4 map的應用實踐&#xff1a;隨機鏈表的復制 5.multimap希望讀者們多多三連支持小編會繼續更新你們的鼓勵就是我前進的動…

    汽車用品商城小程序源碼介紹

    基于ThinkPHPFastAdminUniApp開發的汽車用品商城小程序源碼&#xff0c;從技術架構來看&#xff0c;ThinkPHP作為后端框架&#xff0c;提供了穩定且高效的開發基礎&#xff0c;能夠處理復雜的業務邏輯和數據交互。FastAdmin則進一步簡化了后臺管理系統的開發流程&#xff0c;提…

    力扣hot100——114.二叉樹展開為鏈表

    基于 Morris 遍歷思想 將左子樹插到右子樹的位置&#xff0c;將原來的右子樹插到左子樹的最右結點&#xff0c;遍歷右結點重復以上步驟&#xff0c;直至右結點為空。 class Solution { public:void flatten(TreeNode* root) {if(rootnullptr) return;while(root){if(!root-&g…

    JConsole監控centos服務器中的springboot的服務

    場景 在centos服務器中,有一個aa.jar的springboot服務,我想用JConsole監控它的JVM情況,具體怎么實現。 配置 Spring Boot 應用以啟用 JMX 在java應用啟動項進行配置 java -Djava.rmi.server.hostname=服務器IP -Dcom.sun.management.jmxremote=true \ -Dcom.sun.managem…

    39.RocketMQ高性能核心原理與源碼架構剖析

    1. 源碼環境搭建 1.1 主要功能模塊 ? RocketMQ的官方Git倉庫地址&#xff1a;GitHub - apache/rocketmq: Apache RocketMQ is a cloud native messaging and streaming platform, making it simple to build event-driven applications. ? RocketMQ的官方網站上下載指定版…

    施磊老師rpc(一)

    文章目錄 mprpc項目**項目概述**&#xff1a;深入學習到什么**前置學習建議**&#xff1a;核心內容其他技術與工具**項目特點與要求**&#xff1a;**環境準備**&#xff1a; 技術棧集群和分布式理論單機聊天服務器案例分析集群聊天服務器分析分布式系統介紹多個模塊的局限引入分…

    基于LangChain構建最小智能體(Agent)實現指南

    摘要 本文完整解析基于LangChain的極簡Agent實現方案&#xff0c;通過26行代碼構建具備網絡搜索能力的對話系統&#xff0c;涵蓋Agent初始化、工具集成、流式回調等核心技術要點。適用于LLM應用開發者快速入門Agent開發。(參考項目代碼&#xff1a;Minimal Agent) 系統架構設計…

    AWTK:一鍵切換皮膚,打造個性化UI

    想讓你的應用在不同場景下都能完美呈現嗎&#xff1f;皮膚切換功能必不可少&#xff01;本文將介紹AWTK&#xff0c;一款強大的GUI框架&#xff0c;它通過內置資源管理和優化緩存&#xff0c;輕松實現皮膚切換功能。 前言 當今的UI應用中&#xff0c;為了滿足不同使用場景和…

    【Vagrant+VirtualBox創建自動化虛擬環境】Ansible測試Playbook

    文章目錄 Vagrant安裝vagrant安裝 VirtualBox如何使用 Ansible安裝AnsiblePlaybook測試創建hosts文件創建setup.yml文件 Vagrant Vagrant是一個基于Ruby的工具&#xff0c;用于創建和部署虛擬化開發環境。它使用Oracle的開源VirtualBox虛擬化系統&#xff0c;使用 Chef創建自動…

    AI在醫療領域的10大應用:從疾病預測到手術機器人

    AI在醫療領域的10大應用&#xff1a;從疾病預測到手術機器人 系統化學習人工智能網站&#xff08;收藏&#xff09;&#xff1a;https://www.captainbed.cn/flu 文章目錄 AI在醫療領域的10大應用&#xff1a;從疾病預測到手術機器人摘要引言1. 醫學影像診斷&#xff1a;從靜態…

    Win11 配置 Git 綁定 Github 賬號的方法與問題匯總

    目錄 一、創建 Github 項目庫&#xff08;遠程倉庫&#xff09;二、配置安裝好的 Git1. 設置用戶信息2. 查看已配置的信息3. 建立本地倉庫4. Git 的常用命令1&#xff09;git checkout&#xff08;切換&#xff09;2&#xff09;git push&#xff08;上傳&#xff09;3&#xf…

    6.應用層

    6. 應用層 1. 概述 應用層是計算機網絡體系結構的最頂層&#xff0c;是設計和建立計算機網絡的最終目的&#xff0c;也是計算機網絡中發展最快的部分 早期基于文本的應用&#xff08;電子郵件、遠程登錄、文件傳輸、新聞組&#xff09;20世紀90年代將因特網帶入千家萬戶的萬維…