---
name: godot-skill-upgrade
description: Godot 4技能与升级系统，支持JSON配置技能、解锁条件判断、升级触发逻辑、UI面板设计。
---

# Godot 4 技能与升级系统

完整的技能系统和升级机制，支持JSON配置、技能解锁条件、升级树、经验值系统。

## 何时使用此技能

- 实现玩家技能树
- 角色升级系统
- 技能解锁与前置条件
- 天赋点/技能点系统

## 技能配置格式

### JSON技能配置

```json
{
    "skills": [
        {
            "id": "fireball",
            "name": "火球术",
            "description": "发射一个火球攻击敌人",
            "icon": "res://icons/fireball.png",
            "type": "attack",
            "cost": {
                "mana": 30,
                "cooldown": 2.0
            },
            "unlock": {
                "level_required": 3,
                "skill_required": null,
                "item_required": null
            },
            "upgrade_levels": [
                {
                    "level": 1,
                    "damage": 50,
                    "range": 10.0
                },
                {
                    "level": 2,
                    "damage": 75,
                    "range": 12.0
                },
                {
                    "level": 3,
                    "damage": 100,
                    "range": 15.0
                }
            ]
        },
        {
            "id": "fire_mastery",
            "name": "火焰精通",
            "description": "火焰技能伤害提升15%",
            "icon": "res://icons/fire_mastery.png",
            "type": "passive",
            "cost": {
                "skill_points": 1
            },
            "unlock": {
                "level_required": 5,
                "skill_required": "fireball",
                "item_required": null
            },
            "upgrade_levels": [
                {
                    "level": 1,
                    "damage_bonus": 0.15
                },
                {
                    "level": 2,
                    "damage_bonus": 0.25
                }
            ]
        }
    ]
}
```

### 技能数据结构

```gdscript
# skill_data.gd
class_name SkillData
extends Resource

## 技能唯一标识符
@export var id: StringName

## 技能显示名称
@export var skill_name: String

## 技能描述
@export_multiline var description: String

## 技能图标路径
@export var icon_path: String

## 技能类型
@export_enum("Attack", "Defense", "Support", "Passive", "Ultimate") var skill_type: int = 0

## 消耗
@export var mana_cost: float = 0.0
@export var cooldown: float = 0.0
@export var skill_points_cost: int = 0

## 解锁条件
@export var level_required: int = 1
@export var skill_required: StringName  # 前置技能ID
@export var item_required: StringName   # 所需物品ID

## 升级配置
@export var upgrade_levels: Array[Dictionary] = []

## 最大等级
var max_level: int:
    get: return upgrade_levels.size()

func get_upgrade_data(level: int) -> Dictionary:
    if level <= 0 or level > max_level:
        return {}
    return upgrade_levels[level - 1]

func can_unlock(player_level: int, unlocked_skills: Array, owned_items: Array) -> bool:
    # 检查等级
    if player_level < level_required:
        return false
    
    # 检查前置技能
    if skill_required != null and not skill_required in unlocked_skills:
        return false
    
    return true
```

## 解锁条件判断

### 解锁条件系统

```gdscript
# unlock_condition.gd
class_name UnlockCondition
extends RefCounted

enum ConditionType { LEVEL, SKILL, ITEM, QUEST }

var condition_type: ConditionType
var required_value: Variant

func _init(type: ConditionType, value: Variant) -> void:
    condition_type = type
    required_value = value

static func level_required(level: int) -> UnlockCondition:
    return UnlockCondition.new(ConditionType.LEVEL, level)

static func skill_required(skill_id: StringName) -> UnlockCondition:
    return UnlockCondition.new(ConditionType.SKILL, skill_id)

static func item_required(item_id: StringName) -> UnlockCondition:
    return UnlockCondition.new(ConditionType.ITEM, item_id)

func check(player: Player) -> bool:
    match condition_type:
        ConditionType.LEVEL:
            return player.level >= required_value
        ConditionType.SKILL:
            return player.skills.has(required_value)
        ConditionType.ITEM:
            return player.inventory.has_item(required_value)
    return false
```

### 技能解锁管理器

```gdscript
# skill_unlock_manager.gd
class_name SkillUnlockManager
extends Node

## 可用技能列表
var available_skills: Array[SkillData] = []

## 已解锁技能字典
var unlocked_skills: Dictionary = {}

## 信号：技能解锁
signal skill_unlocked(skill_id: StringName)

## 信号：解锁条件变化
signal unlock_conditions_changed

func _ready() -> void:
    pass

func load_skills_from_json(json_path: String) -> void:
    var file := FileAccess.open(json_path, FileAccess.READ)
    if file == null:
        push_error("无法加载技能配置: ", json_path)
        return
    
    var json_str := file.get_as_text()
    file.close()
    
    var json := JSON.parse_string(json_str)
    if json == null or not json.has("skills"):
        return
    
    for skill_json in json.skills:
        var skill := _create_skill_data(skill_json)
        available_skills.append(skill)

func _create_skill_data(data: Dictionary) -> SkillData:
    var skill := SkillData.new()
    skill.id = data.get("id", "")
    skill.skill_name = data.get("name", "")
    skill.description = data.get("description", "")
    skill.icon_path = data.get("icon", "")
    skill.skill_type = data.get("type", "attack")
    skill.mana_cost = data.get("cost", {}).get("mana", 0)
    skill.cooldown = data.get("cost", {}).get("cooldown", 0)
    skill.skill_points_cost = data.get("cost", {}).get("skill_points", 0)
    skill.level_required = data.get("unlock", {}).get("level_required", 1)
    skill.skill_required = data.get("unlock", {}).get("skill_required")
    skill.item_required = data.get("unlock", {}).get("item_required")
    skill.upgrade_levels = data.get("upgrade_levels", [])
    
    return skill

func try_unlock_skill(skill_id: StringName, player: Player) -> bool:
    var skill := _get_skill_by_id(skill_id)
    if skill == null:
        return false
    
    # 检查是否已解锁
    if unlocked_skills.has(skill_id):
        return false
    
    # 检查解锁条件
    if not skill.can_unlock(player.level, unlocked_skills.keys(), []):
        return false
    
    # 解锁技能
    _unlock_skill(skill_id)
    return true

func _unlock_skill(skill_id: StringName) -> void:
    unlocked_skills[skill_id] = 1  # 等级1
    skill_unlocked.emit(skill_id)

func get_skill_level(skill_id: StringName) -> int:
    return unlocked_skills.get(skill_id, 0)

func can_upgrade_skill(skill_id: StringName) -> bool:
    if not unlocked_skills.has(skill_id):
        return false
    
    var skill := _get_skill_by_id(skill_id)
    if skill == null:
        return false
    
    var current_level := get_skill_level(skill_id)
    return current_level < skill.max_level

func upgrade_skill(skill_id: StringName, player: Player) -> bool:
    if not can_upgrade_skill(skill_id):
        return false
    
    var skill := _get_skill_by_id(skill_id)
    
    # 检查消耗
    if player.skill_points < skill.skill_points_cost:
        return false
    
    # 消耗资源
    player.skill_points -= skill.skill_points_cost
    
    # 升级
    unlocked_skills[skill_id] += 1
    return true

func _get_skill_by_id(skill_id: StringName) -> SkillData:
    for skill in available_skills:
        if skill.id == skill_id:
            return skill
    return null

func get_unlocked_skills() -> Array[SkillData]:
    var result: Array[SkillData] = []
    for skill in available_skills:
        if unlocked_skills.has(skill.id):
            result.append(skill)
    return result

func get_available_skills_for_player(player: Player) -> Array[SkillData]:
    var result: Array[SkillData] = []
    for skill in available_skills:
        if not unlocked_skills.has(skill.id) and skill.can_unlock(player.level, unlocked_skills.keys(), []):
            result.append(skill)
    return result
```

## 升级触发逻辑

### 经验值系统

```gdscript
# experience_system.gd
class_name ExperienceSystem
extends Node

## 信号：升级
signal level_up(new_level: int)

## 信号：经验值变化
signal experience_changed(current: int, required: int)

@export var level_multiplier: float = 1.5
@export var base_exp: int = 100

var current_level: int = 1
var current_exp: int = 0

func _ready() -> void:
    pass

func get_required_exp(level: int) -> int:
    """获取指定等级所需经验值"""
    return int(base_exp * pow(level, level_multiplier))

func add_exp(amount: int) -> bool:
    current_exp += amount
    experience_changed.emit(current_exp, get_required_exp(current_level))
    
    while current_exp >= get_required_exp(current_level):
        current_exp -= get_required_exp(current_level)
        current_level += 1
        level_up.emit(current_level)
    
    return true

func get_exp_progress() -> float:
    var required := get_required_exp(current_level)
    return float(current_exp) / float(required) if required > 0 else 0.0
```

### 技能点系统

```gdscript
# skill_point_system.gd
class_name SkillPointSystem
extends Node

## 信号：技能点变化
signal skill_points_changed(points: int)

@export var base_points_per_level: int = 1
@export var bonus_points_from_quests: int = 0

var total_points: int = 0
var spent_points: int = 0

func _ready() -> void:
    pass

func grant_level_points() -> void:
    total_points += base_points_per_level
    skill_points_changed.emit(get_available_points())

func grant_bonus_points(amount: int) -> void:
    total_points += amount
    skill_points_changed.emit(get_available_points())

func spend_points(amount: int) -> bool:
    if amount > get_available_points():
        return false
    
    spent_points += amount
    skill_points_changed.emit(get_available_points())
    return true

func refund_points(amount: int) -> void:
    spent_points = maxi(spent_points - amount, 0)
    skill_points_changed.emit(get_available_points())

func get_available_points() -> int:
    return total_points - spent_points
```

### 升级触发器

```gdscript
# upgrade_trigger.gd
class_name UpgradeTrigger
extends Node

## 信号：触发升级
signal upgrade_triggered(upgrade_type: StringName, value: Variant)

func _ready() -> void:
    # 连接玩家等级变化
    var exp_system := ExperienceSystem
    exp_system.level_up.connect(_on_level_up)

func _on_level_up(new_level: int) -> void:
    upgrade_triggered.emit("level", new_level)
    
    # 给玩家技能点
    var point_system := SkillPointSystem
    point_system.grant_level_points()
    
    # 检查新技能解锁
    _check_new_skill_unlocks(new_level)

func _check_new_skill_unlocks(player_level: int) -> void:
    var unlock_manager := SkillUnlockManager
    var available := unlock_manager.get_available_skills_for_player(get_parent())
    
    for skill in available:
        if skill.level_required == player_level:
            unlock_manager.try_unlock_skill(skill.id, get_parent())

## 任务完成触发
func trigger_quest_complete(quest_id: StringName) -> void:
    upgrade_triggered.emit("quest", quest_id)

## 物品获得触发
func trigger_item_acquired(item_id: StringName) -> void:
    upgrade_triggered.emit("item", item_id)
```

## UI面板设计

### 技能树面板

```gdscript
# skill_tree_panel.gd
class_name SkillTreePanel
extends Control

@export var skill_unlock_manager: SkillUnlockManager
@export var skill_slot_scene: PackedScene

@onready var scroll_container: ScrollContainer = %ScrollContainer
@onready var skill_grid: GridContainer = %SkillGrid
@onready var skill_points_label: Label = %SkillPointsLabel
@onready var skill_info_panel: PanelContainer = %SkillInfoPanel
@onready var skill_name_label: Label = %SkillNameLabel
@onready var skill_desc_label: Label = %SkillDescLabel
@onready var skill_level_label: Label = %SkillLevelLabel
@onready var upgrade_button: Button = %UpgradeButton

var skill_slotuis: Array[SkillSlotUI] = []
var selected_skill: SkillData = null

func _ready() -> void:
    skill_unlock_manager.skill_unlocked.connect(_on_skill_unlocked)
    skill_unlock_manager.unlock_conditions_changed.connect(_refresh_all)
    
    _create_skill_slots()
    _refresh_all()

func _create_skill_slots() -> void:
    for skill in skill_unlock_manager.available_skills:
        var slot: SkillSlotUI = skill_slot_scene.instantiate()
        slot.skill_data = skill
        slot.clicked.connect(_on_skill_slot_clicked)
        skill_grid.add_child(slot)
        skill_slotuis.append(slot)

func _refresh_all() -> void:
    for slot in skill_slotuis:
        slot.update_display()
    _update_skill_points()

func _on_skill_slot_clicked(skill: SkillData) -> void:
    selected_skill = skill
    _show_skill_info(skill)
    _update_upgrade_button()

func _show_skill_info(skill: SkillData) -> void:
    skill_name_label.text = skill.skill_name
    skill_desc_label.text = skill.description
    
    var current_level := skill_unlock_manager.get_skill_level(skill.id)
    if current_level > 0:
        skill_level_label.text = "等级: %d/%d" % [current_level, skill.max_level]
        
        # 显示当前等级效果
        if current_level > 0 and skill.upgrade_levels.size() >= current_level:
            var upgrade := skill.upgrade_levels[current_level - 1]
            # 显示具体效果...
    else:
        skill_level_label.text = "未解锁"
    
    skill_info_panel.visible = true

func _update_upgrade_button() -> void:
    if selected_skill == null:
        upgrade_button.disabled = true
        return
    
    if skill_unlock_manager.unlocked_skills.has(selected_skill.id):
        if skill_unlock_manager.can_upgrade_skill(selected_skill.id):
            upgrade_button.text = "升级"
            upgrade_button.disabled = false
        else:
            upgrade_button.text = "已满级"
            upgrade_button.disabled = true
    else:
        upgrade_button.text = "解锁"
        upgrade_button.disabled = false

func _on_upgrade_button_pressed() -> void:
    if selected_skill == null:
        return
    
    var player := get_tree().get_first_node_in_group("player")
    if player == null:
        return
    
    if skill_unlock_manager.unlocked_skills.has(selected_skill.id):
        skill_unlock_manager.upgrade_skill(selected_skill.id, player)
    else:
        skill_unlock_manager.try_unlock_skill(selected_skill.id, player)
    
    _refresh_all()
    _show_skill_info(selected_skill)
    _update_upgrade_button()

func _on_skill_unlocked(skill_id: StringName) -> void:
    _refresh_all()

func _update_skill_points() -> void:
    var point_system := SkillPointSystem
    skill_points_label.text = "可用技能点: %d" % point_system.get_available_points()
```

### 技能槽UI

```gdscript
# skill_slot_ui.gd
class_name SkillSlotUI
extends PanelContainer

signal clicked(skill: SkillData)

@export var skill_data: SkillData

@onready var icon_texture: TextureRect = %IconTexture
@onready var lock_icon: TextureRect = %LockIcon
@onready var level_label: Label = %LevelLabel
@onready var name_label: Label = %NameLabel

var is_unlocked: bool = false
var current_level: int = 0

func _ready() -> void:
    mouse_entered.connect(_on_mouse_entered)
    mouse_exited.connect(_on_mouse_exited)
    gui_input.connect(_on_gui_input)

func update_display() -> void:
    if skill_data == null:
        return
    
    var unlock_manager := SkillUnlockManager
    is_unlocked = unlock_manager.unlocked_skills.has(skill_data.id)
    current_level = unlock_manager.get_skill_level(skill_data.id)
    
    # 加载图标
    if skill_data.icon_path != "":
        icon_texture.texture = load(skill_data.icon_path)
    
    # 锁定状态
    lock_icon.visible = not is_unlocked
    
    # 等级显示
    if is_unlocked and current_level > 0:
        level_label.text = "Lv.%d" % current_level
        level_label.visible = true
    else:
        level_label.visible = false
    
    name_label.text = skill_data.skill_name

func _on_gui_input(event: InputEvent) -> void:
    if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
        if event.pressed:
            clicked.emit(skill_data)

func _on_mouse_entered() -> void:
    modulate = Color(1.2, 1.2, 1.2)

func _on_mouse_exited() -> void:
    modulate = Color(1.0, 1.0, 1.0)
```

### 技能信息面板

```gdscript
# skill_info_panel.gd
class_name SkillInfoPanel
extends PanelContainer

@export var name_label: Label
@export var type_label: Label
@export var desc_label: Label
@export var cost_label: Label
@export var effect_label: Label
@export var unlock_condition_label: Label

func show_skill(skill: SkillData, current_level: int) -> void:
    name_label.text = skill.skill_name
    
    var types := ["攻击", "防御", "支援", "被动", "终极"]
    type_label.text = "类型: %s" % types[skill.skill_type]
    
    desc_label.text = skill.description
    
    # 消耗信息
    var cost_text := ""
    if skill.mana_cost > 0:
        cost_text += "魔法消耗: %d\n" % skill.mana_cost
    if skill.cooldown > 0:
        cost_text += "冷却时间: %.1f秒\n" % skill.cooldown
    if skill.skill_points_cost > 0:
        cost_text += "技能点: %d" % skill.skill_points_cost
    cost_label.text = cost_text if cost_text else "无消耗"
    
    # 当前等级效果
    if current_level > 0 and skill.upgrade_levels.size() >= current_level:
        var upgrade := skill.upgrade_levels[current_level - 1]
        var effect_text := ""
        for key in upgrade.keys():
            effect_text += "%s: %s\n" % [key, str(upgrade[key])]
        effect_label.text = effect_text
    else:
        effect_label.text = "效果: 未解锁"
    
    # 解锁条件
    var cond_text := "解锁条件: "
    if skill.level_required > 1:
        cond_text += "等级%d" % skill.level_required
    if skill.skill_required != null:
        cond_text += " | 前置: %s" % skill.skill_required
    unlock_condition_label.text = cond_text
```

## 使用示例

### 玩家角色集成

```gdscript
# player.gd
extends CharacterBody2D

@export var experience_system: ExperienceSystem
@export var skill_point_system: SkillPointSystem
@export var skill_unlock_manager: SkillUnlockManager
@export var buff_component: BuffComponent

var level: int:
    get: return experience_system.current_level

func _ready() -> void:
    skill_unlock_manager.load_skills_from_json("res://data/skills.json")
    
    # 监听升级
    experience_system.level_up.connect(_on_level_up)

func _on_level_up(new_level: int) -> void:
    skill_point_system.grant_level_points()
    
    # 检查自动解锁的技能
    var available := skill_unlock_manager.get_available_skills_for_player(self)
    for skill in available:
        if skill.level_required == new_level:
            skill_unlock_manager.try_unlock_skill(skill.id, self)

func use_skill(skill_id: StringName) -> bool:
    var skill := skill_unlock_manager._get_skill_by_id(skill_id)
    if skill == null:
        return false
    
    var level := skill_unlock_manager.get_skill_level(skill_id)
    if level <= 0:
        return false
    
    # 检查冷却和消耗
    if skill.mana_cost > 0 and _mana < skill.mana_cost:
        return false
    
    if skill.cooldown > 0 and _is_in_cooldown(skill_id):
        return false
    
    # 执行技能效果
    _execute_skill(skill, level)
    return true

func _execute_skill(skill: SkillData, level: int) -> void:
    var upgrade_data := skill.get_upgrade_data(level)
    
    match skill.skill_type:
        0:  # Attack
            _cast_attack_skill(skill.id, upgrade_data)
        1:  # Defense
            _cast_defense_skill(skill.id, upgrade_data)
        2:  # Support
            _cast_support_skill(skill.id, upgrade_data)
        3:  # Passive
            _apply_passive_skill(skill.id, upgrade_data)
        4:  # Ultimate
            _cast_ultimate_skill(skill.id, upgrade_data)

func _cast_attack_skill(skill_id: StringName, data: Dictionary) -> void:
    match skill_id:
        "fireball":
            _cast_fireball(data.get("damage", 50), data.get("range", 10.0))
        "ice_blast":
            _cast_ice_blast(data.get("damage", 30), data.get("slow_duration", 2.0))
```

### 保存与加载

```gdscript
# 保存技能数据
func get_save_data() -> Dictionary:
    return {
        "unlocked_skills": skill_unlock_manager.unlocked_skills,
        "experience": experience_system.current_exp,
        "skill_points_spent": skill_point_system.spent_points
    }

# 加载技能数据
func load_save_data(data: Dictionary) -> void:
    if data.has("unlocked_skills"):
        skill_unlock_manager.unlocked_skills = data.unlocked_skills
    
    if data.has("experience"):
        experience_system.current_exp = data.experience
    
    if data.has("skill_points_spent"):
        skill_point_system.spent_points = data.skill_points_spent
```

## 最佳实践

1. **JSON配置分离** - 技能数据与代码分离，便于策划调整
2. **Resource管理** - SkillData使用Resource便于编辑器编辑
3. **条件检查统一** - 解锁条件集中管理
4. **信号解耦** - UI通过信号监听变化
5. **升级预留** - 支持多级升级配置
6. **点树可视化** - 技能树UI展示前置关系
