Saltar al contenido

Usando el enfoque IPointerDownHandler de Unity3D, pero con “toda la pantalla”

Después de tanto luchar hemos dado con la solución de este inconveniente que tantos lectores de este sitio presentan. Si tienes alguna información que aportar no dudes en aportar tu información.

En primer lugar, debe comprender que solo existen 3 formas de detectar clic en un objeto con el OnPointerDown función:

1.Necesita un componente de interfaz de usuario para detectar el clic con el OnPointerDown función. Esto se aplica a otros eventos de IU similares.

2.Otro método para detectar un clic con el OnPointerDown función en un GameObject 2D / Sprite es adjuntar Physics2DRaycaster a la cámara y luego OnPointerDown se llamará cuando se haga clic en él. Tenga en cuenta que un Colisionador 2D debe estar unido a él.

3.Si se trata de un objeto 3D con un colisionador, no Colisionador 2D, debes tener PhysicsRaycaster conectado a la cámara para que el OnPointerDown función a llamar.

Hacer esto con el primer método parece más razonable en lugar de tener un colisionador grande o un colisionador 2D cubriendo la pantalla. Todo lo que haces es crear un Canvas, Panel GameObject y adjuntar Image componente que se extiende por toda la pantalla.

Amigo, simplemente no veo el uso de .UI como una solución seria: imagina que estamos haciendo un gran juego y estás liderando un equipo que está haciendo toda la interfaz de usuario (me refiero a botones, menús y todo). Dirijo un equipo de robots andantes. De repente te digo “oh, por cierto, no puedo manejar el tacto (“! “), ¿Podrías colocar una interfaz de usuario? Panel, no olvides guardarlo debajo de todo lo que estás haciendo, oh y poner uno en cualquiera / todos los lienzos o cámaras entre los que intercambias, y pásame esa información, ¡de acuerdo! ” 🙂 Quiero decir que es una tontería. Básicamente, uno no puede decir: “oh, Unity no maneja el tacto”.

No es tan difícil como lo describiste. Puede escribir un código largo que podrá crear un Canvas, Panel y un Image. Cambiar la imagen alfa a 0. Todo lo que tienes que hacer es adjuntar ese código a la cámara o un GameObject vacío y realizará todo esto automáticamente en el modo de juego.

Haga que todos los GameObject que quieran recibir eventos en la pantalla se suscriban, luego use ExecuteEvents.Execute para enviar el evento a todas las interfaces en el script adjunto a ese GameObject.

Por ejemplo, el código de muestra a continuación enviará OnPointerDown evento al GameObject llamado target.

ExecuteEvents.Execute(target,
                              eventData,
                              ExecuteEvents.pointerDownHandler);

Problema con el que te encontrarás:

Lo oculto Image El componente bloqueará otra interfaz de usuario o GameObject para que no reciba raycast. Este es el mayor problema aquí.

Solución:

Dado que causará algunos problemas de bloqueo, es mejor hacer que el Lienzo de la imagen esté encima de todo. Esto asegurará que ahora esté 100 bloqueando todas las demás UI / GameObject. Canvas.sortingOrder = 12; debería ayudarnos a hacer esto.

Cada vez que detectamos un evento como OnPointerDown de la imagen, lo haremos a mano enviar reenviar el OnPointerDown evento a todos los demás UI / GameObjects debajo del Image.

En primer lugar, lanzamos un raycast con GraphicRaycaster(Interfaz de usuario), Physics2DRaycaster(Colisionador 2D), PhysicsRaycaster(3D Collider) y almacene el resultado en un List.

Ahora, recorremos el resultado en la Lista y reenviamos el evento que recibimos enviando un evento artificial a los resultados con:

ExecuteEvents.Execute(currentListLoop,
                              eventData,
                              ExecuteEvents.pointerDownHandler);

Otros problemas con los que te encontrarás:

No podrá enviar eventos de emulación en el Toggle componente con GraphicRaycaster. Este es un error en Unity. Me tomó 2 días darme cuenta de esto.

Tampoco se pudo enviar un evento de movimiento deslizante falso al Slider componente. No puedo decir si esto es un error o no.

Aparte de estos problemas mencionados anteriormente, pude implementar esto. Viene en 3 partes. Simplemente cree una carpeta y coloque todos los scripts en ella.

GUIONES:

1.WholeScreenPointer.cs – La parte principal del guión que crea Canvas, GameObject y oculto Image. Hace todas las cosas complicadas para asegurarse de que el Image siempre cubre la pantalla. También envía eventos a todos los GameObject suscritos.

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 registeredGameobjects = new List();

    //////////////////////////////// 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();
        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();
    

    //Adds CanvasScaler component to the Canvas GameObject 
    private void addCanvasScaler()
    
        CanvasScaler cvsl = hiddenCanvas.AddComponent();
        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();
        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();
        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();

        //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();
    

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

    //Add EventSystem
    private void addEventSystemAndInputModule()
    
        //Check if EventSystem exist. If it does not create and add it
        EventSystem eventSys = FindObjectOfType();
        if (eventSys == null)
        
            GameObject evObj = new GameObject("EventSystem");
            EventSystem evs = evObj.AddComponent();
            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();
        if (sdlIpModl == null)
        
            sdlIpModl = eventSys.gameObject.AddComponent();
            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(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(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(registeredGameobjects[i],
                    eventData,
                    ExecuteEvents.pointerUpHandler);
        

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

2.EventForwarder.cs – Simplemente recibe cualquier evento de lo oculto Image y se lo pasa al WholeScreenPointer.cs script para su procesamiento.

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 – Desbloquea los rayos ocultos Image está bloqueando enviando un evento falso a cualquier Objeto por encima de él. Ya sea interfaz de usuario, colisionador 2D o 3D.

public class EventUnBlocker : MonoBehaviour

    List grRayCast = new List(); //UI
    List phy2dRayCast = new List(); //Collider 2D (Sprite Renderer)
    List phyRayCast = new List(); //Normal Collider(3D/Mesh Renderer)

    List resultList = new List();

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

    // 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(target, fakeEventData,
                          ExecuteEvents.dragHandler);
                break;

            case PointerEventType.Down:

                //Send/Simulate Fake OnPointerDown event
                ExecuteEvents.Execute(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

Uso:

1.Adjunta WholeScreenPointer script a un GameObject vacío o la cámara.

2.Para recibir cualquier evento en la escena, simplemente implemente IDragHandler, IPointerDownHandler, IPointerUpHandler en cualquier script, luego llame WholeScreenPointer.Instance.registerGameObject(this.gameObject); una vez. Cualquier evento de la pantalla ahora se enviará a ese script. No olvide darse de baja en el OnDisable() función.

Por ejemplo, adjunte Test a cualquier GameObject que desee recibir eventos táctiles:

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);
    

NOTA:

Solo necesitas llamar WholeScreenPointer.Instance.registerGameObject(this.gameObject); si desea recibir el evento en cualquier lugar de la pantalla. Si solo desea recibir un evento del objeto actual, no tiene que llamar a esto. Si lo hace, recibirá varios eventos.

Otras funciones importantes:

Habilitar evento de pantalla completa - WholeScreenPointer.Instance.enablewholeScreenPointer(true);

Deshabilitar el evento de pantalla completa - WholeScreenPointer.Instance.enablewholeScreenPointer(false);
Finalmente, esto se puede mejorar más.

La pregunta y la respuesta que voy a publicar parecen basarse en opiniones. Sin embargo, voy a responder lo mejor que pueda.

Si está intentando detectar eventos de puntero en la pantalla, no hay nada de malo en representar la pantalla con un objeto. En su caso, utiliza un colisionador 3D para cubrir todo el tronco de la cámara. Sin embargo, hay una forma nativa de hacer esto en Unity, utilizando un objeto de interfaz de usuario 2D que cubre toda la pantalla. La pantalla se puede representar mejor con un objeto 2D. Para mí, esta parece una forma natural de hacerlo.

Utilizo un código genérico para este propósito:

public class Screen : MonoSingleton, 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  ;
    ///  Dont use delta data as it will be wrong. If you are going to use delta, use OnDrag instead. 
    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);
        
    

los Screen es un singleton, porque solo hay una pantalla en el contexto del juego. Los objetos (como la cámara) se suscriben a sus eventos de puntero y se organizan en consecuencia. Esto también mantiene intacta la responsabilidad única.

Usaría esto como anexarlo a un objeto que representa el llamado vidrio (superficie de la pantalla). Si cree que los botones de la interfaz de usuario están saliendo de la pantalla, el vidrio estaría debajo de ellos. Para ello, el vaso tiene que ser el primer hijo del Canvas. Por supuesto, el Canvas tiene que ser renderizado en el espacio de la pantalla para que tenga sentido.

Un truco aquí, que no tiene sentido es agregar un invisible Image componente al vidrio, por lo que recibiría eventos. Esto actúa como el objetivo raycast del vidrio.

Jerarquía

Inspector

También podrías usar Input (Input.touches etc.) para implementar este objeto de vidrio. Funcionaría como comprobar si la entrada cambia en cada Update llama. Esto me parece un enfoque basado en encuestas, mientras que el anterior es un enfoque basado en eventos.

Su pregunta parece buscar una forma de justificar el uso de Input clase. En mi humilde opinión, no te lo pongas más difícil. Usa lo que funciona. Y acepta el hecho de que Unity no es perfecto.

Si estás de acuerdo, puedes dejar una crónica acerca de qué le añadirías a este enunciado.

¡Haz clic para puntuar esta entrada!
(Votos: 2 Promedio: 4.5)


Tags :

Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *