使用 Unity3D 的 IPointerDownHandler 方法,但使用“整个屏幕”

Posted

技术标签:

【中文标题】使用 Unity3D 的 IPointerDownHandler 方法,但使用“整个屏幕”【英文标题】:Using Unity3D's IPointerDownHandler approach, but with "the whole screen" 【发布时间】:2016-10-29 21:07:04 【问题描述】:

在 Unity 中,您需要检测场景中某物上的手指触摸(手指绘制)。

在现代 Unity 中做到这一点的唯一方法非常简单


第 1 步。在那个物体上放一个对撞机。 (“地面”或任何可能的东西。)1

第 2 步。在相机的 Inspector 面板上,单击以添加 Physics Raycaster(相关的 2D 或 3D)。

第 3 步。 只需使用下面示例 A 中的代码即可。

(提示 - 不要忘记确保有一个 EventSystem ... Unity 有时会自动添加,有时不会!)


太棒了,再简单不过了。 Unity 最终通过 UI 层正确处理 un/propagation。在桌面、设备、编辑器等上统一且完美地工作。万岁 Unity。

一切都好。但是,如果您只想“在屏幕上” 绘制呢?

因此,很简单,您希望用户“在屏幕上”进行滑动/触摸/绘图。 (例如,仅用于操作轨道摄像机。)就像在任何普通的 3D 游戏中一样,摄像机四处移动。

您不希望手指在世界空间中某个对象上的位置,您只需要抽象的“手指运动”(即玻璃上的位置)。

然后你用什么对撞机?你能在没有对撞机的情况下做到这一点吗?仅仅因为这个原因添加一个对撞机似乎很愚蠢。

我们做的是这样的

我只是制作了一个平面对撞机,并将其实际安装在相机下方。所以它只是简单地位于相机截头体中并完全覆盖屏幕。

(对于代码,则无需使用 ScreenToWorldPoint,因此只需使用示例 B 中的代码 - 非常简单,完美运行。)

我的问题,不得不使用我描述的“相机下的 colldier”似乎有点奇怪,只是为了触摸玻璃。

这是怎么回事?

(注意 - 请不要回答涉及 Unity 古老的“Touches”系统,该系统现在无法用于实际项目,您不能使用旧方法忽略 .UI。)


代码示例 A - 在场景对象上绘图。使用 ScreenToWorldPoint。

 using UnityEngine;
 using System.Collections;
 using UnityEngine.EventSystems;

 public class FingerMove:MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler
 
     public void OnPointerDown (PointerEventData data)
     
         Debug.Log("FINGER DOWN");
         prevPointWorldSpace =
                 theCam.ScreenToWorldPoint( data.position );
     

     public void OnDrag (PointerEventData data)
     
         thisPointWorldSpace =
                theCam.ScreenToWorldPoint( data.position );
         realWorldTravel =
                thisPointWorldSpace - prevPointWorldSpace;
         _processRealWorldtravel();
         prevPointWorldSpace = thisPointWorldSpace;
     

     public void OnPointerUp (PointerEventData data)
     
         Debug.Log("clear finger...");
     

代码示例 B ...您只关心用户在设备的玻璃屏幕上所做的事情。在这里更容易:

 using UnityEngine;
 using System.Collections;
 using UnityEngine.EventSystems;

 public class FingerMove:MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler
 
     private Vector2 prevPoint;
     private Vector2 newPoint;
     private Vector2 screenTravel;

     public void OnPointerDown (PointerEventData data)
     
         Debug.Log("FINGER DOWN");
         prevPoint = data.position;
     

     public void OnDrag (PointerEventData data)
     
         newPoint = data.position;
         screenTravel = newPoint - prevPoint;
         prevPoint = newPoint;
         _processSwipe();
     

     public void OnPointerUp (PointerEventData data)
     
         Debug.Log("FINEGR UP...");
     

     private void _processSwipe()
     
         // your code here
         Debug.Log("screenTravel left-right.. " + screenTravel.x.ToString("f2"));
     
 

1 如果您是 Unity 新手:很可能在这一步中,将其设为一个名为 say "Draw" 的层;在物理设置中,让“绘图”与任何东西没有交互;在第二步中,使用 Raycaster 将图层设置为“Draw”。

【问题讨论】:

因此,在您的显式用例中,您希望利用整个屏幕并避免 UI 交互,而不使用假碰撞器。我做对了吗? 为什么 UI 面板或 Input.touches 会出错?也许解释你为什么不想要这些会有所帮助。 嗨 Ev,input.touches 完全没用,它不能正确地通过 UI 取消/传播。 (Unity 的整个基本和最著名的问题是他们完全忘记阻止触摸事件通过 Unity.UI 传播。这导致了数十亿工时的编程试图解决这个问题,嘿example of zillions of QA from that era。Unity,最后,使用 PhysicsRaycaster 解决了这个问题,我们现在都将它用作 Unity 的最基本元素。)关于 ... 我了解使用Input API 进行光线投射的问题。这里的解决方案似乎是Input API 或将其Image 颜色 alpha 设置为 0 的 UI 面板。为了确保没有缺点,ImageSource Image 插槽应更改为 None 所以我们基本上想要的是获得对 EventSystem 发送的事件的原始访问,而不需要对任何 GameObjects 进行特定的附加(目前是这样)。在我看来,我们的选项 atm 要么使用 UI 面板 hack,要么扩展 EventSystem 或 Input Module。请告诉我是否有人想出了一个理智的方法。 【参考方案1】:

首先,您需要了解只有3 方法可以使用OnPointerDown 函数检测对对象的点击:

1。您需要一个 UI 组件才能使用 OnPointerDown 函数检测点击。这适用于其他类似的 UI 事件。

2.另一种使用OnPointerDown 函数检测2D/Sprite GameObject 上的点击的方法是将Physics2DRaycaster 附加到Camera,然后在它被调用时调用OnPointerDown点击。请注意,2D Collider 必须附加到它。

3。如果这是一个带有 Collider 而不是 2D Collider 的 3D 对象,您必须将 PhysicsRaycaster 附加到相机上才能使用 OnPointerDown 功能被调用。

用第一种方法这样做似乎更合理,而不是让一个大型碰撞器或 2D 碰撞器覆盖屏幕。你要做的就是创建一个Canvas,Panel GameObject,并将横跨整个屏幕的Image组件附加到它上面。

伙计,我只是不认为使用 .UI 作为一个严肃的解决方案:想象我们是 做一个大型游戏,你正在领导一个负责所有 UI 的团队(我 意思是按钮,菜单和所有)。我正在带领一个团队步行 机器人。我突然对你说“哦,顺便说一句,我不能触摸 (“!”),你可以放入一个 UI.Panel,别忘了把它放在下面 你所做的一切,哦,把一个放在任何/所有画布上,或者 您在它们之间交换的相机 - 并将该信息传回给我,OK!” :) 我 意思是这很愚蠢。本质上不能说:“哦,Unity 没有 手柄触摸”

不像你描述的那样很难。您可以编写一个长代码,该代码将能够创建CanvasPanelImage。将图像 alpha 更改为 0。您所要做的就是将该代码附加到相机或空游戏对象,它会在播放模式下自动为您执行所有这些操作。

让每个想要在屏幕上接收事件的游戏对象都订阅它,然后使用ExecuteEvents.Execute 将事件发送到附加到该游戏对象的脚本中的所有接口。

例如,下面的示例代码将发送OnPointerDown事件到名为target的GameObject。

ExecuteEvents.Execute<IPointerDownHandler>(target,
                              eventData,
                              ExecuteEvents.pointerDownHandler);

您将遇到的问题

隐藏的Image 组件将阻止其他 UI 或 GameObject 接收光线投射。这是这里最大的问题。

解决方案

由于会造成一些阻塞问题,所以最好将Image的Canvas设置在一切之上。这将确保它现在是 100 阻止所有其他 UI/游戏对象。 Canvas.sortingOrder = 12; 应该可以帮助我们做到这一点。

每次我们从图像中检测到诸如OnPointerDown 之类的事件时,我们都会手动OnPointerDown 事件重新发送到Image 下的所有其他UI/GameObjects。

首先,我们使用GraphicRaycaster(UI)、Physics2DRaycaster(2D 对撞机)、PhysicsRaycaster(3D 对撞机) 投射光线投射并将结果存储在List

现在,我们遍历 List 中的结果,并通过向结果发送人工事件来重新发送我们收到的事件:

ExecuteEvents.Execute<IPointerDownHandler>(currentListLoop,
                              eventData,
                              ExecuteEvents.pointerDownHandler);

您会遇到的其他问题

您将无法使用GraphicRaycasterToggle 组件上发送模拟事件。这是 Unity 中的 bug。我花了 2 天时间才意识到这一点。

也无法向Slider 组件发送假滑块移动事件。我不知道这是否是一个错误。

除了上面提到的这些问题,我能够实现这一点。它分为 3 部分。只需创建一个文件夹并将所有脚本放入其中即可。

脚本

1.WholeScreenPointer.cs - 创建Canvas、GameObject 和隐藏Image 的脚本的主要部分。它做了所有复杂的事情来确保Image 总是覆盖屏幕。它还会向所有订阅的 GameObject 发送事件。

public class WholeScreenPointer : MonoBehaviour

    //////////////////////////////// SINGLETON BEGIN  ////////////////////////////////
    private static WholeScreenPointer localInstance;

    public static WholeScreenPointer Instance  get  return localInstance;  
    public EventUnBlocker eventRouter;

    private void Awake()
    
        if (localInstance != null && localInstance != this)
        
            Destroy(this.gameObject);
        
        else
        
            localInstance = this;
        
    
    //////////////////////////////// SINGLETON END  ////////////////////////////////


    //////////////////////////////// SETTINGS BEGIN  ////////////////////////////////
    public bool simulateUIEvent = true;
    public bool simulateColliderEvent = true;
    public bool simulateCollider2DEvent = true;

    public bool hideWholeScreenInTheEditor = false;
    //////////////////////////////// SETTINGS END  ////////////////////////////////


    private GameObject hiddenCanvas;

    private List<GameObject> registeredGameobjects = new List<GameObject>();

    //////////////////////////////// USEFUL FUNCTIONS BEGIN  ////////////////////////////////
    public void registerGameObject(GameObject objToRegister)
    
        if (!isRegistered(objToRegister))
        
            registeredGameobjects.Add(objToRegister);
        
    

    public void unRegisterGameObject(GameObject objToRegister)
    
        if (isRegistered(objToRegister))
        
            registeredGameobjects.Remove(objToRegister);
        
    

    public bool isRegistered(GameObject objToRegister)
    
        return registeredGameobjects.Contains(objToRegister);
    

    public void enablewholeScreenPointer(bool enable)
    
        hiddenCanvas.SetActive(enable);
    
    //////////////////////////////// USEFUL FUNCTIONS END  ////////////////////////////////

    // Use this for initialization
    private void Start()
    
        makeAndConfigWholeScreenPinter(hideWholeScreenInTheEditor);
    

    private void makeAndConfigWholeScreenPinter(bool hide = true)
    
        //Create and Add Canvas Component
        createCanvas(hide);

        //Add Rect Transform Component
        //addRectTransform();

        //Add Canvas Scaler Component
        addCanvasScaler();

        //Add Graphics Raycaster Component
        addGraphicsRaycaster();

        //Create Hidden Panel GameObject
        GameObject panel = createHiddenPanel(hide);

        //Make the Image to be positioned in the middle of the screen then fix its anchor
        stretchImageAndConfigAnchor(panel);

        //Add EventForwarder script
        addEventForwarder(panel);

        //Add EventUnBlocker
        addEventRouter(panel);

        //Add EventSystem and Input Module
        addEventSystemAndInputModule();
    

    //Creates Hidden GameObject and attaches Canvas component to it
    private void createCanvas(bool hide)
    
        //Create Canvas GameObject
        hiddenCanvas = new GameObject("___HiddenCanvas");
        if (hide)
        
            hiddenCanvas.hideFlags = HideFlags.HideAndDontSave;
        

        //Create and Add Canvas Component
        Canvas cnvs = hiddenCanvas.AddComponent<Canvas>();
        cnvs.renderMode = RenderMode.ScreenSpaceOverlay;
        cnvs.pixelPerfect = false;

        //Set Cavas sorting order to be above other Canvas sorting order
        cnvs.sortingOrder = 12;

        cnvs.targetDisplay = 0;

        //Make it child of the GameObject this script is attached to
        hiddenCanvas.transform.SetParent(gameObject.transform);
    

    private void addRectTransform()
    
        RectTransform rctrfm = hiddenCanvas.AddComponent<RectTransform>();
    

    //Adds CanvasScaler component to the Canvas GameObject 
    private void addCanvasScaler()
    
        CanvasScaler cvsl = hiddenCanvas.AddComponent<CanvasScaler>();
        cvsl.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
        cvsl.referenceResolution = new Vector2(800f, 600f);
        cvsl.matchWidthOrHeight = 0.5f;
        cvsl.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;
        cvsl.referencePixelsPerUnit = 100f;
    

    //Adds GraphicRaycaster component to the Canvas GameObject 
    private void addGraphicsRaycaster()
    
        GraphicRaycaster grcter = hiddenCanvas.AddComponent<GraphicRaycaster>();
        grcter.ignoreReversedGraphics = true;
        grcter.blockingObjects = GraphicRaycaster.BlockingObjects.None;
    

    //Creates Hidden Panel and attaches Image component to it
    private GameObject createHiddenPanel(bool hide)
    
        //Create Hidden Panel GameObject
        GameObject hiddenPanel = new GameObject("___HiddenPanel");
        if (hide)
        
            hiddenPanel.hideFlags = HideFlags.HideAndDontSave;
        

        //Add Image Component to the hidden panel
        Image pnlImg = hiddenPanel.AddComponent<Image>();
        pnlImg.sprite = null;
        pnlImg.color = new Color(1, 1, 1, 0); //Invisible
        pnlImg.material = null;
        pnlImg.raycastTarget = true;

        //Make it child of HiddenCanvas GameObject
        hiddenPanel.transform.SetParent(hiddenCanvas.transform);
        return hiddenPanel;
    

    //Set Canvas width and height,to matach screen width and height then set anchor points to the corner of canvas.
    private void stretchImageAndConfigAnchor(GameObject panel)
    
        Image pnlImg = panel.GetComponent<Image>();

        //Reset postion to middle of the screen
        pnlImg.rectTransform.anchoredPosition3D = new Vector3(0, 0, 0);

        //Stretch the Image so that the whole screen is totally covered
        pnlImg.rectTransform.anchorMin = new Vector2(0, 0);
        pnlImg.rectTransform.anchorMax = new Vector2(1, 1);
        pnlImg.rectTransform.pivot = new Vector2(0.5f, 0.5f);
    

    //Adds EventForwarder script to the Hidden Panel GameObject 
    private void addEventForwarder(GameObject panel)
    
        EventForwarder evnfwdr = panel.AddComponent<EventForwarder>();
    

    //Adds EventUnBlocker script to the Hidden Panel GameObject 
    private void addEventRouter(GameObject panel)
    
        EventUnBlocker evtrtr = panel.AddComponent<EventUnBlocker>();
        eventRouter = evtrtr;
    

    //Add EventSystem
    private void addEventSystemAndInputModule()
    
        //Check if EventSystem exist. If it does not create and add it
        EventSystem eventSys = FindObjectOfType<EventSystem>();
        if (eventSys == null)
        
            GameObject evObj = new GameObject("EventSystem");
            EventSystem evs = evObj.AddComponent<EventSystem>();
            evs.firstSelectedGameObject = null;
            evs.sendNavigationEvents = true;
            evs.pixelDragThreshold = 5;
            eventSys = evs;
        

        //Check if StandaloneInputModule exist. If it does not create and add it
        StandaloneInputModule sdlIpModl = FindObjectOfType<StandaloneInputModule>();
        if (sdlIpModl == null)
        
            sdlIpModl = eventSys.gameObject.AddComponent<StandaloneInputModule>();
            sdlIpModl.horizontalAxis = "Horizontal";
            sdlIpModl.verticalAxis = "Vertical";
            sdlIpModl.submitButton = "Submit";
            sdlIpModl.cancelButton = "Cancel";
            sdlIpModl.inputActionsPerSecond = 10f;
            sdlIpModl.repeatDelay = 0.5f;
            sdlIpModl.forceModuleActive = false;
        
    

    /*
     Forwards Handler Event to every GameObject that implements  IDragHandler, IPointerDownHandler, IPointerUpHandler interface
     */

    public void forwardDragEvent(PointerEventData eventData)
    
        //Route and send the event to UI and Colliders
        for (int i = 0; i < registeredGameobjects.Count; i++)
        
            ExecuteEvents.Execute<IDragHandler>(registeredGameobjects[i],
                                    eventData,
                                    ExecuteEvents.dragHandler);
        

        //Route and send the event to UI and Colliders
        eventRouter.routeDragEvent(eventData);
    

    public void forwardPointerDownEvent(PointerEventData eventData)
    
        //Send the event to all subscribed scripts
        for (int i = 0; i < registeredGameobjects.Count; i++)
        
            ExecuteEvents.Execute<IPointerDownHandler>(registeredGameobjects[i],
                              eventData,
                              ExecuteEvents.pointerDownHandler);
        

        //Route and send the event to UI and Colliders
        eventRouter.routePointerDownEvent(eventData);
    

    public void forwardPointerUpEvent(PointerEventData eventData)
    
        //Send the event to all subscribed scripts
        for (int i = 0; i < registeredGameobjects.Count; i++)
        
            ExecuteEvents.Execute<IPointerUpHandler>(registeredGameobjects[i],
                    eventData,
                    ExecuteEvents.pointerUpHandler);
        

        //Route and send the event to UI and Colliders
        eventRouter.routePointerUpEvent(eventData);
    

2.EventForwarder.cs - 它只是从隐藏的Image 接收任何事件并将其传递给WholeScreenPointer.cs 脚本进行处理。

public class EventForwarder : MonoBehaviour, IDragHandler, IPointerDownHandler, IPointerUpHandler

    WholeScreenPointer wcp = null;
    void Start()
    
        wcp = WholeScreenPointer.Instance;
    

    public void OnDrag(PointerEventData eventData)
    
        wcp.forwardDragEvent(eventData);
    

    public void OnPointerDown(PointerEventData eventData)
    
        wcp.forwardPointerDownEvent(eventData);
    

    public void OnPointerUp(PointerEventData eventData)
    
        wcp.forwardPointerUpEvent(eventData);
    

3.EventUnBlocker.cs - 它通过向其上方的任何对象发送虚假事件来解除隐藏的Image 所阻挡的光线。无论是 UI、2D 还是 3D 碰撞器。

public class EventUnBlocker : MonoBehaviour

    List<GraphicRaycaster> grRayCast = new List<GraphicRaycaster>(); //UI
    List<Physics2DRaycaster> phy2dRayCast = new List<Physics2DRaycaster>(); //Collider 2D (Sprite Renderer)
    List<PhysicsRaycaster> phyRayCast = new List<PhysicsRaycaster>(); //Normal Collider(3D/Mesh Renderer)

    List<RaycastResult> resultList = new List<RaycastResult>();

    //For Detecting button click and sending fake Button Click to UI Buttons
    Dictionary<int, GameObject> pointerIdToGameObject = new Dictionary<int, GameObject>();

    // Use this for initialization
    void Start()
    

    

    public void sendArtificialUIEvent(Component grRayCast, PointerEventData eventData, PointerEventType evType)
    
        //Route to all Object in the RaycastResult
        for (int i = 0; i < resultList.Count; i++)
        
            /*Do something if it is NOT this GameObject. 
             We don't want any other detection on this GameObject
             */

            if (resultList[i].gameObject != this.gameObject)
            
                //Check if this is UI
                if (grRayCast is GraphicRaycaster)
                
                    //Debug.Log("UI");
                    routeEvent(resultList[i], eventData, evType, true);
                

                //Check if this is Collider 2D/SpriteRenderer
                if (grRayCast is Physics2DRaycaster)
                
                    //Debug.Log("Collider 2D/SpriteRenderer");
                    routeEvent(resultList[i], eventData, evType, false);
                

                //Check if this is Collider/MeshRender
                if (grRayCast is PhysicsRaycaster)
                
                    //Debug.Log("Collider 3D/Mesh");
                    routeEvent(resultList[i], eventData, evType, false);
                
            
        
    

    //Creates fake PointerEventData that will be used to make PointerEventData for the callback functions
    PointerEventData createEventData(RaycastResult rayResult)
    
        PointerEventData fakeEventData = new PointerEventData(EventSystem.current);
        fakeEventData.pointerCurrentRaycast = rayResult;
        return fakeEventData;
    

    private void routeEvent(RaycastResult rayResult, PointerEventData eventData, PointerEventType evType, bool isUI = false)
    
        bool foundKeyAndValue = false;

        GameObject target = rayResult.gameObject;

        //Make fake GameObject target
        PointerEventData fakeEventData = createEventData(rayResult);


        switch (evType)
        
            case PointerEventType.Drag:

                //Send/Simulate Fake OnDrag event
                ExecuteEvents.Execute<IDragHandler>(target, fakeEventData,
                          ExecuteEvents.dragHandler);
                break;

            case PointerEventType.Down:

                //Send/Simulate Fake OnPointerDown event
                ExecuteEvents.Execute<IPointerDownHandler>(target,
                         fakeEventData,
                          ExecuteEvents.pointerDownHandler);

                //Code Below is for UI. break out of case if this is not UI
                if (!isUI)
                
                    break;
                
                //Prepare Button Click. Should be sent in the if PointerEventType.Up statement
                Button buttonFound = target.GetComponent<Button>();

                //If pointerId is not in the dictionary add it
                if (buttonFound != null)
                
                    if (!dictContains(eventData.pointerId))
                    
                        dictAdd(eventData.pointerId, target);
                    
                

                //Bug in Unity with GraphicRaycaster  and Toggle. Have to use a hack below
                //Toggle Toggle component
                Toggle toggle = null;
                if ((target.name == "Checkmark" || target.name == "Label") && toggle == null)
                
                    toggle = target.GetComponentInParent<Toggle>();
                

                if (toggle != null)
                
                    //Debug.LogWarning("Toggled!: " + target.name);
                    toggle.isOn = !toggle.isOn;
                    //Destroy(toggle.gameObject);
                
                break;

            case PointerEventType.Up:

                //Send/Simulate Fake OnPointerUp event
                ExecuteEvents.Execute<IPointerUpHandler>(target,
                        fakeEventData,
                        ExecuteEvents.pointerUpHandler);

                //Code Below is for UI. break out of case if this is not UI
                if (!isUI)
                
                    break;
                

                //Send Fake Button Click if requirement is met
                Button buttonPress = target.GetComponent<Button>();

                /*If pointerId is in the dictionary, check 

                 */
                if (buttonPress != null)
                
                    if (dictContains(eventData.pointerId))
                    
                        //Check if GameObject matches too. If so then this is a valid Click
                        for (int i = 0; i < resultList.Count; i++)
                        
                            GameObject tempButton = resultList[i].gameObject;
                            if (tempButton != this.gameObject && dictContains(eventData.pointerId, tempButton))
                            
                                foundKeyAndValue = true;
                                //Debug.Log("Button ID and GameObject Match! Sending Click Event");

                                //Send/Simulate Fake Click event to the Button
                                ExecuteEvents.Execute<IPointerClickHandler>(tempButton,
                                      new PointerEventData(EventSystem.current),
                                      ExecuteEvents.pointerClickHandler);
                            
                        
                    
                
                break;
        

        //Remove pointerId since it exist 
        if (foundKeyAndValue)
        
            dictRemove(eventData.pointerId);
        
    

    void routeOption(PointerEventData eventData, PointerEventType evType)
    
        UpdateRaycaster();
        if (WholeScreenPointer.Instance.simulateUIEvent)
        
            //Loop Through All GraphicRaycaster(UI) and throw Raycast to each one
            for (int i = 0; i < grRayCast.Count; i++)
            
                //Throw Raycast to all UI elements in the position(eventData)
                grRayCast[i].Raycast(eventData, resultList);
                sendArtificialUIEvent(grRayCast[i], eventData, evType);
            
            //Reset Result
            resultList.Clear();
        

        if (WholeScreenPointer.Instance.simulateCollider2DEvent)
        
            //Loop Through All Collider 2D (Sprite Renderer) and throw Raycast to each one
            for (int i = 0; i < phy2dRayCast.Count; i++)
            
                //Throw Raycast to all UI elements in the position(eventData)
                phy2dRayCast[i].Raycast(eventData, resultList);
                sendArtificialUIEvent(phy2dRayCast[i], eventData, evType);
            
            //Reset Result
            resultList.Clear();
        

        if (WholeScreenPointer.Instance.simulateColliderEvent)
        
            //Loop Through All Normal Collider(3D/Mesh Renderer) and throw Raycast to each one
            for (int i = 0; i < phyRayCast.Count; i++)
            
                //Throw Raycast to all UI elements in the position(eventData)
                phyRayCast[i].Raycast(eventData, resultList);
                sendArtificialUIEvent(phyRayCast[i], eventData, evType);
            
            //Reset Result
            resultList.Clear();
        
    

    public void routeDragEvent(PointerEventData eventData)
    
        routeOption(eventData, PointerEventType.Drag);
    

    public void routePointerDownEvent(PointerEventData eventData)
    
        routeOption(eventData, PointerEventType.Down);
    

    public void routePointerUpEvent(PointerEventData eventData)
    
        routeOption(eventData, PointerEventType.Up);
    

    public void UpdateRaycaster()
    
        convertToList(FindObjectsOfType<GraphicRaycaster>(), grRayCast);
        convertToList(FindObjectsOfType<Physics2DRaycaster>(), phy2dRayCast);
        convertToList(FindObjectsOfType<PhysicsRaycaster>(), phyRayCast);
    

    //To avoid ToList() function
    void convertToList(GraphicRaycaster[] fromComponent, List<GraphicRaycaster> toComponent)
    
        //Clear and copy new Data
        toComponent.Clear();
        for (int i = 0; i < fromComponent.Length; i++)
        
            toComponent.Add(fromComponent[i]);
        
    

    //To avoid ToList() function
    void convertToList(Physics2DRaycaster[] fromComponent, List<Physics2DRaycaster> toComponent)
    
        //Clear and copy new Data
        toComponent.Clear();
        for (int i = 0; i < fromComponent.Length; i++)
        
            toComponent.Add(fromComponent[i]);
        
    

    //To avoid ToList() function
    void convertToList(PhysicsRaycaster[] fromComponent, List<PhysicsRaycaster> toComponent)
    
        //Clear and copy new Data
        toComponent.Clear();
        for (int i = 0; i < fromComponent.Length; i++)
        
            toComponent.Add(fromComponent[i]);
        
    

    //Checks if object is in the dictionary
    private bool dictContains(GameObject obj)
    
        return pointerIdToGameObject.ContainsValue(obj);
    

    //Checks if int is in the dictionary
    private bool dictContains(int pointerId)
    
        return pointerIdToGameObject.ContainsKey(pointerId);
    

    //Checks if int and object is in the dictionary
    private bool dictContains(int pointerId, GameObject obj)
    
        return (pointerIdToGameObject.ContainsKey(pointerId) && pointerIdToGameObject.ContainsValue(obj));
    

    //Adds pointerId and its value to dictionary
    private void dictAdd(int pointerId, GameObject obj)
    
        pointerIdToGameObject.Add(pointerId, obj);
    

    //Removes pointerId and its value from dictionary
    private void dictRemove(int pointerId)
    
        pointerIdToGameObject.Remove(pointerId);
    

    public enum PointerEventType
    
        Drag, Down, Up
    

用法

1。将WholeScreenPointer 脚本附加到一个空的游戏对象或相机。

2.要接收场景中的任何事件,只需在任何脚本中实现IDragHandlerIPointerDownHandlerIPointerUpHandler,然后调用WholeScreenPointer.Instance.registerGameObject(this.gameObject);一次。屏幕上的任何事件现在都将发送到该脚本。不要忘记在OnDisable() 函数中取消注册。

例如,将Test 附加到您想要接收触摸事件的任何游戏对象:

public class Test : MonoBehaviour, IDragHandler, IPointerDownHandler, IPointerUpHandler

    void Start()
    
        //Register this GameObject so that it will receive events from WholeScreenPointer script
        WholeScreenPointer.Instance.registerGameObject(this.gameObject);
    

    public void OnDrag(PointerEventData eventData)
    
        Debug.Log("Dragging: ");
    

    public void OnPointerDown(PointerEventData eventData)
    
        Debug.Log("Pointer Down: ");
    

    public void OnPointerUp(PointerEventData eventData)
    
        Debug.Log("Pointer Up: ");
    

    void OnDisable()
    
        WholeScreenPointer.Instance.unRegisterGameObject(this.gameObject);
    

注意

如果您想在屏幕上的任何位置接收事件,您只需要调用WholeScreenPointer.Instance.registerGameObject(this.gameObject);。如果您只想从当前对象接收事件,那么您不必调用它。如果这样做,您将收到多个事件。

其他重要功能:

启用全屏事件 - WholeScreenPointer.Instance.enablewholeScreenPointer(true);

禁用全屏事件 - WholeScreenPointer.Instance.enablewholeScreenPointer(false); 最后,这一点还可以进一步改进。

【讨论】:

将他们发送出去的好主意! 等等——一点点。正如我在上面对 Gökhan 提到的:你知道如何在面板上使用不可见的图像,因为 Unity 忘记了制作 Touchable 组件。现在有一个更好的通用解决方案! ***.com/a/36892803/294884 只需使用Touchable 即可使用“不可见图像,因为 Unity 忘记了 Touchable” 我点击了这里的赏金,因为这个答案是如此广泛,而且只剩下几分钟的赏金,干杯! 感谢您的赏金。我看了 Touchable 的东西,不明白它是如何工作的。是否有任何功能必须重写才能检测到触摸? 非常简单:在您的示例中,您附加了一个 Image 组件,Alpha 为零 - 对吧?非常简单:(1) 删除 Image-alpha-zerp 组件 (2) 将其替换为 Touchable。就是这么简单。 (完全没有覆盖、更改、修改。)【参考方案2】:

我要发布的问题和答案似乎都基于意见。不过我会尽量回答。

如果您尝试检测屏幕上的指针事件,那么用对象表示屏幕并没有错。在您的情况下,您使用 3D 对撞机来覆盖相机的整个截锥体。但是,在 Unity 中有一种本地方法可以做到这一点,使用覆盖整个屏幕的 2D UI 对象。屏幕最好用 2D 对象表示。对我来说,这似乎是一种自然的方式。

我为此使用了一个通用代码:

public class Screen : MonoSingleton<Screen>, IPointerClickHandler, IDragHandler, IBeginDragHandler, IEndDragHandler, IPointerDownHandler, IPointerUpHandler, IScrollHandler 
    private bool holding = false;
    private PointerEventData lastPointerEventData;

    #region Events
    public delegate void PointerEventHandler(PointerEventData data);

    static public event PointerEventHandler OnPointerClick = delegate  ;

    static public event PointerEventHandler OnPointerDown = delegate  ;
    /// <summary> Dont use delta data as it will be wrong. If you are going to use delta, use OnDrag instead. </summary>
    static public event PointerEventHandler OnPointerHold = delegate  ;
    static public event PointerEventHandler OnPointerUp = delegate  ;

    static public event PointerEventHandler OnBeginDrag = delegate  ;
    static public event PointerEventHandler OnDrag = delegate  ;
    static public event PointerEventHandler OnEndDrag = delegate  ;
    static public event PointerEventHandler OnScroll = delegate  ;
    #endregion

    #region Interface Implementations
    void IPointerClickHandler.OnPointerClick(PointerEventData e) 
        lastPointerEventData = e;
        OnPointerClick(e);
    

    // And other interface implementations, you get the point
    #endregion

    void Update() 
        if (holding) 
            OnPointerHold(lastPointerEventData);
        
    

Screen 是一个单例,因为游戏上下文中只有一个屏幕。对象(如相机)订阅其指针事件,并相应地安排自己。这也使单一职责保持不变。

您可以将其用作将其附加到表示所谓玻璃(屏幕表面)的对象上。如果您认为 UI 上的按钮会弹出屏幕,那么玻璃会在它们下方。为此,玻璃必须是Canvas 的第一个孩子。当然,Canvas 必须在屏幕空间中渲染才有意义。

这里有一个没有意义的技巧是在玻璃上添加一个不可见的Image 组件,这样它就会接收事件。这就像玻璃的光线投射目标。

您也可以使用InputInput.touches 等)来实现这个玻璃对象。它可以作为检查输入是否在每个Update 调用中发生变化。这对我来说似乎是一种基于轮询的方法,而上面的方法是一种基于事件的方法。

您的问题似乎是在寻找一种方法来证明使用 Input 类的合理性。恕我直言,不要让自己更难。使用有效的方法。并接受 Unity 并不完美的事实。

【讨论】:

我还可以包含这个Screen 类的完整代码。此外,您可能不想将其命名为 Screen,因为它会与 Unity 冲突。 @JoeBlow 您使用 3D 对象来表示始终在镜头前且始终在屏幕上具有相同位置的东西。嗯,就是画面。要回答您的问题,是的,使用 3D 对象似乎很奇怪,但使用 UI 对象时则不然。对于您的第二条评论:正如我所说,玻璃在 UI 上的每个对象下方,当玻璃上有纸时,您不能触摸玻璃,对吧?这可能不方便,但这是自然的方式。这只是我的看法。在某些情况下,玻璃下也会有物体。看一个具体的例子是如何实现互联网地图的。 顺便说一句,不相关 - 等等!我有好消息! 您知道如何在面板上使用不可见的图像,因为 Unity 忘记制作 Touchable 组件。 现在有一个更好的通用解决方案! ***.com/a/36892803/294884 @JoeBlow 是的,但您有权不同意,因为这都是基于意见和偏好的。此外,我从来没有使用过多个 Canvas 的经验,所以我不知道如何回答。我们只需要进行实验。那个 Touchable 是一个宝石,很好的 hack,以前从未注意到它。 对,Touchable 的东西太棒了。以前统一列表上的一个人想出了它,我整理了一下,现在它被广泛使用,非常好用。你说的很对——这里需要有人来试验多个 Canvas,甚至可能不止一个相机,一个相机就是为了这个。

以上是关于使用 Unity3D 的 IPointerDownHandler 方法,但使用“整个屏幕”的主要内容,如果未能解决你的问题,请参考以下文章

基于Unity3D之TimeLine的讲解(一)

Unity3D日常开发Unity3D中协程的使用

unity3d 怎样发布android程序

unity3d脚本不能加载

Unity3D日常开发Unity3D中 C#反射Reflection的使用

Unity3D日常开发Unity3D中 C#反射Reflection的使用