Loading...
Loading...
Expert blueprint for digital card games (CCG/Deckbuilders) including card data structures (Resource-based), deck management (draw/discard/reshuffle), turn logic, hand layout (arcing), drag-and-drop UI, effect resolution (Command pattern), and visual polish (godot-tweening, shaders). Use for CCG, deckbuilders, or tactical card games. Trigger keywords: card_game, deck_manager, card_data, hand_layout, drag_drop_cards, effect_resolution, command_pattern, draw_pile, discard_pile.
npx skill4agent add thedivergentai/gd-agentic-skills godot-genre-card-gamemove_to_front()z_index = 999MANDATORY: Read the appropriate script before implementing the corresponding pattern.
| Phase | Skills | Purpose |
|---|---|---|
| 1. Data | | Defining Card properties (Cost, Type, Effect) |
| 2. UI | | Hand layout, card positioning, tooltips |
| 3. Input | | Dragging cards to targets, hovering |
| 4. Logic | | Executing card effects, turn phases |
| 5. Polish | | Draw animations, holographic foils |
# card_data.gd
extends Resource
class_name CardData
enum Type { ATTACK, SKILL, POWER }
enum Target { ENEMY, SELF, ALL_ENEMIES }
@export var id: String
@export var name: String
@export_multiline var description: String
@export var cost: int
@export var type: Type
@export var target_type: Target
@export var icon: Texture2D
@export var effect_script: Script # Custom logic per card# deck_manager.gd
var draw_pile: Array[CardData] = []
var hand: Array[CardData] = []
var discard_pile: Array[CardData] = []
func draw_cards(amount: int) -> void:
for i in amount:
if draw_pile.is_empty():
reshuffle_discard()
if draw_pile.is_empty():
break # No cards left
var card = draw_pile.pop_back()
hand.append(card)
card_drawn.emit(card)
func reshuffle_discard() -> void:
draw_pile.append_array(discard_pile)
discard_pile.clear()
draw_pile.shuffle()# card_ui.gd
extends Control
var card_data: CardData
var start_pos: Vector2
var is_dragging: bool = false
func _gui_input(event: InputEvent) -> void:
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
if event.pressed:
start_drag()
else:
end_drag()
func _process(delta: float) -> void:
if is_dragging:
global_position = get_global_mouse_position() - size / 2
else:
# Hover effect or return to hand position
passfunc play_card(card: CardData, target: Node) -> void:
if current_energy < card.cost:
show_error("Not enough energy")
return
current_energy -= card.cost
# Execute effect
var effect = card.effect_script.new()
effect.execute(target)
move_to_discard(card)indextotal_cardsfunc update_hand_visuals() -> void:
var center_x = screen_width / 2
var radius = 1000.0
var angle_step = 5.0
for i in hand_visuals.size():
var card = hand_visuals[i]
var angle = deg_to_rad((i - hand_visuals.size() / 2.0) * angle_step)
var target_pos = Vector2(
center_x + sin(angle) * radius,
screen_height + cos(angle) * radius
)
card.target_rotation = angle
card.target_position = target_posmouse_filterz_indexCanvasLayer