Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
L
ldefense
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Packages
Packages
Container Registry
Analytics
CI / CD Analytics
Repository Analytics
Value Stream Analytics
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Gavin An
ldefense
Commits
589d7d59
Commit
589d7d59
authored
Jul 01, 2026
by
Gavin An
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
영웅 시스템 추가
parent
d155dc71
Changes
25
Hide whitespace changes
Inline
Side-by-side
Showing
25 changed files
with
1752 additions
and
30 deletions
+1752
-30
assets/models/hero/dwarf/dwarf.fbx
assets/models/hero/dwarf/dwarf.fbx
+0
-0
assets/models/hero/dwarf/dwarf.fbx.import
assets/models/hero/dwarf/dwarf.fbx.import
+44
-0
assets/models/hero/dwarf/dwarf_0.png
assets/models/hero/dwarf/dwarf_0.png
+0
-0
assets/models/hero/dwarf/dwarf_0.png.import
assets/models/hero/dwarf/dwarf_0.png.import
+45
-0
assets/models/hero/dwarf/dwarf_animations.tres
assets/models/hero/dwarf/dwarf_animations.tres
+725
-0
assets/models/items/weapons/orcish_axe/orcish_axe.glb
assets/models/items/weapons/orcish_axe/orcish_axe.glb
+0
-0
assets/models/items/weapons/orcish_axe/orcish_axe.glb.import
assets/models/items/weapons/orcish_axe/orcish_axe.glb.import
+42
-0
assets/models/items/weapons/orcish_axe/orcish_axe_Baked_BaseColor.png
...s/items/weapons/orcish_axe/orcish_axe_Baked_BaseColor.png
+0
-0
assets/models/items/weapons/orcish_axe/orcish_axe_Baked_BaseColor.png.import
.../weapons/orcish_axe/orcish_axe_Baked_BaseColor.png.import
+45
-0
assets/ui/hero/dwarf_portrait.jpg
assets/ui/hero/dwarf_portrait.jpg
+0
-0
assets/ui/hero/dwarf_portrait.jpg.import
assets/ui/hero/dwarf_portrait.jpg.import
+40
-0
docs/README.md
docs/README.md
+14
-14
project.godot
project.godot
+1
-1
src/autoload/data_manager.gd
src/autoload/data_manager.gd
+18
-0
src/entities/hero/base_hero.gd
src/entities/hero/base_hero.gd
+167
-0
src/entities/hero/base_hero.gd.uid
src/entities/hero/base_hero.gd.uid
+1
-0
src/entities/hero/dwarf/dwarf.tscn
src/entities/hero/dwarf/dwarf.tscn
+5
-0
src/entities/hero/dwarf/dwarf_hero.gd
src/entities/hero/dwarf/dwarf_hero.gd
+251
-0
src/entities/hero/dwarf/dwarf_hero.gd.uid
src/entities/hero/dwarf/dwarf_hero.gd.uid
+1
-0
src/entities/hero/dwarf/dwarf_hero.tscn
src/entities/hero/dwarf/dwarf_hero.tscn
+63
-0
src/entities/monsters/base_monster.gd
src/entities/monsters/base_monster.gd
+9
-1
src/entities/units/base_unit.gd
src/entities/units/base_unit.gd
+2
-2
src/entities/units/ranger/ranger_unit.tscn
src/entities/units/ranger/ranger_unit.tscn
+3
-11
src/entities/units/striker/striker_unit.gd
src/entities/units/striker/striker_unit.gd
+0
-1
src/ui/hud.gd
src/ui/hud.gd
+276
-0
No files found.
assets/models/hero/dwarf/dwarf.fbx
0 → 100644
View file @
589d7d59
File added
assets/models/hero/dwarf/dwarf.fbx.import
0 → 100644
View file @
589d7d59
[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
assets/models/hero/dwarf/dwarf_0.png
0 → 100644
View file @
589d7d59
4.66 MB
assets/models/hero/dwarf/dwarf_0.png.import
0 → 100644
View file @
589d7d59
[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
assets/models/hero/dwarf/dwarf_animations.tres
0 → 100644
View file @
589d7d59
This source diff could not be displayed because it is too large. You can
view the blob
instead.
assets/models/items/weapons/orcish_axe/orcish_axe.glb
0 → 100644
View file @
589d7d59
File added
assets/models/items/weapons/orcish_axe/orcish_axe.glb.import
0 → 100644
View file @
589d7d59
[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
assets/models/items/weapons/orcish_axe/orcish_axe_Baked_BaseColor.png
0 → 100644
View file @
589d7d59
537 KB
assets/models/items/weapons/orcish_axe/orcish_axe_Baked_BaseColor.png.import
0 → 100644
View file @
589d7d59
[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
assets/ui/hero/dwarf_portrait.jpg
0 → 100644
View file @
589d7d59
1.09 MB
assets/ui/hero/dwarf_portrait.jpg.import
0 → 100644
View file @
589d7d59
[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
docs/README.md
View file @
589d7d59
...
@@ -7,32 +7,32 @@ L-Defense 게임 개요
...
@@ -7,32 +7,32 @@ L-Defense 게임 개요
*
스테이지마다 몬스터의 최대 개수는 200마리, 200마리가 넘어가면 게임 오버.
*
스테이지마다 몬스터의 최대 개수는 200마리, 200마리가 넘어가면 게임 오버.
*
영웅, 유닛의 개념이 있음. 영웅은 플레이어블. 유닛은 배치.
*
영웅, 유닛의 개념이 있음. 영웅은 플레이어블. 유닛은 배치.
*
몬스터를 죽여서 얻은 골드를 영웅을 키우는 데 쓸 것이냐. 유닛의 증가 또는 유닛 강화에 쓸 것이냐는 플레이어의 몫
*
몬스터를 죽여서 얻은 골드를 영웅을 키우는 데 쓸 것이냐. 유닛의 증가 또는 유닛 강화에 쓸 것이냐는 플레이어의 몫
*
나는 영웅을 키워서 컨트롤을 통해 스테이지 클리어 vs 컨트롤 필요없이 유닛을 키워서 탄탄하게 스테이지 클리어
*
나는 영웅을 키워서 컨트롤을 통해 스테이지 클리어 vs 컨트롤 필요없이 유닛을 키워서 탄탄하게 스테이지 클리어
*
(추후)멀티플레이가 가능하며, 비슷한 소환사의 레벨대와 경쟁할 수도 있음(내가 처치한 몬스터는 상대방의 스테이지에 보내어 경쟁)
*
(추후)멀티플레이가 가능하며, 비슷한 소환사의 레벨대와 경쟁할 수도 있음(내가 처치한 몬스터는 상대방의 스테이지에 보내어 경쟁)
-
소환사 개념
-
소환사 개념
*
AOS 게임처럼 유닛을 소환하는 소환사 개념이 존재.
*
AOS 게임처럼 유닛을 소환하는 소환사 개념이 존재.
*
소환사는 게임 시작 전에 선택할 수 있음.
*
소환사는 게임 시작 전에 선택할 수 있음.
*
소환사는 레벨 개념이 존재하며, 레벨마다 각 특성을 찍을 수가 있음.
*
소환사는 레벨 개념이 존재하며, 레벨마다 각 특성을 찍을 수가 있음.
*
레벨 개념은 스테이지가 종료 되어야 반영 됨.
*
레벨 개념은 스테이지가 종료 되어야 반영 됨.
*
소환사는 고유 소환사 스킬이 있음. 쿨타임은 꽤 긴 편.
*
소환사는 고유 소환사 스킬이 있음. 쿨타임은 꽤 긴 편.
*
최대 레벨은 100
*
최대 레벨은 100
-
영웅 개념
-
영웅 개념
*
영웅은 플레이어가 실시간으로 직접 컨트롤 가능한 유일한 유닛.
*
영웅은 플레이어가 실시간으로 직접 컨트롤 가능한 유일한 유닛.
*
영웅은 일정 골드를 통해 하나만 뽑을 수 있음. 무작위 확률로 하나가 나옴.
*
영웅은 일정 골드를 통해 하나만 뽑을 수 있음. 무작위 확률로 하나가 나옴.
*
영웅의 레벨은 돈으로 올릴 수 있음. 스킬 레벨업 또한 돈으로 올릴 수 있음
*
영웅의 레벨은 돈으로 올릴 수 있음. 스킬 레벨업 또한 돈으로 올릴 수 있음
*
생성과 동시에 HUD UI에 영웅의 초상화가 뜨고, 영웅의 스킬 2개를 사용할 수 있음.
*
생성과 동시에 HUD UI에 영웅의 초상화가 뜨고, 영웅의 스킬 2개를 사용할 수 있음.
-
유닛 개념
-
유닛 개념
*
유닛 생성시 마다 골드가 소모되며, 모두 무작위 확률로 생성
*
유닛 생성시 마다 골드가 소모되며, 모두 무작위 확률로 생성
*
생성된 유닛은 스테이지의 격자에 각각 배치 가능. 고정되어 있으며 자동 공격함.
*
생성된 유닛은 스테이지의 격자에 각각 배치 가능. 고정되어 있으며 자동 공격함.
*
체력 / 공격력 / 공격속도 / 이동속도
*
체력 / 공격력 / 공격속도 / 이동속도
*
유닛의 등급은 일반 / 고급 / 희귀 / 전설 / 신화 / 유일 등급
*
유닛의 등급은 일반 / 고급 / 희귀 / 전설 / 신화 / 유일 등급
*
일반 ~ 희귀까지는 고유 스킬이 없고 일반 공격만 가능(근거리 / 원거리 구분 존재)
*
일반 ~ 희귀까지는 고유 스킬이 없고 일반 공격만 가능(근거리 / 원거리 구분 존재)
*
전설 ~ 신화는 고유 스킬 존재.
*
전설 ~ 신화는 고유 스킬 존재.
*
유일(영웅) 등급은 레벨이 존재하며 고유 스킬이 2개 존재
*
유일(영웅) 등급은 레벨이 존재하며 고유 스킬이 2개 존재
*
전설 등급 이상 유닛이 생성 되었을 때 알려주는(강조하는) 알림이 뜸
*
전설 등급 이상 유닛이 생성 되었을 때 알려주는(강조하는) 알림이 뜸
-
몬스터 개념
-
몬스터 개념
*
체력 / 이동속도 / 리젠 시간 각각 차이
*
체력 / 이동속도 / 리젠 시간 각각 차이
...
...
project.godot
View file @
589d7d59
...
@@ -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
...
...
src/autoload/data_manager.gd
View file @
589d7d59
...
@@ -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 (기본 밸런스형 몬스터)
...
...
src/entities/hero/base_hero.gd
0 → 100644
View file @
589d7d59
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
src/entities/hero/base_hero.gd.uid
0 → 100644
View file @
589d7d59
uid://bh4gg8bh3gfgk
src/entities/hero/dwarf/dwarf.tscn
0 → 100644
View file @
589d7d59
[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")]
src/entities/hero/dwarf/dwarf_hero.gd
0 → 100644
View file @
589d7d59
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
src/entities/hero/dwarf/dwarf_hero.gd.uid
0 → 100644
View file @
589d7d59
uid://dsckp7uaawijh
src/entities/hero/dwarf/dwarf_hero.tscn
0 → 100644
View file @
589d7d59
[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"]
src/entities/monsters/base_monster.gd
View file @
589d7d59
...
@@ -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
()
...
...
src/entities/units/base_unit.gd
View file @
589d7d59
...
@@ -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
...
...
src/entities/units/ranger/ranger_unit.tscn
View file @
589d7d59
[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"]
src/entities/units/striker/striker_unit.gd
View file @
589d7d59
...
@@ -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"
...
...
src/ui/hud.gd
View file @
589d7d59
...
@@ -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
:
...
@@ -74,12 +83,26 @@ func _ready() -> void:
...
@@ -74,12 +83,26 @@ func _ready() -> void:
clear_units_button
.
text
=
"Clear All Units"
clear_units_button
.
text
=
"Clear All Units"
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
\n
Cooldown:
%.1f
s"
%
[
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
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment