Running the code from string

Godot Version

v4.3-stable_win64

Question

Is there any way to run the code from the string?

For example:

@onready var label = $Label
var count = 0

func example():
    code = "label.queue_free()
            count += 1"
    run(code) # Kind of running the code from string

Result: the label is freed and var count equals 1

1 Like

You can parse and execute expressions via the Expression class. You can even call your own functions with it. Be aware if you have errors in your script, Godot will show an error too, and I never found a way to stop that (as there are not try…catch blocks).

2 Likes

This LUA plugin for Godot 4 might be helpful. Your example is very generic, so it’s hard to say. LuaAPI 4.0.x - Godot Asset Library

1 Like

Actually, I’m coding a tutorial level in my game. And I created a file that contains guides and hints like that. Here’s another example:

#Global Script Guides.gd
var tutorial_part_2_1 = {
    "text": "You have to kill 3 enemies",
    "position": Vector2(576, 268),
    "command": "Spawner.spawn_enemy(3); await Spawner.enemy_died"

var tutorial_part_2_2 = {
    "text": "Here we are. Keep going, soldier",
    "position": Vector2(576, 268),
    "command": null


var tutorial = [tutorial_part_2_1, tutorial_part_2_1]

Code of the level:

@onready var Spawner = $Manager/$Spawner

 func tutorial():
    for guide in Guides.tutorial:
        var hint = hint_preload.instantiate()
        hint.text = guide["text"]
        hint.position = guide["position"]
        var command = guide["command"]
        add_child(hint) # The hint appears
        run(command) # Spawner.spawn_enemy(3); await Spawner.enemy_died
1 Like

The Expression class isn’t going to be generic enough for you. You’d want to use a Callable. However, if you’re going to use a Callable, the Godot way would be to just set up signals.

It looks like you might be over-engineering the problem and trying to optimize something that doesn’t need to be optimized. My recommendation would be to just use a Match statement and pass in enum values for each part. Your tutorial is going to be finite, and while your solution is elegant, it may take more time to figure out how to implement it than it’s worth. Once you have it working with a simpler method, you can always refactor it into the array of dictionaries.

2 Likes

You seem to be right but I dont want to make my code a trash can

I’m not sure what you consider a trash can, but there are considerations beyond how pretty one’s code looks. First and foremost is: Does it work?

Second after that is: Is it easily understandable and maintainable by someone other than the original coder? Is it documented either by being easily readable or by being well-commented? If you come along behind yourself in a year, are you going to understand what you did?

Third: How much time is it taking to do “the right way” according to one’s idea of ideal code?

If you don’t like the quick-and-dirty Match statement, a more elegant possibility would be using the Single Responsibility Principle. Godot is very object-oriented. So, create a TutorialStep class that inherits from Node:

extends Node
class_name TutorialStep

@export var text: String
@export var position: Vector2

func run_command(arg1: Variant) -> void:
    return

Then create TutorialStep objects that extend that code.

extends TutorialStep

func run_command(arg1: Variant) -> void:
    arg1.spawn_enemy(3)
    await arg1.enemy_died #Note this will make your game unplayable as the game will stop executing until this condition happens

Then in global_script_guides.gd:

extends Node

@export var tutorial: Array[TutorialStep]

Drag-and-Drop all your tutorial steps into the exported array. Then in the code of the level:

@onready var spawner = $Manager/$Spawner #All variables start with lowercase in gdscript by convention. Uppercase is reserved for class names.

func tutorial() -> void:
    for guide in guides.tutorial
        var hint = hint_preload.instantiate()
        hint.text = guide.text
        hint.position = guide.position
        add_child(hint) # The hint appears
        guide.run_command(spawner)

This is one possible implementation that keeps the modularity you’re looking for, but leverages the OOP architecture of Godot. In my opinion it’s still overly complicated. Instead of editing a single Match statement, you now have a bunch of tutorial objects in a folder to maintain. But it is elegant.

A few additional thoughts:

  1. You are better off avoiding await in your code in gdscript. It doesn’t work the way it does in JavaScript, C#, Java, etc. The only time I would personally recommend using it is in unit testing.
  2. In my opinion, it’s worth it to learn the GDScript style guide — Godot Engine (stable) documentation in English. Then when you are looking at code examples, or you are sharing code examples, you avoid confusion.
  3. You might consider just having a label called $Hint in your UI, and passing it the text and changing its visibility. If not, you definitely want to garbage collect your hint object before it goes out of scope.
2 Likes

What do you mean? I use await all the time without any problems. Can’t do coroutines otherwise. It also doesn’t stop the execution like you claim.

2 Likes

Perhaps I am using it incorrectly then. I’ve only used it for unit testing in Godot. I apologize @raven-xr for giving you incorrect information. Thanks @ratrogue for straightening me out.

1 Like

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