I'm struggling with a persistent problem in my Godot 4.4.1 project involving "power slots" arranged around a table or arena background. No matter how I structure my nodes and update my code, I keep running into these problems:
- Power slot nodes and cards overlap or clump near the center of the table instead of spacing out neatly around the oval.
- Card sprites sometimes appear under the table background or UI, instead of in front.
- Attempts to fan cards horizontally in each slot either result in them stacking, rotating oddly, or being invisible.
- Debug prints confirm my slot node names and setup are correct, but cards/cards_container layering and arc math never work as intended.
I've reviewed tutorials and forum posts, but can't get reliable results. I've cleared the .godot cache, double-checked z-indexes, and strictly parented all power slots to a Node2D container. Still, the arc arrangement and card stacking issues persist.
Each player will have 3 cards.
What I'm Trying To Achieve
- Multiple "agent/power slots" should be evenly distributed in an arc around an oval or arena.
- Each slot displays an avatar, name, and a fan of power cards, spaced out, not overlapping or hidden.
- Cards must always show above the table background and UI.
What I've Already Tried
- Parent all slots under a Node2D container, not a Control or Panel.
- Calculate arc positions and set slot rotation so each "looks in" or stays upright.
- Use z_index and set it high for slots and cards.
- Manually position/fan out TextureRects for cards under each slot node (inside a Node2D or Control).
- Check that slot and card node names/types match the script.
Problems Still Present
- Slots/cards still overlap at the center or don't follow the arc at all.
- Sometimes cards appear below the background or UI, despite z_index changes.
- When fanning three cards, they may stack or rotate incorrectly, even with custom code.
- No runtime or inspector errors, only persistent render issues and layout bugs.
Questions
- What are the most reliable ways in Godot 4 to keep Node2D-based slots distributed in an exact arc/ellipse—especially when referencing a Control or Panel background for arc math?
- How can I guarantee cards/cards_container appear visually above all background or UI, no matter what?
- Is there a trick for getting fanned cards to always appear at the right position, not stacking or floating off-screen?
- What are common gotchas with z-index, CanvasLayer, or parent/child setup that cause cards or slots to vanish or overlap?
Current Scene Structure
MainScreen (Node2D) ├── Background (TextureRect/Panel) # Arena visual ├── PowerSlotContainer (Node2D) │ ├── PowerSlot1 (Node2D) │ └── PowerSlot2 (Node2D) │ └── SlotLayout (VBoxContainer) │ ├── NameLabel (Label) │ ├── PowerLabel (Label) │ └── CardFan (Node2D) ├── CanvasLayer │ └── UI Controls/Panels Example Card Display Function (pseudo-GDScript)
for i in range(card_count): var card_sprite = TextureRect.new() # assign texture, set card_sprite.custom_minimum_size, etc. card_sprite.position.x = i * (card_width * (1 - overlap)) card_sprite.rotation_degrees = base_angle + (fan_angle * i / max(1, card_count - 1)) card_sprite.z_index = 25 card_fan.add_child(card_sprite) Still, this produces stacking, invisible, or under-table card visuals.
Screenshots/Wireframe
What Am I Missing?
- Is there a bug or change in Godot 4.4.1 that affects layering or Node2D–Control interaction?
- Should I be doing the arc calculations in global or local space for best accuracy?
- Is CanvasLayer/Control always drawn on top of Node2D, regardless of z-index?
- Could Control-based containers (VBox/HBox) inside a Node2D slot cause unexpected stacking?
Any practical advice, working code samples, or best practices for slot arc/distribution and reliable 2D card fanning/layering in Godot 4 would be a huge help. Thank you!
Update as of July 26th:
The Path (
MainPlayScreen.tscn):- The main scene contains a
Path2Dnode namedTablePath. - This
TablePathhas aCurve2Dresource where the desired layout (a "U" shape or arc) is visually drawn directly in the editor. This path defines the exact line that the player slots will follow.
- The main scene contains a
The Follower (
PlayerSlot.tscn):- This scene represents a single player's UI area.
- Its root node is a
PathFollow2D, which is a special node designed to automatically stick to and move along aPath2D. - Inside, it contains a
Node2DnamedCardFanwhich acts as a stable pivot point for the "card" graphics.
Code Logic Summary
1. mainplayscreen.gd (The "Conductor")
This script is responsible for creating the player slots and telling them where to sit on the path.
_arrange_players_on_path()function: This is the core layout function. It loops through all active players and for each one:- Creates a new instance of
PlayerSlot.tscn. - Adds the new instance as a child of the
TablePathnode. - Calculates a
ratio(e.g., 0.0 for the first player, 0.1 for the second, etc.). - Sets the
progress_ratioproperty of thePlayerSlot(PathFollow2D) to thisratio. This is the key step that automatically moves the slot to the correct position along the visually drawn curve.
# In mainplayscreen.gd func _arrange_players_on_path(): player_slot_nodes.clear() for child in table_path.get_children(): child.queue_free() var player_count = players.size() if player_count == 0: return for i in range(player_count): var slot = PlayerSlotScene.instantiate() slot.deck_node = deck_node table_path.add_child(slot) var ratio = float(i) / float(player_count) slot.progress_ratio = 1.0 - ratio // Spreads slots evenly slot.set_player_data(players[i]) player_slot_nodes.append(slot)- Creates a new instance of
display_hand()function: After dealing, this function finds the correctPlayerSlotinstance and delegates the task of showing the cards to it. It does not handle any visual logic itself.# In mainplayscreen.gd func display_hand(player_to_display: Player, player_index: int): if player_slot_nodes.size() > player_index: var slot = player_slot_nodes[player_index] slot.display_cards(player_to_display.current_hand, ...)
2. PlayerSlot.gd (The "Follower" and "Card Displayer")
This script is attached to PlayerSlot.tscn and is responsible for displaying the cards correctly. This is where the card sizing logic is located.
display_cards()function: This function receives the card data and is responsible for creating the visual card graphics.- It calculates the desired
card_size(e.g., a smallVector2(60, 84)for AI players). - It loops through the card data and for each card, creates a new
TextureRectnode. - Crucially, it attempts to control the size of the
TextureRectusing three properties to force the large card texture to shrink down and fit into the smallcard_sizerectangle.
# In PlayerSlot.gd func display_cards(cards_array: Array, show_faces: bool, is_ai: bool): for c in card_fan.get_children(): c.queue_free() var card_size = Vector2(80, 112) if not is_ai else Vector2(60, 84) for i in range(cards_array.size()): var sprite = TextureRect.new() sprite.texture = _get_card_texture(...) # --- This is the logic that is intended to control the card size --- sprite.custom_minimum_size = card_size sprite.expand_mode = TextureRect.EXPAND_KEEP_SIZE sprite.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED # ----------------------------------------------------------------- # This logic creates the fanned effect # ... (code for position and rotation) ... card_fan.add_child(sprite)- It calculates the desired
Here's the scene view along with scene structure and screenshot:
MainPlayScreen Scene Structure:
PlayerSlot Scene Structure:
In-game screenshot:









