Skip to content

Usage Guide

One-shot query (allocating)

csharp
using Carrot.Interaction;

InteractionSettings settings = InteractionSettings.Default;
settings.MaxDistance = 10f;

InteractionResult result = Interaction3D.FromScreen(Camera.main, screenPosition, settings);

if (result.Hit)
{
    result.Reactor.OnInteract(/* context */);
}

// Everything the probe touched, near to far — including non-reactive blockers:
foreach (InteractionHit hit in result.All)
{
    Debug.Log($"{hit.GameObject.name} @ {hit.Distance} (reactor: {hit.IsReactor})");
}

Per-frame query (zero-alloc)

Pre-allocate the buffers once and reuse them:

csharp
private readonly RaycastHit[]     raycasts = new RaycastHit[16];
private readonly InteractionHit[] hits     = new InteractionHit[16];

void Update()
{
    int count = Interaction3D.FromRayNonAlloc(ray, settings, raycasts, hits,
                                              out InteractionHit reactor, out int reactorIndex);

    if (reactorIndex >= 0)
    {
        // reactor.Receiver is the thing that should react
    }
}

Interaction2D.FromScreen (overlap a sprite) and InteractionUI.FromScreen (EventSystem) share the same InteractionResult shape. UI is allocating only — the EventSystem raycast allocates internally.

Sphere vs ray, blocking, masks

csharp
settings.Radius = 0.25f;            // > 0 → sphere cast instead of a thin ray
settings.BlockedByCollision = true; // a nearer non-reactor (a wall) hides the reactor behind it
settings.Layers = LayerMask.GetMask("Interactable", "Default");
settings.Mask = InteractionMask.Channel(0) | InteractionMask.Channel(2);

A receiver responds only when settings.Mask overlaps its own InteractionMask. The LayerMask culls the physics cast; the InteractionMask decides which of the hit things actually react.

Components

csharp
// On the interactor (e.g. the player / camera):
var host = gameObject.AddComponent<Interaction3DHost>();

// Wire the gesture layer to it (input stays decoupled):
PointerGestureSource.Gesture.Add(g =>
{
    host.ScreenPosition = g.Position;          // ScreenPoint mode
    if (g.Phase == PointerGesturePhase.Clicked) host.Interact();
});

// React globally:
Interaction3DHost.FocusGained.Add(ctx => /* highlight ctx... */ );

Put an InteractionReceiver on anything reactive (it needs a Collider / Collider2D / Graphic to be hit), set its mask, and wire the UnityEvents or subclass and override OnFocus / OnUnfocus / OnInteract.

"Has the camera clearly seen it?"

csharp
var eye = Camera.main.gameObject.AddComponent<InteractionCamera>();
eye.Register(door.transform);

// Later — drive a state machine / sequence off it:
InteractionCamera.TargetSeen.Add(t => Debug.Log($"Player has clearly seen {t.name}"));
// or poll: if (eye.HasSeen(door.transform)) { ... }

"Clearly seen" = inside the frustum, within range, optionally unoccluded and large enough on screen, held continuously for the dwell time.

Carrot