跳到主要內容

Unity UGUI Button OnClick 與 Input System 的時間差問題

 

Unity UGUI Button OnClick 與 Input System 的時間差問題

在 Unity 中同時使用 UGUI Button 的 OnClick 事件與 New Input System 監聽同一個點擊動作時,會發現兩者幾乎總是同時觸發,但 Input System 永遠比 UGUI OnClick 早約 0.05 秒(50ms)

真實點擊發生:      12:00:00.000
Input System 觸發:  12:00:00.000  ✅ 幾乎即時
UGUI OnClick 觸發:  12:00:00.050  ⏳ 延遲約 50ms

這不是隨機的誤差,而是架構上的必然結果

兩者的時間點並不一樣

Input System 的執行路徑

Input System 在每幀極早期直接從設備讀取狀態,幾乎與真實輸入同步:

硬體輸入 → Input System polling → callback 觸發

它走的是 事件驅動(event-driven) 架構,輸入狀態改變的當下就觸發,不需要等待任何 UI 系統。

UGUI OnClick 的執行路徑

UGUI 走的是完全不同的路徑,需要經過多個系統的串聯處理:

EventSystem.Update()
  → StandaloneInputModule / InputSystemUIInputModule
    → PointerEventData 建立
      → GraphicRaycaster 射線檢測
        → ExecuteEvents.Execute()
          → Button.OnClick.Invoke()  ← 這裡才觸發

整條鏈都在 EventSystem.Update() 裡執行,屬於普通的 MonoBehaviour.Update 呼叫順序。



有幾個因素疊加造成這個延遲差距固定約 50ms

1. 幀邊界延遲
Input System 在幀的早期觸發,而 EventSystem.Update() 在同幀較晚才執行。即使在同一幀,也存在執行順序差距。

2. PointerEventData 狀態機
UGUI 必須確認完整的 PointerDown → PointerUp → Click 流程發生在同一個 UI 元件上,這個確認本身就跨越了多個處理步驟。

3. GraphicRaycaster 射線檢測
每次 EventSystem.Update() 都要對 UI Canvas 做射線檢測,複雜的 UI 層級會增加這個耗時。

4. 一幀延遲(最少)
在某些設定下,InputSystemUIInputModule 讀取輸入狀態和 Input System callback 觸發之間,存在至少一幀的讀取時間差(約 16ms @ 60fps)。


麻煩點在于 UGUI OnClick 的時間戳不代表真實點擊時間

這是最核心的問題所在:

UGUI OnClick 觸發的時刻,記錄的是「EventSystem 處理到這個事件的時間」,而不是「使用者真正點擊的時間」。因此,比較兩者的觸發時間,等同於在比較兩個不同的時間零點,這個差距無法透過程式邏輯消除。


解決方案

Case 1 用 Input System 記錄真實時間戳

如果你需要精確的點擊時間(例如音遊判定、Combo 系統),應以 Input System 的時間為唯一基準,UGUI OnClick 只負責觸發動作:

private double _realClickTime;

void OnEnable()
{
    _clickAction.performed += ctx =>
    {
        // ctx.time 才是真實的輸入時間
        _realClickTime = ctx.time;
    };
}

// Button 的 OnClick 事件指向這裡
public void OnButtonClick()
{
    // 使用 Input System 記錄的真實時間,而非 Time.time
    ProcessClick(_realClickTime);
}

Case2 不要用 UGUI OnClick,自己做 Raycast

如果不需要 UGUI 的事件系統,可以在 Input System callback 裡自行判斷是否點在目標上:

_clickAction.performed += ctx =>
{
    if (IsPointerOverTargetButton())
    {
        HandleClick(ctx.time);
    }
};

private bool IsPointerOverTargetButton()
{
    var pointerData = new PointerEventData(EventSystem.current)
    {
        position = Mouse.current.position.ReadValue()
    };

    var results = new List<RaycastResult>();
    EventSystem.current.RaycastAll(pointerData, results);

    return results.Any(r => r.gameObject == targetButton.gameObject);
}

Case3. 選一個系統,不要兩個同時監聽

最簡單的做法是只用其中一個系統處理同一個動作,避免時間基準不一致的問題:

// 只用 Input System,Button 的 OnClick 留空
_clickAction.performed += ctx => HandleClick();

// 或者只用 UGUI OnClick,完全不監聽 Input System
// (在 Button Inspector 裡設定即可)

留言

這個網誌中的熱門文章

comfyUI 運行出錯 FATAL: kernel fmha_cutlassF_f32_aligned_64x64_rf_sm80 is for sm80-sm100, but was built for sm37

在公司的圖形機器上運行 ComfyUI + WAN 時出現下面問題   FATAL: kernel fmha_cutlassF_f32_aligned_64x64_rf_sm80 is for sm80-sm100, but was built for sm37 算圖機使用的 torch 版本如下 torch 2.7.1+cu118 torchaudio 2.7.1+cu118 torchsde 0.2.6 torchvision 0.22.1+cu118 檢查 cuda 版本 GeForce RTX 5090 CUDA Version: 13.0 從官網看 RTX 5090 支持 12.0 版本 CUDA 這是因爲本地的 pytorch 版本太舊 到官網查詢版本 https://pytorch.org/get-started/locally/ $ pip3 install torch torchvision --index-url https://download.pytorch.org/whl/cu126 查詢目前版本 $ pip list | findstr torch 使用這個版本后出現下面問題 後面查詢 no kernel image 問題其實還是 pytorch 並不支持 sm_120( Blackwell架構) 查看后發現并沒有支持 sm_120 CUDA architectures : [' sm_37 ', 'sm_50', 'sm_60', 'sm_61', 'sm_70', 'sm_75', 'sm_80', 'sm_86', 'sm_90', 'compute_37'] sm_50, sm_52, sm_53 : Maxwell (GTX 900 系列) sm_60, sm_61, sm_62 : Pascal (GTX 1000, P100) sm_70, sm_72, sm_75 : Volta/Turing (V100, RTX ...
仔細回想最近的作的 stripping level,還是回頭來說說 import dll 這回事吧;因為從事遊戲改版所以很多事情是言不由衷的,某天發現 server 發送給 client 封包是 big5 encode,第一時間覺得為何不用 unicode 呢?但仔細想也許是遊戲剛規劃時 DB 都已經存了 big5 編碼字串可能已經改不動了,想想也是就算了,結果一包到 android 就發生了 GetEncoding error,因為 android 是採用 unicode 編碼的,在網路上找了找終於找到解法,可以參考底下網址的討論串,主要是下載  I18N.dll、I18N.CJK.dll、I18N.West.dll 並丟到 Assets 資料夾下就可以了,當時還不是很了解為什麼只知道把 dll 都丟進去在手機上就可以跑,直到使用 stripping level 後整個又掛了,才認真去 trace, 問題在於勾選 strip asm 包檔後 I18N.CJK 就沒被包到 apk 裡面,而要解決 strip asm(~"~ 沒辦法 strip asm 很吸引我,一定要想辦法克服)採用了 link.xml 的方式,但這樣要怎麼知道弄進哪些 lib 呢?

熟悉 Unity Memory

    打開始跟 app memory 奮戰,摸索了好一陣子,不管是跑在 andorid 還是 Ios,之前做 WebPlayer 的經驗都派不上用場,也好在網路上資源很多,只要不恥繁找就能找到你所想要的,在此分享的主要是 android(ps. ~"~ 手頭上只有兩台 android 設備)。