GDQuest / godot-steering-ai-framework

A complete framework for Godot to create beautiful and complex AI motion. Works both in 2D and in 3D.
http://gdquest.com/docs/godot-steering-ai-framework
MIT License
1.17k stars 81 forks source link

Automatically build a GUI by looking at the exported variables and their types #23

Open Razoric480 opened 4 years ago

Razoric480 commented 4 years ago

While the Demo Selector is useful, one downside is that you can't change values quite as easily; needing to go into the Remote Tree to do so. So it's nice for showcase, but for actual study and playing around with the variables, opening the actual demo will still be called for.

Occasionally, Godot's keep-window-on-top option also stops working for unexplained reasons, depending on the state of your alt-tabbing and clicking, so using the inspector for all tweaking starts to lose some of its lustre.

An in-demo GUI system that we don't have to manually build but that automatically outputs the exported variables that are on the root node would alleviate all of those issues.

NathanLovato commented 4 years ago

I don't know if it's possible to get the exported variables specifically - well, with the language server's json export it is, but that could be a little heavy to probe.

In any case, that'd be a good general debug tool, so we could make it part of another repo where we have debug and/or UI components

Razoric480 commented 4 years ago

If we use the language server, we'd have to grab the data before running, because the language server is only available in Editor as a tool script. With that in mind, then I can see an EditorScript that generates/updates a TSCN file.

If we want to go on-the-fly, then we'd probably stick with parsing the script as a text document, and regex-match variables based on some parameters of our choosing.

But yes, it should be its own project.

Razoric480 commented 4 years ago

On my own time/for fun, was messing around and wrote the following:

tool
extends EditorScript

export(int, 0, 200, 5) var range_test: int = 50
export var standard_value_test := 20.0
export var variant_test = ""
export(ImageTexture) var resource_test

func _run() -> void:
    var workspace = (
            Engine.get_singleton('GDScriptLanguageProtocol').get_workspace()
    )
    workspace.parse_local_script("res://SymbolTest.gd")
    var api: Dictionary = workspace.generate_script_api("res://SymbolTest.gd")
    var file := File.new()
    file.open("res://SymbolTest.gd", File.READ)
    var content := file.get_as_text()
    file.close()

    var values := []

    for m in api.members:
        if m.export:
            var var_name: String = m.name
            var var_value: String = str(m.default_value) if m.default_value else ""
            var type: String = m.data_type
            var export_line := ""

            var regex := RegEx.new()
            regex.compile("export[ \t]*?\\((.*?)\\)[ \t]*?var " + var_name)
            var line := regex.search(content)
            var has_hints := not line == null

            if has_hints:
                var export_data: PoolStringArray = line.strings.slice(1, line.strings.size()-1)
                export_line = export_data.join(", ")
            values.append({name = var_name, value = var_value, controls = has_hints})

    var scene := _build_scene(values)
    ResourceSaver.save("res://SymbolTestGUI.tscn", scene)

func _build_scene(values: Array) -> PackedScene:
    var node := MarginContainer.new()
    node.set("custom_constants/margin_top", 20)
    node.set("custom_constants/margin_left", 20)
    node.set("custom_constants/margin_bottom", 20)
    node.set("custom_constants/margin_right", 20)

    var vbox := VBoxContainer.new()

    node.add_child(vbox)
    vbox.owner = node

    for v in values:
        var name: String = v.name
        var value: String = v.value
        var controls: bool = v.controls

        var name_label := Label.new()
        name_label.text = name + (" = " if not value.empty() else "")

        var value_label := Label.new()
        value_label.text = value

        var hbox := HBoxContainer.new()
        hbox.add_child(name_label)
        hbox.add_child(value_label)

        var controls_node: Label

        if controls:
            controls_node = Label.new()
            controls_node.text = "Slider/etc goes here"
            hbox.add_child(controls_node)

        vbox.add_child(hbox)

        name_label.owner = node
        value_label.owner = node
        hbox.owner = node
        if controls_node:
            controls_node.owner = node

    var scene := PackedScene.new()
    scene.pack(node)

    return scene

As a show of idea.

mrcdk commented 4 years ago

You can get the object variables at runtime with Object.get_property_list() and then filter those. This might work:

func _ready():
    var list = get_property_list()
    for v in list:
        if v.usage & PROPERTY_USAGE_SCRIPT_VARIABLE and v.usage & PROPERTY_USAGE_EDITOR:
            print(v)