Search Unity

Use reticle like mouse for WorldSpace UI's

Discussion in 'UGUI & TextMesh Pro' started by Chris-Trueman, Feb 2, 2015.

  1. Chris-Trueman

    Chris-Trueman

    Joined:
    Oct 10, 2014
    Posts:
    1,261
    I'm currently trying to get my world space UI to react to the reticle which is in the center of the screen. I have setup an Input module and added it to the EventSystem.

    Here is the code for my Input module.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UI;
    3. using UnityEngine.EventSystems;
    4. using System.Collections;
    5. using System.Collections.Generic;
    6.  
    7. public class AimerInputModule : BaseInputModule
    8. {
    9.     public string activateAxis = "Interact";
    10.  
    11.     public static GameObject objectUnderAimer;
    12.  
    13.     protected AimerInputModule() {}
    14.  
    15.     public override void UpdateModule ()
    16.     {
    17.         GetObjectUnderAimer();
    18.     }
    19.  
    20.     public override void Process ()
    21.     {
    22.         if (objectUnderAimer)
    23.         {
    24.             if (Input.GetButtonDown(activateAxis))
    25.             {
    26.                 BaseEventData eventData = GetBaseEventData();
    27.                 eventData.selectedObject = objectUnderAimer;
    28.                 ExecuteEvents.Execute(objectUnderAimer, eventData, ExecuteEvents.submitHandler);
    29.             }
    30.         }
    31.     }
    32.  
    33.     List<RaycastResult> results = new List<RaycastResult>();
    34.  
    35.     private bool GetObjectUnderAimer()
    36.     {
    37.         PointerEventData pointerData = new PointerEventData(eventSystem);
    38.         pointerData.worldPosition = PlayerCharacterScript.currentPlayerCamera.transform.position;
    39.  
    40.         eventSystem.RaycastAll(pointerData, results);
    41.  
    42.         if (results.Count > 0)
    43.         {
    44.             RaycastResult rayResult = FindFirstRaycast(results);
    45.             if (objectUnderAimer != rayResult.gameObject)
    46.             {
    47.                 Debug.Log(rayResult.gameObject.name);
    48.                 objectUnderAimer = rayResult.gameObject;
    49.                 BaseEventData eData = GetBaseEventData();
    50.                 eData.selectedObject = objectUnderAimer;
    51.                 ExecuteEvents.Execute(objectUnderAimer, eData, ExecuteEvents.pointerEnterHandler);
    52.             }
    53.  
    54.             results.Clear();
    55.             return true;
    56.         }
    57.  
    58.         //We didn't hit anything
    59.  
    60.         if (objectUnderAimer)
    61.         {
    62.             BaseEventData eData = GetBaseEventData();
    63.             eData.selectedObject = objectUnderAimer;
    64.  
    65.             ExecuteEvents.Execute(objectUnderAimer, eData, ExecuteEvents.pointerExitHandler);
    66.         }
    67.  
    68.         results.Clear();
    69.         objectUnderAimer = null;
    70.         return false;
    71.     }
    72. }
    It seems to be giving me results but not when I am pointed at the UI, only from up and to the right of the UI, like its offset but still doesn't give the correct results.

    The canvas has an image for the background, some text for a title and a button in the middle.

    I have been struggling with this for a few days trying to use info from here, using the Unity provided source from here and searching the forums. Any help would be appreciated greatly.
     
  2. Chris-Trueman

    Chris-Trueman

    Joined:
    Oct 10, 2014
    Posts:
    1,261
    Okay, I pretty much got it working now using the info from Unity's TouchInputModule. It has bugs but itworks. The bug is that it keeps switching between pointer enter and pointer exit while I move the camera around, but only after I trigger the button. The code is a little long so I won't post it here, I'll try to figure it out.

    If anyone has another method to do what I'm trying to do please let me know. I do not want to use 2D or 3D colliders on the canvas and so far this is working without and lets me know if I have an event system object under my reticle so I can inform the user that they can interact with the object or buttons.
     
  3. Chris-Trueman

    Chris-Trueman

    Joined:
    Oct 10, 2014
    Posts:
    1,261
    Fixed the bug. The mouse was causing the problem. I disabled the StandAloneInputModule and it solved the issue. Time to write my own module so I can disable the mouse from interacting with a world space canvas and use the reticle instead.
     
  4. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
  5. Chris-Trueman

    Chris-Trueman

    Joined:
    Oct 10, 2014
    Posts:
    1,261
    Thanks for showing me that site, I will do that for sure.
     
  6. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    Might want to check it again @Chris Trueman , just released a big update. 19 controls, all tidied up / unified and complete with Editor Menu options to create (most) of them.

    Need to update the docs on the front page next :S
     
  7. Chris-Trueman

    Chris-Trueman

    Joined:
    Oct 10, 2014
    Posts:
    1,261
    I attempted to post the code to https://bitbucket.org/ddreaper/unity-ui-extensions. I setup an account then forked your project, then made the changes. I've never done this before so I'm not sure I did it right.

    In any case I will post the code here as well. Might have some bugs, its not fully tested and I haven't had much time to work on it. It is pretty much a copy of the mouse handling code from the standalone module.

    Code (CSharp):
    1. /// <summary>
    2. /// Aimer input module.
    3. /// Credit Chris Trueman
    4. /// </summary>
    5.  
    6. using UnityEngine;
    7. using UnityEngine.EventSystems;
    8. using System.Collections.Generic;
    9.  
    10. namespace UnityEngine.EventSystems.Extensions
    11. {
    12.     [RequireComponent(typeof(EventSystem))]
    13.     [AddComponentMenu("UI/Extensions/Aimer Input Module")]
    14.     public class AimerInputModule : PointerInputModule
    15.     {
    16.         /// <summary>
    17.         /// The Input axis name used to activate the object under the aimer.
    18.         /// Default is Submit which is the enter key on the keyboard.
    19.         /// </summary>
    20.         public string activateAxis = "Submit";
    21.  
    22.         /// <summary>
    23.         /// The aimer position. You can change this to whatever position you want.
    24.         /// </summary>
    25.         public Vector2 aimerOffset = new Vector2(0 , 0);
    26.  
    27.         /// <summary>
    28.         /// The object under aimer. A static access field that lets you know what is under the aimer.
    29.         /// This field can return null. Only returns a valid GameObject if a Submit event is attached to it.
    30.         /// </summary>
    31.         public static GameObject objectUnderAimer;
    32.  
    33.         protected AimerInputModule() {}
    34.  
    35.         public override void ActivateModule()
    36.         {
    37.             StandaloneInputModule StandAloneSystem = GetComponent<StandaloneInputModule>();
    38.      
    39.             if (StandAloneSystem != null && StandAloneSystem.enabled)          
    40.             {
    41.                 Debug.LogError("Aimer Input Module is incompatible with the StandAloneInputSystem, " +
    42.                     "please remove it from the Event System in this scene or disable it when this module is in use");
    43.             }
    44.         }
    45.  
    46.         public override void Process ()
    47.         {
    48.             bool pressed = Input.GetButtonDown(activateAxis);
    49.             bool released = Input.GetButtonUp(activateAxis);
    50.  
    51.             PointerEventData pointer = GetAimerPointerEventData();
    52.  
    53.             ProcessInteraction(pointer, pressed, released);
    54.  
    55.             if (!released)
    56.                 ProcessMove(pointer);
    57.             else
    58.                 RemovePointerData(pointer);
    59.         }
    60.  
    61.         protected virtual PointerEventData GetAimerPointerEventData()
    62.         {
    63.             PointerEventData pointerData;
    64.  
    65.             //Not certain on the use of this.
    66.             //I know that -1 is the mouse and anything positive would be a finger/touch, 0 being the first finger, 1 beign the second and so one till the system limit is reached.
    67.             //So that is the reason I choose -2.
    68.             GetPointerData(-2, out pointerData, true);
    69.      
    70.             pointerData.Reset();
    71.  
    72.             pointerData.position = new Vector2(Screen.width * 0.5f, Screen.height * 0.5f) + aimerOffset;
    73.      
    74.             eventSystem.RaycastAll (pointerData, m_RaycastResultCache);
    75.             var raycast = FindFirstRaycast (m_RaycastResultCache);
    76.             pointerData.pointerCurrentRaycast = raycast;
    77.             m_RaycastResultCache.Clear();
    78.             return pointerData;
    79.         }
    80.  
    81.         private void ProcessInteraction(PointerEventData pointer, bool pressed, bool released)
    82.         {
    83.             var currentOverGo = pointer.pointerCurrentRaycast.gameObject;
    84.  
    85.             objectUnderAimer = ExecuteEvents.GetEventHandler<ISubmitHandler>(currentOverGo);//we only want objects that we can submit on.
    86.  
    87.             if (pressed)
    88.             {
    89.                 pointer.eligibleForClick = true;
    90.                 pointer.delta = Vector2.zero;
    91.                 pointer.pressPosition = pointer.position;
    92.                 pointer.pointerPressRaycast = pointer.pointerCurrentRaycast;
    93.  
    94.                 // search for the control that will receive the press
    95.                 // if we can't find a press handler set the press
    96.                 // handler to be what would receive a click.
    97.                 var newPressed = ExecuteEvents.ExecuteHierarchy(currentOverGo, pointer, ExecuteEvents.submitHandler);
    98.          
    99.                 // didnt find a press handler... search for a click handler
    100.                 if (newPressed == null)
    101.                 {
    102.                     newPressed = ExecuteEvents.ExecuteHierarchy(currentOverGo, pointer, ExecuteEvents.pointerDownHandler);
    103.                     if (newPressed == null)
    104.                         newPressed = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
    105.                 }
    106.                 else
    107.                 {
    108.                     pointer.eligibleForClick = false;
    109.                 }
    110.      
    111.                 if (newPressed != pointer.pointerPress)
    112.                 {
    113.                     pointer.pointerPress = newPressed;
    114.                     pointer.rawPointerPress = currentOverGo;
    115.                     pointer.clickCount = 0;
    116.                 }
    117.  
    118.                 // Save the drag handler as well
    119.                 pointer.pointerDrag = ExecuteEvents.GetEventHandler<IDragHandler> (currentOverGo);
    120.          
    121.                 if (pointer.pointerDrag != null)
    122.                     ExecuteEvents.Execute<IBeginDragHandler> (pointer.pointerDrag, pointer, ExecuteEvents.beginDragHandler);
    123.             }
    124.  
    125.             if (released)
    126.             {
    127.                 //Debug.Log("Executing pressup on: " + pointer.pointerPress);
    128.                 ExecuteEvents.Execute (pointer.pointerPress, pointer, ExecuteEvents.pointerUpHandler);
    129.          
    130.                 //Debug.Log("KeyCode: " + pointer.eventData.keyCode);
    131.          
    132.                 // see if we mouse up on the same element that we clicked on...
    133.                 var pointerUpHandler = ExecuteEvents.GetEventHandler<IPointerClickHandler> (currentOverGo);
    134.          
    135.                 // PointerClick
    136.                 if (pointer.pointerPress == pointerUpHandler && pointer.eligibleForClick)
    137.                 {
    138.                     float time = Time.unscaledTime;
    139.              
    140.                     if (time - pointer.clickTime < 0.3f)
    141.                         ++pointer.clickCount;
    142.                     else
    143.                         pointer.clickCount = 1;
    144.                     pointer.clickTime = time;
    145.              
    146.                     ExecuteEvents.Execute (pointer.pointerPress, pointer, ExecuteEvents.pointerClickHandler);
    147.                 }
    148.                 else if (pointer.pointerDrag != null)
    149.                 {
    150.                     ExecuteEvents.ExecuteHierarchy (currentOverGo, pointer, ExecuteEvents.dropHandler);
    151.                 }
    152.          
    153.                 pointer.eligibleForClick = false;
    154.                 pointer.pointerPress = null;
    155.                 pointer.rawPointerPress = null;
    156.          
    157.                 if (pointer.pointerDrag != null)
    158.                     ExecuteEvents.Execute (pointer.pointerDrag, pointer, ExecuteEvents.endDragHandler);
    159.          
    160.                 pointer.pointerDrag = null;
    161.             }
    162.         }
    163.  
    164.         public override void DeactivateModule()
    165.         {
    166.             base.DeactivateModule ();
    167.             ClearSelection ();
    168.         }
    169.     }
    170. }
     
    Last edited: Feb 13, 2015
  8. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    Good point @Chris Trueman , I've done instructions for how to do GIT, I'll also put some on the project for Mercurial. The process is similar.
    I'll update your code above in the latest develop branch.

    If you have already made the changes locally, you simply need to:
    • Commit your changes to a new branch - (Has to be a new branch to ensure you do not dirty your copy of the main project)
    • Push your new branch to the server
    • Once up, go to the web page for your Fork and create a Pull Request (PR) back to the main project.
    • I then pick it up from there.
    Great work!
     
  9. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    @Chris Trueman The code above has a slight bug. It makes reference to a variable called "aimerOffset" However that variable is never declared.
     
  10. Chris-Trueman

    Chris-Trueman

    Joined:
    Oct 10, 2014
    Posts:
    1,261
    This line is wrong
    Code (CSharp):
    1. /// <summary>
    2.         /// The aimer position. You can change this to whatever position you want.
    3.         /// </summary>
    4.         public Vector2 aimerPosition = new Vector2(Screen.width * 0.5f, Screen.height * 0.5f);
    Needs to change to.
    Code (CSharp):
    1. /// <summary>
    2.         /// The aimer offset position. Aimer is center screen use this offset to change that.
    3.         /// </summary>
    4.         public Vector2 aimerOffset = new Vector2(0, 0);
     
  11. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    :D
    Right 1 step forward, another step back.

    On running if complains of a missing input "Interact", did you add a custom input handle?
     
  12. Chris-Trueman

    Chris-Trueman

    Joined:
    Oct 10, 2014
    Posts:
    1,261
    Yeah I did, that is why I made it a public field so it can be changed to one that you setup. Then I just looked at the Input settings and saw Submit being on there by default. I will update the code to reflect that so you can "plug and play".
     
  13. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    Thanks @Chris Trueman , yeah it's key that these scripts "just work" without complicated setup (unless supported by docs :D)
     
  14. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    Yo @Chris Trueman did you get round to updating your script for the issue above. Just about to release a new update, so would love to include this in the docs.

    *Edit, just changed the action from Interact to Submit and it compiles and runs fine. Just trying to figure out how best to demonstrate it now. Do you have a sample scene to work from?
     
    Last edited: Aug 30, 2015
  15. Chris-Trueman

    Chris-Trueman

    Joined:
    Oct 10, 2014
    Posts:
    1,261
    Sorry haven't had much time as of late. It is currently built into only one project that I was working on, I would need to setup a sample scene when I have some time. If you can get something going that would be awesome.