Trouble updating UI labels and playing card

Godot Version

4.3

Question

So, I have this card game where you drag cards to different slots on the game board. Each of these slots has a int called “value” that starts at zero. The cards each have a value as well, and when a card is played on a slot, the slot’s value should increase (or decrease if the card’s value is negative) based on the value of the card. I’m running into a couple of problems with it that have me stumped.

First, when I drag a card to the slot, it updates to the card’s value like it should. But if I drag another card on top of that one, it doesn’t change the slot’s value for the second card.

Second, after I drop a card into a slot, I want to be disable that card and make it so that I can no longer drag that card. As of now, I can pick up and drag a card from one slot to another as much as I want. Furthermore, when I do drag that card to a new slot, it updates that slot, and if I drag it to a slot where it’s already been placed, it adds the value of that card again!

Here are the relevant bits of code:
This is my _can_drop_data and _drop_data functions. Note that I tried removing it from the “draggable” group once it’s dropped, but that doesn’t work and it’s still draggable.

Screenshot 2024-12-17 103701

And here is my _drop_data function that updates the bank total with the value of the card first card but doesn’t add the value of subsequent cards.

Screenshot 2024-12-17 103434

I’m sorry if this seems sort of confusing, I’ll do my best to explain it more clearly if it’s difficult to understand my problem.

Well, the draggable isn’t going to work one is “Draggable” the other is "draggable.

from a writing standpoint you could change bank_total = bank_total + data.value to bank_total += data.value

That was a small oversight when I took the screenshots that I have sense corrected and it made no difference.

I tried that and it doesn’t change anything. The issue seems to be that I’m dropping the card on another card, not onto the slot, since the slot already has a card in it. I have the drop_data function on the slot as well as on the cards, so that I can drop the cards in a stack where one covers the previous, and so on. But since that makes it think it’s dropping it on a card and not on the bank, it doesn’t update. So, for example, if I play a 5 in one slot and then play another card on top of that 5, say, a 9, it doesn’t update to 14, it stays at 5. However, If I move that 5 to a different slot and then drop the 9 where the 5 was, it updates to 14! It definitely has something to do with the game not being able to “see” the slot when there is already a card there. I don’t know how to fix that.

ETA: when I free the card after it’s played, leaving the slot empty, and then play the next card, it updates as it should. It’s definitely something to do with the next card not being able to “see” the slot when the first card is there.

Okay, so I don’t know how your game works exactly, but maybe you don’t need to actually have cards that you move.

So, I created an inventory slot system, where the slots held a textureRect for displaying the item, and held a variable with a reference to the item. However, the item didn’t exist per say accept in that stored reference. So, when I moved the item, I would move the reference to the next slot, and change the texture. This prevented me from having to keep adding and removing the item. All I transfered was a reference and the new texture for the slot.

func add_item(item_info : Object) -> void:
	empty = false
	if item_info is PackedScene:
		item_stored = item_info.instantiate()
	else:
		item_stored = item_info
	$Icon.texture = item_stored.icon


func remove_item() -> void:
	empty = true
	item_stored = null
	$Icon.texture = null


func _get_drag_data(_position) -> Array:
     # This creates a texture that follows mouse while dragging
	var preview = TextureRect.new()
	preview.texture = $Icon.texture
	set_drag_preview(preview)
	if item_stored == null:
		return []
	return [self,item_stored,item_stored.type]


func _can_drop_data(_at_position: Vector2, _data: Variant) -> bool:
	return true


func _drop_data(_at_position: Vector2, data: Variant) -> void:
	if not active:
		return
	if not data == []:
		add_item(data[1])
		data[0].remove_item()

That is what I did, if you need to prevent movement, you can just add on

get_drag_data()
     if not empty:
          return [ ]

and under _drop_data():

if not data == [ ]:
	add_item(data[1])
	data[0].remove_item()

or something like that

However, just storing the reference saves you from have the card literally blocking the input reception. If you need to remember the card that is underneath you can save it in an array, so that way if you remove the top card, you repopulate the top card reference with the first one in the array and remove it from the array.

var empty : bool = true
var card_stored : Object
var stacked_cards : Array
var banked_value : int

func add_item(item_info : Object) -> void:
        if not item_stored == null:
              stored_cards.append(item_stored) 
	empty = false
	item_stored = item_info
	$Icon.texture = item_stored.icon
        banked_value += item_stored.value

func remove_item() -> void:
        if not cards_stored.is_empty():
                banked_value -= item_stored.value
                item_stored = cards_stored[0]
                cards_stored.pop_front()
                $Icon.texture = item_stored.texture
                return
        banked_value -= item_stored.value
	empty = true
	item_stored = null
	$Icon.texture = null

func _get_drag_data(_position) -> Array:
	var preview = TextureRect.new()
	preview.texture = $Icon.texture
	set_drag_preview(preview)
	if item_stored == null:
		return [ ]
	return [self,item_stored]


func _can_drop_data(_at_position: Vector2, _data: Variant) -> bool:
		return true


func _drop_data(_at_position: Vector2, data: Variant) -> void:
        if not data[0].empty:
                #this is if you don't want the card moved once it is in a slot
                return
	if not data == [ ]:
		add_item(data[1])
                # this references the slot that sends the data
		data[0].remove_item()

This is just a quick adaptation, I don’t know how you have it set up or if you need physical cards, but it would be a lot easier to eliminate physical cards, and just pass an instantiated ref.

If you have cards being created and destroyed though this method could cause a problem with a lot of unaccounted for loose uncoupled nodes, so if that is the case, maybe inheriting from RefCounted would be better for that case, but if you have a finite amount of cards this shouldn’t be a problems, especially if you never lose the reference for the cards, you can then queue_free() them at the end whenever you do “destroy” them.

Okay, I fixed it…somehow. Originally, each “card” was a button. This was left over from when I was going to do a point and click interface instead of a drag and drop. I changed them to TextureRects, and then I changed each of the slots to TextureRects and just set the texture to the sprite of the card and it seems to work now. I can drop cards and the slot total increases, and I can drop cards on top of other cards and it properly calculates and displayes the slot totals. I’m not quite sure how what I did fixed it, but I’m not asking questions as long as it works.

1 Like