Display pickup names and pick them when clicked on their name

Hi there!

I’m trying to improve the current pickup system with some functionalities:

  1. Ability to display the name of the pickup above the pickup gameObject
  2. Ability to go and collect the gameObject when the pickup name is clicked (just as if the player had clicked on the pickup’s collider).
  3. Ability to toggle display pickup names in game when a key is pressed (or on key down)

1.Ability to display the name of the pickup above the pickup gameObject

The first point was quite straightforward: I added, in the root pickup prefab , a canvas (just as we did for the health bar), and added a TMP text in it. After that, I referenced that text field in my ClickablePickup script and populated it with the item name.


2. Ability to go and collect the gameObject when the pickup name is clicked
The second point was more tricky…
I’ll put it with my own words and try to explain what I have understood.
Because the TMP textfield is a UI element:

  • it reacts to the EventSystem.current.IsPointerOverGameObject method that we used for InteractWithUi
  • it is not trapped by the Physics.Raycast that we use in InteractWithComponents

So, here is what I’ve done:

  • I’ve added, to the gameObject holding the TMP text field component, a component that implements the IRaycastable interface: InGameClickableText
  • This component also gets a reference to the pickup root gameObject (which holds a ClickablePickup component, that implements IRaycastable too)
  • At the end of the day, the InGameClickableText component is just used to call the IRaycastable methods of its referenced gameObject.
    Here is the code:
public class InGameClickableText : MonoBehaviour, IRaycastable
{
    [SerializeField] GameObject IRaycastableGameObject;
    IRaycastable retarget = null;
    private void Start()
    {
        IRaycastableGameObject.TryGetComponent<IRaycastable>(out retarget);
    }

    public CursorType GetCursorType()
    {
        if (retarget != null)
        {
            return retarget.GetCursorType();
        }
        return CursorType.None;
    }

    public bool HandleRaycast(PlayerController callingController)
    {
        if (retarget != null)
        {
            return retarget.HandleRaycast(callingController);
        }
        return false;
    }
}

(NB: I had to play do some weird stuff because it is not possible (as far as I know) to get a serialized field of an interface type. I could have got a serialized field of type ClickablePickup, but I want my InGameClickableText to be generic. Who knows, maybe tomorrow I’ll want to start a dialogue with an NPC by clicking his name?)

  • Anyway, once this script was set up, I just had to tweak the InteractWithUI method, in order to verify if the UI we were hovering over was implementing IRaycastable:
private bool InteractWithUi()
{
    if (isInteractingWithUI && Input.GetMouseButtonUp(0))
        isInteractingWithUI = false;
    
    if (EventSystem.current.IsPointerOverGameObject())
    {
        if (!CanInteractWithUIOnly())
        {
            PointerEventData pointer = new PointerEventData(EventSystem.current);
            pointer.position = new Vector2(Input.mousePosition.x, Input.mousePosition.y);
            List<RaycastResult> raycastResults = new List<RaycastResult>();
            EventSystem.current.RaycastAll(pointer, raycastResults);
            if (raycastResults.Count > 0)
            {
                //We only check the first object: if it's IRaycastable, do the IRaycast job
                //Otherwise it's a basic UI element
                GameObject target = raycastResults[0].gameObject;

                if (target.TryGetComponent<IRaycastable>(out IRaycastable interactableInGameUI))
                {
                    //Interacting with InGameUI
                    if (interactableInGameUI.HandleRaycast(this))
                    {
                        SetCursor(interactableInGameUI.GetCursorType());
                        return true;
                    }
                }
            }
        }

        if (Input.GetMouseButtonDown(0))
            isInteractingWithUI = true;
        SetCursor(CursorType.UI);
        return true;
    }
    return isInteractingWithUI;
}

(The idea here, is: if an UI element implements IRaycastable, it’s an inGame text, that the player can interact with → treat it as a component, otherwise, it’s a “true UI element” → treat it as UI)
Also, I know there may be a flaw here, since I only check the “first” element of the rayCastResults… and we’ve seen before that the order in which Raycast renders it’s result is not guaranteed

I hope that my solution is not too messy or dirty. Any suggestion (event if it’s a radically different approach) is welcome!


3.Ability to toggle display pickup names in game when a key is pressed (or on key down)

The third point is the point that I struggle the most with. I can’t figure out what approach would be the best solution, in terms of performance and from a global architecture point of view.
Do I need to implement, in every pickup, a listener to an event triggered by a button? This listener would be in charge of toggling display / hiding of the text? What if a have a lot of pickups in my scene? What if I decide to have the player to hold the button to display pickup name instead of simply pressing it?
Do I need to handle the button input in the PlayerController, and populate a bool variable, reflecting the display / hiding of the pickup text? Then, if the value of this variable changes, it triggers the event that the pickups are listening to?

I also thought of a global canvas that would contain all the pickup names in different text fields, each one having a reference to the pickup they display the name of. That would handle point 3, but functionalities 1 look more complicated (ie calculate the position of the text in the canvas in order to look like it is above its pickup)?

Any other idea or approach?

Thanks for your help!

Ok, I got the solution here: I only needed to use a Layer Mask.

Here it goes:

3.Ability to toggle display pickup names in game when a key is pressed (or on key down)

  • Create a dedicated LayerMask
  • Add the TMP textfield that displays the name of the pickup to this layer
  • On the camera, add a component that toggles the display of this layer (I found the backbone of this code on Unity forum):
    public class ToggleCameraLayerVisibility : MonoBehaviour
    {
        [SerializeField] KeyCode toggleKey = KeyCode.Escape;
        [SerializeField] string targetLayer;

        void Start()
        {
            Hide();
        }


        void Update()
        {
            if (Input.GetKeyDown(toggleKey))
            {
                Show();
            }
            if (Input.GetKeyUp(toggleKey))
            {
                Hide();
            }
        }


        // Turn on the bit using an OR operation:
        private void Show()
        {
            Camera.main.cullingMask |= 1 << LayerMask.NameToLayer(targetLayer);
        }
        // Turn off the bit using an AND operation with the complement of the shifted int:
        private void Hide()
        {
            Camera.main.cullingMask &= ~(1 << LayerMask.NameToLayer(targetLayer));
        }


        // Toggle the bit using a XOR operation:
        private void Toggle()
        {
            Camera.main.cullingMask ^= 1 << LayerMask.NameToLayer(targetLayer);
        }
    }

(Toggle function is here only for information, not used in my code, but could definitely be if we choose to toggle view on button pressed)

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms