Godot 4.2.1 Card Swapping

Godot 4.2.1

I am currently working on my small card game, and I recently watched a video of someone recreating Balatro’s card effects, movement, shaders, and swapping mechanics. Among these features, I am particularly interested in adding the swapping mechanism to my game, which will allow users to organize their hand by swapping the positions of cards.

The problem is that I have no clue where to start with this idea. If anyone could provide guidance on how to implement this feature, it would be wonderful. Thank you!

Here’s the video(timestamp 1:46-2:09): https://youtu.be/I1dAZuWurw4

My game(currently):

#Hand Code#

extends Control

var card_size = Vector2(24,40)


@export var hand_curve: Curve
@export var rotation_curve: Curve

@export var max_rotation_degrees : int 
@export var x_sep : int
@export var y_min : int
@export var y_max : int

func _process(_delta):
	_update_cards()

func _update_cards() -> void:
	var cards = get_child_count()
	var all_cards_size = card_size.x * cards + x_sep * (cards - 1)
	var final_x_sep = x_sep
	
	if all_cards_size > size.x:
		final_x_sep = (size.x - card_size.x * cards) / (cards - 1)
		all_cards_size = size.x

	var offset = (size.x - all_cards_size) / 2
	
	for i in cards:
		var card := get_child(i)
		var y_multiplier := hand_curve.sample(1.0 / (cards-1) * i)
		var rot_multiplier := rotation_curve.sample(1.0 / (cards-1) * i)
		
		if cards == 1:
			y_multiplier = 0.0
			rot_multiplier = 0.0
		
		var final_x: float = offset + card_size.x * i + final_x_sep * i
		var final_y: float = y_min + y_max * y_multiplier
		
		card.position = Vector2(final_x, final_y)
		card.rotation_degrees = max_rotation_degrees * rot_multiplier

func _on_mouse_entered():
	Game.mouse_on_hand = true


func _on_mouse_exited():
	Game.mouse_on_hand = false

#Basic Card movement and dragging code#

extends Control

@export var angle_x_max: float = 25.0
@export var angle_y_max: float = 25.0

var is_selected : bool = false
var following_mouse : bool = false
var is_placed : bool = false
var is_on_slot : bool = false

var tween_hover : Tween

var initial_pos : Vector2 = Vector2.ZERO


func _ready():
	pass


func _process(_delta):
	if following_mouse:
		global_position = get_global_mouse_position()
	#if is_selected:
	#	fake_3d()


func _gui_input(event):
	if event is InputEventMouseButton and event.button_index == 1:
		if event.button_mask == 1 and is_selected:
			Game.card_scene = load("res://Cards/Scenes/ace_c.tscn")
			Game.card_name = name
			initial_pos = position
			following_mouse = true
			
		if event.button_mask == 0:
			var hand = get_tree().get_root().get_child(1).get_child(1).get_child(0)
			
			if Game.mouse_on_hand and is_on_slot:
				
				add_card_to_hand(hand)
			
			if Game.mouse_on_slot:
				
				var slot = get_tree().get_root().get_child(1).get_child(0).find_child(Game.slot_name)
				
				if not slot.has_card:
					place_card_on_slot(slot)
				else:
					cancel_card_move()
			
			else:
				cancel_card_move()
			
			
			reset_card_selection()


func add_card_to_hand(hand):
	var temp_card = Game.card_scene.instantiate()
	hand.add_child(temp_card)
	Game.cards_in_hand += 1
	queue_free()


func place_card_on_slot(slot):
	var temp_card = Game.card_scene.instantiate()
	slot.add_child(temp_card)
	if not is_on_slot:
		Game.cards_in_hand -= 1  
	temp_card.is_on_slot = true
	queue_free()


func cancel_card_move():
	following_mouse = false
	is_selected = false
	position = initial_pos


func reset_card_selection():
	Game.card_scene = null
	Game.card_name = null
	is_selected = false
	following_mouse = false

func fake_3d():
	var mouse_pos: Vector2 = get_local_mouse_position()
	
	var diff: Vector2 = (position + size) - mouse_pos
	
	var lerp_val_x: float = remap(mouse_pos.x, 0.0, size.x, 0, 1)
	var lerp_val_y: float = remap(mouse_pos.y, 0.0, size.y, 0, 1)
	
	var rot_x: float = rad_to_deg(lerp_angle(-angle_x_max, angle_x_max, lerp_val_x))
	var rot_y: float = rad_to_deg(lerp_angle(angle_y_max, -angle_y_max, lerp_val_y))
	
	$Texture.material.set_shader_parameter("x_rot", rot_y)
	$Texture.material.set_shader_parameter("y_rot", rot_x)

func _on_mouse_entered():
	is_selected = true
	
	if tween_hover and tween_hover.is_running():
		tween_hover.kill()
	tween_hover = create_tween().set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_ELASTIC)
	tween_hover.tween_property(self, "scale", Vector2(2.15, 2.15), 0.5)


func _on_mouse_exited():
	if not following_mouse:
		is_selected = false
	
		if tween_hover and tween_hover.is_running():
			tween_hover.kill()
		tween_hover = create_tween().set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_ELASTIC)
		tween_hover.tween_property(self, "scale", Vector2(2,2), 0.55)


Board Node Structure:
board

Card Node Structure:
basiccards