godot-turn-system

📁 thedivergentai/gd-agentic-skills 📅 3 days ago
0
总安装量
3
周安装量
安装命令
npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-turn-system

Agent 安装分布

opencode 3
gemini-cli 3
codex 3
claude-code 2
github-copilot 2

Skill 文档

Turn System

Turn order calculation, action points, phase management, and timeline systems define turn-based combat.

Available Scripts

active_time_battle.gd

Framework for Active Time Battle (ATB) systems with async action support.

timeline_turn_manager.gd

Expert timeline-based turn manager with interrupts and simultaneous actions.

NEVER Do in Turn Systems

  • NEVER recalculate turn order every action — Sort 50 combatants after every move? O(n log n) × actions = lag. Calculate once per round, update on stat changes only.
  • NEVER use speed ties without determinism — Two units same speed, random order? Non-reproducible replays. Break ties with secondary stat (ID, position, etc.).
  • NEVER forget to validate action costs — Allow action without checking points? Negative AP = exploits. ALWAYS if can_perform_action(cost) before deducting.
  • NEVER hardcode phase transitions — if phase == 0: phase = 1 for 10 phases? Unmaintainable. Use enum + match OR array of phase handlers.
  • NEVER skip turn timeout for networked games — Wait forever for player input? Griefing exploit. ALWAYS implement turn timer with default action.
  • NEVER emit turn_ended before cleanup — Signal listeners start next turn, previous hasn’t cleaned up? State corruption. Cleanup FIRST, then emit.

# turn_manager.gd (AutoLoad)
extends Node

signal turn_started(combatant: Node)
signal turn_ended(combatant: Node)
signal round_ended

var combatants: Array[Node] = []
var turn_order: Array[Node] = []
var current_turn_index: int = 0

func start_combat(participants: Array[Node]) -> void:
    combatants = participants
    calculate_turn_order()
    start_next_turn()

func calculate_turn_order() -> void:
    turn_order = combatants.duplicate()
    turn_order.sort_custom(func(a, b): return a.speed > b.speed)

func start_next_turn() -> void:
    if current_turn_index >= turn_order.size():
        current_turn_index = 0
        round_ended.emit()
        calculate_turn_order()  # Recalculate each round
    
    var current := turn_order[current_turn_index]
    turn_started.emit(current)

func end_turn() -> void:
    var current := turn_order[current_turn_index]
    turn_ended.emit(current)
    current_turn_index += 1
    start_next_turn()

Action Point System

# combatant.gd
extends Node

@export var max_action_points: int = 3
var current_action_points: int = 3

func start_turn() -> void:
    current_action_points = max_action_points

func can_perform_action(cost: int) -> bool:
    return current_action_points >= cost

func perform_action(cost: int) -> bool:
    if not can_perform_action(cost):
        return false
    
    current_action_points -= cost
    return true

Turn Phases

enum Phase { DRAW, MAIN, END }

var current_phase: Phase = Phase.DRAW

func advance_phase() -> void:
    match current_phase:
        Phase.DRAW:
            current_phase = Phase.MAIN
        Phase.MAIN:
            current_phase = Phase.END
        Phase.END:
            TurnManager.end_turn()
            current_phase = Phase.DRAW

Best Practices

  1. Speed-Based – Initiative determines order
  2. Action Points – Limit actions per turn
  3. Timeout – Add turn timer for online play

Reference

  • Related: godot-combat-system, godot-rpg-stats

Related