Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Interactable Minimap - Using Raw Image / Render Texture [SOLVED]

Discussion in 'Scripting' started by unitynoob24, Apr 7, 2018.

  1. unitynoob24

    unitynoob24

    Joined:
    Dec 27, 2014
    Posts:
    398
    Hey guys!

    I started working on a mini map for my game and I have it functioning well.

    I am currently trying to make it so I can click the mini map and interact with it - namely move the player to the world point reflecting where the minimap was clicked.

    My game currently uses three cameras. One is the main player view / main camera which is a 3rd person camera. One is a top down orthographic camera, looking at the player which is what I use to create the mini map. Then the final camera is the canvas camera, which displays a render texture / raw image which projects the top down camera.

    After hours of searching I was able to achieve exactly what I am attempting but NOT using the canvas / render texture / raw image. I found a working solution using a generic plane in my 3rd person camera.. which is wonky.. but it works.

    So basically I have been trying to re-write this working code to work with my canvas /render texture / projected orthographic view / camera whatever you want to call it! lol I am hitting a brick wall but feel like I am getting close! All I need is the equivalent of hit.textureCoord leveraging the EventSystem / PointerEventData / current interface.

    Here is a working solution use a PLANE in the main camera.. which is not ideal I really need to be using the UI render texture / raw image.

    Code (csharp):
    1.  
    2.  
    3.  if (Input.GetMouseButtonDown(0))
    4.         {
    5.             RaycastHit hit;
    6.             Ray ray = canvasCamera.ScreenPointToRay(Input.mousePosition);
    7.  
    8.             // do we hit our minimap plane?
    9.             if (Physics.Raycast(ray, out hit, Mathf.Infinity, miniMap))
    10.             {
    11.                 Debug.Log("Main Camera hit: " + hit.collider.gameObject);
    12.  
    13.                 var localPoint = hit.textureCoord;
    14.  
    15.                 // convert the hit texture coordinates into camera coordinates
    16.                 Ray miniMapRay = this.GetComponent<Camera>().ScreenPointToRay(new Vector2(localPoint.x *
    17.                     this.GetComponent<Camera>().pixelWidth, localPoint.y * this.GetComponent<Camera>().pixelHeight));
    18.  
    19.                 RaycastHit miniMapHit;
    20.  
    21.                 // test these camera coordinates in another raycast test
    22.                 if (Physics.Raycast(miniMapRay, out miniMapHit))
    23.                 {
    24.                     Debug.Log("miniMapHit: " + miniMapHit.collider.gameObject);
    25.                 }
    26.  
    27.              
    28.             }
    29.  
    30.  
    Like I said, this works perfectly.. just using a 3d element floating around the scene looks wonky compared to a nice clean UI element.

    Here is what I am pulling my hair out over.. lol sorry it's been a long night. I think I am very close though.

    Code (csharp):
    1.  
    2.  
    3.  if (Input.GetMouseButton(0))
    4.         {
    5.             if (EventSystem.current.IsPointerOverGameObject())
    6.             {
    7.  
    8.                 PointerEventData pointer = new PointerEventData(EventSystem.current);
    9.                 pointer.position = Input.mousePosition;
    10.  
    11.                 List<RaycastResult> raycastResults = new List<RaycastResult>();
    12.                 EventSystem.current.RaycastAll(pointer, raycastResults);
    13.  
    14.                 if (raycastResults[0].gameObject.name == "RawImage")
    15.                 {
    16.  
    17.                     if (raycastResults.Count > 0)
    18.                     {
    19.                         foreach (var go in raycastResults)
    20.                         {
    21.                             Debug.Log(go.gameObject.name, go.gameObject);
    22.                             if (go.gameObject.name == "RawImage")
    23.                             {
    24.                                 break;
    25.                             }
    26.                         }
    27.                     }
    28.  
    29.                     var localPoint = raycastResults[0].worldPosition;
    30.  
    31.                     Ray miniMapRay = this.GetComponent<Camera>().ScreenPointToRay(new Vector2(localPoint.x *
    32.                         this.GetComponent<Camera>().pixelWidth, localPoint.y * this.GetComponent<Camera>().pixelHeight));
    33.  
    34.                     RaycastHit miniMapHit;
    35.  
    36.                     if (Physics.Raycast(miniMapRay, out miniMapHit, Mathf.Infinity))
    37.                     {
    38.                         Debug.Log("miniMapHit: " + miniMapHit.collider.gameObject);
    39.                     }
    40.  
    41.                 }
    42.  
    43.             }
    44.         }
    45.  
    46.  
    Thanks in advance for any input or assistance guys! :D
     
  2. Velo222

    Velo222

    Joined:
    Apr 29, 2012
    Posts:
    1,437
    Ok, bear with me because I did this a long time ago, and I really don't remember exactly how I did it. Even though I wrote myself some notes. Although this code applies to my main camera, I think you could easily make it work for your character (or units). It currently works pretty well when I click on my minimap (raw image minimap).

    However, I remember it being a lot of trial and error in order to get my settings close to what I wanted them to be. So you will probably have to adjust values to see what works for you. I'm just posting this so you might be able to study it, and see if you can get it to work.

    Here is my code:
    Code (CSharp):
    1. //--------------------   Click on Minimap Location Code  ------------------------------------
    2.                 //Get the 4 corners (in world space) of the raw image gameobject's rect transform on the GUI
    3.                 Vector3[] corners = new Vector3[4];
    4.                 minimapRawImage.rectTransform.GetWorldCorners(corners);
    5.                 Rect newRect = new Rect(corners[0], corners[2] - corners[0]);
    6.  
    7.                 //Get the pixel offset amount from the current mouse position to the left edge of the minimap
    8.                 //rect transform.  And likewise for the y offset position (mouse to minimap top or bottom edge --
    9.                 //I actually don't know on that one).
    10.                 float xPositionDeltaPoint = Input.mousePosition.x - newRect.x;
    11.                 float yPositionDeltaPoint = Input.mousePosition.y - newRect.y;
    12.  
    13.                 //Debug.Log("The x position delta is: " + xPositionDeltaPoint);
    14.                 //Debug.Log("The y position delta is: " + yPositionDeltaPoint);
    15.  
    16.                 //Calculate how much to scale the "delta offset point" by to compensate for screen resolution
    17.                 //AND to compensate for the Canvas's "Canvas Scaler" script.  Because I currently have my Canvas
    18.                 //Scaler script set to a screen match mode of "Match Width Or Height", and because the "Match"
    19.                 //parameter is set to a value of "1" (i.e. match height fully), I only need to set the x compensate
    20.                 //value based on the uGUI's scale factor.  The y value, however, (since I'm matching the y height)
    21.                 //needs to be based off of the "Reference Resolution" y value on the Canvas Scaler script.  If, in the
    22.                 //future, I do it another way or have different Canvas Scaler settings, I'll need to adjust my
    23.                 //calculations here based on those settings.
    24.                 //The value "170" is the raw image size.
    25.                 float compensateForScalingX = 170 * mainCanvasObjectCanvasScript.scaleFactor;
    26.                 //"600" is the current reference resolution height on the Canvas Scaler script.
    27.                 float compensateForScalingY = 170 * (Screen.height / 600) * mainCanvasObjectCanvasScript.scaleFactor;
    28.  
    29.                 //If the game screen height resolution and the canvas scaler script's "y reference resolution" are
    30.                 //exactly the same, then the division value will be zero.  Since you can't divide by zero, I need
    31.                 //to check for this here.
    32.                 if (compensateForScalingY == 0)
    33.                 {
    34.                     compensateForScalingY = 170;
    35.                 }
    36.  
    37.                 //The value "170" is the raw image size currently
    38.                 float xPositionCameraCoordinates = (xPositionDeltaPoint / compensateForScalingX);
    39.                 float yPositionCameraCoordinates = (yPositionDeltaPoint / compensateForScalingY);
    40.  
    41.                 //Debug.Log("The x viewport position is: " + xPositionCameraCoordinates);
    42.                 //Debug.Log("The y viewport position is: " + yPositionCameraCoordinates);
    43.  
    44.                 Ray mouseray1 = minimapCamera.ViewportPointToRay(new Vector3(xPositionCameraCoordinates, yPositionCameraCoordinates, 0));
    45.                 //Ray mouseray1 = minimapCamera.ScreenPointToRay(new Vector2(localPoint.x * minimapCamera.pixelWidth, localPoint.y * minimapCamera.pixelHeight));
    46.                 RaycastHit rayhitlocation1;
    47.  
    48.                 //If I click on a building, do the following
    49.                 if (Physics.Raycast(mouseray1, out rayhitlocation1, Mathf.Infinity, (1<<26), QueryTriggerInteraction.Collide))
    50.                 {
    51.                     //Move the main camera to the location clicked upon.  The "newrect contains" condition was
    52.                     //necessary because for some reason clicking on unit build buttons on the GUI also moved
    53.                     //the minimap camera -- which was undesired.
    54.                     if (newRect.Contains(Input.mousePosition))
    55.                     {
    56.                         Camera.main.transform.position = new Vector3(rayhitlocation1.point.x, mainCamTrans.position.y, rayhitlocation1.point.z - 10);
    57.                     }
    58.                 }
     
    unitynoob24 likes this.
  3. unitynoob24

    unitynoob24

    Joined:
    Dec 27, 2014
    Posts:
    398
    Oh awesome! I appreciate the help! I actually just figured this out leveraging the IPointerClickHandler interface!

    This is what I ended up with, which uses a canvas raw image + render texture projecting my orthographic top down view. The canvas render mode is set to Screen Space - Camera; for anyone replicating this.

    After days of tweaking with this I got it working great for my application! Just attach this script to the RawImage.

    Code (csharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.EventSystems;
    5. using UnityEngine.UI;
    6.  
    7. public class MiniMapClick : MonoBehaviour, IPointerClickHandler
    8. {
    9.     //Drag Orthographic top down camera here
    10.     public Camera miniMapCam;
    11.  
    12.     public void OnPointerClick(PointerEventData eventData)
    13.     {
    14.  
    15.         Vector2 localCursor = new Vector2(0, 0);
    16.  
    17.         if (RectTransformUtility.ScreenPointToLocalPointInRectangle(GetComponent<RawImage>().rectTransform, eventData.pressPosition, eventData.pressEventCamera, out localCursor))
    18.         {
    19.  
    20.             Texture tex = GetComponent<RawImage>().texture;
    21.             Rect r = GetComponent<RawImage>().rectTransform.rect;
    22.  
    23.             //Using the size of the texture and the local cursor, clamp the X,Y coords between 0 and width - height of texture
    24.             float coordX = Mathf.Clamp(0, (((localCursor.x - r.x) * tex.width) / r.width), tex.width);
    25.             float coordY = Mathf.Clamp(0, (((localCursor.y - r.y) * tex.height) / r.height), tex.height);
    26.  
    27.             //Convert coordX and coordY to % (0.0-1.0) with respect to texture width and height
    28.             float recalcX = coordX / tex.width;
    29.             float recalcY = coordY / tex.height;
    30.  
    31.             localCursor = new Vector2(recalcX, recalcY);
    32.  
    33.             CastMiniMapRayToWorld(localCursor);
    34.          
    35.         }
    36.  
    37.     }
    38.  
    39.     private void CastMiniMapRayToWorld(Vector2 localCursor)
    40.     {
    41.         Ray miniMapRay = miniMapCam.ScreenPointToRay(new Vector2(localCursor.x * miniMapCam.pixelWidth, localCursor.y * miniMapCam.pixelHeight));
    42.  
    43.         RaycastHit miniMapHit;
    44.  
    45.         if (Physics.Raycast(miniMapRay, out miniMapHit, Mathf.Infinity))
    46.         {
    47.             Debug.Log("miniMapHit: " + miniMapHit.collider.gameObject);
    48.         }
    49.  
    50.     }
    51.  
    52.  
    53. }
    54.  
    55.  
    I hope this helps someone in the future! I spent since Friday messing around with this! :)
     
    Last edited: Apr 9, 2018
    awsapps, MrWorner, phil-R and 10 others like this.
  4. Lichemperor

    Lichemperor

    Joined:
    Dec 8, 2015
    Posts:
    1
    Good job and thank you ;)

    I hope I at least managed to supply moral support :D
     
    unitynoob24 likes this.
  5. unitynoob24

    unitynoob24

    Joined:
    Dec 27, 2014
    Posts:
    398
  6. Velo222

    Velo222

    Joined:
    Apr 29, 2012
    Posts:
    1,437
    Glad you got it working. Your solution looks a little less code heavy than mine. I'll have to try it out the next time I have to do another minimap, like with an RTS. Nice work :)
     
    unitynoob24 likes this.
  7. jesse12521

    jesse12521

    Joined:
    Jun 28, 2017
    Posts:
    2
    Thank you so much for this unitynoob24! It's so very appreciated.

    I did want to add one thing that I had trouble with after I added your script. I don't usually use the OnPointerClick method (instead usually use OnMouseDown) but I learned that OnPointerClick is specifically for UI and I had to disable my other UI element's "Raycast Target" property before OnPointerClick would even be called on the raw image. See attached image if needed.

    Thanks again!
     

    Attached Files:

  8. unitynoob24

    unitynoob24

    Joined:
    Dec 27, 2014
    Posts:
    398
    Glad I was able to help out! Is there a chance you had another UI element over top of the raw image that you are trying to cast on? The raw image that you are casting on would have to be on top I believe for this to work properly :D
     
  9. jesse12521

    jesse12521

    Joined:
    Jun 28, 2017
    Posts:
    2
    Yes that was the issue. I had a semi transparent image over my minimap that was in the way. I also disabled the other UI elements as I only needed the raycast to hit the render texture.

    Again, nice job on figuring this out!
     
  10. unitynoob24

    unitynoob24

    Joined:
    Dec 27, 2014
    Posts:
    398
    Ah gotcha! Also if you just rearrange the child index in the editor, under the canvas parent, the last child is always on top, so you could put the Raw Image there; then you won't have to mess with raycast target property! At least I think! lol

    And no problem! Glad I could help! I spent way too long on this, and once I finally figured it out I was like.. I can't be the only one trying to figure this out lol
     
    Vegeta_DTX likes this.
  11. Vegeta_DTX

    Vegeta_DTX

    Joined:
    Aug 20, 2015
    Posts:
    3
    I felt very obligated to post and say BIG THANK YOU to unitynoob24 and everyone else who contributed to this solution! :) This reaaally saved me a lot of time and spared me of hair pulling and headache :)

    The fact that you posted not only code but the project itself is sooo nice of you!
    This is what I like about Unity community! :)
     
    unitynoob24 likes this.
  12. unitynoob24

    unitynoob24

    Joined:
    Dec 27, 2014
    Posts:
    398
    Glad I was able to help! i remember this being a doozy to figure out and figured I would pass on the knowledge! :D
     
  13. zh4r0naX

    zh4r0naX

    Joined:
    Oct 30, 2016
    Posts:
    71
    Hey is there any way to have this for a 2d Game ? I mean i dont have normal colliders, anyway to transform make this work with raycast 2D ? Basically i want objects on the minimap to get highlighted when cursor or crosshair is over them. Any ideas ?
     
  14. calpolican

    calpolican

    Joined:
    Feb 2, 2015
    Posts:
    400
    What type of 2D game are you doing that would need a minimap? The whole idea of a minimap is showing the X & Z axis. Is it like a first person 2D? Do you follow your character form behind?
     
  15. Creiz

    Creiz

    Joined:
    Jun 6, 2017
    Posts:
    125
    @unitynoob24 thank you so much for this, I was looking for something like this. I can now click through my render cameras. Is there a way to make it listen to mouse hover events, though?
     
  16. Neltor

    Neltor

    Joined:
    Sep 9, 2015
    Posts:
    5
    Wow, this is great. Thanks for the post. Any idea of how you would get this working in a world space UI. With a collider or some type pointer?
     
  17. GameSecretLisa

    GameSecretLisa

    Joined:
    Jun 15, 2018
    Posts:
    1
    You've done a great job! Thanks a lot!!
     
  18. unity_V6C2VjJnkACZgQ

    unity_V6C2VjJnkACZgQ

    Joined:
    Oct 27, 2019
    Posts:
    8
    Is it possible to do a MouseOver, to create Tooltips with the Object names ?
     
  19. Farol

    Farol

    Joined:
    Mar 9, 2017
    Posts:
    16
    y made my days
     
  20. nico_st_29

    nico_st_29

    Joined:
    Mar 14, 2020
    Posts:
    68
    That's dedication right there.

    Thanks for the tip man, you saved me a lot of hassle.
     
  21. Ajikozau

    Ajikozau

    Joined:
    Mar 14, 2017
    Posts:
    6
    2 Years later this is still super useful. Wish it were a Unity3d integrated method :(
     
    nico_st_29 likes this.
  22. RubenHeeren

    RubenHeeren

    Joined:
    Dec 9, 2019
    Posts:
    9
    This worked for me. Thanks a lot man :)

     
  23. spark76

    spark76

    Joined:
    Mar 25, 2020
    Posts:
    1
    Bro You are Amazing,You saved aloooooot of my time ,Thanks alot
     
  24. ScetticoBlu

    ScetticoBlu

    Joined:
    Nov 26, 2020
    Posts:
    17
    Dude if I were you, I'd make a video tutorial because it's full of tutorial on how to make a minimap, but none of them explain how to make it interactable. And since now, I've looked for an answer. Actually, I've never heard about IPointerClickHandler, I suppose I'm the "unitynoob" here. Really thank you! <3
     
    nico_st_29 likes this.
  25. DIGITII

    DIGITII

    Joined:
    Sep 5, 2019
    Posts:
    1
  26. phil-R

    phil-R

    Joined:
    Nov 20, 2020
    Posts:
    9
    Also saved me a ton of time here - thank you!
     
  27. FloaterTS

    FloaterTS

    Joined:
    Sep 29, 2019
    Posts:
    2
    Hi, your solution has been a great help for me. However, I managed to simplify it a bit be removing some unnecessary parts:

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.EventSystems;
    3. using UnityEngine.UI;
    4.  
    5. public class MiniMapClick : MonoBehaviour, IPointerClickHandler
    6. {
    7.     public Camera miniMapCam;
    8.  
    9.     public void OnPointerClick(PointerEventData eventData)
    10.     {
    11.         if (RectTransformUtility.ScreenPointToLocalPointInRectangle(GetComponent<RawImage>().rectTransform, eventData.pressPosition, eventData.pressEventCamera, out Vector2 localCursorPoint))
    12.         {
    13.             Rect imageRectSize = GetComponent<RawImage>().rectTransform.rect;
    14.  
    15.             //localCursorPoint is the distance on x and y axis from the rect center point
    16.             //off we add the imageRectSize (by substracting because it's negative) which is the half size
    17.             //the rectangle so we can get the local coordinates x and y inside the rectangle
    18.             //then we divide them by the rectSize so we can get their ratios (between 0.0 - 1.0)
    19.             localCursorPoint.x = (localCursorPoint.x - imageRectSize.x) / imageRectSize.width;
    20.             localCursorPoint.y = (localCursorPoint.y - imageRectSize.y) / imageRectSize.height;
    21.        
    22.             CastMiniMapRayToWorld(localCursorPoint);
    23.         }
    24.     }
    25.  
    26.     private void CastMiniMapRayToWorld(Vector2 localCursor)
    27.     {
    28.         //we multiply the local ratios inside the minimap image rect with the minimap camera's pixelWidth so we can get the right pixel coordinates for the ray
    29.         Ray miniMapRay = miniMapCam.ScreenPointToRay(new Vector2(localCursor.x * miniMapCam.pixelWidth, localCursor.y * miniMapCam.pixelHeight));
    30.  
    31.         //we cast the ray through the minimap camera, which will hit the world point that it pointed towards
    32.         if (Physics.Raycast(miniMapRay, out RaycastHit miniMapHit, Mathf.Infinity))
    33.         {
    34.             GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
    35.             cube.transform.position = miniMapHit.point;
    36.         }
    37.     }
    38. }
     
    Liberation85 likes this.
  28. galaboy

    galaboy

    Joined:
    Dec 2, 2012
    Posts:
    8
    thanks for sharing the functionality, this helped me in my work. Do you have a tutorial for this functionality, so that i can understand how exactly the functionality works behind.Thanks for the help.