Area_entered/exiting not working immediately? Delay?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By PIZZA_ALERT

TLDR; Are there subsecond situations where things go “wrong” with area_exited/entered signals for 2D collision? i.e. running functions for the condition that would be true if the area was entered, but it’s since been exited?

My game has a dialog system where you can press an assigned “check” button at any time during normal play to check the environment directly in front of the player’s facing direction. A dialog box appears when you activate this ability, discussing whatever it determines you’re “checking.” Think Mother series, if you’ve played it. It should work like this:

The player has a small, capsule-shaped “checkbox” collision that rotates with their facing direction, and all checkable assets in the world have a rectangular “seenbox” collision roughly the size of their world collision shape (the collision that keeps them from traveling thru walls, etc.). See here, where the player is the fox thing facing right, the NPC is the man facing up, the green line is going around half of the checkbox, and the red line is going around half of the seenbox:

Once you press the “check” button, you get a dialog box at the bottom of the screen that either reads “No problem here” if there’s no interference between the “checkbox” and any “seenbox” or reads whatever I have loaded into a JSON file for that specific asset. If that asset is an NPC, they are given a name and a secondary textbox appears to show this name. In the instance of the NPC in the image above, I have him named Tim and he says “Suh, dude?” An autoload script called DialogManager loads in the JSON file at bootup and assigns global variables called “checkName” (the name of the checked NPC, if the checked asset is an NPC) and “checkText” (the dialog to be read when an asset is checked) continuously. The game then determines which dialog ID to use based on these variables. Here’s DialogManager’s script:

extends Control

# Constants
export(String, FILE, "*.json") var dialog_file


# Variables
var dialog = []
var current_dialog_index = 0

onready var nextText = "Empty"
onready var checkText = "No problem here."
onready var checkType = "Item"
onready var checkName = "Error"


# Functions
func _ready():
	play()


func play():
	dialog = load_dialog()


func load_dialog():
	var file = File.new()
	if file.file_exists(dialog_file):
		file.open(dialog_file, file.READ)
		return parse_json(file.get_as_text())


func run_dialog():
	checkText = dialog[current_dialog_index]['Text']
	checkType = dialog[current_dialog_index]['Type']
	if checkType == "NPC":
		checkName = dialog[current_dialog_index]['Name']

There’s also a textbox script and a world UI script that manage the rest like displaying things correctly. Just a warning that those are some very long scripts though as there are a lot of conditionals.

Textbox:

# Extensions
extends Control

# Variables
var state = IDLE
var pause_char = "="
var break_char = "|"
var split_text = []
var next_split_text = ""
var find_delimiter = 0
var remote_next_text = ""
var last_text = ""
var next_percent_visible = 0.0
var next_split = []
var pause_split = []

export (bool) var text = true
export var text_lines = 1
export var size_x = 30
export var size_y = 30
export var read_speed = 0.025

onready var textOnScreen = false
onready var pauseDelay = false
onready var bubble = $Bubble
onready var nextLine = $NextLine
onready var label = $Label
onready var tween = $Tween
onready var startTimer = $StartTimer
onready var pauseTimer = $PauseTimer


# Enumerations
enum {
	IDLE,
	READING,
	FINISHED,
}


# Functions
func _ready():
	if text == true:
		label.show()
		size_y = 15 * text_lines + 15
		label.rect_size.x = size_x - 20
		label.max_lines_visible = text_lines
	else:
		label.hide()
	
	bubble.rect_size.x = size_x
	bubble.rect_size.y = size_y
	nextLine.rect_size.x = size_x
	nextLine.rect_size.y = size_y
	
	if size_x < bubble.rect_min_size.x:
		size_x = bubble.rect_min_size.x
	if size_y < bubble.rect_min_size.y:
		size_y = bubble.rect_min_size.y
	
	nextLine.hide()


func _process(delta):
	match state:
		IDLE:                                                      
			idle_state(delta)
		READING:
			reading_state(delta)
		FINISHED:
			finished_state(delta)
	
	if visible:
		bubble.rect_size.x = size_x
		bubble.rect_size.y = size_y
		nextLine.rect_size.x = size_x
		nextLine.rect_size.y = size_y
	
		if size_x < bubble.rect_min_size.x:
			size_x = bubble.rect_min_size.x
		if size_y < bubble.rect_min_size.y:
			size_y = bubble.rect_min_size.y


func idle_state(delta):
	nextLine.hide()
	label.percent_visible = 0.0
	if textOnScreen == true:
		if pauseDelay == false:
			if Input.is_action_just_pressed("ui_accept") or Input.is_action_just_pressed("check"):
				startTimer.start()


func reading_state(delta):
	if textOnScreen == true:
		if pauseDelay == false:
			if Input.is_action_just_pressed("ui_accept") or Input.is_action_just_pressed("check"):
				label.percent_visible = 1.0
				tween.seek(100)
				if pauseDelay == false:
					if Input.is_action_just_pressed("ui_accept") or Input.is_action_just_pressed("check"):
						_on_Tween_all_completed()


func finished_state(delta):
	label.percent_visible = 1.0
	pauseDelay = false
	if textOnScreen == true:
		if pauseDelay == false:
			if Input.is_action_just_pressed("ui_accept") or Input.is_action_just_pressed("check"):
				state = IDLE


func set_current_frame(value):
	nextLine.texture.current_frame = value


func _on_StartTimer_timeout():
	next_percent_visible = 0.0
	display_text(DialogManager.nextText)


func display_text(next_text):
	nextLine.hide()
	remote_next_text = next_text
	if break_char in next_text or pause_char in next_text:
		delimit_text(next_text)
	else:
		label.percent_visible = next_percent_visible
		label.text = next_text
		tween.interpolate_property(label, "percent_visible",
			next_percent_visible, 1.0, len(next_text) * read_speed,
			Tween.TRANS_LINEAR)
		tween.start()


func delimit_text(next_text):
	if break_char in next_text and pause_char in next_text:
		find_delimiter = next_text.find(break_char) - next_text.find(pause_char)
		if find_delimiter <= 1:
			split_text = next_text.split(break_char, true, 1)
		elif find_delimiter > 1:
			split_text = next_text.split(pause_char, true, 1)
	elif break_char in next_text and not pause_char in next_text:
		split_text = next_text.split(break_char, true, 1)
		find_delimiter = 1
	elif not break_char in next_text and pause_char in next_text:
		split_text = next_text.split(pause_char, true, 1)
		find_delimiter = 2
	
	next_text = split_text[0]
	next_split_text = split_text[1]
	
	display_text(next_text)


func _on_Tween_started(object, key):
	state = READING


func _on_Tween_all_completed():
	if find_delimiter != 0:
		if find_delimiter <= 1:
			next_percent_visible = 0.0
			set_current_frame(0)
			nextLine.show()
			if label.percent_visible == 1.0 and tween.is_active() == false:
				if pauseDelay == false:
					if Input.is_action_just_pressed("ui_accept") or Input.is_action_just_pressed("check"):
						return_to_display()
		else:
			pauseTimer.start()
			pauseDelay = true
	
	elif find_delimiter == 0:
		state = FINISHED
		set_current_frame(0)
		nextLine.show()
		split_text = []
		next_split_text = ""
		find_delimiter = 0


func _on_PauseTimer_timeout():
	pauseDelay = false
	last_text = remote_next_text
	next_split_text = str(remote_next_text,next_split_text)
	
	if break_char in next_split_text or pause_char in next_split_text:
		if break_char in next_split_text and pause_char in next_split_text:
			find_delimiter = next_split_text.find(break_char) - next_split_text.find(pause_char)
			if find_delimiter <= 1:
				pause_split = next_split_text.split(break_char, true, 1)
			elif find_delimiter > 1:
				pause_split = next_split_text.split(pause_char, true, 1)
		elif break_char in next_split_text and not pause_char in next_split_text:
			pause_split = next_split_text.split(break_char, true, 1)
		elif not break_char in next_split_text and pause_char in next_split_text:
			pause_split = next_split_text.split(pause_char, true, 1)
	
		next_percent_visible = 0.05 + float(len(last_text)) / float(len(pause_split[0]))
	else:
		next_percent_visible = 0.05 + float(len(last_text)) / float(len(next_split_text))
	
	return_to_display()


func return_to_display():
	if not break_char in next_split_text and not pause_char in next_split_text:
		find_delimiter = 0
	
	display_text(next_split_text)

Part 1 of 2…see comments for part 2:

Part 2/2

WorldUI possibly relevant snippets:

# Extensions
extends CanvasLayer

# Variables
var state = UNPAUSED

onready var textBoxes = $TextBoxes
onready var dialogBox = $TextBoxes/DialogBox
onready var nameBox = $TextBoxes/NameBox
onready var dialogBoxLabel = $TextBoxes/DialogBox/Label
onready var nameBoxLabel = $TextBoxes/NameBox/Label
onready var tweenDialogBoxIn = $Tweens/TweenDialogBoxIn
onready var tweenDialogBoxOut = $Tweens/TweenDialogBoxOut
onready var tweenNameBoxIn = $Tweens/TweenNameBoxIn
onready var tweenNameBoxOut = $Tweens/TweenNameBoxOut

# Enumerations
enum {
	UNPAUSED,
	PAUSEBAR,
	INVENTORY,
	PARTY,
	SETTINGS,
	DIALOG,
	CUTSCENE
}


# Functions
func _ready():
	textBoxes.show()
	dialogBox.hide()
	nameBox.hide()


func _process(delta):
	match state:
		UNPAUSED:                                                      
			unpaused_state(delta)
		PAUSEBAR:
			pausebar_state(delta)
		INVENTORY:
			inventory_state(delta)
		PARTY:
			party_state(delta)
		SETTINGS:
			settings_state(delta)
		DIALOG:
			dialog_state(delta)
		CUTSCENE:
			cutscene_state(delta)
	

func unpaused_state(delta):
	get_tree().paused = false
	dialogBox.textOnScreen = false

	DialogManager.nextText = "Empty"
	
	dialogBox.state = 0

	if Input.is_action_just_pressed("check"):
		DialogManager.nextText = DialogManager.checkText
		dialogBox.startTimer.start()
		
		tweenDialogBoxIn.interpolate_property(dialogBox, "rect_position",
			Vector2(0, 180), Vector2(0, 135),
			Tween.TRANS_SINE)
		tweenDialogBoxIn.start()
				
		tweenTopPanel.interpolate_property(topPanel, "rect_position",
			Vector2(0, -24), Vector2(0, 0),
			Tween.TRANS_SINE)
		tweenTopPanel.start()
		
		if DialogManager.checkType == "NPC":
			tweenNameBoxIn.interpolate_property(nameBox, "rect_position",
				Vector2(-100, 112), Vector2(0, 112),
				Tween.TRANS_SINE)
			tweenNameBoxIn.start()
		
		state = DIALOG


func dialog_state(delta):
	get_tree().paused = true
	
	dialogBox.textOnScreen = true
	
	dialogBox.show()
	topPanel.show()
	
	if DialogManager.checkType == "NPC":
		nameBox.show()
		nameBoxLabel.text = DialogManager.checkName
		var get_font = nameBoxLabel.get_font("res://UI/Fonts/apple_kid.ttf")
		var get_string_size = get_font.get_string_size(nameBoxLabel.text)
		nameBoxLabel.rect_size.x = get_string_size.x + 21
		nameBox.size_x = nameBoxLabel.rect_size.x
		nameBox.state = 2
		nameBox.nextLine.hide()
	elif DialogManager.checkType == "Item":
		nameBox.hide()
	
	if dialogBox.state == 2:
		if Input.is_action_just_pressed("check") or Input.is_action_just_pressed("ui_accept"):
			tweenDialogBoxOut.interpolate_property(dialogBox, "rect_position",
				Vector2(0, 135), Vector2(0, 180),
				Tween.TRANS_SINE)
			tweenDialogBoxOut.start()
			
			tweenTopPanel.interpolate_property(topPanel, "rect_position",
				Vector2(0, 0), Vector2(0, -24),
				Tween.TRANS_SINE)
			tweenTopPanel.start()
			
			if DialogManager.checkType == "NPC":
				tweenNameBoxOut.interpolate_property(nameBox, "rect_position",
					Vector2(0, 112), Vector2(-100, 112),
					Tween.TRANS_SINE)
				tweenNameBoxOut.start()
			
			state = UNPAUSED


func _on_TweenDialogBoxOut_tween_all_completed():
	dialogBox.hide()


func _on_TweenNameBoxOut_tween_all_completed():
	nameBox.hide()

I just randomly came across a repeatable (but not totally easy to repeat) glitch in my dialog system where if I press the “check” button just as (we’re talking milliseconds after) the player slides against and then off of the NPC’s collision and the checkbox exits the seenbox, the game will still consider the current dialog text to be whatever the asset would normally say, but removes the nameBox if it was an NPC. This can be seen in that same image:
Checkbox (green) and Seenbox (red) - Album on Imgur

Here’s the video of it happening. Notice that the second time I speak to the NPC the name is there, but it wasn’t there the first time. Ignore that the name is Timmmmmmmmmm lol, I was testing something else at the time of recording and was just lucky to catch this.
Glitched dialog - Album on Imgur

Thoughts? Anything unclear? I know that was a lot but I see too many posts that aren’t thorough enough lol.

PIZZA_ALERT | 2022-09-07 00:38