如何在没有 setter 函数的情况下创建 getter 函数?

How to create a getter function without a setter function?

我的脚本中有多个导出变量,每当更改一个变量时,我想调用一个公共 getter 并自动设置值

tool

export(float) var sample1 setget ,smthn_changed;
export(float) var sample2 setget ,smthn_changed;
export(float) var sample3 setget ,smthn_changed;

func smthn_changed():
    print("something changed!")

但这不起作用,我必须为每个变量创建一个 setter

有解决办法吗?

请注意,您将这些属性的 smthn_changed 定义为 getter。 getter 是在您尝试读取它们时调用的,而不是在您尝试分配它们时调用的。

好的,假设您确实想知道何时分配变量。为此,您通常会使用 setters,如下所示:

export var property:bool setget set_property

func set_property(new_value:bool) -> void:
    if property == new_value:
        return

    property = new_value
    print("value changed") # or emit a signal or whatever

setter 会在变量被外部赋值的任何时候被调用(或者内部使用 self.property = value,如果你不使用 self 你可以直接赋值变量而不用触发 setter).

但是,由于您需要从 setter 写入实际变量,这意味着为每个变量创建一个 setter(如果您对多个变量使用相同的 setter,你不知道要设置哪个)。


您还可以尝试其他方法:_set_set 的问题是只会为未在脚本中声明的变量调用。

所以,这是计划:

  • 我们将使用不同的名称声明支持变量,而不是导出它们。
  • 我们将使用 _set_set 来处理它们。
  • 我们将使用 _get_property_list 导出它们。

让我们看看只有一个变量的情况:

tool
extends Spatial


var _x:String setget _no_set


func _set(property: String, value) -> bool:
    if property == "x":
        _x = value
        smth_changed()
        return true

    return false


func _get(property: String):
    if property == "x":
        return _x

    return null


func _get_property_list() -> Array:
    if not Engine.editor_hint or not is_inside_tree():
        return []

    return [
        {
            name = "x",
            type = TYPE_STRING,
            usage = PROPERTY_USAGE_DEFAULT
        }
    ]


func _no_set(_new_value) -> void:
    pass


func smth_changed() -> void:
    print("something changed!")

与简单的 setter 相比,这不值得付出努力。

setter _no_set 是一个什么都不做(甚至不设置变量)的 setter。我已经添加它以防止通过直接设置支持变量来从外部绕过该机制。 您可以在此处添加警告,因为这不是您的代码应该做的事情。另一方面,您的代码不应该这样做的事实也可以被视为反对 _no_set.

的论据

但让我们看看它如何扩展到多个变量:

tool
extends Spatial


var _x:String setget _no_set
var _y:String setget _no_set


func _set(property: String, value) -> bool:
    match property:
        "x":
            _x = value
        "y":
            _y = value
        _:
            return false

    smth_changed()
    return true


func _get(property: String):
    match property:
        "x":
            return _x
        "y":
            return _y

    return null


func _get_property_list() -> Array:
    if not Engine.editor_hint or not is_inside_tree():
        return []

    return [
        {
            name = "x",
            type = TYPE_STRING,
            usage = PROPERTY_USAGE_DEFAULT
        },
        {
            name = "y",
            type = TYPE_STRING,
            usage = PROPERTY_USAGE_DEFAULT
        }
    ]


func _no_set(_new_value) -> void:
    pass


func smth_changed() -> void:
    print("something changed!")

仍然不太好,因为我们不得不多次重复变量。我仍然希望有多个 setter,即使它们都有相同的代码。


任意一组属性的通用情况很棘手,因为从 _get 调用 get,或从 _set 调用 set,或 get_property_list以导致堆栈溢出的方式形成 _get_property_list 将使 Godot 崩溃(并在打开项目时继续崩溃)。 所以写这段代码的时候要小心。

为了避免从 _get_property_list 调用 get_property_list,我要做的是将我们想要的属性放入字典中:

tool
extends Spatial


var _properties := {
    "x": "",
    "y": ""
} setget _no_set, _no_get


func _set(property: String, value) -> bool:
    if _properties.has(property):
        _properties[property] = value
        smth_changed()
        return true

    return false


func _get(property: String):
    if _properties.has(property):
        return _properties[property]

    return null


func _get_property_list() -> Array:
    if not Engine.editor_hint or not is_inside_tree():
        return []

    var result := []
    for property_name in _properties.keys():
        result.append(
            {
                name = property_name,
                type = typeof(_properties[property_name]),
                usage = PROPERTY_USAGE_DEFAULT
            }
        )

    return result


func _no_set(_new_value) -> void:
    pass


func _no_get():
    return null


func smth_changed() -> void:
    print("something changed!")

另请注意,我正在根据 typeof 的值报告类型。

我会留给您来决定这种方法是否值得付出努力。例如,如果变量集可以更改,则可能是这样。 我提醒你,你可以调用 property_list_changed_notify 以便 Godot 调用 _get_property_list 并使用新的属性集更新检查面板。

尽管 _no_set,仍然可以从外部读取和操作字典。所以我添加了一个 getter _no_get returns null 来防止这种情况发生。 如果您喜欢 _no_set 中的警告,您可能也想要 _no_get 中的警告。


附录:这是一个变体,它使用数组来存储要导出的属性的名称。这样你仍然可以有常规变量而不是处理字典。保持阵列最新由您决定。

tool
extends Spatial


var _property_names := ["x", "y"] setget _no_set, _no_get
var _x:String
var _y:String


func _set(property: String, value) -> bool:
    if _property_names.has(property):
        set("_" + property, value)
        smth_changed()
        return true

    return false


func _get(property: String):
    if _property_names.has(property):
        return get("_" + property)

    return null


func _get_property_list() -> Array:
    if not Engine.editor_hint or not is_inside_tree():
        return []

    var result := []
    for property_name in _property_names:
        if not "_" + property_name in self:
            push_warning("Not existing variable: " + property_name)
            continue

        result.append(
            {
                name = property_name,
                type = typeof(get("_" + property_name)),
                usage = PROPERTY_USAGE_DEFAULT
            }
        )

    return result


func _no_set(_new_value) -> void:
    pass


func _no_get():
    return null


func smth_changed() -> void:
    print("something changed!")

请注意,我添加了一个检查以防止在没有支持变量的情况下导出,这也会发出警告。公开它们并不是灾难性的,因为它们只会被视为 null。

另请注意,我必须从该版本的变量中删除 _no_set。原因是我用 set 设置它们,这导致调用 setter,并且由于 _no_set 没有设置变量,结果是它没有保存值。


关于重置值的附录

如果你想添加那个箭头来重置你需要实现几个 (yikes) 未记录的方法的值:

func property_can_revert(property:String) -> bool:
    if property in self:
        return true

    return false


func property_get_revert(property:String):
    match typeof(get(property)):
        TYPE_NIL:
            return null
        TYPE_BOOL:
            return false
        TYPE_INT:
            return 0
        TYPE_REAL:
            return 0.0
        TYPE_STRING:
            return ""
        TYPE_VECTOR2:
            return Vector2()
        TYPE_RECT2:
            return Rect2()
        TYPE_VECTOR2:
            return Vector3()
        TYPE_TRANSFORM2D:
            return Transform2D()
        TYPE_PLANE:
            return Plane()
        TYPE_QUAT:
            return Quat()
        TYPE_AABB:
            return AABB()
        TYPE_BASIS:
            return Basis()
        TYPE_TRANSFORM:
            return Transform()
        TYPE_COLOR:
            return Color()
        TYPE_NODE_PATH:
            return NodePath()
        TYPE_RID:
            return RID(Object())
        TYPE_OBJECT:
            return Object()
        TYPE_DICTIONARY:
            return {}
        TYPE_ARRAY:
            return []
        TYPE_RAW_ARRAY:
            return PoolByteArray()
        TYPE_INT_ARRAY:
            return PoolIntArray()
        TYPE_REAL_ARRAY:
            return PoolRealArray()
        TYPE_STRING_ARRAY:
            return PoolStringArray()
        TYPE_VECTOR2_ARRAY:
            return PoolVector2Array()
        TYPE_VECTOR3_ARRAY:
            return PoolVector3Array()
        TYPE_COLOR_ARRAY:
            return PoolColorArray()

    return null

想法是 property_can_revert 将 return true 用于任何 属性 将具有重置箭头。并且 property_get_revert 将给出单击所述箭头时将设置的值。 这必须在 source code 中找到,因为它没有记录。