How to pass parameters as reference?

Godot Version

4.3.stable.mono

Question

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

Thank you for your time!

This might be a bit easier to read:

func hoverButtonBehaviour(button: TextureButton, direction: bool) -> bool:
    if button.is_hovered():
        button.position.y += 0.125 if direction else -0.125
        if abs(button.position.y) > 3:
            direction = !direction
    else:
        button.position.y = move_toward(button.position.y, 0, 0.125)
    
    return direction

I would also use ‘is_moving_up’ for your current ‘direction’ boolean for clarity. Direction is a bit vague.

PS I am not sure exactly what you are asking. Are you asking to achieve this without the direction bool?

3 Likes

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

1 Like
	var button:=$"0/right/string/icon"
	button.mouse_entered.connect(func():
		button.create_tween().tween_property(button, "position:y", -10, 0.1)
		)
	button.mouse_exited.connect(func():
		button.create_tween().tween_property(button, "position:y", 0, 0.1)
		)

just replace to valid node path and it works nice

1 Like

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.

1 Like

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.

1 Like

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
3 Likes

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.