How do I make text change on a click

Godot Version

4.7

Question

So I’m trying to make a game where I can click on objects and they’ll display a description of item on the screen. So far I’ve been able to make them clickable and show the text but my problem is if the description has multiple lines i have to click off of the object for the next line to show up but I want to just click the object for the next line to show up if that makes sense but so far nothing I’ve tried has worked out.

This is the code on my items

class_name Item
extends Node2D


@export_color_no_alpha var col: Color
@export var dialogue_manager: CanvasLayer 
@export var area: Area2D
@export var item_sprite: Sprite2D
@export var item_description: Array[String] = [" "]
var selected: bool = false
var counter: int = 0


func _ready() -> void:
	area.area_entered.connect(_on_area_2d_area_entered)
	area.area_exited.connect(_on_area_2d_area_exited)


func _on_area_2d_area_entered(area: Area2D) -> void:
	selected = true


func _on_area_2d_area_exited(area: Area2D) -> void:
	selected = false


func _input(event):
	if is_visible():
		if selected and event is InputEventMouseButton:
			if event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
				dialogue_manager.start_dialogue(item_description)
			if event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
				dialogue_manager.advance_dialogue()

This is my dialogue manager code

extends CanvasLayer


@onready var dialogue_box: Control = $DialogueBox
@onready var dialogue_text: Label = $DialogueBox/DialogueText


var dialogue_lines: Array[String] = []
var current_line_index: int = 0
var is_dialogue_active: bool = false


func _ready() -> void:
	dialogue_box.visible = false


func start_dialogue(lines: Array[String]):
	dialogue_lines = lines
	current_line_index = 0
	is_dialogue_active = true
	dialogue_box.visible = true
	dialogue_text.text = dialogue_lines[current_line_index]


func _input(event):
	if not is_dialogue_active:
		return
	if event is InputEventMouseButton:
		if event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
			advance_dialogue()


func advance_dialogue():
	if current_line_index < dialogue_lines.size() - 1:
		current_line_index += 1
		dialogue_text.text = dialogue_lines[current_line_index]
	else:
		is_dialogue_active = false
		dialogue_box.visible = false

I think your item’s _input you have two identical if blocks, so a single click calls start_dialogue and advance_dialogue back to back. And on top of that the manager’s own _input also reacts to the same click. So every click restarts the dialogue at line 0 and immediately advances it, which is exactly the “stuck until I click off” behavior you’re seeing.

The fix is to give each script one job. The item only starts the dialogue (and only if one isn’t already running), and the manager alone handles advancing.

Item side, replace the whole click section with:

func _input(event):
	if is_visible() and selected and event is InputEventMouseButton:
		if event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
			if not dialogue_manager.is_dialogue_active:
				dialogue_manager.start_dialogue(item_description)

Manager side, there’s one subtlety left: the click that starts the dialogue sets is_dialogue_active to true, and depending on input order the manager’s _input can process that same click and advance right past line 1. A small flag handles it:

var just_started: bool = false

func start_dialogue(lines: Array[String]):
	dialogue_lines = lines
	current_line_index = 0
	is_dialogue_active = true
	just_started = true
	dialogue_box.visible = true
	dialogue_text.text = dialogue_lines[current_line_index]

func _input(event):
	if not is_dialogue_active:
		return
	if event is InputEventMouseButton:
		if event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
			if just_started:
				just_started = false
				return
			advance_dialogue()

Now the first click shows line 1, every following click advances (whether you’re still on the item or not), and the box closes after the last line. Clicking the item mid dialogue won’t restart it either because of the is_dialogue_active check.

Thank you! This does mostly work although for some reason now it only advances the dialogue on my third click instead of the second

I think the input order landing the other way from what the flag assumed. On your first click the manager’s _input ran before the item’s, and since is_dialogue_active was still false it just returned, nobody consumed the flag. So click 2 got eaten clearing just_started and click 3 finally advanced.

The order proof fix is to compare frames instead of using a flag. Both callbacks for one click happen on the same frame no matter which node processes first:

var started_frame: int = -1

func start_dialogue(lines: Array[String]):
	dialogue_lines = lines
	current_line_index = 0
	is_dialogue_active = true
	started_frame = Engine.get_process_frames()
	dialogue_box.visible = true
	dialogue_text.text = dialogue_lines[current_line_index]

func _input(event):
	if not is_dialogue_active:
		return
	if event is InputEventMouseButton:
		if event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
			if Engine.get_process_frames() == started_frame:
				return
			advance_dialogue()

Delete the just_started variable entirely. The manager now ignores only the exact click that opened the dialogue, first click shows line 0, second click advances, regardless of processing order.

Thank you this fixed it!