godot-genre-card-game

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Genre: Card Game

游戏类型:卡牌游戏

Expert blueprint for digital card games with data-driven design and juicy UI.
采用数据驱动设计与精美UI的数字卡牌游戏专家蓝图。

NEVER Do

绝对不要做的事

  • NEVER hardcode card effects in card scripts — Use Command pattern or effect_script Resource. Enables designers to create cards without code.
  • NEVER use global_position for hand layout — Hand cards should use local positions relative to hand container. global_position breaks with camera movement.
  • NEVER forget to shuffle discard into draw pile — When draw_pile is empty, reshuffle discard_pile. Otherwise game soft-locks.
  • NEVER skip z_index management — Dragged cards must have highest z_index. Use
    move_to_front()
    or set
    z_index = 999
    .
  • NEVER use instant card movements — Cards without tween animations feel terrible. Even 0.2s tweens massively improve feel.

  • 绝对不要在卡牌脚本中硬编码卡牌效果 — 使用Command pattern或effect_script Resource。让设计师无需编写代码即可创建卡牌。
  • 绝对不要使用global_position进行手牌布局 — 手牌应使用相对于手牌容器的本地位置。global_position会在相机移动时失效。
  • 绝对不要忘记将弃牌堆洗入抽牌堆 — 当抽牌堆为空时,重洗弃牌堆。否则游戏会陷入软锁状态。
  • 绝对不要跳过z_index管理 — 被拖动的卡牌必须拥有最高的z_index。使用
    move_to_front()
    或设置
    z_index = 999
  • 绝对不要使用即时卡牌移动 — 没有补间动画的卡牌体验极差。即使是0.2秒的补间也能大幅提升手感。

Available Scripts

可用脚本

MANDATORY: Read the appropriate script before implementing the corresponding pattern.
强制要求:在实现对应模式前,请阅读相应脚本。

card_effect_resolution.gd

card_effect_resolution.gd

FILO stack for card effect resolution. Enables reaction/counter cards (last-in resolves first), visual pass for animations, and polymorphic effect dispatch.

先进后出(FILO)栈用于卡牌效果解析。支持反应/反击卡牌(最后进入的先解析)、动画视觉过渡以及多态效果分发。

Core Loop

核心循环

  1. Draw: Player draws cards from a deck into their hand.
  2. Evaluate: Player assesses board state, mana/energy, and card options.
  3. Play: Player plays cards to trigger effects (damage, buff, summon).
  4. Resolve: Effects occur immediately or go onto a stack.
  5. Discard/End: Unused cards are discarded (roguelike) or kept (TCG), turn ends.
  1. 抽卡:玩家从卡组中抽牌加入手牌。
  2. 评估:玩家评估场上状态、 mana/能量以及卡牌选项。
  3. 出牌:玩家打出卡牌以触发效果(伤害、增益、召唤)。
  4. 解析:效果立即生效或进入栈中等待处理。
  5. 弃牌/结束回合:未使用的卡牌被弃置(roguelike类)或保留(TCG类),回合结束。

Skill Chain

技能链

PhaseSkillsPurpose
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
阶段技能用途
1. 数据
resources
,
custom-resources
定义卡牌属性(费用、类型、效果)
2. UI
control-nodes
,
layout-containers
手牌布局、卡牌定位、提示框
3. 输入
drag-and-drop
,
state-machines
将卡牌拖动到目标、悬停交互
4. 逻辑
command-pattern
,
signals
执行卡牌效果、回合阶段管理
5. 优化
godot-tweening
,
shaders
抽卡动画、全息箔纸效果

Architecture Overview

架构概述

1. Card Data (Resource-based)

1. 卡牌数据(基于Resource)

Godot Resources are perfect for card data.
gdscript
undefined
Godot Resources是存储卡牌数据的理想选择。
gdscript
undefined

card_data.gd

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
undefined
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
undefined

2. Deck Manager

2. 卡组管理器

Handles the piles: Draw Pile, Hand, Discard Pile, Exhaust Pile.
gdscript
undefined
处理各种牌堆:抽牌堆、手牌、弃牌堆、耗尽堆。
gdscript
undefined

deck_manager.gd

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()
undefined
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()
undefined

3. Card Visual (UI)

3. 卡牌视觉(UI)

The interactive node representing a card in hand.
gdscript
undefined
代表手牌中卡牌的交互节点。
gdscript
undefined

card_ui.gd

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 pass
undefined
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 pass
undefined

Key Mechanics Implementation

核心机制实现

Effect Resolution (Command Pattern)

效果解析(Command Pattern)

Decouple the "playing" of a card from its "effect".
gdscript
func 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)
将“出牌”与“效果”解耦。
gdscript
func 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)

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
.
gdscript
func 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_pos
手牌通常排列成弧形。使用数学公式(贝塞尔曲线或圆形)根据
index
total_cards
定位卡牌。
gdscript
func 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_pos

Common Pitfalls

常见陷阱

  1. Complexity Overload: Too many keywords. Fix: Stick to 3-5 core keywords (e.g., Taunt, Poison, Shield) and expand slowly.
  2. Unreadable Text: Tiny fonts on cards. Fix: Use icons for common stats (Damage, Block) and keep text short.
  3. Animation Lock: Waiting for slow animations to finish before playing the next card. Fix: Allow queueing actions or keep animations snappy (< 0.3s).
  1. 复杂度过载:过多关键词。解决方法:坚持3-5个核心关键词(如嘲讽、中毒、护盾),再逐步扩展。
  2. 文本可读性差:卡牌上的字体过小。解决方法:为常见属性(伤害、格挡)使用图标,保持文本简短。
  3. 动画锁定:等待缓慢动画完成才能打出下一张牌。解决方法:允许排队操作或保持动画简洁(< 0.3秒)。

Godot-Specific Tips

Godot专属小贴士

  • MouseFilter: Getting drag/drop to work with overlapping UI requires careful setup of
    mouse_filter
    (Pass vs Stop).
  • Z-Index: Use
    z_index
    or
    CanvasLayer
    to 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.
  • MouseFilter:要让重叠UI上的拖放功能正常工作,需要仔细设置
    mouse_filter
    (Pass vs Stop)。
  • Z-Index:使用
    z_index
    CanvasLayer
    确保被拖动的卡牌始终位于所有元素之上。
  • Tweens:必不可少!对位置、旋转和缩放应用补间,营造《炉石传说》/《杀戮尖塔》那样的“丝滑”手感。

Reference

参考资料

  • Master Skill: godot-master
  • 大师技能:godot-master