Hello!
I’m developing a First Person Perspective game in which one of the main mechanics is about interacting with objects found in the environment.
The end result I’m looking for is: I move the camera around and when I’m looking at an interactable object at a given distance, a text will display on top of that object (“Pick up herb”). When this text is being displayed, you click a key and an action happens.
Right now I have this system working but I’m curious as to what other approaches you guys would take for this. I feel like my approach is kind of ugly and there’s room for optimizing.
My approach looks like this:
- My character controller / camera object has a Sphere Trigger on it, with a radius of 5 units or so. Interactable objects have also a sphere trigger slightly bigger than the objects themselves.
- Interactable objects have a script (abstract class Interactable.cs) which has a OnTriggerEnter, checking if the colliding trigger is the Character’s. If so, OnTriggerEnter starts the Interacting coroutine (found in the Interaction.cs script, in the Character Controller game object).
- Interactable objects have a OnTriggerExit which, when the colliding trigger is the Character’s, calls a Interaction.cs script with this code (to avoid stopping the interaction if at least 1 object is in interacting radius):
Collider[] intersectingColliders = Physics.OverlapSphere(transform.position, GetComponent<SphereCollider>().radius, m_interactableLayer, QueryTriggerInteraction.Collide);
if (intersectingColliders.Length == 0)
{
m_interactionObject = null;
m_interactionUI.SetActive(false);
m_coroutineRunning = false;
StopCoroutine("Interacting");
}
- Interacting coroutine looks like this:
m_coroutineRunning = true;
while (true)
{
m_ray = Camera.main.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0)); // Middle point in the game screen
if (Physics.Raycast(m_ray, out m_rayHit, 5, m_interactableLayer, QueryTriggerInteraction.Collide))
{
m_interactionObject = m_rayHit.collider.gameObject;
m_interactionUI.GetComponent<UITextPosition>().m_object = m_interactionObject;
m_interactionUI.SetActive(true);
Interactable interactable = m_interactionObject.GetComponent<Interactable>();
m_interactionUI.GetComponentInChildren<TextMeshProUGUI>().text = interactable.m_textUI;
}
else
{
m_interactionUI.SetActive(false);
}
yield return new WaitForSeconds(0.05f);
}
- m_coroutineRunning is used to avoid starting this coroutine multiple times by multiple interactable objects near each other.
- m_ray and m_rayHit are a Ray and a RaycastHit which I cache for better performance.
- m_interactionObject is the interactable object. I keep a reference to it to know which object’s Interact method to call after receiving input.
- m_interactionUI is a Panel whose child is a TextMeshProUGUI where custom text (depending on the object) will be written. This panel will be active if a raycast hits an interactable object’s sphere trigger, inactive otherwise.
- I found 0.05f to be a good value for WaitForSeconds to give good (smooth) results, after trying 0.1f - 0.2f and not being satisfied with the results.
Some extra code, for instance the calculations for figuring out the panel’s location on the camera, are left out as they are not too important.
I’m really looking forward to seeing what you guys would to to tackle this problem. My approach works just fine for me and I don’t think any optimization is necessary but hopefully I can learn some nice tricks from you so I can apply them next time!
Regards.