Code optimazation

Godot Version

4.3

suggestions

I made a script that handles dialogues with a dictionary and enums based on conditions or no conditions at all, my script works as it should, but I would like it to be more optimized if anyone has suggestions on how to make it more organized and easy to add new dialogues. the problem I’m facing is that whenever I add a new dialogue, I have to remodify other parts in the code for the flow to keep going as it is, when a new dialogue is added the flow is sometimes broken, depending on where the dialogue index is located.

extends Node2D

# Enum for dialogue keys
enum DialogueKeys { WELCOME, TEACH_MOVE, PROCEED, DISCOVER, STATIC, TRANSFORM }

@onready var player = $"../Player"
@onready var text_label = $RichTextLabel
@onready var ui_prompt = $ui_container  
@onready var plant = $"../tree"

var typing_speed: float = 0.05
var is_typing: bool = false
var char_index: int = 0  
var current_text: String = ""  
var current_dialogue_key: int = DialogueKeys.WELCOME

# flags 
var dialogue_active: bool = true  
var track_tutorial_inputs: bool = false  # Controls input tracking for the tutorial
var transform_dialogue_shown: bool = false
var static_dialogue_shown: bool = false  

var required_keys_pressed: Dictionary = {
	"left": false,
	"right": false,
	"attacking": false
}

var dialogues: Dictionary = {
	DialogueKeys.WELCOME: {
		"text": "Welcome! My name is the one. I'm glad you have arrived.\nI'm going to teach you the basics of this world and how to survive.",
		"on_finish": null
	},
	DialogueKeys.TEACH_MOVE: {
		"text": "Press the keys to move and attack.",
		"on_finish": show_ui_prompt  # Start tracking inputs here
	},
	DialogueKeys.PROCEED: {
		"text": "Great! Let's proceed with the adventure.",
		"on_finish": null
	},
	DialogueKeys.DISCOVER: {
		"text": "Oh look, there is a plant growing.\nLet's cut it out. Go and attack it.",
		"on_finish": discover_plant
	},
	DialogueKeys.STATIC: {
		"text": "dialogue text.",
		"on_finish": null
	},
	DialogueKeys.TRANSFORM: {
		"text": "look, the bush has transformed into a tree.\nkeep attacking it.",
		"on_finish": null
	}
}

func _ready() -> void:
	player.disable_inputs()
	ui_prompt.visible = false  
	start_typing_dialogue(current_dialogue_key)  

func _process(delta: float) -> void:
	# Check if the plant health is 10 and the dialogue hasn't been shown yet
	if plant.health == 10 and !transform_dialogue_shown:
		transform_dialogue_shown = true  
		plant.transform_to_tree()  

func start_typing_dialogue(key: int) -> void:
	if dialogues.has(key):
		dialogue_active = true  
		var dialogue = dialogues[key]
		start_typing(dialogue["text"])

func start_typing(text: String) -> void:
	char_index = 0
	text_label.text = text  
	text_label.visible_characters = 0  
	is_typing = true  
	current_text = text  
	_type_character()  

func _type_character() -> void:
	if is_typing and char_index < current_text.length():
		char_index += 1
		text_label.visible_characters = char_index  
		await get_tree().create_timer(typing_speed).timeout
		_type_character()  
	else:
		is_typing = false  
		handle_dialogue_finish()  

func handle_dialogue_finish() -> void:
	var dialogue = dialogues[current_dialogue_key]
	var on_finish = dialogue.get("on_finish", null)
	if on_finish and on_finish is Callable:
		on_finish.call()  
	else:
		move_to_next_dialogue()  

func move_to_next_dialogue() -> void:
	if current_dialogue_key + 1 in dialogues:
		current_dialogue_key += 1  
		start_typing_dialogue(current_dialogue_key)  
	else:
		print("All dialogues have been completed.")
		dialogue_active = false  
		track_tutorial_inputs = false  # Stop tracking inputs after tutorial

func show_ui_prompt() -> void:
	ui_prompt.visible = true  
	player.enable_inputs()  
	track_tutorial_inputs = true  # Start tracking inputs now

func discover_plant() -> void:
	plant.show_bush() 
	plant.activate_regeneration()
	print("Dialogue complete. Plant discovered and activated!")

	# Directly show STATIC dialogue immediately after DISCOVER
	if !static_dialogue_shown:
		static_dialogue_shown = true  # Prevent this from re-triggering
		start_typing_dialogue(DialogueKeys.STATIC)  # Show STATIC dialogue

func _input(event: InputEvent) -> void:
	if not dialogue_active or not track_tutorial_inputs:
		return  # Ignore inputs unless explicitly allowed

	if event.is_action_pressed("left"):
		required_keys_pressed["left"] = true
	elif event.is_action_pressed("right"):
		required_keys_pressed["right"] = true
	elif event.is_action_pressed("attacking"): 
		required_keys_pressed["attacking"] = true

	if all_keys_pressed():
		ui_prompt.visible = false  
		track_tutorial_inputs = false  # Stop tracking inputs after completion
		move_to_next_dialogue()  

func all_keys_pressed() -> bool:
	for key in required_keys_pressed.keys():
		if not required_keys_pressed[key]:
			return false
	return true

I would do the if functions like this

var plant :=

func _process(delta: float) -> void:
	if plant.health != 10 or !transform_dialogue_shown:
		return
	transform_dialogue_shown = true  
	plant.transform_to_tree()

I’m not actually sure why this is better but I picked it up from yosoyfreeman who’s quite good soooooo

this is not what im looking for, i get the optimization to do :=, in process I’m thinking return is simply making the computer have less overload on it? but anyway, this is not what I mean, I want suggestions on how I can modify my dictionary and the functions that work with it to work better, as I said, every time I add a new dialogue, the flow breaks and I got to modify the code. if there is a way to optimize the code to allow easy adding of dialogue index and they work flawlessly when the game runs, no need to change code or modify or create more flags or whatever. if that is understood better

Since your DialogueKeys are used in sequence anyways could you change your dialogues to an array and remove DialogueKeys entirely?

var dialogues: Array[Dictionary] = [
	{
		"text": "Welcome! My name is the one. I'm glad you have arrived.\nI'm going to teach you the basics of this world and how to survive.",
		"on_finish": null
	},
	{
		"text": "Press the keys to move and attack.",
		"on_finish": show_ui_prompt  # Start tracking inputs here
	},
# etc...
]

I would recommend loading dialogue as files over hard-coding dialogue within the scripts.

2 Likes

i get the optimization to do :=

What? I only did that so the autoformatting wouldn’t turn everything red
anyway
So by optimise you mean make it more modular and malleable - I’d assign each dialogue to an int, and make the code get the dialogue assigned to the int designated by the contextual player input, that way each dialogue segment would be composed of the text followed by options for what dialogue to play next, that way you could make it all flowchart style
But tbh I don’t know much about this stuff yet so listen to gertkeno

it works with int too, i can call dialogue by int already but thx anyway

1 Like

I did use array before, but it was kind of messy. Someone offered the dictionary for me, and I think it’s quite good because it has a more organized setup. I can assign functions to them, of course. It can be improved. The keys work well so that I can call a specific dialogue somewhere in my code, like func discover_plant.
about the loading files. Do you mean loading them as JSON files? I checked about that, but I’m not sure how it will work or if it suits my needs best. I’m still learning Godot, so I never tried loading JSON files.

use StringName keys in dictionary. It will help to debug and keeps memory usage low. You don’t have to use arrays at all. Just remembering its address is enough. You don’t have to use enums, StringName is similar to it but its like global big enum with frequently used strings.
like

var dialogs:={
		&'welcome':{
			&'text':"Hello there!",
			&'actions':{
				&'show_text_buble':callable,
				&'check_inventory':callable,
				},
			&'variants':{},
			}
		}

access it with
dialogs[&'welcome'][&'text']
NOT
dialogs.welcome.text
do not use lua syntax to avoid unneeded transfromations from StringName to String
just DO NOT use StringName’s for dialog text

ill try it out and see how it work for me

I would also check to see if state machine concepts apply to your case.

Here is a tutorial although bit long.

your setup seems to be quite good to use, I’m still checking it but so far its going well for me

1 Like

I’m using similar for my project. I’ve got a dictionary with ‘scenes’ related to current tile. Every scene key contains &'run key, which runs all callables from &‘actions’ like

list['GATES']={
		&'run':func():
			for f in list['GATES'][&'actions'].values():f.call()

action callables check their conditions while player enters tile and so on

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