Interaction System
Raycast-based interaction with prompt UI, hold-to-interact option, and IInteractable interface. Press E to interact with anything.
How to Use
Attach InteractionSystem to your player (or camera)
Set interact range and layer mask in inspector
Implement IInteractable on any object you want to interact with:
public class Door : MonoBehaviour, IInteractable {
public string InteractionPrompt => "Open Door";
public void Interact(GameObject interactor) { /* open */ }
}
Hook OnPromptShow/OnPromptHide to your UI text
Press E (configurable) to interact, or enable hold-to-interact
Features
- Raycast-based detection from camera or player forward direction
- IInteractable interface for flexible object interaction
- Hold-to-interact mode with progress callback for radial UI
- UnityEvent callbacks for prompt show/hide, hold progress, and interaction
- Configurable interact key, range, and layer mask
- Automatic fallback to Main Camera if no raycast origin is assigned
When to Use This
Perfect for RPGs, horror games, adventure games, and any first-person or third-person game where the player interacts with objects in the world. Use this for doors, NPCs, levers, pickups, computers, and any interactable prop. The hold-to-interact mode is ideal for survival games and immersive sims where interactions feel more deliberate.
Common Mistakes
Objects must implement the IInteractable interface to be detected — simply having a collider is not enough. If raycastOrigin is not assigned, the script falls back to Camera.main, which can be null if your camera is not tagged MainCamera. The interactLayer mask must include the layer of your interactable objects, or the raycast will pass right through them.
Source Code
using UnityEngine;
using UnityEngine.Events;
/// <summary>
/// Interface for objects that can be interacted with.
/// Implement this on doors, NPCs, pickups, levers, etc.
/// </summary>
public interface IInteractable
{
string InteractionPrompt { get; }
void Interact(GameObject interactor);
}
/// <summary>
/// Raycast-based interaction system. Attach to the player or camera.
/// Detects IInteractable objects and shows a prompt.
/// </summary>
public class InteractionSystem : MonoBehaviour
{
[Header("Raycast")]
[SerializeField] private float interactRange = 3f;
[SerializeField] private LayerMask interactLayer = ~0;
[SerializeField] private Transform raycastOrigin;
[Header("Input")]
[SerializeField] private KeyCode interactKey = KeyCode.E;
[SerializeField] private bool holdToInteract = false;
[SerializeField] private float holdDuration = 1f;
[Header("Events")]
public UnityEvent<string> OnPromptShow;
public UnityEvent OnPromptHide;
public UnityEvent<float> OnHoldProgress;
public UnityEvent<GameObject> OnInteracted;
private IInteractable currentTarget;
private GameObject currentTargetObject;
private float holdTimer;
private bool isPromptShowing;
private void Start()
{
if (raycastOrigin == null)
{
Camera cam = Camera.main;
if (cam != null) raycastOrigin = cam.transform;
else raycastOrigin = transform;
}
}
private void Update()
{
CheckForInteractable();
HandleInput();
}
private void CheckForInteractable()
{
Ray ray = new Ray(raycastOrigin.position, raycastOrigin.forward);
IInteractable found = null;
GameObject foundObject = null;
if (Physics.Raycast(ray, out RaycastHit hit, interactRange, interactLayer))
{
found = hit.collider.GetComponent<IInteractable>();
if (found == null)
found = hit.collider.GetComponentInParent<IInteractable>();
if (found != null)
foundObject = hit.collider.gameObject;
}
if (found != currentTarget)
{
currentTarget = found;
currentTargetObject = foundObject;
holdTimer = 0f;
if (found != null)
{
OnPromptShow?.Invoke(found.InteractionPrompt);
isPromptShowing = true;
}
else if (isPromptShowing)
{
OnPromptHide?.Invoke();
isPromptShowing = false;
}
}
}
private void HandleInput()
{
if (currentTarget == null) return;
if (holdToInteract)
{
if (Input.GetKey(interactKey))
{
holdTimer += Time.deltaTime;
OnHoldProgress?.Invoke(holdTimer / holdDuration);
if (holdTimer >= holdDuration)
{
PerformInteraction();
holdTimer = 0f;
}
}
else
{
holdTimer = 0f;
OnHoldProgress?.Invoke(0f);
}
}
else
{
if (Input.GetKeyDown(interactKey))
PerformInteraction();
}
}
private void PerformInteraction()
{
currentTarget.Interact(gameObject);
OnInteracted?.Invoke(currentTargetObject);
}
}