String formatting in the property editor with an export variable?

Godot Version

4.4.1

Question

Is it possible to do string formatting in the property editor using an export variable?

I have a game with items that have different descriptions, and I’d like to use an export variable so I can quickly write them inside the editor. However, I’d also like to do string formatting, so I don’t have to rewrite it if something minor, like a number, changes. For example:
"-%s Health to attacker when owner is attacked" % str(damage)

Currently, I set the description in the _ready(): function for descriptions with formatting, but I was wondering if there’s a way to do those in the editor instead?

Hello, I often do string formatting like this:

## A common func to get the final text content
func fetch(content: String, dic_param: Dictionary = {}) -> String:
  if dic_param:
    return content.format(dic_param)
  return content
  
# The definition of text with param, can be in editor
var some_text = '-{damage} health and...'
# Use fetch func to parse the final text
var damage = 300  # Get this value from other part
var final_text = fetch(some_text, {'damage': damage})

So you can define a string template with named parameters(0 to many, no matter the locations), and use a wrapper func to get the real string.
The con is all keys of dictionary must be spelled exactly right. So it’s just one way, and glad to see a better solution.

2 Likes

Here is one using setters/getters and expression evaluation. I added some code to print when message changes with signals which you can discard. Just set the @export msg string expression in the inspector and the new result will print. You dont need \" when you type it inspector.

extends Node2D

var damage=100
var message
signal message_changed

@export var msg: String = "\"%s health is \" % str(damage)":
		set = set_msg

func _ready():
	message = eval(msg)
	message_changed.connect(Callable(self,"print_msg"))
	message_changed.emit()
	
func set_msg(new_msg: String) -> void:	
	# You can add logic here to validate the value, trigger other actions, etc.
	message = eval(new_msg)
	message_changed.emit()
	
func eval(new_msg):
	var expr=Expression.new()
	var err=expr.parse(new_msg)
	if expr.has_execute_failed():
		print(err)
		return
	return expr.execute([],self)

func print_msg():
	print(message)
1 Like

Thanks a bunch! One downside is that I always have to put quotation marks around my string, even if the string doesn’t have any formatting, but it works great otherwise!

I think this has a lot of downsides for regular use. For one it is a entire expression, you could put any code inside the string, opening up potential security issues if this is somehow user-facing, and messing up could be much worse than a wonky string. Re-running this message string for say updating heath is more expensive than other operations.

@gamefang 's solution addresses these issues, here’s another example of how to use it

@export_multiline var message_raw: String = "-{damage} health to attacker with bonus {bonus_damage}x ({bonus_damage_calculated}) against {strong_type}"

func get_message() -> String:
    return message_raw.format(get_parameters())

Then your objects/actors/players can define a function to get their parameters and the message can be supplied

# potentially overriden for each type of object you want to support
func get_parameters() -> Dictionary:
    return {
        "name": self.name,
        "damage": self.damage,
        "strong_type": self.strong_type,
        "weak_type": self.weak_type,
        "bonus_damage": self.bonus_damage,
        "bonus_damage_calculated": self.damage * self.bonus_damage,
        "durability": self.durability,
    }
1 Like

Good point you need extra validations on the setter. Not sure how much flexibility op is looking for.

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