Commit 589d7d59 authored by Gavin An's avatar Gavin An

영웅 시스템 추가

parent d155dc71
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://cd6ffpkexhiql"
path="res://.godot/imported/dwarf.fbx-27c324cea2bfcd67340733ff5df13dd1.scn"
[deps]
source_file="res://assets/models/hero/dwarf/dwarf.fbx"
dest_files=["res://.godot/imported/dwarf.fbx-27c324cea2bfcd67340733ff5df13dd1.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=true
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
fbx/importer=0
fbx/allow_geometry_helper_nodes=false
fbx/embedded_image_handling=1
fbx/naming_version=2
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c80w7d4fpvj4"
path.s3tc="res://.godot/imported/dwarf_0.png-a19cdb5d63853fe1900cd65a3edf2068.s3tc.ctex"
path.etc2="res://.godot/imported/dwarf_0.png-a19cdb5d63853fe1900cd65a3edf2068.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
generator_parameters={
"md5": "dbb60c8a2b727899d9f295a090d94ab2"
}
[deps]
source_file="res://assets/models/hero/dwarf/dwarf_0.png"
dest_files=["res://.godot/imported/dwarf_0.png-a19cdb5d63853fe1900cd65a3edf2068.s3tc.ctex", "res://.godot/imported/dwarf_0.png-a19cdb5d63853fe1900cd65a3edf2068.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
This source diff could not be displayed because it is too large. You can view the blob instead.
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://dsbt11ss58cj6"
path="res://.godot/imported/orcish_axe.glb-d573335f34c5e7ec2c033333f3121a40.scn"
[deps]
source_file="res://assets/models/items/weapons/orcish_axe/orcish_axe.glb"
dest_files=["res://.godot/imported/orcish_axe.glb-d573335f34c5e7ec2c033333f3121a40.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
gltf/naming_version=2
gltf/embedded_image_handling=1
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dvfqbajqbarbo"
path.s3tc="res://.godot/imported/orcish_axe_Baked_BaseColor.png-93011b66af7e42565859109e05e41b28.s3tc.ctex"
path.etc2="res://.godot/imported/orcish_axe_Baked_BaseColor.png-93011b66af7e42565859109e05e41b28.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
generator_parameters={
"md5": "3ac5e6ee528fe4e227b66bc7aa01cdb7"
}
[deps]
source_file="res://assets/models/items/weapons/orcish_axe/orcish_axe_Baked_BaseColor.png"
dest_files=["res://.godot/imported/orcish_axe_Baked_BaseColor.png-93011b66af7e42565859109e05e41b28.s3tc.ctex", "res://.godot/imported/orcish_axe_Baked_BaseColor.png-93011b66af7e42565859109e05e41b28.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d1f5klhem6irc"
path="res://.godot/imported/dwarf_portrait.jpg-88098d38151fd8b2e19b3e7494307fcc.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/ui/hero/dwarf_portrait.jpg"
dest_files=["res://.godot/imported/dwarf_portrait.jpg-88098d38151fd8b2e19b3e7494307fcc.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
...@@ -25,7 +25,7 @@ SceneChanger="*res://src/autoload/scene_changer.tscn" ...@@ -25,7 +25,7 @@ SceneChanger="*res://src/autoload/scene_changer.tscn"
window/size/viewport_width=1280 window/size/viewport_width=1280
window/size/viewport_height=720 window/size/viewport_height=720
window/size/mode=3 window/size/mode=2
window/stretch/mode="canvas_items" window/stretch/mode="canvas_items"
window/handheld/orientation=4 window/handheld/orientation=4
......
...@@ -4,11 +4,14 @@ extends Node ...@@ -4,11 +4,14 @@ extends Node
# 유닛 리소스 템플릿 목록 # 유닛 리소스 템플릿 목록
var unit_templates: Array[UnitData] = [] var unit_templates: Array[UnitData] = []
# 영웅 리소스 템플릿 목록
var hero_templates: Array[UnitData] = []
# 몬스터 리소스 템플릿 목록 # 몬스터 리소스 템플릿 목록
var monster_templates: Array[MonsterData] = [] var monster_templates: Array[MonsterData] = []
func _ready() -> void: func _ready() -> void:
_initialize_unit_templates() _initialize_unit_templates()
_initialize_hero_templates()
_initialize_monster_templates() _initialize_monster_templates()
## 5종의 유닛 능력치 데이터 초기 셋업 ## 5종의 유닛 능력치 데이터 초기 셋업
...@@ -62,6 +65,21 @@ func _initialize_unit_templates() -> void: ...@@ -62,6 +65,21 @@ func _initialize_unit_templates() -> void:
unit_templates.append(mage) unit_templates.append(mage)
## 영웅 능력치 데이터 초기 셋업
func _initialize_hero_templates() -> void:
# 1. Dwarf Hero (근접 공격형 강인한 영웅)
var dwarf: UnitData = UnitData.new()
dwarf.unit_id = "Hero_Dwarf"
dwarf.unit_name = "Dwarf Hero"
dwarf.damage = 40.0
dwarf.cooldown = 1.2
dwarf.move_speed = 4.5
dwarf.attack_range = 1.8
dwarf.is_ranged = false
dwarf.unit_color = Color(0.85, 0.12, 0.85, 1.0) # 보랏빛 하이라이트
hero_templates.append(dwarf)
## 몬스터 능력치 데이터 초기 셋업 ## 몬스터 능력치 데이터 초기 셋업
func _initialize_monster_templates() -> void: func _initialize_monster_templates() -> void:
# 1. Noble Man (기본 밸런스형 몬스터) # 1. Noble Man (기본 밸런스형 몬스터)
......
class_name BaseHero
extends BaseUnit
## 영웅의 공통 기반 시스템 정의 스크립트 (BaseHero)
## - BaseUnit을 상속받아 RTS 조작 및 탐색 기능을 공유하며, 레벨 및 스킬 시스템을 추가합니다.
# 영웅 레벨 및 경험치 관련 시그널
signal xp_changed(current_xp: int, needed_xp: int)
signal level_changed(new_level: int)
signal skill_cooldown_changed(skill_slot: String, remaining: float, duration: float)
# 영웅 능력치 한계 상수
const MAX_HERO_LEVEL: int = 20
# 레벨 및 경험치 상태 변수
var level: int = 1:
set(value):
level = value
level_changed.emit(level)
var xp: int = 0
var xp_to_next_level: int = 100
# 스킬 관리용 데이터 사전
var skill_cooldowns: Dictionary = {
"Q": 0.0,
"W": 0.0,
"E": 0.0,
"R": 0.0
}
var skill_max_cooldowns: Dictionary = {
"Q": 6.0,
"W": 12.0,
"E": 15.0,
"R": 50.0
}
var skill_mana_costs: Dictionary = {
"Q": 0,
"W": 0,
"E": 0,
"R": 0
}
# 3D 이중 선택 링 캐싱
@onready var _selection_ring_outer: MeshInstance3D = $SelectionRingOuter
func _ready() -> void:
super._ready()
# 영웅 그룹에 별도로 등록
add_to_group("heroes")
# 레벨 1 경험치 요구량 설정
xp_to_next_level = _calculate_xp_requirement(level)
xp_changed.emit(xp, xp_to_next_level)
level_changed.emit(level)
func _physics_process(delta: float) -> void:
super._physics_process(delta)
# 스킬 쿨타임 타이머 실시간 차감
for slot: String in skill_cooldowns.keys():
if skill_cooldowns[slot] > 0.0:
skill_cooldowns[slot] = maxf(0.0, skill_cooldowns[slot] - delta)
skill_cooldown_changed.emit(slot, skill_cooldowns[slot], skill_max_cooldowns[slot])
# 영웅이 선택되었을 때 이중 선택 링을 서로 반대 방향으로 회전시켜 세련된 연출 가동
if is_selected:
if _selection_ring:
_selection_ring.rotate_y(delta * 1.5)
if _selection_ring_outer:
_selection_ring_outer.rotate_y(-delta * 2.0)
## 경험치 획득 처리 함수
func gain_xp(amount: int) -> void:
if level >= MAX_HERO_LEVEL:
return
xp += amount
print("Hero ", name, " gained ", amount, " XP. (Current: ", xp, "/", xp_to_next_level, ")")
# 레벨업 체크 루프
while xp >= xp_to_next_level and level < MAX_HERO_LEVEL:
xp -= xp_to_next_level
_level_up()
xp_to_next_level = _calculate_xp_requirement(level)
xp_changed.emit(xp, xp_to_next_level)
## 레벨업 시 능력치 증가 및 연출
func _level_up() -> void:
level += 1
print("★ LEVEL UP ★ Hero ", name, " reached Level ", level, "!")
# 레벨당 공격력(damage) 15% 및 사거리 등 추가 보강
if unit_data:
# 영웅은 런타임에 기획 데이터를 직접 스케일업
unit_data.damage = unit_data.damage + 5.0
unit_data.move_speed = unit_data.move_speed + 0.1
# 레벨업 시 체력을 가득 채우거나 특정 보상 지급 연출 가능
# 3D 공간 상에 레벨업 이펙트를 출력하는 코드 등 보강
_play_level_up_visual_effect()
## 레벨업 기하급수 경험치 연산 공식 (레벨 * 100)
func _calculate_xp_requirement(lvl: int) -> int:
return lvl * 100
## 레벨업 비주얼 파티클 연출용 가상 함수
func _play_level_up_visual_effect() -> void:
# 선택 링의 에너지를 잠시 폭발시킴
if _selection_ring_outer:
var mat: StandardMaterial3D = _selection_ring_outer.mesh.material as StandardMaterial3D
if mat:
var tween: Tween = create_tween()
tween.tween_property(mat, "emission_energy_multiplier", 10.0, 0.2)
tween.tween_property(mat, "emission_energy_multiplier", 2.0, 0.5)
## 키보드 스킬 단축키 입력 수신 처리
func _unhandled_input(event: InputEvent) -> void:
if GameManager.is_game_over or not is_selected:
return
if event is InputEventKey and event.is_pressed() and not event.is_echo():
var key_event: InputEventKey = event as InputEventKey
match key_event.keycode:
KEY_Q:
use_skill("Q")
KEY_W:
use_skill("W")
KEY_E:
use_skill("E")
KEY_R:
use_skill("R")
## 스킬 시도 및 쿨타임 검증
func use_skill(slot: String) -> void:
if skill_cooldowns.get(slot, 0.0) > 0.0:
print("Skill ", slot, " is on cooldown! (", skill_cooldowns[slot], "s remaining)")
return
# 스킬 시전 성공 시 쿨타임 가동
var success: bool = false
match slot:
"Q": success = _cast_skill_q()
"W": success = _cast_skill_w()
"E": success = _cast_skill_e()
"R": success = _cast_skill_r()
if success:
skill_cooldowns[slot] = skill_max_cooldowns[slot]
skill_cooldown_changed.emit(slot, skill_cooldowns[slot], skill_max_cooldowns[slot])
# 가상 함수 정의 (자식 클래스에서 영웅 스펙에 맞춰 재정의)
func _cast_skill_q() -> bool: return false
func _cast_skill_w() -> bool: return false
func _cast_skill_e() -> bool: return false
func _cast_skill_r() -> bool: return false
## 선택 상태 하이라이트 오버라이드
func select() -> void:
super.select()
if _selection_ring_outer:
_selection_ring_outer.visible = true
func deselect() -> void:
super.deselect()
if _selection_ring_outer:
_selection_ring_outer.visible = false
[gd_scene format=3 uid="uid://lswmiam1ftlw"]
[ext_resource type="PackedScene" uid="uid://cd6ffpkexhiql" path="res://assets/models/hero/dwarf/dwarf.fbx" id="1_l6u5b"]
[node name="dwarf" unique_id=290391373 instance=ExtResource("1_l6u5b")]
class_name DwarfHero
extends BaseHero
## 드워프 영웅 유닛 제어 클래스 (DwarfHero)
## - 4개의 고유 스킬(Q: 강격, W: 전장의 가속, E: 천둥벼락, R: 광폭화)을 실구현하고 애니메이션을 연동합니다.
# 버프 상태 추적 변수
var _is_avatar_active: bool = false
var _original_damage: float = 0.0
var _original_scale: Vector3 = Vector3.ONE
func _setup_unit_visuals() -> void:
# 자식 노드 중 드워프 3D 모델 노드 이름을 명시하여 인스턴스를 확보합니다.
var m_name: String = "dwarf"
var node: Node3D = get_node_or_null(m_name) as Node3D
if not node:
push_error("DwarfHero: Model node '" + m_name + "' not found under children.")
return
_model_instance = node
_original_scale = scale
# 드워프 영웅 전용 스킨 이미지와 무기 메쉬를 결합합니다.
UnitHelper.apply_skin(_model_instance, "res://assets/models/hero/dwarf/dwarf_0.png")
UnitHelper.attach_weapon(_model_instance, unit_data)
# dwarf.fbx 내에 내장된 AnimationPlayer 노드를 캐싱합니다.
_model_anim_player = UnitHelper.find_animation_player(_model_instance)
if _model_anim_player:
print("DwarfHero: Linked embedded model & AnimationPlayer: ", m_name)
## 드워프 영웅 애니메이션 갱신 제어
func _update_animations() -> void:
if not _model_anim_player:
return
var has_custom_anims: bool = _model_anim_player.has_animation("Idle")
var target_anim: String = ""
var play_speed: float = 1.0
var blend_time: float = 0.25
if has_custom_anims:
# fbx 내장 애니메이션 이름 매핑 (Attack, Running, Idle_11)
var attack_anim: String = "Attack"
var run_anim: String = "Running"
var idle_anim: String = "Idle"
# 1. 공격 중일 때 (공격 선딜/모션 락 타이머가 작동 중인 경우)
if _attack_lock_timer > 0.0:
var base_speed: float = 1.2
if unit_data and unit_data.cooldown < 0.6:
base_speed = 0.6 / unit_data.cooldown
play_speed = clampf(base_speed, 1.0, 3.0)
target_anim = attack_anim
blend_time = 0.1
# 2. 이동 중일 때 (속도 벡터 크기가 0.1보다 클 경우)
elif velocity.length() > 0.1:
target_anim = run_anim
play_speed = 1.1
# 3. 그 외 기본 대기 상태일 때
else:
target_anim = idle_anim
play_speed = 1.0
if target_anim != "" and _model_anim_player.current_animation != target_anim:
_model_anim_player.play(target_anim, blend_time, play_speed)
else:
# Fallback 처리
var fallback_anim: String = "Armature|Armature|Basic_Jump|baselayer"
if not _model_anim_player.has_animation(fallback_anim):
return
if _attack_lock_timer > 0.0:
target_anim = fallback_anim
play_speed = 2.5
elif velocity.length() > 0.1:
target_anim = fallback_anim
play_speed = 1.5
else:
target_anim = ""
_model_anim_player.stop()
if target_anim != "" and _model_anim_player.current_animation != target_anim:
_model_anim_player.play(target_anim, blend_time, play_speed)
# ==========================================
# 영웅 고유 스킬 실구현
# ==========================================
## Q 스킬: 강격 (Heavy Strike)
## - 대상에게 강력한 피해(공격력의 3배)를 가하고 3초간 기절시킵니다.
func _cast_skill_q() -> bool:
if not _target_monster or not is_instance_valid(_target_monster):
print("Q Skill Failed: No target monster to strike!")
return false
var dist: float = global_position.distance_to(_target_monster.global_position)
if dist > unit_data.attack_range * 1.5:
print("Q Skill Failed: Target is too far!")
return false
print("Dwarf Cast Q: Heavy Strike on ", _target_monster.name)
# 공격 락 시간 임시 설정 및 공격 애니메이션 강제 재생
_attack_lock_timer = 0.8
_update_animations()
# 타겟 데미지 가하기 및 기절 상태 부여
var target_ref: Node3D = _target_monster
var damage_amount: float = unit_data.damage * 3.0
# 몬스터의 현재 속도를 백업하고 0으로 만들어 기절 상태로 만듭니다.
var orig_speed: float = target_ref.get("speed")
target_ref.set("speed", 0.0)
if target_ref.has_method("take_damage"):
target_ref.call("take_damage", damage_amount)
# 3초 후 기절 상태 해제 타이머 가동
get_tree().create_timer(3.0).timeout.connect(func() -> void:
if is_instance_valid(target_ref) and not target_ref.get("is_dead"):
target_ref.set("speed", orig_speed)
print("Monster ", target_ref.name, " recovered from Stun.")
)
# 이펙트 연출
if _selection_ring:
var mat = _selection_ring.mesh.material as StandardMaterial3D
if mat:
var tween = create_tween()
tween.tween_property(mat, "emission_energy_multiplier", 12.0, 0.1)
tween.tween_property(mat, "emission_energy_multiplier", 3.0, 0.4)
return true
## W 스킬: 전장의 가속 (Battle Haste)
## - 영웅 주변 10m 내 모든 아군 유닛의 공격 속도 배율을 1.5배로 5초간 강화합니다.
func _cast_skill_w() -> bool:
print("Dwarf Cast W: Battle Haste activated!")
var affected_units: Array[Node] = []
var all_units = get_tree().get_nodes_in_group("units")
for unit: Node in all_units:
if is_instance_valid(unit) and unit is Node3D and unit != self:
var dist: float = global_position.distance_to(unit.global_position)
if dist <= 10.0:
# 아군 공격 속도 배율 임시 강화 (base_unit.gd의 attack_speed_mult 참고)
unit.set("attack_speed_mult", 1.5)
affected_units.append(unit)
print(" Hasted unit: ", unit.name)
# 5초 뒤 버프 해제
get_tree().create_timer(5.0).timeout.connect(func() -> void:
for unit: Node in affected_units:
if is_instance_valid(unit):
unit.set("attack_speed_mult", 1.0)
print("Battle Haste duration ended. Stats restored.")
)
# 비주얼 스파크 효과
if _selection_ring_outer:
var mat = _selection_ring_outer.mesh.material as StandardMaterial3D
if mat:
var tween = create_tween()
tween.tween_property(mat, "emission_energy_multiplier", 8.0, 0.2)
tween.tween_property(mat, "emission_energy_multiplier", 2.0, 0.5)
return true
## E 스킬: 천둥벼락 (Thunder Clap)
## - 주변 8m 내 모든 몬스터에게 1.5배 범위 피해를 입히고 3초간 속도를 50% 느리게 만듭니다.
func _cast_skill_e() -> bool:
print("Dwarf Cast E: Thunder Clap triggered!")
var monsters = get_tree().get_nodes_in_group("monsters")
var damage_amount: float = unit_data.damage * 1.5
for monster: Node in monsters:
if is_instance_valid(monster) and monster is Node3D:
var m_3d = monster as Node3D
var dist: float = global_position.distance_to(m_3d.global_position)
if dist <= 8.0:
if m_3d.has_method("take_damage"):
m_3d.call("take_damage", damage_amount)
# 몬스터의 속도를 3초 동안 50% 둔화시킵니다.
var orig_speed: float = m_3d.get("speed")
m_3d.set("speed", orig_speed * 0.5)
var m_ref = m_3d
get_tree().create_timer(3.0).timeout.connect(func() -> void:
if is_instance_valid(m_ref) and not m_ref.get("is_dead"):
m_ref.set("speed", orig_speed)
)
# 천둥소리 느낌의 이중 링 파동 연출
if _selection_ring and _selection_ring_outer:
var mat1 = _selection_ring.mesh.material as StandardMaterial3D
var mat2 = _selection_ring_outer.mesh.material as StandardMaterial3D
if mat1 and mat2:
var tween = create_tween().set_parallel(true)
tween.tween_property(mat1, "emission_energy_multiplier", 15.0, 0.15)
tween.tween_property(mat2, "emission_energy_multiplier", 15.0, 0.15)
var tween_back = create_tween().set_parallel(true)
tween_back.tween_property(mat1, "emission_energy_multiplier", 3.0, 0.6)
tween_back.tween_property(mat2, "emission_energy_multiplier", 2.0, 0.6)
return true
## R 스킬: 드워프의 광폭화 (Avatar) - 궁극기
## - 10초 동안 영웅이 거대화(스케일 1.8배)되며, 공격력이 2배 증가하고 모든 체력을 완전 회복합니다.
func _cast_skill_r() -> bool:
if _is_avatar_active:
print("Avatar is already active!")
return false
print("Dwarf Cast R: AVATAR MODE ACTIVATE!")
_is_avatar_active = true
_original_damage = unit_data.damage
# 공격력 2배 상향 및 1.8배로 크기 확장
unit_data.damage = _original_damage * 2.0
var tween = create_tween().set_parallel(true)
tween.tween_property(self, "scale", _original_scale * 1.8, 0.5)
# 레벨업 효과 발광 극대화
if _selection_ring_outer:
var mat = _selection_ring_outer.mesh.material as StandardMaterial3D
if mat:
tween.tween_property(mat, "emission_energy_multiplier", 12.0, 0.5)
# 10초 뒤 광폭화 해제 및 크기/스펙 원상복귀
get_tree().create_timer(10.0).timeout.connect(func() -> void:
_is_avatar_active = false
if unit_data:
unit_data.damage = _original_damage
var tween_back = create_tween().set_parallel(true)
tween_back.tween_property(self, "scale", _original_scale, 0.5)
if _selection_ring_outer:
var mat = _selection_ring_outer.mesh.material as StandardMaterial3D
if mat:
tween_back.tween_property(mat, "emission_energy_multiplier", 2.0, 0.5)
print("Avatar Mode deactivated. Stats restored.")
)
return true
[gd_scene format=3 uid="uid://np5fiyv02pk"]
[ext_resource type="PackedScene" uid="uid://lswmiam1ftlw" path="res://src/entities/hero/dwarf/dwarf.tscn" id="1_b0qj8"]
[ext_resource type="Script" uid="uid://dsckp7uaawijh" path="res://src/entities/hero/dwarf/dwarf_hero.gd" id="1_cthrw"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_pe3j2"]
radius = 0.4
height = 1.2
[sub_resource type="CapsuleMesh" id="CapsuleMesh_0yhxm"]
radius = 0.4
height = 1.2
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_71vph"]
albedo_color = Color(0.85, 0.12, 0.85, 1)
emission_enabled = true
emission = Color(0.85, 0.12, 0.85, 1)
emission_energy_multiplier = 3.0
[sub_resource type="TorusMesh" id="TorusMesh_gniq3"]
material = SubResource("StandardMaterial3D_71vph")
inner_radius = 0.65
outer_radius = 0.7
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ueb8j"]
albedo_color = Color(1, 0.85, 0, 1)
emission_enabled = true
emission = Color(1, 0.85, 0, 1)
emission_energy_multiplier = 2.0
[sub_resource type="TorusMesh" id="TorusMesh_fcx2y"]
material = SubResource("StandardMaterial3D_ueb8j")
inner_radius = 0.75
outer_radius = 0.78
[node name="DwarfHero" type="CharacterBody3D" unique_id=1158278965]
script = ExtResource("1_cthrw")
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1912534245]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.6, 0)
shape = SubResource("CapsuleShape3D_pe3j2")
[node name="VisualMesh" type="MeshInstance3D" parent="." unique_id=383881199]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.6, 0)
visible = false
mesh = SubResource("CapsuleMesh_0yhxm")
[node name="SelectionRing" type="MeshInstance3D" parent="." unique_id=2072891932]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.02, 0)
visible = false
mesh = SubResource("TorusMesh_gniq3")
[node name="SelectionRingOuter" type="MeshInstance3D" parent="." unique_id=2127703735]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.01, 0)
visible = false
mesh = SubResource("TorusMesh_fcx2y")
[node name="NavigationAgent3D" type="NavigationAgent3D" parent="." unique_id=536619037]
[node name="dwarf" parent="." unique_id=290391373 instance=ExtResource("1_b0qj8")]
transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, 0, 0, 0)
[editable path="dwarf"]
...@@ -96,10 +96,18 @@ func _die() -> void: ...@@ -96,10 +96,18 @@ func _die() -> void:
# 골드 보상 획득 # 골드 보상 획득
GameManager.add_gold(gold_reward) GameManager.add_gold(gold_reward)
# 영웅에게 경험치 지급 (모든 생존한 영웅이 경험치를 얻음)
var heroes: Array[Node] = get_tree().get_nodes_in_group("heroes")
for hero: Node in heroes:
if is_instance_valid(hero) and hero.has_method("gain_xp"):
# 골드 획득량과 동일한 비율로 경험치를 지급하되, 최소 15 XP 보장
var xp_reward: int = max(15, gold_reward)
hero.call("gain_xp", xp_reward)
# 필드 활성 몬스터 수 감소 # 필드 활성 몬스터 수 감소
GameManager.decrement_monster_count() GameManager.decrement_monster_count()
print("Monster ", name, " died! Rewarded ", gold_reward, " gold.") print("Monster ", name, " died! Rewarded ", gold_reward, " gold and granted XP.")
# 씬에서 제거 # 씬에서 제거
queue_free() queue_free()
......
...@@ -205,7 +205,7 @@ func _find_nearest_target() -> void: ...@@ -205,7 +205,7 @@ func _find_nearest_target() -> void:
if nearest_monster: if nearest_monster:
_target_monster = nearest_monster _target_monster = nearest_monster
## 타겟에게 격 가함 ## 타겟에게 격 가함
func _try_attack() -> void: func _try_attack() -> void:
if _attack_timer > 0.0 or not _target_monster: if _attack_timer > 0.0 or not _target_monster:
return return
...@@ -282,7 +282,7 @@ func _show_attack_visual() -> void: ...@@ -282,7 +282,7 @@ func _show_attack_visual() -> void:
else: else:
print("Unit ", name, " strikes ", _target_monster.name) print("Unit ", name, " strikes ", _target_monster.name)
## 유닛을 제자리에 고정하고 사거리 내의 적만 공격하도록 설정 (Hold) ## h 버튼을 누르면 유닛을 제자리에 고정하고 사거리 내의 적만 공격하도록 설정 (Hold)
func hold_position() -> void: func hold_position() -> void:
_is_holding = true _is_holding = true
_target_monster = null _target_monster = null
......
[gd_scene format=3 uid="uid://qdvm4bgh48q6"] [gd_scene format=3 uid="uid://qdvm4bgh48q6"]
[ext_resource type="Script" path="res://src/entities/units/ranger/ranger_unit.gd" id="1_base_unit"] [ext_resource type="Script" uid="uid://dmo2ypkevm8ma" path="res://src/entities/units/ranger/ranger_unit.gd" id="1_base_unit"]
[ext_resource type="PackedScene" path="res://src/entities/units/ranger/ranger.tscn" id="2_ranger_scene"] [ext_resource type="PackedScene" uid="uid://ch457fojl2l5g" path="res://src/entities/units/ranger/ranger.tscn" id="2_ranger_scene"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_body"] [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_body"]
radius = 0.4 radius = 0.4
...@@ -42,30 +42,22 @@ mesh = SubResource("TorusMesh_selection") ...@@ -42,30 +42,22 @@ mesh = SubResource("TorusMesh_selection")
[node name="ranger" parent="." unique_id=1235075234 instance=ExtResource("2_ranger_scene")] [node name="ranger" parent="." unique_id=1235075234 instance=ExtResource("2_ranger_scene")]
[node name="Skeleton3D" parent="ranger/Armature" parent_id_path=PackedInt32Array(1235075234, 456601137) index="0" unique_id=1884876701] [node name="Skeleton3D" parent="ranger/Armature" parent_id_path=PackedInt32Array(1235075234, 737057940) index="0" unique_id=641195763]
bones/0/position = Vector3(0.03514373, 0.0034280196, 0.6635236) bones/0/position = Vector3(0.03514373, 0.0034280196, 0.6635236)
bones/0/rotation = Quaternion(0.20349285, -0.5581575, -0.767251, 0.24161276) bones/0/rotation = Quaternion(0.20349285, -0.5581575, -0.767251, 0.24161276)
bones/1/rotation = Quaternion(0.5304922, 0.57593215, -0.61864984, 0.06444053) bones/1/rotation = Quaternion(0.5304922, 0.57593215, -0.61864984, 0.06444053)
bones/2/rotation = Quaternion(0.59826106, 0.1739482, 0.06382933, 0.7795843) bones/2/rotation = Quaternion(0.59826106, 0.1739482, 0.06382933, 0.7795843)
bones/3/rotation = Quaternion(-0.6715887, -0.20650962, 0.092862934, 0.7054777) bones/3/rotation = Quaternion(-0.6715887, -0.20650962, 0.092862934, 0.7054777)
bones/4/rotation = Quaternion(-0.27814108, -0.049392484, 0.057641834, 0.95753616)
bones/5/rotation = Quaternion(0.8261204, 0.0028015971, 0.36372316, 0.4303752) bones/5/rotation = Quaternion(0.8261204, 0.0028015971, 0.36372316, 0.4303752)
bones/6/rotation = Quaternion(0.5529449, -0.009049176, 0.13317788, 0.82245594) bones/6/rotation = Quaternion(0.5529449, -0.009049176, 0.13317788, 0.82245594)
bones/7/rotation = Quaternion(-0.5409971, -0.107743785, 0.04905413, 0.83265066) bones/7/rotation = Quaternion(-0.5409971, -0.107743785, 0.04905413, 0.83265066)
bones/8/rotation = Quaternion(-0.26788568, 0.073798016, -0.031191472, 0.96011364)
bones/9/rotation = Quaternion(0.3045851, 0.05863059, 0.009242283, 0.950634)
bones/10/rotation = Quaternion(-0.025334273, 0.053094994, -0.033819344, 0.997695)
bones/11/rotation = Quaternion(-0.0011097626, -0.049749736, -0.0720563, 0.9961584) bones/11/rotation = Quaternion(-0.0011097626, -0.049749736, -0.0720563, 0.9961584)
bones/12/rotation = Quaternion(0.49171034, 0.6453104, -0.13713944, 0.5683207) bones/12/rotation = Quaternion(0.49171034, 0.6453104, -0.13713944, 0.5683207)
bones/13/rotation = Quaternion(0.28274685, -0.08206125, -0.5247269, 0.79873765) bones/13/rotation = Quaternion(0.28274685, -0.08206125, -0.5247269, 0.79873765)
bones/14/rotation = Quaternion(-0.060595874, -0.09316963, 0.093224026, 0.9894225) bones/14/rotation = Quaternion(-0.060595874, -0.09316963, 0.093224026, 0.9894225)
bones/15/rotation = Quaternion(0.002734622, 0.14336169, 0.053758636, 0.98820543)
bones/16/rotation = Quaternion(-0.22393328, 0.59772503, -0.31282783, -0.7033615) bones/16/rotation = Quaternion(-0.22393328, 0.59772503, -0.31282783, -0.7033615)
bones/17/rotation = Quaternion(0.2881412, 0.53661305, -0.6094496, 0.50753546) bones/17/rotation = Quaternion(0.2881412, 0.53661305, -0.6094496, 0.50753546)
bones/18/rotation = Quaternion(0.43169993, 0.23694667, -0.78221, 0.38162687) bones/18/rotation = Quaternion(0.43169993, 0.23694667, -0.78221, 0.38162687)
bones/19/rotation = Quaternion(-0.033562582, -0.33096007, -0.44027492, 0.8339646)
bones/20/rotation = Quaternion(0.019875612, 0.059954792, -0.025400154, 0.99768)
bones/21/rotation = Quaternion(0.15988994, 0.5659004, -0.5430847, 0.5993756) bones/21/rotation = Quaternion(0.15988994, 0.5659004, -0.5430847, 0.5993756)
bones/22/rotation = Quaternion(-0.23428716, -6.378701e-09, -0.002581273, 0.9721641)
[editable path="ranger"] [editable path="ranger"]
...@@ -2,7 +2,6 @@ class_name StrikerUnit ...@@ -2,7 +2,6 @@ class_name StrikerUnit
extends BaseUnit extends BaseUnit
## Striker 유닛 고유 동작 및 비주얼 제어 클래스 ## Striker 유닛 고유 동작 및 비주얼 제어 클래스
func _setup_unit_visuals() -> void: func _setup_unit_visuals() -> void:
# 자식 노드 중 스트라이커 모델 노드 명시적 확보 # 자식 노드 중 스트라이커 모델 노드 명시적 확보
var m_name: String = "striker" var m_name: String = "striker"
......
...@@ -19,6 +19,15 @@ extends Control ...@@ -19,6 +19,15 @@ extends Control
var _pause_button: Button = null var _pause_button: Button = null
var _summoner: Summoner = null var _summoner: Summoner = null
# 영웅 관리용 상태 및 UI 노드 캐시
var hero_spawn_button: Button = null
var hero_panel: Panel = null
var _active_hero: BaseHero = null
const HERO_SPAWN_COST: int = 500
var _skill_cooldown_overlays: Dictionary = {} # {"Q": ColorRect, "W": ColorRect, ...}
var _skill_cooldown_labels: Dictionary = {} # {"Q": Label, "W": Label, ...}
func _ready() -> void: func _ready() -> void:
...@@ -75,11 +84,25 @@ func _ready() -> void: ...@@ -75,11 +84,25 @@ func _ready() -> void:
bottom_bar.add_child(clear_units_button) bottom_bar.add_child(clear_units_button)
clear_units_button.pressed.connect(_on_clear_units_pressed) clear_units_button.pressed.connect(_on_clear_units_pressed)
# 영웅 소환 버튼 동적 생성
hero_spawn_button = Button.new()
hero_spawn_button.name = "SummonHeroButton"
hero_spawn_button.text = "Summon Hero (500 G)"
# 멋진 보라색 영웅 테두리/에너지 느낌의 테마 스타일 적용
var sb: StyleBoxFlat = StyleBoxFlat.new()
sb.bg_color = Color(0.35, 0.1, 0.45, 0.9)
sb.set_corner_radius_all(4)
hero_spawn_button.add_theme_stylebox_override("normal", sb)
bottom_bar.add_child(hero_spawn_button)
hero_spawn_button.pressed.connect(_on_summon_hero_pressed)
# 골드 UI 갱신 및 소환 한도 검증 # 골드 UI 갱신 및 소환 한도 검증
func _on_gold_changed(gold: int) -> void: func _on_gold_changed(gold: int) -> void:
gold_label.text = "GOLD: " + str(gold) + " G" gold_label.text = "GOLD: " + str(gold) + " G"
# 골드가 부족하면 유닛 소환 버튼 비활성화 # 골드가 부족하면 유닛 소환 버튼 비활성화
spawn_button.disabled = (gold < GameManager.unit_spawn_cost) spawn_button.disabled = (gold < GameManager.unit_spawn_cost)
if hero_spawn_button:
hero_spawn_button.disabled = (gold < HERO_SPAWN_COST)
# 스테이지 UI 갱신 # 스테이지 UI 갱신
func _on_stage_changed(stage: int) -> void: func _on_stage_changed(stage: int) -> void:
...@@ -294,3 +317,256 @@ func _on_summoner_selected(data: SummonerData) -> void: ...@@ -294,3 +317,256 @@ func _on_summoner_selected(data: SummonerData) -> void:
var stage_mgr: Node = get_node_or_null("../StageManager") var stage_mgr: Node = get_node_or_null("../StageManager")
if stage_mgr and stage_mgr.has_method("start_game"): if stage_mgr and stage_mgr.has_method("start_game"):
stage_mgr.call("start_game") stage_mgr.call("start_game")
# 영웅 소환 버튼 클릭 처리
func _on_summon_hero_pressed() -> void:
if GameManager.is_game_over:
return
# 골드 소모 검증
if GameManager.spend_gold(HERO_SPAWN_COST):
# 현재 영웅 템플릿 중 첫 번째(Dwarf Hero) 선정
if DataManager.hero_templates.is_empty():
print("HUD Error: No hero templates registered in DataManager!")
return
var chosen_template: UnitData = DataManager.hero_templates[0]
var hero_scene: PackedScene = load("res://src/entities/hero/dwarf/dwarf_hero.tscn") as PackedScene
if not hero_scene:
print("HUD Error: Failed to load dwarf_hero.tscn!")
return
var new_hero: BaseHero = hero_scene.instantiate() as BaseHero
var parent_scene: Node = get_tree().current_scene
# 스폰 좌표 설정 (맵 중앙 0, 0, 0 근처)
var spawn_pos: Vector3 = Vector3(
randf_range(-1.0, 1.0),
1.0,
randf_range(-1.0, 1.0)
)
new_hero.set("unit_data", chosen_template)
parent_scene.add_child(new_hero)
# 월드 좌표 할당
new_hero.global_position = spawn_pos
_active_hero = new_hero
print("Spawned Hero: Dwarf Hero at ", spawn_pos)
# 영웅 좌측 상단 HUD 상태창 구성
_setup_hero_ui(new_hero)
# 영웅 전용 좌측 상단 HUD 구성
func _setup_hero_ui(hero: BaseHero) -> void:
# 기존 영웅 UI 패널이 존재하면 완전 제거
if hero_panel and is_instance_valid(hero_panel):
hero_panel.queue_free()
_skill_cooldown_overlays.clear()
_skill_cooldown_labels.clear()
# 1. 메인 패널 생성 및 스타일 적용
hero_panel = Panel.new()
hero_panel.name = "HeroStatusPanel"
hero_panel.custom_minimum_size = Vector2(290, 120)
# 화면 좌측 상단에 알맞은 마진 할당
hero_panel.position = Vector2(20, 120)
var sb: StyleBoxFlat = StyleBoxFlat.new()
sb.bg_color = Color(0.12, 0.08, 0.16, 0.85) # 어두운 보랏빛 판타지 배경
sb.border_width_left = 2
sb.border_width_top = 2
sb.border_width_right = 2
sb.border_width_bottom = 2
sb.border_color = Color(0.85, 0.12, 0.85, 0.9) # 화려한 퍼플 테두리
sb.set_corner_radius_all(6)
hero_panel.add_theme_stylebox_override("panel", sb)
add_child(hero_panel)
# 2. HBox 컨테이너 배치 (초상화 | 상세정보)
var hbox: HBoxContainer = HBoxContainer.new()
hbox.name = "HBox"
hbox.anchor_right = 1.0
hbox.anchor_bottom = 1.0
hbox.offset_left = 8
hbox.offset_top = 8
hbox.offset_right = -8
hbox.offset_bottom = -8
hbox.add_theme_constant_override("separation", 12)
hero_panel.add_child(hbox)
# 3. 초상화 구성
var portrait_panel: Panel = Panel.new()
portrait_panel.custom_minimum_size = Vector2(75, 75)
var port_sb: StyleBoxFlat = StyleBoxFlat.new()
port_sb.bg_color = Color(0.05, 0.05, 0.05, 1.0)
port_sb.border_width_left = 1
port_sb.border_width_top = 1
port_sb.border_width_right = 1
port_sb.border_width_bottom = 1
port_sb.border_color = Color(1.0, 0.85, 0.0, 0.8) # 황금빛 초상화 테두리
port_sb.set_corner_radius_all(4)
portrait_panel.add_theme_stylebox_override("panel", port_sb)
var portrait: TextureRect = TextureRect.new()
portrait.name = "Portrait"
portrait.anchor_right = 1.0
portrait.anchor_bottom = 1.0
portrait.offset_left = 2
portrait.offset_top = 2
portrait.offset_right = -2
portrait.offset_bottom = -2
portrait.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
portrait.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED
var p_path: String = "res://assets/ui/hero/dwarf_portrait.jpg"
if ResourceLoader.exists(p_path):
portrait.texture = load(p_path) as Texture2D
portrait_panel.add_child(portrait)
hbox.add_child(portrait_panel)
# 4. 상세 정보 레이아웃 (VBox)
var vbox: VBoxContainer = VBoxContainer.new()
vbox.name = "VBox"
vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
vbox.add_theme_constant_override("separation", 4)
hbox.add_child(vbox)
# (1) 영웅 이름 및 레벨 표시 라벨
var name_label: Label = Label.new()
name_label.name = "NameLabel"
name_label.text = "Lv. %d %s" % [hero.level, hero.unit_data.unit_name]
name_label.add_theme_font_size_override("font_size", 13)
name_label.add_theme_color_override("font_color", Color(1.0, 0.9, 0.3, 1.0)) # 연황금색
vbox.add_child(name_label)
# (2) 경험치 바 그룹 (HBox)
var xp_hbox: HBoxContainer = HBoxContainer.new()
xp_hbox.add_theme_constant_override("separation", 6)
var xp_bar: ProgressBar = ProgressBar.new()
xp_bar.name = "XPBar"
xp_bar.show_percentage = false
xp_bar.custom_minimum_size = Vector2(110, 10)
xp_bar.size_flags_vertical = Control.SIZE_SHRINK_CENTER
var xp_bg: StyleBoxFlat = StyleBoxFlat.new()
xp_bg.bg_color = Color(0.1, 0.1, 0.1, 1.0)
xp_bg.set_corner_radius_all(2)
var xp_fg: StyleBoxFlat = StyleBoxFlat.new()
xp_fg.bg_color = Color(0.7, 0.15, 0.85, 1.0) # 강렬한 마젠타/보라색 게이지
xp_fg.set_corner_radius_all(2)
xp_bar.add_theme_stylebox_override("background", xp_bg)
xp_bar.add_theme_stylebox_override("fill", xp_fg)
xp_hbox.add_child(xp_bar)
var xp_label: Label = Label.new()
xp_label.name = "XPLabel"
xp_label.text = "%d / %d" % [hero.xp, hero.xp_to_next_level]
xp_label.add_theme_font_size_override("font_size", 9)
xp_hbox.add_child(xp_label)
vbox.add_child(xp_hbox)
# (3) 스킬 단축키 그룹 (HBox)
var skill_hbox: HBoxContainer = HBoxContainer.new()
skill_hbox.name = "SkillGroup"
skill_hbox.add_theme_constant_override("separation", 6)
vbox.add_child(skill_hbox)
# 4개 스킬 슬롯 배치 (Q, W, E, R)
var slots: Array[String] = ["Q", "W", "E", "R"]
var skill_names: Dictionary = {
"Q": "Strike",
"W": "Haste",
"E": "Clap",
"R": "Avatar"
}
for slot: String in slots:
var btn: Button = Button.new()
btn.name = "Skill_" + slot
btn.text = slot
btn.custom_minimum_size = Vector2(32, 32)
btn.add_theme_font_size_override("font_size", 10)
# 스킬 슬롯 배경 스타일링
var btn_sb: StyleBoxFlat = StyleBoxFlat.new()
btn_sb.bg_color = Color(0.2, 0.15, 0.3, 1.0)
btn_sb.border_width_left = 1
btn_sb.border_width_top = 1
btn_sb.border_width_right = 1
btn_sb.border_width_bottom = 1
btn_sb.border_color = Color(0.6, 0.4, 0.7, 0.8)
btn_sb.set_corner_radius_all(3)
btn.add_theme_stylebox_override("normal", btn_sb)
# 툴팁 힌트 세팅
btn.tooltip_text = "%s Skill: %s\nCooldown: %.1fs" % [slot, skill_names[slot], hero.skill_max_cooldowns[slot]]
# 클릭 시 스킬 사용 바인딩
btn.pressed.connect(func() -> void:
if is_instance_valid(hero):
hero.use_skill(slot)
)
# 자식 쿨다운 오버레이 컬러렉트
var overlay: ColorRect = ColorRect.new()
overlay.name = "CooldownOverlay"
overlay.color = Color(0.0, 0.0, 0.0, 0.6)
overlay.anchor_right = 1.0
overlay.anchor_bottom = 1.0
overlay.mouse_filter = Control.MOUSE_FILTER_IGNORE
overlay.visible = false
btn.add_child(overlay)
_skill_cooldown_overlays[slot] = overlay
# 자식 쿨다운 숫자 라벨
var label: Label = Label.new()
label.name = "CooldownLabel"
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
label.anchor_right = 1.0
label.anchor_bottom = 1.0
label.add_theme_font_size_override("font_size", 9)
label.add_theme_color_override("font_color", Color(1.0, 1.0, 1.0, 1.0))
label.visible = false
btn.add_child(label)
_skill_cooldown_labels[slot] = label
skill_hbox.add_child(btn)
# 5. 영웅 시그널 연결 (실시간 UI 데이터 주입)
hero.level_changed.connect(func(new_lvl: int) -> void:
if is_instance_valid(name_label) and is_instance_valid(hero):
name_label.text = "Lv. %d %s" % [new_lvl, hero.unit_data.unit_name]
)
hero.xp_changed.connect(func(curr_xp: int, need_xp: int) -> void:
if is_instance_valid(xp_bar) and is_instance_valid(xp_label):
xp_bar.max_value = need_xp
xp_bar.value = curr_xp
xp_label.text = "%d / %d" % [curr_xp, need_xp]
)
hero.skill_cooldown_changed.connect(func(slot_name: String, remaining: float, duration: float) -> void:
var overlay: ColorRect = _skill_cooldown_overlays.get(slot_name)
var label: Label = _skill_cooldown_labels.get(slot_name)
if is_instance_valid(overlay) and is_instance_valid(label):
if remaining > 0.0:
overlay.visible = true
label.visible = true
label.text = "%.1f" % remaining
else:
overlay.visible = false
label.visible = false
)
# 초기 1회성 데이터 동기화 강제 발행
hero.xp_changed.emit(hero.xp, hero.xp_to_next_level)
hero.level_changed.emit(hero.level)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment