简体   繁体   中英

interact with gameobjects in Unity

I want to interact with gameobjects in Unity. These objects might be keys, doors, chests, ...

Let's say there is a key on a table. When the player comes closer it should get outlined by a shader and a text appears "pick up key".

I created an interface for interactable gameobjects.

public interface IInteractable
{
    string InteractabilityInfo { get; }

    void ShowInteractability();

    void Interact();
}

By doing this I can add the interface component to my Key script

public class Key : MonoBehaviour, IInteractable
{
    public string InteractabilityInfo { get { return "some text here"; } }

    public void ShowInteractability()
    {
        // Outline Shader maybe?
    }

    public void Interact()
    {
        // Do something with the key
    }
}

When it comes to a script that checks if something is interactable I created a script that creates a raycast which checks for interactables. (I attach this script to my FPS camera)

public class InteractabilityCheck : MonoBehaviour
{
    private const int RANGE = 3;

    private void Update()
    {
        RaycastHit hit;
        if (Physics.Raycast(transform.position, transform.forward, out hit, RANGE))
        {
            IInteractable interactable = hit.collider.GetComponent<IInteractable>();

            if (interactable != null)
            {
                interactable.ShowInteractability();

                if (Input.GetKeyDown(KeyCode.E))
                {
                    interactable.Interact();
                }
            }
        }
    }
}

This script tries to get the interface component and calls the methods from it if it's not null.

This code works fine but what I don't like is that the Raycast fires one per frame. Is there another way achieving interactability?

Yes, raycasting is "expensive" and calling it every single frame is not cool at all and there are many ways to avoid that. In your case you can simple restructure the code like that:

private void Update()
{

    if (Input.GetKeyDown(KeyCode.E))
    {
      RaycastHit hit;
      if (Physics.Raycast(transform.position, transform.forward, out hit, RANGE))
      {
          IInteractable interactable = hit.collider.GetComponent<IInteractable>();

          if (interactable != null)
          {
              interactable.ShowInteractability();
              interactable.Interact();      
          }
      }
  }
}

Also a good idea is to get the gameplay logic in a separed method just so so you can modify it easier later and avoid spaggeti xaxa. Like so:

public void interactWithYourObject()
{
    RaycastHit hit;
    if (Physics.Raycast(transform.position, transform.forward, out hit, RANGE))
      {
          IInteractable interactable = hit.collider.GetComponent<IInteractable>();

          if (interactable != null)
          {
              interactable.ShowInteractability();
              interactable.Interact();      
          }
      }
}

and then in your update just call it if your condition is true like that:

private void Update()
{
   RaycastHit hit;
   if (Physics.Raycast(transform.position, transform.forward, out hit, RANGE))
   {
      interactWithYourObject()
   }
}

An alternative method is to place a spherical trigger on the interactables , you can then control the range through the radius of the spherical trigger. This is similar to another question I just answered so I've just reworked the code.

using UnityEngine;

[RequireComponent(typeof(SphereCollider))]
internal abstract class CollisionTrigger : MonoBehaviour
{
    private bool _isPlayerInsideTrigger = false;

    private void Awake()
    {
        if(!GetComponent<SphereCollider>().isTrigger)
        {
            Debug.LogError("Please set the sphere collider to a trigger.");
            enabled = false;
            return;
        }
    }

    private void Update()
    {
        if(_isPlayerInsideTrigger)
        {
            FakeOnTriggerStay();
        }
    }

    private void OnTriggerEnter(Collider collider)
    {
        if(!collider.CompareTag("Player")) return;
        _isPlayerInsideTrigger = true;
    }

    public abstract void FakeOnTriggerStay();

    private void OnTriggerExit(Collider collider)
    {
        if(!collider.CompareTag("Player")) return;
        _isPlayerInsideTrigger = false;
    }
}

This is an example to demonstrate what your Key class would look like using the provided class above.

internal class Key : CollisionTrigger
{
    public override void FakeOnTriggerStay()
    {
        // Show the user they can now interact with it.
        // Outline Shader maybe?

        if(Input.GetKeyDown(KeyCode.E))
        {
            // Do something with the key.
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM