I’m creating a simple 2d card game in Godot 4.3, I started with the following setup:
/game
├── CardManager
│ ├── Card.tscn # (Node2D Playing card)
├── UI
│ ├── PlayerHand.tscn # (Control - Player hand UI)
│ ├── EnemyUI.tscn # (Control - Enemy Mainly BackgroundRect + name)
│ │ ├── CardSlot.tscn # (Control - Slot for Card, pure placeholder color)
│ ├── Table.tscn # (Control - Central play area)
So it’s a pure GameUI with (max) 52 Node2d’s that are the playing cards
- Cards can be selected
- Cards can be dragged
- Card should receive any CardSlot underneath while dragging.
I think I made a mistake by combining Control and Node2d, but since I want the EnemyUI to be sticky to the TopRight corner, and the Table to always be centered I started using UI elements.
My current issue that I’m not able to ‘place’ the Card on top of a Slot. They are not re-parent, just with global_position
.:
func update_card_positions() -> void:
for slot in slots:
var card = Global.get_card(slot.card_up)
card.position = slot.global_position
I guess the Stretch mode set to viewport
scales some things and thus doesn’t the position align anymore.
What would be a good way to creating such an ‘almost-only-ui’ game?
Should I forget Control UI nodes, make everything Node2d’s and just ‘recalculate’ there position if the viewport changes?
For making a purely 2d card game I wouldn’t use Node2Ds at all. It’s basically all ui anyways so why not use Control nodes 
As for your scaling problem, it’s hard to say anything about what’s causing it without any screenshots. However, viewport
shouldn’t really mess anything up. You can try spawning sprites where you drop the card, adding visualization to the card slot and stuff like that to find out what the problem is. I do note that in your code sample you’re setting card.position
to the slot’s global_position
, should it be card.global_position
?
I did a quick card game prototype some time back and for that I used reparenting. When a card was grabbed it was reparented to a CardDragger
control which synced its position to the mouse. When the mouse was released, it checked if the mouse was over PlayArea
, DiscardArea
(both controls) and reparented the card to that. If not, it was reparented to PlayerHandArea
.
Note that these weren’t Area2Ds but controls. The areas were responsible for what to do with the card. PlayArea
did a quick animation of the card fading and executed the effects, PlayerHandArea
was a custom container that laid out the cards in a row with an animation and so on.
There are many ways to do this but I found that implementation very nice to work with.
1 Like
Thanks, this helps a lot.
The reason I went for Node2D’s is because I thought that would give me more control to implement stuff like drag&drop, hover effects and animations.
But It seems like all those UI actions should be doable with Control nodes only.
I’ll guess it’s time to refactor everything to Control nodes!
Thanks again!
1 Like