:使用 GlobalTouchSystem 統一處理 UI 與場景交互)
上一篇介紹了InputSystem它負責收集鼠標和觸摸狀態。但觸點按下之后到底命中了哪個 UI、哪個場景物體以及事件是否繼續向下穿透還需要另一套系統處理。MyFramework 使用GlobalTouchSystem統一完成 UI 與場景物體的射線檢測和交互事件分發。項目地址https://github.com/ZHOURUIH/MyFramework一、GlobalTouchSystem 負責什么GlobalTouchSystem的定位是替代傳統 UGUIEventSystem讓框架中的 UI 和場景對象使用同一套交互流程。它主要負責注冊可交互對象 按照顯示層級檢測 UI 按照距離檢測場景物體 處理點擊、按下、懸停和移動 管理射線穿透 支持多個攝像機 限制當前允許操作的對象整體流程為InputSystem 收集觸點 ↓ GlobalTouchSystem 發射射線 ↓ 先檢測 UI ↓ 允許穿透時繼續檢測場景對象 ↓ 向命中的對象發送交互事件二、注冊普通 UI 點擊UI 節點使用myUGUIObject注冊public class UIMain : LayoutScript { protected myUGUIObject mStartButton; public override void assignWindow() { newObject(out mStartButton, StartButton); } public override void onGameState() { base.onGameState(); mStartButton.registeCollider( onStartClick); } protected void onStartClick() { log(點擊開始游戲); } }注冊時框架會查找或創建碰撞體 將節點加入 GlobalTouchSystem 注冊點擊回調 默認阻擋后方射線 開啟節點更新節點不需要掛 Unity 原生的Button組件。需要按鈕的懸停變暗和按下位移效果時可以使用mStartImage.registeColliderImage( onStartClick);其中mStartImage為myUGUIImageSimple。三、監聽更完整的交互狀態除了點擊還可以同時監聽懸停和按下狀態mStartButton.registeCollider( onStartClick, onStartHover, onStartPress); protected void onStartClick() { log(點擊); } protected void onStartHover(bool hover) { log(懸停狀態: hover); } protected void onStartPress(bool press) { log(按下狀態: press); }框架中的ComponentInteractive還支持單擊與雙擊 觸點進入與離開 觸點按下與抬起 觸點移動與靜止 長按 全屏按下與抬起 拖拽懸停與接收例如注冊長按mStartButton.addLongPress( onLongPress, 1.0f); protected void onLongPress() { log(長按1秒); }四、注冊場景中的物體場景物體需要繼承或包裝為MovableObject并指定用于射線檢測的攝像機protected MovableObject mMonster; protected void initMonster() { mMonster.setClickCallback( onMonsterClick); mMonster.setHoverCallback( onMonsterHover); mMonster.setPassRay(false); mGlobalTouchSystem.registeCollider( mMonster, mCameraManager.getMainCamera()); } protected void onMonsterClick() { log(點擊怪物); } protected void onMonsterHover(bool hover) { log(怪物懸停: hover); }場景對象必須能夠獲得Collider。GlobalTouchSystem會根據射線交點與攝像機的距離排序優先通知距離更近的對象。MovableObject.destroy()時會自動從系統中注銷不需要業務層再次清理。五、UI 為什么會優先于場景物體全局射線檢測會先遍歷 UI 攝像機按照攝像機深度排序 ↓ 按照 UI 深度排序 ↓ 檢測命中的 UI 節點只有 UI 允許射線繼續穿透時才會檢測場景物體。因此玩家點擊界面按鈕時不會同時點中按鈕后面的怪物。UI 的前后關系由框架生成的UIDepth決定而不是簡單依賴對象注冊順序。六、控制射線穿透默認注冊點擊節點時不允許穿透mButton.registeCollider( onClick);等價于mButton.registeCollider( onClick, false);需要點擊自己后繼續檢測下方對象mTransparentArea.registeCollider( onAreaClick, true);也可以單獨修改mTransparentArea.setPassRay(true);射線穿透適合純裝飾 UI 不會阻擋場景操作的透明區域 需要同時通知上下層對象的特殊交互普通按鈕、輸入框和滑動區域通常不應該穿透。七、滑動區域中的父子節點滑動列表中子節點只有位于Viewport范圍內時才應該響應點擊。框架可以綁定bindPassOnlyParent(mViewport);它表示子節點必須位于父節點碰撞區域內 才能繼續接收射線檢測LayoutScript.registeScrollRect()已經會自動完成相關注冊registeScrollRect( mScroll, mViewport, mContent);業務層一般不需要再手動處理滑動列表的射線裁剪。八、限制當前可操作對象新手引導經常要求玩家只能點擊指定按鈕其他位置都不響應。可以設置mGlobalTouchSystem.setActiveOnlyObject( mGuideButton);如果目標位于滑動列表或多層父節點中應使用mGlobalTouchSystem .setActiveOnlyObjectWithAllParent( mGuideButton);它會把目標及已經注冊的父節點一起加入允許列表保證父級滑動或遮罩邏輯仍然正常。允許多個對象mGlobalTouchSystem.addActiveOnlyObject( mConfirmButton); mGlobalTouchSystem.addActiveOnlyObject( mCancelButton);解除限制時可以傳入空對象mGlobalTouchSystem.setActiveOnlyObject( null);這類接口適合新手引導 強制確認彈窗 指定區域教學 UGC 編輯器當前工具限制九、查詢當前位置命中的對象需要主動判斷鼠標當前指向誰時可以調用IMouseEventCollect hoverObject mGlobalTouchSystem.getHoverObject( Input.mousePosition);獲取全部命中對象protected HashSetIMouseEventCollect mHoverList new(); protected void checkHover() { mGlobalTouchSystem.getAllHoverObject( mHoverList, Input.mousePosition); }返回列表已經按照 UI 層級或場景距離排列可以用于拖拽目標檢測、編輯器選擇和自定義提示。十、對象生命周期注冊過的對象不應繞過框架直接銷毀。UI 節點通過框架銷毀時會自動執行mGlobalTouchSystem.unregisteCollider( window);MovableObject.destroy()也會自動注銷。需要臨時取消交互但保留對象時可以使用mButton.setHandleInput(false);恢復mButton.setHandleInput(true);這比反復注冊和注銷更適合短時間禁用按鈕。十一、總結MyFramework 的輸入處理分為兩層InputSystem 收集鍵盤、鼠標和觸摸狀態 GlobalTouchSystem 檢測觸點命中的對象并分發事件常用接口包括obj.registeCollider(...); obj.registeColliderImage(...); obj.setPassRay(...); obj.setHandleInput(...); mGlobalTouchSystem.registeCollider( movable, camera); mGlobalTouchSystem.setActiveOnlyObject(...); mGlobalTouchSystem.getHoverObject(...);通過統一管理 UI 深度、場景距離、射線穿透和對象生命周期業務層不需要分別維護 UGUI 點擊、Physics Raycast 和移動端觸摸邏輯。