Hello, I’m doing some experiments and learning with an already delivered game for a gamejam I’ve participated and I was wondering how to workaround this issue. I’ve searched on the documentation and on the Godot forums and I guess that the answer is that there’s no way to pass a parameter as reference, except for arrays, dictionaries and some other variables.
What I’m trying to achieve is to make two buttons move up and down when you hover over them. So I made a handmade code and then I made a method to generalize it and make it usable for both of them.
The issue was that the code was working properly, but the bool variable (startDirection/exitDirection) were not being updated to true, so the button only went up.
My workaround was to return the value and assign it to the actual boolean, but I wanted to know if there’s a real solution for this or, on the other hand, if there’s a better way to do this hover feature in the Control Node properties, using other node or somewhere else.
My current code is this, and if you take out the return value and the startDirection = hover…() it doesn’t work as explained:
func _process(delta):
startDirection = hoverButtonBehaviour(start,startDirection)
exitDirection = hoverButtonBehaviour(exit,exitDirection)
func hoverButtonBehaviour(button:TextureButton, direction:bool):
if button.is_hovered():
if direction:
button.position.y += 0.125
if button.position.y > 3:
direction = false
else:
button.position.y -= 0.125
if button.position.y < -3:
direction = true
else:
button.position.y = 0
return direction
As you said, you cannot pass a bool by reference. I don’t really see any issues with your workaround but if you have many buttons and you don’t want to make a boolean variable for every one of them, maybe you can use a dictionary (which you can pass by reference) and work with it. Something like this:
@onready var start = $PathToStartButton
@onready var exit = $PathToStartButton
@onready var buttonsDirections = {
start : true,
exit: true,
}
func _process(delta):
hoverButtonBehaviour(start)
hoverButtonBehaviour(exit)
func hoverButtonBehaviour(button:TextureButton):
if button.is_hovered():
if buttonsDirections[button]:
button.position.y += 0.125
if button.position.y > 3:
buttonsDirections[button]= false
else:
button.position.y -= 0.125
if button.position.y < -3:
buttonsDirections[button]= true
else:
button.position.y = 0
Note that the @onready decorator is needed as buttonsDirections uses references to your buttons as keys that are also set with @onready
Hi, thank you for your answer.
I’ve seen time to time these tween solutions but I don’t understand them fully even with the documentation. At the moment, I kinda see your implementation as a signal, but with some different implementation (?). Is that too wrong or in the right path?
Do you recommend any resource, tutorial or video where I could learn properly how to use them?
On the other hand, in the documentation I’ve also seen tweens used to make things I thought it was okay just to use normal code. For example, one of the first examples is to use a tween to change something in color, size and the queue_free() it. Why would I use a tween for queue_free instead of using queue_free directly from itself?
Other questions that pop into my mind (perhaps they’re are too many and out of this topic) are:
what’s the difference between a get_tree tween and a node tween.
Should I create a tween at the start of the script and use that everytime or create them as I need them?
Node class has something like a variable/slot for tweener, Tween is Refcounted, every create_tween() on node that already has one makes old invalid and freed, that why animation can be canceled even if it a middle of its progress.
get_tree() tweener can live any time till game session is active, but nodes can be temp things, I believe thats only a difference. get_tree() tweener can be called from any place if used like Engine.get_main_loop() (returns active scene tree node)
you can even create a timer using tweener, it has some advantages over standard
await get_tree().create timer
tweeners (instances of it) only exist when they play or awaiting to send callback, so to start animation you just have to create new tweener. Its becomes easier to understand when you start freely use of lambda functions.
As a problem of mapping a single variable to a button I would recommend using set_meta and get_meta. Using @pauldrewett 's ternary shortened example
func hoverButtonBehaviour(button: TextureButton) -> void:
# get direction from the button
var direction: bool = button.get_meta("direction", false)
if button.is_hovered():
button.position.y += 0.125 if direction else -0.125
if abs(button.position.y) > 3:
direction = !direction
# apply modified direction to the button
button.set_meta("direction", direction)
else:
button.position.y = move_toward(button.position.y, 0, 0.125)
@solver10 's got my favorite idea, to use tweens or even an AnimationPlayer would be better, and signals to connect should be favored over using _process. Though the tween code posted does not loop up and down, and needs to be killed/stopped when the mouse leaves.
You may find combining the metadata approach and a tween the most code-friendly approach without having to add AnimationPlayers to each button. But it’s for extra credit haha, you can get more complex animations out of Tweens if you choose to use them.
func _ready() -> void:
# connecting signals, using a for loop if any more than your example
for button in [start, exit]:
button.mouse_entered.connect(start_button_tween.bind(button))
button.mouse_exited.connect(stop_button_tween.bind(button))
func start_button_tween(button: TextureButton) -> void:
# if a tween is found, resume it.
var current_tween: Tween = button.get_meta("bobbing", null)
if current_tween:
current_tween.play()
return
var new_tween: Tween = button.create_tween()
# loop until killed
new_tween.set_loops()
# ease in and out
new_tween.set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN_OUT)
# as_relative so buttons bob relative to their current position
# three pixels is not a lot but you go girl
new_tween.tween_property(button, "position:y", 3, 0.25).as_relative()
new_tween.tween_property(button, "position:y", -3, 0.25).as_relative()
# set metadata
button.set_meta("bobbing", new_tween)
func stop_button_tween(button: TextureButton) -> void:
# get metadata or null
var current_tween: Tween = button.get_meta("bobbing", null)
# check not null then pause
if current_tween and current_tween.is_valid():
current_tween.pause()
I think the example you are refering to is tween_callback which is used to run a function in the middle or at the end of a tween, it’s an example of something turning red and shrinking as a death animation, then deleted once the animation is finished.
Another way to do things like this is to create a class to handle it. See code
( You can even put the class in a separate .gd script file and add class_name to the top and reference the class by using the full class name E.g. ButtonUtils.ButtXManager)
# Assume you have start and exit TextureButtons already
# Then the code below creates and inits objects for each one
var btn_start=ButtXManager.new(start, false)
var btn_exit=ButtXManager.new(exit, false)
func _process(delta):
# Start alternating the buttons
var startDirection = btn_start.hoverButtonBehaviour()
var exitDirection = btn_exit.hoverButtonBehaviour()
print(btn_start.direction, btn_exit.direction)
# ButtXManager class.
class ButtXManager:
var button: TextureButton
var direction: bool=false
# Initialize class with button and direction
func _init(abutton, adirection):
button=abutton
direction = adirection
# Hover method
func hoverButtonBehaviour():
if button.is_hovered():
if direction:
button.position.y += 0.125
if button.position.y > 3:
direction = false
else:
button.position.y -= 0.125
if button.position.y < -3:
direction = true
else:
button.position.y = 0
return direction