Skill v1.0.1
Automated scan100/10013 files
version: "1.0.1" name: godot-genre-card-game description: "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."
Genre: Card Game
Expert blueprint for digital card games with data-driven design and juicy UI.
NEVER Do (Expert Anti-Patterns)
Logic & Architecture
- NEVER hardcode card logic inside UI scripts; strictly encapsulate gameplay effects in `Callable` objects or Command resources pushed to a LIFO stack.
- NEVER perform board-state calculations (Power/Toughness) in
_process(); strictly use Signal-driven triggers or a centralizedEffectStackresolver. - NEVER forget LIFO Stack Resolution; strictly use `Array.push_back()` and `Array.pop_back()` to resolve reactions from top-to-bottom.
UX & Animation
- NEVER skip Z-Index management during drag-and-drop; strictly raise the card to the front on click to prevent it sliding under other cards.
- NEVER allow instant card "teleportation" between piles; strictly use Tween animations (0.2s+) to give cards a tactile, physical feel.
- NEVER use
global_positionfor cards in hand; strictly position them using a `Curve2D` (Bezier) layout with `sample_baked()` for smooth, non-circular arcs. - NEVER allow instant card "teleportation" between piles; strictly use `create_tween()` and `tween_property` chainings (0.2s+) for juicy card-feel.
Deck & State Management
- NEVER forget to handle Empty Deck scenarios; strictly implement auto-reshuffle of the discard pile to prevent soft-locks.
- NEVER use floating point numbers for discrete card stats; strictly use
intfor Costs, Attack, and Health to avoid precision drift. - NEVER use standard Control nodes for mass tokens/battlefields; strictly use `_draw()` custom drawing to bypass SceneTree overhead when rendering 100+ cards or map icons.
- NEVER rely on SceneTree order for hand logic; strictly manage logical order in an Array and update visuals via `queue_redraw()`.
- NEVER erase array elements during a standard
forloop; strictly iterate in reverse or usefilter()to avoid indexing errors. - NEVER forget to provide parameterless constructors in
_init(); otherwise, Resources will fail to load in the Inspector.
🛠 Expert Components (scripts/)
Original Expert Patterns
- card_effect_resolution.gd - Stack-based effect resolver (LIFO/FIFO) handling nested triggers and counter-play.
Modular Components
- card_data_resource.gd - Data-driven card definitions allowing Inspector-based design.
- deck_shuffle_bag.gd - Secure randomization patterns for uniform card distribution.
- turn_state_machine.gd - Managing rigid phases (Draw, Play, Combat) via state matching.
- card_drag_drop.gd - Implementation of native
_get_drag_data()for Control nodes. - board_query_filter.gd - Functional
filter()patterns for querying board metadata. - card_tween_manager.gd - Managing interruptible card juice and board transitions.
- reactive_card_ui.gd - Resource-signal driven UI for automatic visual state updates.
- board_state_dictionary.gd - Grid-based tracking (Vector2i) decoupled from Node order.
- match_state_resetter.gd - Clean-up pattern for in-match temporary Resource modifications.
- deck_builder_validator.gd - Backend logic for deck-building constraints and mana curves.
Core Loop
- Draw: Player draws cards from a deck into their hand.
- Evaluate: Player assesses board state, mana/energy, and card options.
- Play: Player plays cards to trigger effects (damage, buff, summon).
- Resolve: Effects occur immediately or go onto a stack.
- Discard/End: Unused cards are discarded (roguelike) or kept (TCG), turn ends.
Skill Chain
| Phase | Skills | Purpose | |
|---|---|---|---|
| 1. Data | resources, custom-resources | Defining Card properties (Cost, Type, Effect) | |
| 2. UI | control-nodes, layout-containers | Hand layout, card positioning, tooltips | |
| 3. Input | drag-and-drop, state-machines | Dragging cards to targets, hovering | |
| 4. Logic | command-pattern, signals | Executing card effects, turn phases | |
| 5. Polish | godot-tweening, shaders | Draw animations, holographic foils |
Architecture Overview
1. Card Data (Resource-based)
Godot Resources are perfect for card data.
# card_data.gdextends Resourceclass_name CardDataenum 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
2. Deck Manager
Handles the piles: Draw Pile, Hand, Discard Pile, Exhaust Pile.
# deck_manager.gdvar 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 leftvar 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()
3. Card Visual (UI)
The interactive node representing a card in hand.
# card_ui.gdextends Controlvar card_data: CardDatavar start_pos: Vector2var is_dragging: bool = falsefunc _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 / 2else:# Hover effect or return to hand positionpass
Key Mechanics Implementation
Effect Resolution (Command Pattern)
Decouple the "playing" of a card from its "effect".
func play_card(card: CardData, target: Node) -> void:if current_energy < card.cost:show_error("Not enough energy")returncurrent_energy -= card.cost# Execute effectvar effect = card.effect_script.new()effect.execute(target)move_to_discard(card)
Hand Layout (Arching)
Cards in hand usually form an arc. Use a math formula (Bezier or Circle) to position them based on index and total_cards.
func update_hand_visuals() -> void:var center_x = screen_width / 2var radius = 1000.0var angle_step = 5.0for 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 = anglecard.target_position = target_pos
Common Pitfalls
- Complexity Overload: Too many keywords. Fix: Stick to 3-5 core keywords (e.g., Taunt, Poison, Shield) and expand slowly.
- Unreadable Text: Tiny fonts on cards. Fix: Use icons for common stats (Damage, Block) and keep text short.
- Animation Lock: Waiting for slow animations to finish before playing the next card. Fix: Allow queueing actions or keep animations snappy (< 0.3s).
Godot-Specific Tips
- MouseFilter: Getting drag/drop to work with overlapping UI requires careful setup of
mouse_filter(Pass vs Stop). - Z-Index: Use
z_indexorCanvasLayerto ensure the dragged card is always on top of everything else. - Tweens: Essential! Tween position, rotation, and scale for that "juicy" Hearthstone/Slay the Spire feel.
🚀 Elite Technical Implementations (Batch 09)
1. Holographic Foil (Shader Script)
Add visual rarity and "juice" to cards using a holographic shader. This script uses iridescence based on TIME and UV coordinates to create a shifting rainbow effect.
shader_type canvas_item;uniform float foil_speed : hint_range(0.1, 5.0) = 1.0;uniform float foil_intensity : hint_range(0.0, 1.0) = 0.5;void fragment() {vec4 base_color = texture(TEXTURE, UV);// Create shifting rainbow iridescencevec3 holo_color = vec3(0.5 + 0.5 * sin(TIME * foil_speed + UV.x * 10.0),0.5 + 0.5 * sin(TIME * foil_speed + UV.y * 10.0 + 2.0),0.5 + 0.5 * sin(TIME * foil_speed + (UV.x + UV.y) * 10.0 + 4.0));// Blend with base texture alphaCOLOR = vec4(mix(base_color.rgb, holo_color, foil_intensity * base_color.a), base_color.a);}
2. Card-History Logging (Action Tracking)
Track card actions (Played, Drawn, Discarded) for a history panel using a custom Logger. This intercepts messages tagged with [CARD] and routes them to a turn history buffer.
class_name CardHistoryLogger extends Loggersignal history_updated(entry: String)var turn_history: Array[String] = []func _log_message(message: String, error: bool) -> void:if not error and message.begins_with("[CARD]"):turn_history.append(message)history_updated.emit(message)# To register (in an Autoload):# func _init(): OS.add_logger(CardHistoryLogger.new())
3. Hand-Limit Logic (Over-Draw Protection)
Encapsulate hand data and enforce a maximum size. Use signals to notify the UI when a card is successfully drawn or discarded due to being overdrawn.
class_name HandManager extends Nodesignal card_drawn(card: Resource)signal card_overdrawn(card: Resource)@export var max_hand_size: int = 10var _current_hand: Array[Resource] = []func draw_card(new_card: Resource) -> void:if _current_hand.size() >= max_hand_size:# Hand is full; trigger overdrawcard_overdrawn.emit(new_card)else:_current_hand.append(new_card)card_drawn.emit(new_card)
- Master Skill: godot-master