Godot游戏能力系统Skill godot-ability-system

这个技能系统专为Godot游戏引擎设计,提供构建RPG和动作游戏中角色能力系统的专家级模式,包括冷却管理、连击系统、技能树、升级路径和资源管理,适用于游戏开发中的技能实现和角色进展。关键词:Godot、能力系统、游戏开发、技能树、冷却策略、连击、角色能力、游戏引擎、可扩展系统。

游戏开发 0 次安装 0 次浏览 更新于 3/23/2026

name: godot-ability-system description: “用于RPG/动作能力系统的专家模式,包括冷却策略、连击系统、技能链、具有先决条件的技能树、升级路径和资源管理。适用于实现可解锁能力、角色进展或复杂技能系统。触发关键词:PlayerAbility、AbilityManager、cooldown、SkillTree、SkillNode、prerequisites、can_use、execute、ComboSystem、ability_chain、global_cooldown、charge_system、upgrade_path。”

能力系统

构建灵活、可扩展能力系统的专家指导。

绝不这样做

  • 绝不在_process()中跟踪冷却 — 使用计时器或在_physics_process()中手动跟踪delta。_process()具有可变的delta,在慢速帧中会导致冷却不同步。
  • 绝不忘记全局冷却(GCD) — 没有GCD,玩家会滥用瞬发能力。在所有能力施放之间添加一个小的通用冷却(0.5-1.5秒)。
  • 绝不将能力效果硬编码在管理器代码中 — 使用策略模式。每个能力都是一个具有execute()方法的资源,而不是一个巨大的switch语句。
  • 绝不允许在动画锁定期间使用能力 — 在允许新施放前检查is_castinganimation_playing。中断动画会破坏状态机。
  • 绝不保存冷却状态而不进行时间标准化 — 保存“cooldown_end_time”(OS.get_unix_time() + 剩余时间),而不是“remaining_time”。防止利用(更改系统时钟、重新加载游戏)。

可用脚本

强制:在实现相应模式前阅读适当脚本。

ability_manager.gd

能力编排,包括冷却注册、can_use检查和视觉冷却进度。与角色逻辑解耦,适用于玩家、敌人或炮塔。

ability_resource.gd

可脚本化的能力资源基类,具有元数据、统计数据和效果数组。虚拟execute()方法用于继承(ProjectileAbility、BuffAbility)。


架构模式

基于资源的能力

# ability_base.gd - 所有能力的基础类
class_name Ability
extends Resource

@export var ability_id: String
@export var display_name: String
@export var icon: Texture2D
@export var description: String

@export_group("成本")
@export var mana_cost: int = 0
@export var stamina_cost: int = 0
@export var health_cost: int = 0  # 生命汲取能力

@export_group("时间")
@export var cooldown: float = 5.0
@export var cast_time: float = 0.0  # 0 = 瞬发
@export var channel_time: float = 0.0  # 引导能力

@export_group("解锁")
@export var unlock_level: int = 1
@export var prerequisites: Array[String] = []  # 其他能力ID

## 覆盖这些方法
func can_cast(caster: Node) -> bool:
    return true  # 额外检查(范围、目标等)

func execute(caster: Node, target: Node = null) -> void:
    pass  # 能力效果

func on_cast_start(caster: Node) -> void:
    pass  # 动画、效果

func on_cast_complete(caster: Node) -> void:
    execute(caster)

func on_cancel(caster: Node) -> void:
    pass  # 退还资源

具体能力示例

# fireball.gd
class_name FireballAbility
extends Ability

@export var damage: int = 50
@export var projectile_scene: PackedScene
@export var range: float = 500.0

func can_cast(caster: Node) -> bool:
    var target = caster.get_target()
    if not target:
        return false
    
    var distance := caster.global_position.distance_to(target.global_position)
    return distance <= range

func execute(caster: Node, target: Node = null) -> void:
    var projectile := projectile_scene.instantiate()
    caster.get_parent().add_child(projectile)
    projectile.global_position = caster.global_position
    projectile.target = target
    projectile.damage = damage

能力管理器(集中式)

核心管理器

# ability_manager.gd
class_name AbilityManager
extends Node

signal ability_cast(ability_id: String)
signal ability_ready(ability_id: String)
signal cooldown_started(ability_id: String, duration: float)

var abilities: Dictionary = {}  # ability_id → Ability
var cooldowns: Dictionary = {}  # ability_id → float (剩余时间)
var is_casting: bool = false
var global_cooldown: float = 0.0  # GCD计时器

@export var gcd_duration: float = 1.0  # 全局冷却

func register_ability(ability: Ability) -> void:
    abilities[ability.ability_id] = ability
    cooldowns[ability.ability_id] = 0.0

func can_use_ability(ability_id: String, caster: Node) -> bool:
    var ability := abilities.get(ability_id) as Ability
    if not ability:
        return false
    
    # 检查GCD
    if global_cooldown > 0.0:
        return false
    
    # 检查特定冷却
    if cooldowns.get(ability_id, 0.0) > 0.0:
        return false
    
    # 检查是否已在施放
    if is_casting and ability.cast_time > 0.0:
        return false
    
    # 检查资源
    if not has_resources(caster, ability):
        return false
    
    # 能力特定检查
    return ability.can_cast(caster)

func use_ability(ability_id: String, caster: Node, target: Node = null) -> bool:
    if not can_use_ability(ability_id, caster):
        return false
    
    var ability := abilities[ability_id]
    
    # 消耗资源
    consume_resources(caster, ability)
    
    # 开始施放
    if ability.cast_time > 0.0:
        start_cast(ability, caster, target)
    else:
        # 瞬发施放
        ability.execute(caster, target)
        trigger_cooldown(ability_id, ability.cooldown)
    
    ability_cast.emit(ability_id)
    return true

func start_cast(ability: Ability, caster: Node, target: Node) -> void:
    is_casting = true
    ability.on_cast_start(caster)
    
    # 为施放完成创建计时器
    var timer := get_tree().create_timer(ability.cast_time)
    await timer.timeout
    
    if is_casting:  # 未中断
        ability.on_cast_complete(caster)
        trigger_cooldown(ability.ability_id, ability.cooldown)
    
    is_casting = false

func interrupt_cast() -> void:
    if is_casting:
        is_casting = false
        # 如果需要,触发ability.on_cancel()

func trigger_cooldown(ability_id: String, duration: float) -> void:
    cooldowns[ability_id] = duration
    global_cooldown = gcd_duration
    cooldown_started.emit(ability_id, duration)

func _physics_process(delta: float) -> void:
    # 滴答冷却
    for ability_id in cooldowns.keys():
        if cooldowns[ability_id] > 0.0:
            cooldowns[ability_id] -= delta
            if cooldowns[ability_id] <= 0.0:
                ability_ready.emit(ability_id)
    
    # 滴答GCD
    if global_cooldown > 0.0:
        global_cooldown -= delta

func has_resources(caster: Node, ability: Ability) -> bool:
    return (caster.mana >= ability.mana_cost and
            caster.stamina >= ability.stamina_cost and
            caster.health > ability.health_cost)

func consume_resources(caster: Node, ability: Ability) -> void:
    caster.mana -= ability.mana_cost
    caster.stamina -= ability.stamina_cost
    caster.health -= ability.health_cost

高级模式

连击系统

# combo_tracker.gd
extends Node

var combo_chain: Array[String] = []
var combo_window: float = 2.0  # 继续连击的秒数
var last_ability_time: float = 0.0

func register_ability_use(ability_id: String) -> void:
    var current_time := Time.get_ticks_msec() * 0.001
    
    # 如果时间过长,重置
    if current_time - last_ability_time > combo_window:
        combo_chain.clear()
    
    combo_chain.append(ability_id)
    last_ability_time = current_time
    
    # 检查连击完成
    check_combos()

func check_combos() -> void:
    # 示例:“slash” → “slash” → “spin” = “whirlwind”
    if combo_chain.size() >= 3:
        var last_three := combo_chain.slice(-3)
        if last_three == ["slash", "slash", "spin"]:
            trigger_combo_ability("whirlwind")
            combo_chain.clear()

func trigger_combo_ability(combo_id: String) -> void:
    # 执行强大的连击能力
    pass

充能能力

# charge_ability.gd - 具有多次充能的能力(如《英雄联盟》中的闪现)
class_name ChargeAbility
extends Ability

@export var max_charges: int = 2
@export var charge_recharge_time: float = 20.0

var current_charges: int = max_charges
var recharge_timer: float = 0.0

func can_cast(caster: Node) -> bool:
    return current_charges > 0

func execute(caster: Node, target: Node = null) -> void:
    current_charges -= 1
    
    # 如果未达到最大充能,开始充能
    if current_charges < max_charges and recharge_timer == 0.0:
        recharge_timer = charge_recharge_time

func tick(delta: float) -> void:
    if recharge_timer > 0.0:
        recharge_timer -= delta
        if recharge_timer <= 0.0:
            current_charges += 1
            if current_charges < max_charges:
                recharge_timer = charge_recharge_time  # 继续充能
            else:
                recharge_timer = 0.0

技能树系统

技能节点

# skill_node.gd
class_name SkillNode
extends Resource

@export var skill_id: String
@export var display_name: String
@export var description: String
@export var icon: Texture2D

@export_group("要求")
@export var prerequisites: Array[String] = []  # 其他skill_ids
@export var character_level_required: int = 1
@export var points_required: int = 1
@export var mutually_exclusive_with: Array[String] = []  # 不能同时拥有

@export_group("进展")
@export var max_rank: int = 1
@export var current_rank: int = 0

@export_group("效果")
@export var unlocks_ability: String = ""  # 授予的能力ID
@export var stat_bonuses: Dictionary = {}  # “strength”: 5, “crit_chance”: 0.05

func can_unlock(player_skills: Dictionary, player_level: int, available_points: int) -> bool:
    # 已达到最大等级
    if current_rank >= max_rank:
        return false
    
    # 点数不足
    if available_points < points_required:
        return false
    
    # 等级要求
    if player_level < character_level_required:
        return false
    
    # 先决条件
    for prereq_id in prerequisites:
        if not player_skills.has(prereq_id) or player_skills[prereq_id].current_rank == 0:
            return false
    
    # 互斥性
    for exclusive_id in mutually_exclusive_with:
        if player_skills.has(exclusive_id) and player_skills[exclusive_id].current_rank > 0:
            return false
    
    return true

func unlock() -> void:
    current_rank += 1

技能树管理器

# skill_tree.gd
class_name SkillTree
extends Node

signal skill_unlocked(skill_id: String, rank: int)
signal points_changed(new_total: int)

var skills: Dictionary = {}  # skill_id → SkillNode
var skill_points: int = 0

func add_skill(skill: SkillNode) -> void:
    skills[skill.skill_id] = skill

func can_unlock_skill(skill_id: String, player_level: int) -> bool:
    var skill := skills.get(skill_id) as SkillNode
    if not skill:
        return false
    
    return skill.can_unlock(skills, player_level, skill_points)

func unlock_skill(skill_id: String, player_level: int) -> bool:
    if not can_unlock_skill(skill_id, player_level):
        return false
    
    var skill := skills[skill_id]
    skill.unlock()
    skill_points -= skill.points_required
    
    # 应用效果
    apply_skill_effects(skill)
    
    skill_unlocked.emit(skill_id, skill.current_rank)
    points_changed.emit(skill_points)
    return true

func apply_skill_effects(skill: SkillNode) -> void:
    # 如果指定,授予能力
    if skill.unlocks_ability != "":
        var ability_manager := get_node("/root/AbilityManager")
        # 注册新能力
    
    # 应用统计加成
    var player := get_tree().get_first_node_in_group("player")
    for stat_name in skill.stat_bonuses.keys():
        var bonus = skill.stat_bonuses[stat_name]
        player.set(stat_name, player.get(stat_name) + bonus)

func add_skill_points(amount: int) -> void:
    skill_points += amount
    points_changed.emit(skill_points)

func reset_tree(refund_points: bool = true) -> void:
    var total_spent := 0
    for skill in skills.values():
        total_spent += skill.current_rank * skill.points_required
        skill.current_rank = 0
    
    if refund_points:
        skill_points += total_spent
        points_changed.emit(skill_points)

冷却策略

每能力冷却(标准)

# 已在AbilityManager中展示
# 每个能力有独立的冷却

共享冷却(炉石传说风格)

# 所有“召唤”类型的能力共享冷却
var summon_cooldown: float = 0.0

func use_summon_ability(ability: Ability) -> void:
    ability.execute()
    summon_cooldown = 3.0  # 所有召唤有3秒冷却

充能系统(已在上方展示)

多次使用,随时间充能。


边界情况

冷却持久化

# save_system.gd
func save_ability_cooldowns() -> Dictionary:
    var data := {}
    var current_time := Time.get_unix_time_from_system()
    
    for ability_id in ability_manager.cooldowns.keys():
        var remaining := ability_manager.cooldowns[ability_id]
        if remaining > 0.0:
            data[ability_id] = current_time + remaining  # 绝对时间
    
    return data

func load_ability_cooldowns(data: Dictionary) -> void:
    var current_time := Time.get_unix_time_from_system()
    
    for ability_id in data.keys():
        var end_time: float = data[ability_id]
        var remaining := max(0.0, end_time - current_time)
        ability_manager.cooldowns[ability_id] = remaining

动画锁定

# 防止在攻击动画期间滥用能力
func _on_animation_player_animation_started(anim_name: String) -> void:
    if anim_name.begins_with("attack_"):
        ability_manager.is_casting = true

func _on_animation_player_animation_finished(anim_name: String) -> void:
    if anim_name.begins_with("attack_"):
        ability_manager.is_casting = false

参考