AgentSkillsCN

godot-turn-system

提供基于回合制战斗的专家蓝图,涵盖回合顺序、行动点、阶段管理与时间轴系统,适用于策略/角色扮演游戏。涵盖基于速度的先攻、打断机制与同时回合。在实现回合制战斗或战术系统时使用。关键词:回合制、先攻、行动点、阶段、回合、回合顺序、战斗。

SKILL.md
--- frontmatter
name: godot-turn-system
description: "Expert blueprint for turn-based combat with turn order, action points, phase management, and timeline systems for strategy/RPG games. Covers speed-based initiative, interrupts, and simultaneous turns. Use when implementing turn-based combat OR tactical systems. Keywords turn-based, initiative, action points, phase, round, turn order, combat."

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 transitionsif 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.

gdscript
# 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

gdscript
# 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

gdscript
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