All Area3D nodes are triggered when only one should be

Godot Version

Godot 4.7 dev4

Question

I have a component that lets the player interact with an object. Depending on if the player is close enough, if the player is standing in front of (or behind) it, and if the player is facing the correct direction, it will call a signal to let the object know that it can do its thing.

That’s not the issue (though I’d welcome advice/improvements on it). It’s just that when there’s multiple objects with this ComponentInteractable node, all the ComponentInteractable Area3D nodes will be triggered when I enter/exit and I only want the one the player has entered/exited.

Here is the code:

@tool
class_name ComponentInteractable
extends Node


## A component that handles [code]Player[/code] interactability with game objects (e.g. signposts, treasure chests, and NPCs).


@export var enabled: bool = true

@export_group("Use Distance for Detection")
@export_custom(PROPERTY_HINT_GROUP_ENABLE, "Use Distance for Detection") var use_distance_for_detection: bool = true:
	set(value):
		if value:
			area.body_entered.connect(_on_body_entered)
			area.body_exited.connect(_on_body_exited)
@export_range(0, 150, 0.001) var range_threshold: float = 2


@export_group("Use Dot Product to Find Face")
@export_custom(PROPERTY_HINT_GROUP_ENABLE, "Use Dot Product to Find Face") var use_dot_product: bool = true
@export_range(-1, 0, 0.001) var can_use_front_threshold: float = -0.45:
	set(value):
		value = clampf(abs(value), -1, 0)
		can_use_front_threshold = value
		if not unlink_front_back_thresholds:
			can_use_back_threshold = -can_use_front_threshold


@export_subgroup("Unlink Front-Back Thresholds")
@export_custom(PROPERTY_HINT_GROUP_ENABLE, "Unlink Front-Back Thresholds") var unlink_front_back_thresholds: bool = false:
	set(value):
		if not value:
			can_use_front_threshold = can_use_front_threshold # Activate setter
@export_range(0, 1, 0.001) var can_use_back_threshold: float = 0.45:
	set(value):
		value = clampf(value, 0, 1)
		can_use_back_threshold = value


@export_group("Player Direction Angles")
@export_range(0, 360, 0.001) var player_direction_angle_threshold_front: float = 110
@export_range(0, 360, 0.001) var player_direction_angle_threshold_back: float = 50


@export_group("")
@export var show_debug: bool = true


@onready var area: Area3D = $Area
@onready var lbl_debug: Label = $Label


var in_range: bool
var can_use_front: bool
var can_use_back: bool


signal use


func _ready() -> void:
	if not use_distance_for_detection:
		if not area.body_entered.is_connected(_on_body_entered):
			area.body_entered.connect(_on_body_entered)
		if not area.body_exited.is_connected(_on_body_exited):
			area.body_exited.connect(_on_body_exited)



func ascertain_usability(object: Node3D = get_parent(), player: Player = Global.player) -> void:
# Either use the player's distance to the object or Area3D nodes to determine if the player is in range.
	var player_distance_to_object: float = player.get_distance_to(object)
	if use_distance_for_detection:
		in_range = player_distance_to_object <= range_threshold

	if enabled and in_range:
		can_use_front = true
		can_use_back = true

	if enabled and in_range and use_dot_product:
# This determines if the player is in front of, behind, or to the sides of the object. 
		var object_global_forward_direction: Vector3 = Vector3.FORWARD.rotated(Vector3.UP, object.global_rotation.y) # Get the object's direction in global space
		var player_global_direction_from_object: Vector3 = player.global_position.direction_to(object.global_position) # Get the player's direction to the object in global space
		var dot: float = object_global_forward_direction.dot(player_global_direction_from_object) # Returns a range where -1 for directly in front, 0 for perpendicular on either side, 1 for directly behind.
# Determine if the player is facing the object or not.
		var object_angle_to_player_direction: float = object_global_forward_direction.angle_to(Vector3.FORWARD.rotated(Vector3.UP, player.global_rotation.y))
		can_use_front = dot <= can_use_front_threshold and object_angle_to_player_direction >= deg_to_rad(player_direction_angle_threshold_front)
		can_use_back = dot >= can_use_back_threshold and object_angle_to_player_direction <= deg_to_rad(player_direction_angle_threshold_back)

		#region Debug Stuff
		lbl_debug.text = """Distance to Player: {dist}
In Range?: {in_range}
Dot Product: {dot}
Obj Forward Dir θ to Player Dir: {angle}
Can Use Front: {front}
Can Use Back: {back}
""".format(
			{
				"dist": snappedf(player_distance_to_object, 0.01),
				"in_range": in_range, 
				"dot": snappedf(dot, 0.01),
				"angle": str(snappedf( rad_to_deg(object_angle_to_player_direction), 0.01) ) + "°",
				"front": can_use_front,
				"back": can_use_back,
			}
		)
		#endregion

# Do something when the action button is pressed.
	if enabled and Input.is_action_just_pressed("action"):
		if can_use_front:
			use.emit("front")
			print('front')
		if can_use_back:
			use.emit("back")
			print('back')



func _on_body_entered(body: CharacterBody3D) -> void:
	if body is Player:
		in_range = true
		if OS.is_debug_build():
			lbl_debug.show()
		print("body entered")


func _on_body_exited(body: CharacterBody3D) -> void:
	if body is Player:
		in_range = false
		if OS.is_debug_build():
			lbl_debug.hide()
		print("body exited")

(Also if anyone has any advice on how to make a visualization of the dot product and ranges to create a kind of vision cone that, especially one that appears in the Editor, that would be super awesome too!)

extending Node is certainly not going to help, this breaks the Node3D transform inheritance; if you add “ComponentInteractable” as a child of any other Node3D the Node will ignore it’s parent’s transform which is unlikely to be intentional, if you haven’t accounted for this all of the Area’s will be at the same place.

1 Like

I’ve changed it to extends Node3D and changed the node itself to Node3D and that seems to have fixed it! I didn’t expect that this disconnect could happen if mixing different Node types since Node3D (and all other nodes) inherits from Node.

Thank you for your help! ^^

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.