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