godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.1k stars 69 forks source link

Expose additional properties of `@export` #8117

Open gregcsokas opened 10 months ago

gregcsokas commented 10 months ago

Describe the project you are working on

A 3D space game

Describe the problem or limitation you are having in your project

I'm working on a resource-based system for my game, and this resource has a bunch of @export values. In the resources, I don't have too many options to check if a value is higher than the range 'limits'. Basically, I need to control a timer with these values.

class_name StatResource
extends Resource

@export var name : String = 'StatResource'
@export_range(0.0, 100.0) var value : float = 0.0:
    set(new_value):
        value = new_value
        emit_changed()
        if value in thresholds:
            threshold_reached.emit(name, value)

@export var can_be_negative : bool = false
@export_enum('GROWING', 'DECAYING') var base_direction
@export var base_rate : float = 0.0

@export var thresholds : PackedInt32Array = [30, 60, 90]

signal threshold_reached(name, value)

I wrote a Component to handle these resources. The Component has a timer and every time the timer timeouts. I'm increment or decrement the value of the resource.

But currently the value can be higher than 100.0 so I need to check if it is higher or not.

This is the component's code

class_name StatComponent
extends Node

@export var resources : Array[StatResource]

@onready var hunger_bar : ProgressBar = $"../Bar"
@onready var timer : Timer = get_node("Timer")

func _ready() -> void:
    for resource in resources:

        resource.threshold_reached.connect(_on_threshold_reached)
        if resource.name == "stat":
            hunger_bar.value = resource.value

func _on_timer_timeout() -> void:
    for resource in resources:
        match resource.base_direction:
            0:
                resource.value += resource.base_rate
                if resource.name == "stat":
                    hunger_bar.value = resource.value
            1:
                resource.value -= resource.base_rate

func _on_threshold_reached(name, value) -> void:
    if name == "stat":
        if value == 30:
            var style_box = hunger_bar.get("theme_override_styles/fill")
            style_box.bg_color = Color("F94D00")
        if value == 60:
            var style_box = hunger_bar.get("theme_override_styles/fill")
            style_box.bg_color = Color("CE2029")

Describe the feature / enhancement and how it helps to overcome the problem or limitation

With this proposal, I can get additional properties from the export. And in this specific case, I can control the value changes in my code.

class_name StatComponent
extends Node

@export var resources : Array[StatResource]

@onready var hunger_bar : ProgressBar = $"../Bar"
@onready var timer : Timer = get_node("Timer")

func _ready() -> void:
    for resource in resources:
        resource.threshold_reached.connect(_on_threshold_reached)

        if resource.name == "stat":
           hunger_bar.value = resource.value

func _on_timer_timeout() -> void:
    for resource in resources:
        match resource.base_direction:
            0:
                #here is the magic I can check if the the value is in the range
                if resource.value < resource.value.max:  
                    resource.value += resource.base_rate
            1:
                resource.value -= resource.base_rate

func _on_threshold_reached(name, value) -> void:
    if name == "stat":
        if value == 30:
            var style_box = hunger_bar.get("theme_override_styles/fill")
            style_box.bg_color = Color("F94D00")
        if value == 60:
            var style_box = hunger_bar.get("theme_override_styles/fill")
            style_box.bg_color = Color("CE2029")

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

@export_range(0.0, 100.0) var value : float = 0.0

value.min  #this will be equal to 0.0
value.max  #this will be 100.0
@export_range(0.0, 100.0, 0.1) var value : float = 0.0

value.min  #this will be equal to 0.0
value.max  #this will be 100.0
value.step  #this will be 0.1
@export_range(0.0, 100.0, 0.1, "lowest value", "highest value") var value : float = 0.0

value.min  #this will be equal to 0.0
value.max  #this will be 100.0
value.step  #this will be 0.1

value.min_hint  #this will be equal to "lowest value"
value.max_hint  #this will be equal to "highest value"

I don't know currently how this thing will work with the other @export variables! But for the enums, this is my best idea currently.

@export_enum(thing1, thing2, thing3) var something

something.values  # [thing1, thing2, thing3] maybe the keys are better name for this
something.thing1  # 0
something.thing2  # 1

If this enhancement will not be used often, can it be worked around with a few lines of script?

Yeah, but it's kinda a pain in the ass, I can use only the _init() function for this. and I need to get the hint_text from the get_property_list() convert it to the proper type, and save it into a variable.

Is there a reason why this should be core and not an add-on in the asset library?

It's related to the @export variables, and I don't think writing a plugin for it is possible.

Calinou commented 10 months ago

The issue with the proposed syntax is that it could conflict with member variables (or methods) called min, max and step. Also, it could be misleading as people may think these are member variables when they're not. This should probably be a built-in keyword or global scope method instead.

gregcsokas commented 10 months ago

built-in keyword or global scope method

That sounds good to me

Stronkkey commented 10 months ago

It's already possible to min or max variables but it requires setter and getters functions.


@export var min_value: int = 1:
    set(value):
        min_value = clamp(value, -20, INF)
    get:
        return min_value

func _ready():
    min_value = -500
    print(min_value) # -20

Also, this won't show as a slider in the editor 🙃

dalexeev commented 10 months ago

1. You can use constant expressions as annotation arguments:

const VALUE_MIN = 0.0
const VALUE_MAX = 100.0
const VALUE_STEP = 0.1
@export_range(VALUE_MIN, VALUE_MAX, VALUE_STEP) var value: float = 0.0

2. You can get property information via get_property_list().