Search Unity

Question Highlighting Interactable Objects

Discussion in 'Game Design' started by m_daniel26, May 13, 2023.

  1. m_daniel26

    m_daniel26

    Joined:
    Feb 7, 2023
    Posts:
    66
    I'm in the midst of teaching myself Unity to program my first game (a life-long dream, lol), and I'm currently trying to figure out how to highlight my interactable objects. I prefer something along the lines of the attached screen grab from "Bendy and the Dark Revival," where it's just a highlighted outline of the object, as opposed to completely changing the color of the object. I've tried numerous YouTube and other tutorials, but can't seem to find anything that A) actually works, and B) does what I'm looking for.

    I'm handling all of my interactables via a layer labeled "interactable", and using raycasting vs. OnMouseOver (which I hear is better for VR anyway, which I do eventually want to explore). Almost every tutorial I've found uses OnMouseOver, and I've tried modifying the scripts to fit with my raycasting interactable abstract class, but haven't gotten it to work. Any tips or suggestions (or even a referral to a good tutorial that works!) would be greatly appreciated!
     

    Attached Files:

  2. m_daniel26

    m_daniel26

    Joined:
    Feb 7, 2023
    Posts:
    66
    I'm working in 2021.3.20f1. Here's an example of the code I made for opening my interactable doors/cabinets, etc, and adding a UI open/close message to each event - I'm sure there's a way to just add an extra function onto this that will highlight the same object, but I haven't figured it out yet (and hoping to avoid spending two weeks on this like I did on figuring out the doors, lol).


    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class OpenInteractable : Interactable
    6. {
    7.     public Animator ANI;
    8.     public GameObject openText;
    9.     public GameObject closeText;
    10.  
    11.     public bool stayOpen = true;
    12.  
    13.     private bool open;
    14.  
    15.     private bool inReach;
    16.  
    17.     void Start()
    18.     {
    19.         openText.SetActive(false);
    20.         closeText.SetActive(false);
    21.  
    22.         ANI.SetBool("open", false);
    23.         ANI.SetBool("close", false);
    24.  
    25.         open = false;
    26.         inReach = false;
    27.     }
    28.  
    29.     public override void OnFocus()
    30.     {
    31.         if (!open)
    32.         {
    33.             inReach = true;
    34.             openText.SetActive(true);
    35.         }
    36.  
    37.         else if (stayOpen && open)
    38.         {
    39.             inReach = true;
    40.             closeText.SetActive(false);
    41.             openText.SetActive(false);
    42.         }
    43.  
    44.         else if (!stayOpen && open)
    45.         {
    46.             inReach = true;
    47.             closeText.SetActive(true);
    48.             openText.SetActive(false);
    49.         }
    50.     }
    51.  
    52.     public override void OnInteract()
    53.     {
    54.         if (inReach)
    55.         {
    56.             DoorFunction();
    57.         }
    58.     }
    59.  
    60.     public override void OnLoseFocus()
    61.     {
    62.         inReach = false;
    63.         openText.SetActive(false);
    64.         closeText.SetActive(false);
    65.     }
    66.  
    67.     void DoorFunction()
    68.     {
    69.         if (!open && inReach && Input.GetButtonDown("Interact"))
    70.         {
    71.             ANI.SetBool("open", true);
    72.             ANI.SetBool("close", false);
    73.             open = true;
    74.             openText.SetActive(false);
    75.             inReach = false;
    76.  
    77.             if (gameObject.tag == "Doors/Door")
    78.             {
    79.                 FMODUnity.RuntimeManager.PlayOneShot("event:/SFX/Doors/DoorOpen");
    80.             }
    81.  
    82.             else if (gameObject.tag == "Doors/Drawer")
    83.             {
    84.                 FMODUnity.RuntimeManager.PlayOneShot("event:/SFX/Doors/DrawerOpen");
    85.             }
    86.  
    87.             else if (gameObject.tag == "Doors/FilingCabinet")
    88.             {
    89.                 FMODUnity.RuntimeManager.PlayOneShot("event:/SFX/Doors/FilingCabinetOpen");
    90.             }
    91.  
    92.             else if (gameObject.tag == "Doors/Locker")
    93.             {
    94.                 FMODUnity.RuntimeManager.PlayOneShot("event:/SFX/Doors/LockerOpen");
    95.             }
    96.         }
    97.  
    98.         else if (!stayOpen && open && inReach && Input.GetButtonDown("Interact"))
    99.         {
    100.             ANI.SetBool("open", false);
    101.             ANI.SetBool("close", true);
    102.             open = false;
    103.             closeText.SetActive(false);
    104.             inReach = false;
    105.  
    106.             if (gameObject.tag == "Doors/Door")
    107.             {
    108.                 FMODUnity.RuntimeManager.PlayOneShot("event:/SFX/Doors/DoorClose");
    109.             }
    110.  
    111.             else if (gameObject.tag == "Doors/Drawer")
    112.             {
    113.                 FMODUnity.RuntimeManager.PlayOneShot("event:/SFX/Doors/DrawerClose");
    114.             }
    115.  
    116.             else if (gameObject.tag == "Doors/FilingCabinet")
    117.             {
    118.                 FMODUnity.RuntimeManager.PlayOneShot("event:/SFX/Doors/FilingCabinetClose");
    119.             }
    120.  
    121.             else if (gameObject.tag == "Doors/Locker")
    122.             {
    123.                 FMODUnity.RuntimeManager.PlayOneShot("event:/SFX/Doors/LockerClose");
    124.             }
    125.  
    126.         }
    127.  
    128.         else if (stayOpen && open && inReach && Input.GetButtonDown("Interact"))
    129.         {
    130.             ANI.SetBool("open", false);
    131.             ANI.SetBool("close", true);
    132.             open = true;
    133.             openText.SetActive(false);
    134.             closeText.SetActive(false);
    135.             inReach = false;
    136.  
    137.             if (gameObject.tag == "Doors/Door")
    138.             {
    139.                 FMODUnity.RuntimeManager.PlayOneShot("event:/SFX/Doors/DoorClose");
    140.             }
    141.  
    142.             else if (gameObject.tag == "Doors/Drawer")
    143.             {
    144.                 FMODUnity.RuntimeManager.PlayOneShot("event:/SFX/Doors/DrawerClose");
    145.             }
    146.  
    147.             else if (gameObject.tag == "Doors/FilingCabinet")
    148.             {
    149.                 FMODUnity.RuntimeManager.PlayOneShot("event:/SFX/Doors/FilingCabinetClose");
    150.             }
    151.  
    152.             else if (gameObject.tag == "Doors/Locker")
    153.             {
    154.                 FMODUnity.RuntimeManager.PlayOneShot("event:/SFX/Doors/LockerClose");
    155.             }
    156.         }
    157.     }
    158. }
     
  3. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,195
    This isn't super trivial if you want an outline as opposed to the entire object just glowing. For the outline effect, you're generally talking about having a shader on the material(s) that has an edge outline. When the object should be highlighted, you'd probably set some parameters on the material itself (via Material.SetInt or Material.SetFloat, or similar) to tell it to glow, and turn that off when the object is no longer hovered over. This involves learning how to make a shader (GLSL, or ShaderGraph probably) with edge outlining, or finding one you can use.

    A somewhat simpler option is just to adjust the Emission color of the object when it's hovered or selected. This would make the whole thing glow instead of just the outline, but it's something you could do with pretty much any existing shader. Just set the emissive color based on whether the object is hovered/selected. This approach, however, won't work well for objects that already have their own emission.
     
    Mallaboro likes this.
  4. BIGTIMEMASTER

    BIGTIMEMASTER

    Joined:
    Jun 1, 2017
    Posts:
    5,181
    Cloward makes great in depth tutorials for shaders:



    covers both unreal and unity, and covers theory enough that if you can find a video related only a little to the subject you want, by the time you are finished you'll probably be able to understand what to do for your own case.
     
  5. m_daniel26

    m_daniel26

    Joined:
    Feb 7, 2023
    Posts:
    66
    After the two weeks I spent getting my doors script to work, I think I'll settle for easy over exactly what I want on this relatively trivial detail, lol. How do you go about toggling the emission on or off via script?
     
  6. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,195
    This link has a simple example:

    https://answers.unity.com/questions/1019974/how-to-access-emission-color-of-a-material-in-scri.html

    Materials have a "SetColor" method. You'd need to double-check that your material's shader has "_EmissionColor" for its emission. (I think the built-in renderer uses "_EmissionColor", but HDRP uses "_EmissiveColor" instead.). If you click on a material in the Project window, look at the top-right corner of the Inspector window for a "...". Click it, and choose "Select Shader". Now the shader will be chosen in the inspector, and it will list all the properties that shader has:

    upload_2023-5-17_11-57-27.png
     
  7. DouglasPotesta

    DouglasPotesta

    Joined:
    Nov 6, 2014
    Posts:
    109
    m_daniel26 likes this.
  8. m_daniel26

    m_daniel26

    Joined:
    Feb 7, 2023
    Posts:
    66
    Yassss, that's exactly what I was looking for! I'm just having trouble figuring out how to toggle it on/off within my interactable script.

    What I've done as a temporary workaround is have a deactivated duplicate of each interactable with the highlight code attached, and toggling the highlighted version or the unhighlighted version as active accordingly. Which works in a pinch, but I'm sure there must be a more effective way of doing it, lol.

    Here's my Usable Interactable (for sinks/toilets/simple levers etc. - more or less the same as my door script quoted above, but simpler and also updated as I've been tweaking the scripts trying to sort out this highlighting thing) script as it currently stands - you'll see in the //comments that I attempted to incorporate the script that was included in the ReadMe file, but I couldn't quite get it to work right. It would highlight as soon as I looked at the object in question, but A) the highlight remains after looking away from the object and B) the interaction glitches and won't work.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class UsableInteractable : Interactable
    6. {
    7.     [Header("Animation Options")]
    8.     public Animator ANI;
    9.     public GameObject useText;
    10.  
    11.     //public GameObject highlightTarget;
    12.  
    13.     public bool oneUse = false;
    14.  
    15.     [Header("Highlight Options")]
    16.     public GameObject normalObject;
    17.     public GameObject highlightedObject;
    18.  
    19.     private bool used;
    20.  
    21.     private bool inReach;
    22.  
    23.     void Start()
    24.     {
    25.         useText.SetActive(false);
    26.  
    27.         ANI.SetBool("inUse", false);
    28.  
    29.         normalObject.SetActive(true);
    30.         highlightedObject.SetActive(false);
    31.  
    32.         used = false;
    33.         inReach = false;
    34.     }
    35.  
    36.     public override void OnFocus()
    37.     {
    38.         if (!used)
    39.         {
    40.             inReach = true;
    41.             useText.SetActive(true);
    42.  
    43.             /*var outline = highlightTarget.AddComponent<Outline>();
    44.  
    45.             outline.OutlineMode = Outline.Mode.OutlineVisible;
    46.             outline.OutlineColor = Color.blue;
    47.             outline.OutlineWidth = 10f;*/
    48.  
    49.             normalObject.SetActive(false);
    50.             highlightedObject.SetActive(true);
    51.         }
    52.  
    53.         else if (oneUse && used)
    54.         {
    55.             inReach = true;
    56.             useText.SetActive(false);
    57.  
    58.             normalObject.SetActive(true);
    59.             highlightedObject.SetActive(false);
    60.         }
    61.  
    62.         else if (!oneUse && used)
    63.         {
    64.             inReach = true;
    65.             useText.SetActive(true);
    66.  
    67.             /*var outline = highlightTarget.AddComponent<Outline>();
    68.  
    69.             outline.OutlineMode = Outline.Mode.OutlineVisible;
    70.             outline.OutlineColor = Color.blue;
    71.             outline.OutlineWidth = 10f;*/
    72.  
    73.             normalObject.SetActive(false);
    74.             highlightedObject.SetActive(true);
    75.         }
    76.     }
    77.  
    78.     public override void OnInteract()
    79.     {
    80.         if (inReach)
    81.         {
    82.             UseFunction();
    83.         }
    84.     }
    85.  
    86.     public override void OnLoseFocus()
    87.     {
    88.         inReach = false;
    89.         useText.SetActive(false);
    90.  
    91.         normalObject.SetActive(true);
    92.         highlightedObject.SetActive(false);
    93.     }
    94.  
    95.     void UseFunction()
    96.     {
    97.         if (!used && inReach && Input.GetButtonDown("Interact"))
    98.         {
    99.             ANI.SetBool("inUse", true);
    100.          
    101.             useText.SetActive(false);
    102.  
    103.             normalObject.SetActive(true);
    104.             highlightedObject.SetActive(false);          
    105.  
    106.             if (gameObject.tag == "Usable/Toilet")
    107.             {
    108.                 FMODUnity.RuntimeManager.PlayOneShot("event:/SFX/Usables/ToiletHandle");
    109.             }
    110.  
    111.             else if (gameObject.tag == "Usable/Sink")
    112.             {
    113.                 FMODUnity.RuntimeManager.PlayOneShot("event:/SFX/Usables/FaucetSqueak");
    114.             }
    115.  
    116.             else if (gameObject.tag == "Usable/Elevator1")
    117.             {
    118.                 FMODUnity.RuntimeManager.PlayOneShot("event:/SFX/Usables/Elevator1");
    119.             }
    120.  
    121.             used = true;
    122.             inReach = false;
    123.  
    124.         }
    125.  
    126.         else if (!oneUse && used && inReach && Input.GetButtonDown("Interact"))
    127.         {
    128.             ANI.SetBool("inUse", true);
    129.            
    130.             useText.SetActive(false);
    131.  
    132.             normalObject.SetActive(true);
    133.             highlightedObject.SetActive(false);
    134.  
    135.             if (gameObject.tag == "Usable/Toilet")
    136.             {
    137.                 FMODUnity.RuntimeManager.PlayOneShot("event:/SFX/Usables/ToiletHandle");
    138.             }
    139.  
    140.             else if (gameObject.tag == "Usable/Sink")
    141.             {
    142.                 FMODUnity.RuntimeManager.PlayOneShot("event:/SFX/Usables/FaucetSqueak");
    143.             }
    144.  
    145.             else if (gameObject.tag == "Usable/Elevator1")
    146.             {
    147.                 FMODUnity.RuntimeManager.PlayOneShot("event:/SFX/Usables/Elevator1");
    148.             }
    149.  
    150.             used = true;
    151.             inReach = false;
    152.  
    153.         }
    154.  
    155.         else if (oneUse && used && inReach && Input.GetButtonDown("Interact"))
    156.         {
    157.             ANI.SetBool("inUse", false);
    158.            
    159.             useText.SetActive(false);
    160.  
    161.             normalObject.SetActive(true);
    162.             highlightedObject.SetActive(false);
    163.  
    164.             used = true;
    165.             inReach = false;
    166.         }
    167.     }
    168. }
     
    DouglasPotesta likes this.