| name | godot-skill-upgrade |
| description | Godot 4技能与升级系统,支持JSON配置技能、解锁条件判断、升级触发逻辑、UI面板设计。 |
Godot 4 技能与升级系统
完整的技能系统和升级机制,支持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
}
]
}
]
}
技能数据结构
# 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
解锁条件判断
解锁条件系统
# 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
技能解锁管理器
# 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
升级触发逻辑
经验值系统
# 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
技能点系统
# 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
升级触发器
# 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面板设计
技能树面板
# 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
# 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)
技能信息面板
# 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
使用示例
玩家角色集成
# 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))
保存与加载
# 保存技能数据
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
最佳实践
- JSON配置分离 - 技能数据与代码分离,便于策划调整
- Resource管理 - SkillData使用Resource便于编辑器编辑
- 条件检查统一 - 解锁条件集中管理
- 信号解耦 - UI通过信号监听变化
- 升级预留 - 支持多级升级配置
- 点树可视化 - 技能树UI展示前置关系