Interaction System

Area2D proximity, Press E, highlights, groups, and collision exceptions.

stablegodotinteractionnpc
On this page
NPC name label and Press E prompt when player is in range
Proximity highlight: name above the guest, Press 'E' below — toggled when the player enters the guest Area2D.

Pattern

  1. Guest owns an Area2D (InteractionArea) with a tall rectangle.
  2. body_entered / body_exited track player_in_range when the body is in group "player".
  3. _unhandled_input on the guest listens for interact (E).
  4. Guest finds the dialog box via group "dialog_box" and calls start_dialog.
func _unhandled_input(event: InputEvent) -> void:
	if not can_talk() or not player_in_range:
		return
	if not event.is_action_pressed("interact"):
		return
	var dialog_box = get_tree().get_first_node_in_group("dialog_box")
	if dialog_box != null and not dialog_box.is_open:
		start_conversation()
		get_viewport().set_input_as_handled()

set_input_as_handled() stops the same E from also advancing dialog in the same frame (dialog box also listens for interact).

Highlight UI

InteractionHighlight is a Control child with name + “Press ‘E’” labels. Visibility:

$InteractionHighlight.visible = player_in_range and can_talk() and not _in_conversation

Implemented: highlight only; no world-space outline shader.

Who is interactable?

can_talk() allows AT_STATION or WALKING — you can intercept a walking guest. Talking sets _in_conversation, zeroes velocity, and reconnects resume logic on dialog_closed.

Collision exceptions

Guests must not shove the clerk. On ready (deferred) and on enter:

add_collision_exception_with(player)
player.add_collision_exception_with(self)

Guests still collide with each other (layer 2) so queues form at stations.

Layers / masks (interaction vs physics)

LayerWho
1Player
2Guests
3 (bit value 4)Walls / furniture

The interaction Area2D detects physics bodies; keep the player on a layer the area can see (default masks usually include layer 1).

Signals vs hard references

We intentionally do not export a NodePath to the dialog box. Groups survive scene reparents better. Hard $"../dialog_box" broke when UI moved under a CanvasLayer.

Reusable recipe

See Press-E interaction.