How to create a getter function without a setter function? - godot

I have multiple exported variables in my script and anytime a single one is changed I want to invoke a common getter and let the values be set automatically
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!")
but this doesn't work and I have to create a setter for every single variable
is there any solution around this?

Please notice that you are defining smthn_changed as getter for those properties. And the getters are called when you try to read them, not when you try to assign them.
Alright, let us say you do want to know when the variables are being assigned. For that you would usually use setters, like this:
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
The setter will be called at any time the variable is asignad externally (or internally with self.property = value, if you don't use self you can assign the variable directly without trigering the setter).
However, since you need to write the actual variable from the setter, this implies making a setter for each variable (if you used the same setter for multiple variable, you would not know which to set).
There is something else you can try: _set. The issue with _set is that will only be called for variables that are not declared in the script.
So, here is the plan:
We are going to declare backing variables with a different name, not export them.
We are going to use _set and _set to handle them.
And we are going to use _get_property_list to export them.
Let us see the case of just one variable:
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!")
That is not worth the effort compared to a simple setter.
The setter _no_set is a setter that does nothing (not even set the variable). I have added it to prevent bypassing the mechanism externally by setting to the backing variable directly. You could add a warning there, as that is not something you code should be doing. On the flip the fact that your code should not be doing it could also be taken as an argument against having _no_set.
But let us see how it scales to multiple variables:
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!")
Still not great, since we are having to repeat the variables multiple times. I would still prefer to have multiple setters, even if they all have the same code.
A generic case for an arbitrary set of properties is tricky, because calling get from _get, or set from _set, or get_property_list form _get_property_list in such way that it causes a stack overflow will crash Godot (and continue crashing it upon opening the project). So be careful when writing this code.
What I'm going to do to avoid calling get_property_list from _get_property_list is to put the properties we want in a dictionary:
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!")
Notice also that I'm reporting the type based on the value with typeof.
I'll leave it to you to decide if this approach is worth the effort. It might be, if the set of variables can change, for example. And I remind you that you can call property_list_changed_notify so that Godot calls _get_property_list and updates the inspector panel with the new set of properties.
Despite the _no_set, the dictionary could still be read and manipulated externally. So I added a getter _no_get that returns null to prevent that. If you like a warning in your _no_set, you may want a warning in your _no_get too.
Addendum: Here is a variation that uses an array for the names of the properties you want to export. This way you can still have regular variables instead of dealing with a dictionary. It is up to you to keep the array up to date.
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!")
Note that I have added a check to prevent exporting without a backing variable, which also pushes a warning. It is not catastrophic to expose them as they would just be handled as null.
Also note that I had to remove the _no_set from the variables in this version. The reason being that I set them with set, which results in calling the setter, and since the _no_set didn't set the variable the result was it wasn't saving the values.
Addendum on resetting the value
If you want to add that arrow to reset the value you need to implement a couple of (yikes) undocumented methods:
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_VECTOR3:
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
The idea is that property_can_revert will return true for any property that will have the reset arrow. And property_get_revert will give the value that will be set when you click said arrow. This had to be found in the source code since it is not documented.

Related

Switch between 2D layers programmatically

NOTE this is about Godot 4.0
In a particular scene, I've a set of layers, of which only one is visible at a time. My code is able to decide how a switch from one layer to other occurs.
I've implemented a class for solutioning this: ViewSwitch. That class has a set of ViewSwitchItem. The layers have to be a subtype of ViewSwitchItem.
The problem I'm facing is that, only once, from a layer first_menu, after I click the "Start Game" button in my first layer and switch to another layer start_game_menu, the game switches back to first_menu even if the player didn't meant so. Like I said, it happens only once. After you click like, say, the second time, you'll be transitioned to start_game_menu without being redirected to first_menu again. All this is done using GDScript. Something is wrong in my logic.
gd_util/ViewSwitch.gd
class_name ViewSwitch
var current_item: ViewSwitchItem = null
var items: Array[ViewSwitchItem] = []
func initialize() -> void:
swap(null)
for sw in items:
sw.parent_switch = self
sw.initialize()
func swap(swap: ViewSwitchItem) -> void:
if current_item == swap:
return
if current_item != null:
current_item.end(swap)
else:
immediate_swap(swap)
func immediate_swap(swap: ViewSwitchItem) -> void:
for sw in items:
sw.node.visible = false
if swap == null:
return
current_item = swap
swap.node.visible = true
swap.start()
gd_util/ViewSwitchItem.gd
class_name ViewSwitchItem
var parent_switch: ViewSwitch = null
var node: Node = null
func initialize():
pass
func start():
pass
func end(swap: ViewSwitchItem):
if parent_switch.current_item == swap:
return
immediate_swap(swap)
func immediate_swap(swap: ViewSwitchItem):
if parent_switch.current_item == swap:
return
parent_switch.immediate_swap(swap)
scenes/mainMenuScene/MainMenuScene.gd
extends Node2D
var view_switch: ViewSwitch = ViewSwitch.new()
var first_menu := MainMenuScene_firstMenu.new()
var start_game_menu := MainMenuScene_startGameMenu.new()
# Called when the node enters the scene tree for the first time.
func _ready():
view_switch.items = [
first_menu,
start_game_menu,
]
first_menu.node = $root/animation/container1
start_game_menu.node = $root/animation/container2_startGame
view_switch.initialize()
view_switch.swap(first_menu)
# first_menu
$root/animation/container1/startGameBtn.pressed.connect(func():
view_switch.swap(start_game_menu))
$root/animation/container1/exitBtn.pressed.connect(func():
get_tree().quit())
# start_game_menu
$root/animation/container2_startGame/panel/PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/returnBtn.pressed.connect(func():
view_switch.swap(first_menu))
$root/animation.animation_finished.connect(func(animation_name):
if animation_name == "Anim":
view_switch.swap(first_menu))
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
# first_menu
if view_switch.current_item == first_menu:
pass
# start_game_menu
elif view_switch.current_item == start_game_menu:
if Input.is_action_just_released("back"):
view_switch.swap(first_menu)
scenes/mainMenuScene/switches/MainMenuScene_firstMenu.gd
class_name MainMenuScene_firstMenu
extends ViewSwitchItem
var start_game_button = null
func initialize():
start_game_button = self.node.get_tree().root.get_child(0).get_node("root/animation/container1/startGameBtn")
func start():
start_game_button.grab_focus()
func end(swap: ViewSwitchItem):
immediate_swap(swap)
scenes/mainMenuScene/switches/MainMenuScene_startGameMenu.gd
class_name MainMenuScene_startGameMenu
extends ViewSwitchItem
var panel1 = null
func initialize():
panel1 = self.node.get_tree().root.get_child(0).get_node("root/animation/container2_startGame/panel")
panel1.after_popup.connect(func():
self.node.get_tree().root.get_child(0).get_node("root/animation/container2_startGame/panel/PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/returnBtn").grab_focus())
panel1.after_collapse.connect(func():
# switch to first_menu
immediate_swap(parent_switch.items[0]))
func start():
panel1.popup()
func end(swap: ViewSwitchItem):
panel1.collapse()
Thanks!

Avoid invoking setget function on starting up

I have a simple script like this:
tool
extends Node2D
export(int) var example_value=0 setget set_example_value
func set_example_value(val):
print("Setting example_value=",val)
#time/memory consuming code segment here
example_value=val
and I set example_value to 3 and then exit the game engine
Now when I startup godot again the set_example_value() gets invoked to set the value,
Is there any way where the example_value will get set to 3 without the setter function being invoked?
why am I doing this?
because I have a time/memory consuming function which generates sprites when the value is changed,
so when I startup godot I don't want to recreate those sprites I only want the value to be changed to what it was before closing godot
Please read my answer for Play animation without invoking setget? first.
The difference is that we will tell Godot to only store one of them. We can do that with _get_property_list. Thus, we are not going to use export with it.
For example we can do this:
var example_value := 0
func _get_property_list() -> Array:
return [
{
name = "example_value",
type = TYPE_INT,
usage = PROPERTY_USAGE_EDITOR
}
]
And the editor will show the variable because it has PROPERTY_USAGE_EDITOR, but it won't be stored because it does not have PROPERTY_USAGE_STORAGE.
If it is not stored, then when loading Godot won't find it, and won't set it (note that it might be already stored before you told Godot to not store it… Saving the resource again will fix it, or use an external editor).
Now the problem is that you are not saving the value at all. So we are going to have a two properties. One is only for the editor, and one is only for storage. And the storage one will not do the expensive process. Like this:
tool
extends Node
var example_value := 0 setget set_example_value
func set_example_value(mod_value:int) -> void:
print("HELLO")
example_value = mod_value
var example_value_storage:int setget set_example_value_storage, get_example_value_storage
func get_example_value_storage() -> int:
return example_value
func set_example_value_storage(mod_value:int) -> void:
example_value = mod_value
func _get_property_list() -> Array:
return [
{
name = "example_value",
type = TYPE_INT,
usage = PROPERTY_USAGE_EDITOR
},
{
name = "example_value_storage",
type = TYPE_INT,
usage = PROPERTY_USAGE_STORAGE
}
]
By the way, while we are at it, checking if the value is being modified in the setter might be a good idea:
func set_example_value(mod_value:int) -> void:
if example_value == mod_value:
return
print("HELLO")
example_value = mod_value
Just incase someone wanted a simpler (but hacky) workaround
tool
extends Node2D
export(int) var example_value=0 setget set_example_value
var startup=true
func set_example_value(val):
print("Setting example_value=",val)
if(startup):
example_value=val
return
#time/memory consuming code segment here
example_value=val
func _process(delta):
startup=false
set_process(false)
func _init():
set_process(true)

Finding list/map of free variable(s) of a closure in groovy

This is my simple groovy script;
def fourtify(String str) {
def clsr = {
str*4
}
return clsr
}
def c = fourtify("aa")
println("binding variables: ${c.getBinding().getVariables()}")
...
All I'm trying to do here is being able to access the free variable "str" using the closure instance to understand how closure works behind the scenes a bit more better. Like, perhaps, Python's locals() method.
Is there a way to do this?
The closure you have defined does not store anything in binding object - it simply returns String passed as str variable, repeated 4 times.
This binding object stores all variables that were defined without specifying their types or using def keyword. It is done via Groovy metaprogramming feature (getProperty and setProperty methods to be more specific). So when you define a variable s like:
def clsr = {
s = str*4
return s
}
then this closure will create a binding with key s and value evaluated from expression str * 4. This binding object is nothing else than a map that is accessed via getProperty and setProperty method. So when Groovy executes s = str * 4 it calls setProperty('s', str * 4) because variable/property s is not defined. If we make a slightly simple change like:
def clsr = {
def s = str*4 // or String s = str * 4
return s
}
then binding s won't be created, because setProperty method does not get executed.
Another comment to your example. If you want to see anything in binding object, you need to call returned closure. In example you have shown above the closure gets returned, but it never gets called. If you do:
def c = fourtify("aa")
c.call()
println("binding variables: ${c.getBinding().getVariables()}")
then your closure gets called and binding object will contain bindings (if any set). Now, if you modify your example to something like this:
def fourtify(String str) {
def clsr = {
def n = 4 // it does not get stored as binding
s = str * n
return s
}
return clsr
}
def c = fourtify("aa")
c.call()
println("binding variables: ${c.getBinding().getVariables()}")
you will see following output in return:
binding variables: [args:[], s:aaaaaaaa]
Hope it helps.
in your example str is a parameter of the method/function fortify
however maybe following example will give you better Closure understanding:
def c={ String s,int x-> return s*x }
println( c.getClass().getSuperclass() ) // groovy.lang.Closure
println( c.getMaximumNumberOfParameters() ) // 2
println( c.getParameterTypes() ) // [class java.lang.String, int]
the locals() Python's function better matches groovy.lang.Script.getBinding()
and here is a simple example with script:
Script scr = new GroovyShell().parse('''
println this.getBinding().getVariables() // print "s" and "x"
z = s*(x+1) // declare a new script-level var "z"
println this.getBinding().getVariables() // print "s", "x", and "z"
return s*x
''')
scr.setBinding( new Binding([
"s":"ab",
"x":4
]) )
println scr.run() // abababab
println scr.getBinding().getVariables() // print "s", "x", and "z"

Struct's zero value

Here is sample code:
package main
import (
"fmt"
)
type A struct {
Name string
}
func (this *A) demo(tag string) {
fmt.Printf("%#v\n", this)
fmt.Println(tag)
}
func main() {
var ele A
ele.demo("ele are called")
ele2 := A{}
ele2.demo("ele2 are called")
}
Run results:
&main.A{Name:""}
ele are called
&main.A{Name:""}
ele2 are called
It looks like those are the same about var ele A and ele2 := A{}
So, the struct's Zero value is not nil, but a struct that all of the property are initialized Zero value. Is the guess right?
If the guess is right, then the nature of var ele A and ele2 := A{} are the same right?
Why guess (correctly) when there's some documentation ?
When storage is allocated for a variable, either through a declaration or a call of new, or when a new value is created, either through a composite literal or a call of make, and no explicit initialization is provided, the variable or value is given a default value.
Each element of such a variable or value is set to the zero value for its type:
false for booleans,
0 for integers,
0.0 for floats,
"" for strings,
and nil for pointers, functions, interfaces, slices, channels, and maps.
This initialization is done recursively, so for instance each element of an array of structs will have its fields zeroed if no value is specified.
Note that there's no way to set a struct value to nil (but you could set the value of a pointer to a struct to nil).
I don't believe the top-voted answer is clearly worded to answer the question, so here is a more clear explanation:
"The elements of an array or struct will have its fields zeroed if no value is specified. This initialization is done recursively:"
Source
Demonstrating #Denys Séguret's first-rate answer. Each element of such a variable or value is set to the zero value for its type (https://golang.org/ref/spec#The_zero_value):
package main
import "fmt"
func main() {
// false for booleans,
var bl bool // false
//0 for numeric types,
var in int // 0
// "" for strings,
var st string // ""
// and nil for pointers, functions, interfaces, channels,
var pi *int // <nil>
var ps *string // <nil>
var fu func() // <nil> Go vet error. https://stackoverflow.com/a/56663166/12817546
var ir interface{} // <nil>
var ch chan string // <nil>
fmt.Println(bl, in, st, pi, ps, fu, ir, ch)
// slices, and maps.
var sl []int // true
var mp map[int]string // true
var pm *map[int]string // <nil>
fmt.Printf("%v %v %v\n", sl == nil, mp == nil, pm)
}

How do I create and access the global variables in Groovy?

I need to store a value in a variable in one method and then I need to use that value from that variable in another method or closure. How can I share this value?
In a Groovy script the scoping can be different than expected. That is because a Groovy script in itself is a class with a method that will run the code, but that is all done runtime. We can define a variable to be scoped to the script by either omitting the type definition or in Groovy 1.8 we can add the #Field annotation.
import groovy.transform.Field
var1 = 'var1'
#Field String var2 = 'var2'
def var3 = 'var3'
void printVars() {
println var1
println var2
println var3 // This won't work, because not in script scope.
}
def i_am_not_global = 100 // This will not be accessible inside the function
i_am_global = 200 // this is global and will be even available inside the
def func()
{
log.info "My value is 200. Here you see " + i_am_global
i_am_global = 400
//log.info "if you uncomment me you will get error. Since i_am_not_global cant be printed here " + i_am_not_global
}
def func2()
{
log.info "My value was changed inside func to 400 . Here it is = " + i_am_global
}
func()
func2()
here i_am_global variable is a global variable used by func and then again available to func2
if you declare variable with def it will be local, if you don't use def its global
class Globals {
static String ouch = "I'm global.."
}
println Globals.ouch
Like all OO languages, Groovy has no concept of "global" by itself (unlike, say, BASIC, Python or Perl).
If you have several methods that need to share the same variable, use a field:
class Foo {
def a;
def foo() {
a = 1;
}
def bar() {
print a;
}
}
Just declare the variable at class or script scope, then access it from inside your methods or closures. Without an example, it's hard to be more specific for your particular problem though.
However, global variables are generally considered bad form.
Why not return the variable from one function, then pass it into the next?
I think you are talking about class level variables.
As mentioned above using global variable/class level variables are not a good practice.
If you really want to use it. and if you are sure that there will not be impact...
Declare any variable out side the method. at the class level with out the variable type
eg:
{
method()
{
a=10
print(a)
}
// def a or int a wont work
a=0
}
def sum = 0
// This method stores a value in a global variable.
def add =
{
input1 , input2 ->
sum = input1 + input2;
}
// This method uses stored value.
def multiplySum =
{
input1 ->
return sum*input1;
}
add(1,2);
multiplySum(10);
Could not figure out what you want, but you need something like this ? :
​def a = { b -> b = 1 }
​bValue = a()
println b // prints 1
Now bValue contains the value of b which is a variable in the closure a. Now you can do anything with bValue Let me know if i have misunderstood your question

Resources