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
9d46295f
Commit
9d46295f
authored
Jun 23, 2026
by
Gavin An
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
소환사 선택 화면
parent
c35090ea
Changes
17
Hide whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
640 additions
and
7 deletions
+640
-7
assets/ui/summoner/portraits/default_summoner.png
assets/ui/summoner/portraits/default_summoner.png
+0
-0
assets/ui/summoner/portraits/default_summoner.png.import
assets/ui/summoner/portraits/default_summoner.png.import
+36
-0
assets/ui/summoner/portraits/warlock_summoner.png
assets/ui/summoner/portraits/warlock_summoner.png
+0
-0
assets/ui/summoner/portraits/warlock_summoner.png.import
assets/ui/summoner/portraits/warlock_summoner.png.import
+36
-0
assets/ui/summoner/spells/spell_haste.png
assets/ui/summoner/spells/spell_haste.png
+0
-0
assets/ui/summoner/spells/spell_haste.png.import
assets/ui/summoner/spells/spell_haste.png.import
+36
-0
assets/ui/summoner/spells/spell_smite.png
assets/ui/summoner/spells/spell_smite.png
+0
-0
assets/ui/summoner/spells/spell_smite.png.import
assets/ui/summoner/spells/spell_smite.png.import
+36
-0
docs/agents_docs/architecture.md
docs/agents_docs/architecture.md
+3
-2
src/core/stage_manager.gd
src/core/stage_manager.gd
+5
-1
src/entities/monsters/noble_man/noble_man_monster.tscn
src/entities/monsters/noble_man/noble_man_monster.tscn
+1
-1
src/entities/summoner/summoner.gd
src/entities/summoner/summoner.gd
+222
-0
src/entities/summoner/summoner.gd.uid
src/entities/summoner/summoner.gd.uid
+1
-0
src/entities/summoner/summoner_data.gd
src/entities/summoner/summoner_data.gd
+12
-0
src/entities/summoner/summoner_data.gd.uid
src/entities/summoner/summoner_data.gd.uid
+1
-0
src/entities/units/base_unit.gd
src/entities/units/base_unit.gd
+4
-3
src/ui/hud.gd
src/ui/hud.gd
+247
-0
No files found.
assets/ui/summoner/portraits/default_summoner.png
0 → 100644
View file @
9d46295f
1.04 MB
assets/ui/summoner/portraits/default_summoner.png.import
0 → 100644
View file @
9d46295f
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bnwwqm2dvwbsc"
valid=false
[deps]
source_file="res://assets/ui/summoner/portraits/default_summoner.png"
[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
assets/ui/summoner/portraits/warlock_summoner.png
0 → 100644
View file @
9d46295f
939 KB
assets/ui/summoner/portraits/warlock_summoner.png.import
0 → 100644
View file @
9d46295f
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://nugrj2mmrqng"
valid=false
[deps]
source_file="res://assets/ui/summoner/portraits/warlock_summoner.png"
[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
assets/ui/summoner/spells/spell_haste.png
0 → 100644
View file @
9d46295f
726 KB
assets/ui/summoner/spells/spell_haste.png.import
0 → 100644
View file @
9d46295f
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b426g8d4pt5fq"
valid=false
[deps]
source_file="res://assets/ui/summoner/spells/spell_haste.png"
[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
assets/ui/summoner/spells/spell_smite.png
0 → 100644
View file @
9d46295f
756 KB
assets/ui/summoner/spells/spell_smite.png.import
0 → 100644
View file @
9d46295f
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c6u8dh0qlfmv2"
valid=false
[deps]
source_file="res://assets/ui/summoner/spells/spell_smite.png"
[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/agents_docs/architecture.md
View file @
9d46295f
...
@@ -5,10 +5,11 @@
...
@@ -5,10 +5,11 @@
ldefense/
ldefense/
├── .godot/
├── .godot/
├── docs/ #
개발
문서 폴더 (.md)
├── docs/ #
AI 지침
문서 폴더 (.md)
│ ├── agents.md
│ ├── agents.md
│ ├── game_design.md
│ ├── game_design.md
│ └── architecture.md
│ ├── architecture.md
│ ├──
├── src/ # 실제 게임 소스 코드 폴더
├── src/ # 실제 게임 소스 코드 폴더
│ ├── autoload/ # 싱글톤 (AutoLoad) 스크립트
│ ├── autoload/ # 싱글톤 (AutoLoad) 스크립트
│ ├── core/ # 메인 게임 루프 및 관리자 스크립트
│ ├── core/ # 메인 게임 루프 및 관리자 스크립트
...
...
src/core/stage_manager.gd
View file @
9d46295f
...
@@ -44,7 +44,11 @@ func _ready() -> void:
...
@@ -44,7 +44,11 @@ func _ready() -> void:
# GameManager 시그널 구독 (스테이지 변경 및 몬스터 수 체크)
# GameManager 시그널 구독 (스테이지 변경 및 몬스터 수 체크)
GameManager
.
active_monster_count_changed
.
connect
(
_on_active_monster_count_changed
)
GameManager
.
active_monster_count_changed
.
connect
(
_on_active_monster_count_changed
)
# 3초 뒤에 첫 번째 스테이지 대기 시간 가동
# 대기 시간(쿨다운) 시작을 대기 (소환사 선택 완료 시 start_game()으로 기동)
## 소환사 선택 완료 후 게임을 정식으로 가동하는 수동 엔트리
func
start_game
()
->
void
:
print
(
"StageManager: Game started manually after summoner selection."
)
_start_next_stage_cooldown
(
3.0
)
_start_next_stage_cooldown
(
3.0
)
## 다음 스테이지 준비 대기 시간 시작
## 다음 스테이지 준비 대기 시간 시작
...
...
src/entities/monsters/noble_man/noble_man_monster.tscn
View file @
9d46295f
[gd_scene format=3 uid="uid://
dimlohck6ebg7
"]
[gd_scene format=3 uid="uid://
ygm6ry4fu5x0
"]
[ext_resource type="Script" uid="uid://dk2xux8f0hp7m" path="res://src/entities/monsters/noble_man/noble_man_monster.gd" id="1_base_monster"]
[ext_resource type="Script" uid="uid://dk2xux8f0hp7m" path="res://src/entities/monsters/noble_man/noble_man_monster.gd" id="1_base_monster"]
[ext_resource type="PackedScene" uid="uid://dy3qfnuybgjg6" path="res://src/entities/monsters/noble_man/noble_man.tscn" id="2_noble_man"]
[ext_resource type="PackedScene" uid="uid://dy3qfnuybgjg6" path="res://src/entities/monsters/noble_man/noble_man.tscn" id="2_noble_man"]
...
...
src/entities/summoner/summoner.gd
0 → 100644
View file @
9d46295f
class_name
Summoner
extends
Node
## 소환사 비즈니스 로직 및 주문(Spell) 제어 핵심 클래스
signal
mana_changed
(
current
:
float
,
max
:
float
)
signal
spell_cooldown_changed
(
spell_name
:
String
,
remaining
:
float
,
duration
:
float
)
@
export
var
summoner_data
:
SummonerData
var
current_mana
:
float
=
0.0
var
max_mana
:
float
=
100.0
var
mana_regen
:
float
=
2.5
# 각 스펠의 쿨다운 현황 및 기본 쿨다운 설정
# spells_cooldown = { "haste": 0.0, "smite": 0.0 }
var
spell_cooldowns
:
Dictionary
=
{}
var
spell_max_cooldowns
:
Dictionary
=
{
"haste"
:
15.0
,
"smite"
:
10.0
}
var
spell_mana_costs
:
Dictionary
=
{
"haste"
:
40.0
,
"smite"
:
50.0
}
var
is_initialized
:
bool
=
false
func
_ready
()
->
void
:
# setup_summoner()를 통해 외부에서 데이터를 직접 바인딩할 때까지 대기
pass
## 소환사 데이터를 주입받아 초기 셋업을 완료하는 메서드
func
setup_summoner
(
data
:
SummonerData
)
->
void
:
summoner_data
=
data
max_mana
=
data
.
max_mana
current_mana
=
max_mana
mana_regen
=
data
.
mana_regen
# 쿨다운 딕셔너리 초기화
spell_cooldowns
.
clear
()
for
spell
in
data
.
spells
:
spell_cooldowns
[
spell
]
=
0.0
is_initialized
=
true
# 초기 마나 신호 발신
mana_changed
.
emit
(
current_mana
,
max_mana
)
func
_physics_process
(
delta
:
float
)
->
void
:
if
not
is_initialized
or
GameManager
.
is_game_over
:
return
# 1. 실시간 마나 리젠
if
current_mana
<
max_mana
:
current_mana
=
minf
(
max_mana
,
current_mana
+
mana_regen
*
delta
)
mana_changed
.
emit
(
current_mana
,
max_mana
)
# 2. 실시간 스펠 쿨다운 갱신
for
spell
in
spell_cooldowns
.
keys
():
if
spell_cooldowns
[
spell
]
>
0.0
:
spell_cooldowns
[
spell
]
=
maxf
(
0.0
,
spell_cooldowns
[
spell
]
-
delta
)
var
max_cd
=
spell_max_cooldowns
.
get
(
spell
,
1.0
)
spell_cooldown_changed
.
emit
(
spell
,
spell_cooldowns
[
spell
],
max_cd
)
## 주문 시전 핸들러
func
cast_spell
(
spell_name
:
String
)
->
bool
:
if
not
is_initialized
or
GameManager
.
is_game_over
:
return
false
if
not
spell_cooldowns
.
has
(
spell_name
):
print
(
"Summoner: Spell '"
,
spell_name
,
"' is not equipped."
)
return
false
# 쿨다운 중인지 체크
if
spell_cooldowns
[
spell_name
]
>
0.0
:
print
(
"Summoner: Spell '"
,
spell_name
,
"' is on cooldown ("
,
spell_cooldowns
[
spell_name
],
"s remaining)."
)
return
false
# 마나 비용 체크
var
cost
=
spell_mana_costs
.
get
(
spell_name
,
0.0
)
if
current_mana
<
cost
:
print
(
"Summoner: Insufficient mana for '"
,
spell_name
,
"'. Need: "
,
cost
,
", Current: "
,
current_mana
)
return
false
# 주문 실행 분기
var
cast_success
=
false
if
spell_name
==
"haste"
:
cast_success
=
_execute_haste
()
elif
spell_name
==
"smite"
:
cast_success
=
_execute_smite
()
if
cast_success
:
# 마나 소모 및 쿨다운 가동
current_mana
-=
cost
spell_cooldowns
[
spell_name
]
=
spell_max_cooldowns
[
spell_name
]
mana_changed
.
emit
(
current_mana
,
max_mana
)
spell_cooldown_changed
.
emit
(
spell_name
,
spell_cooldowns
[
spell_name
],
spell_max_cooldowns
[
spell_name
])
print
(
"Summoner: Casted spell '"
,
spell_name
,
"' successfully."
)
return
true
return
false
## 1. 가속 주문 (Haste)
func
_execute_haste
()
->
bool
:
var
units
=
get_tree
()
.
get_nodes_in_group
(
"units"
)
if
units
.
is_empty
():
print
(
"Summoner: No units on field to apply Haste."
)
return
false
# 5초간 모든 유닛의 공격 속도 배율을 2배로 상승
for
unit
in
units
:
if
is_instance_valid
(
unit
)
and
"attack_speed_mult"
in
unit
:
unit
.
set
(
"attack_speed_mult"
,
2.0
)
# 비주얼 피드백을 위해 임시 파란색 광원 생성
_attach_temporary_light
(
unit
,
Color
(
0.9
,
0.8
,
0.1
),
5.0
)
# 5초 후 원복
get_tree
()
.
create_timer
(
5.0
)
.
timeout
.
connect
(
func
():
var
current_units
=
get_tree
()
.
get_nodes_in_group
(
"units"
)
for
unit
in
current_units
:
if
is_instance_valid
(
unit
)
and
"attack_speed_mult"
in
unit
:
unit
.
set
(
"attack_speed_mult"
,
1.0
)
print
(
"Summoner: Haste spell expired. Unit attack speeds reverted to normal."
)
)
print
(
"Summoner: Applied Haste to "
,
units
.
size
(),
" units for 5 seconds."
)
return
true
## 2. 강타 주문 (Smite)
func
_execute_smite
()
->
bool
:
var
monsters
=
get_tree
()
.
get_nodes_in_group
(
"monsters"
)
if
monsters
.
is_empty
():
print
(
"Summoner: No active monsters for Smite."
)
return
false
# 살아있는 유효한 몬스터 필터링
var
active_monsters
:
Array
[
Node
]
=
[]
for
m
in
monsters
:
if
is_instance_valid
(
m
)
and
not
m
.
get
(
"is_dead"
):
active_monsters
.
append
(
m
)
if
active_monsters
.
is_empty
():
print
(
"Summoner: No alive monsters found."
)
return
false
# 출구와 가까운 몬스터 (PathFollow3D의 progress가 큰 순서) 기준으로 정렬
active_monsters
.
sort_custom
(
func
(
a
:
Node
,
b
:
Node
)
->
bool
:
var
progress_a
=
a
.
get
(
"progress"
)
if
"progress"
in
a
else
0.0
var
progress_b
=
b
.
get
(
"progress"
)
if
"progress"
in
b
else
0.0
return
progress_a
>
progress_b
)
# 최전방 최대 5마리 타겟팅
var
targets_count
=
min
(
5
,
active_monsters
.
size
())
print
(
"Summoner: Smite targeting "
,
targets_count
,
" monsters."
)
for
i
in
range
(
targets_count
):
var
monster
=
active_monsters
[
i
]
if
is_instance_valid
(
monster
):
# 60의 즉시 데미지
if
monster
.
has_method
(
"take_damage"
):
monster
.
call
(
"take_damage"
,
60.0
)
# 낙뢰 이펙트 및 조명 시각화 연동
_create_lightning_strike_effect
(
monster
.
global_position
)
return
true
## 헬퍼: 유닛에게 일정 시간 붙어있는 버프 조명 연출
func
_attach_temporary_light
(
parent_node
:
Node3D
,
light_color
:
Color
,
duration
:
float
)
->
void
:
var
light
=
OmniLight3D
.
new
()
light
.
light_color
=
light_color
light
.
light_energy
=
3.0
light
.
omni_range
=
3.0
light
.
position
=
Vector3
(
0
,
1.2
,
0
)
parent_node
.
add_child
(
light
)
get_tree
()
.
create_timer
(
duration
)
.
timeout
.
connect
(
func
():
if
is_instance_valid
(
light
):
light
.
queue_free
()
)
## 헬퍼: 3D 월드 상에 번개 기둥 시각화 연출
func
_create_lightning_strike_effect
(
strike_pos
:
Vector3
)
->
void
:
var
root
=
get_tree
()
.
current_scene
if
not
root
:
return
# 1. 번개 기둥 역할을 할 실린더 메쉬 인스턴스 생성
var
mesh_inst
=
MeshInstance3D
.
new
()
var
cyl_mesh
=
CylinderMesh
.
new
()
cyl_mesh
.
top_radius
=
0.05
cyl_mesh
.
bottom_radius
=
0.15
cyl_mesh
.
height
=
10.0
var
mat
=
StandardMaterial3D
.
new
()
mat
.
albedo_color
=
Color
(
0.1
,
0.6
,
1.0
)
# 강렬한 하늘색
mat
.
emission_enabled
=
true
mat
.
emission
=
Color
(
0.2
,
0.7
,
1.0
)
mat
.
emission_energy_multiplier
=
4.0
mesh_inst
.
mesh
=
cyl_mesh
mesh_inst
.
material_override
=
mat
root
.
add_child
(
mesh_inst
)
# 번개 기둥 위치 정렬: 하늘에서 내리치는 연출이므로 y 오프셋
mesh_inst
.
global_position
=
strike_pos
+
Vector3
(
0.0
,
5.0
,
0.0
)
# 2. 번개 순간 발광 조명 생성
var
flash_light
=
OmniLight3D
.
new
()
flash_light
.
light_color
=
Color
(
0.3
,
0.7
,
1.0
)
flash_light
.
light_energy
=
8.0
flash_light
.
omni_range
=
6.0
root
.
add_child
(
flash_light
)
flash_light
.
global_position
=
strike_pos
+
Vector3
(
0.0
,
1.0
,
0.0
)
# 3. 0.2초 뒤 이펙트 삭제
get_tree
()
.
create_timer
(
0.2
)
.
timeout
.
connect
(
func
():
if
is_instance_valid
(
mesh_inst
):
mesh_inst
.
queue_free
()
if
is_instance_valid
(
flash_light
):
flash_light
.
queue_free
()
)
src/entities/summoner/summoner.gd.uid
0 → 100644
View file @
9d46295f
uid://c0u7s4x3mtusw
src/entities/summoner/summoner_data.gd
0 → 100644
View file @
9d46295f
class_name
SummonerData
extends
Resource
## 소환사의 이름, 초상화 에셋 및 주문 목록을 관리하는 데이터 리소스
@
export
var
summoner_name
:
String
=
"Default Summoner"
@
export
var
portrait_texture
:
Texture2D
=
null
@
export
var
max_mana
:
float
=
100.0
@
export
var
mana_regen
:
float
=
2.5
# 초당 회복량
# 소환사가 보유한 기본 스펠 목록 (예: ["haste", "smite"])
@
export
var
spells
:
Array
[
String
]
=
[
"haste"
,
"smite"
]
src/entities/summoner/summoner_data.gd.uid
0 → 100644
View file @
9d46295f
uid://d1uwgc35f7dkf
src/entities/units/base_unit.gd
View file @
9d46295f
...
@@ -32,6 +32,7 @@ var _model_anim_player: AnimationPlayer = null
...
@@ -32,6 +32,7 @@ var _model_anim_player: AnimationPlayer = null
var
_last_anim_state
:
String
=
""
var
_last_anim_state
:
String
=
""
var
_is_currently_attacking
:
bool
=
false
var
_is_currently_attacking
:
bool
=
false
var
_attack_lock_timer
:
float
=
0.0
# 공격 모션 완주 및 물리 이동 차단을 위한 타이머
var
_attack_lock_timer
:
float
=
0.0
# 공격 모션 완주 및 물리 이동 차단을 위한 타이머
var
attack_speed_mult
:
float
=
1.0
# 소환사 주문(가속) 등으로 변경 가능한 공속 배율
func
_ready
()
->
void
:
func
_ready
()
->
void
:
# 유닛 그룹에 등록
# 유닛 그룹에 등록
...
@@ -58,11 +59,11 @@ func _physics_process(delta: float) -> void:
...
@@ -58,11 +59,11 @@ func _physics_process(delta: float) -> void:
velocity
=
Vector3
.
ZERO
velocity
=
Vector3
.
ZERO
return
return
# 공격 타이머 및 자동 타겟팅 쿨타임 차감
# 공격 타이머 및 자동 타겟팅 쿨타임 차감
(공속 배율 반영)
if
_attack_timer
>
0.0
:
if
_attack_timer
>
0.0
:
_attack_timer
-=
delta
_attack_timer
-=
delta
*
attack_speed_mult
if
_attack_lock_timer
>
0.0
:
if
_attack_lock_timer
>
0.0
:
_attack_lock_timer
-=
delta
_attack_lock_timer
-=
delta
*
attack_speed_mult
if
_auto_target_cooldown
>
0.0
:
if
_auto_target_cooldown
>
0.0
:
_auto_target_cooldown
-=
delta
_auto_target_cooldown
-=
delta
...
...
src/ui/hud.gd
View file @
9d46295f
...
@@ -17,6 +17,7 @@ extends Control
...
@@ -17,6 +17,7 @@ extends Control
# 몬스터 일시정지 버튼 런타임 저장 변수
# 몬스터 일시정지 버튼 런타임 저장 변수
var
_pause_button
:
Button
=
null
var
_pause_button
:
Button
=
null
var
_summoner
:
Summoner
=
null
...
@@ -38,6 +39,15 @@ func _ready() -> void:
...
@@ -38,6 +39,15 @@ func _ready() -> void:
_on_stage_changed
(
GameManager
.
current_stage
)
_on_stage_changed
(
GameManager
.
current_stage
)
_on_monster_changed
(
GameManager
.
active_monster_count
)
_on_monster_changed
(
GameManager
.
active_monster_count
)
# 소환사 노드 동적 생성 및 바인딩 (선택 전 데이터 주입 보류)
var
summoner_script
:
GDScript
=
preload
(
"res://src/entities/summoner/summoner.gd"
)
_summoner
=
summoner_script
.
new
()
as
Summoner
_summoner
.
name
=
"ActiveSummoner"
add_child
(
_summoner
)
# 소환사 선택 오버레이 화면 띄우기
_show_summoner_selection_screen
()
# 몬스터 생성 일시 정지, 게임 재시작 및 모든 유닛 삭제 버튼 런타임 생성
# 몬스터 생성 일시 정지, 게임 재시작 및 모든 유닛 삭제 버튼 런타임 생성
var
bottom_bar
:
Control
=
get_node_or_null
(
"BottomBar"
)
as
Control
var
bottom_bar
:
Control
=
get_node_or_null
(
"BottomBar"
)
as
Control
if
bottom_bar
:
if
bottom_bar
:
...
@@ -151,3 +161,240 @@ func _on_pause_spawning_pressed() -> void:
...
@@ -151,3 +161,240 @@ func _on_pause_spawning_pressed() -> void:
_pause_button
.
text
=
"Resume Spawning"
_pause_button
.
text
=
"Resume Spawning"
else
:
else
:
_pause_button
.
text
=
"Pause Spawning"
_pause_button
.
text
=
"Pause Spawning"
## 런타임에 소환사 선택 패널을 오버레이로 생성하여 현시
func
_show_summoner_selection_screen
()
->
void
:
var
selection_overlay
:
ColorRect
=
ColorRect
.
new
()
selection_overlay
.
name
=
"SummonerSelectionOverlay"
selection_overlay
.
color
=
Color
(
0.08
,
0.08
,
0.12
,
0.95
)
# 어두운 판타지 테마의 반투명 배경
selection_overlay
.
set_anchors_and_offsets_preset
(
Control
.
PRESET_FULL_RECT
)
add_child
(
selection_overlay
)
var
vbox
:
VBoxContainer
=
VBoxContainer
.
new
()
vbox
.
name
=
"SelectionVBox"
vbox
.
add_theme_constant_override
(
"separation"
,
30
)
vbox
.
set_anchors_and_offsets_preset
(
Control
.
PRESET_CENTER
)
vbox
.
grow_horizontal
=
Control
.
GROW_DIRECTION_BOTH
vbox
.
grow_vertical
=
Control
.
GROW_DIRECTION_BOTH
selection_overlay
.
add_child
(
vbox
)
# 타이틀 라벨
var
title_lbl
:
Label
=
Label
.
new
()
title_lbl
.
name
=
"TitleLabel"
title_lbl
.
text
=
"SELECT YOUR SUMMONER"
title_lbl
.
horizontal_alignment
=
HORIZONTAL_ALIGNMENT_CENTER
title_lbl
.
add_theme_font_size_override
(
"font_size"
,
24
)
vbox
.
add_child
(
title_lbl
)
# 소환사 카드 그룹
var
hbox
:
HBoxContainer
=
HBoxContainer
.
new
()
hbox
.
name
=
"CardsHBox"
hbox
.
add_theme_constant_override
(
"separation"
,
40
)
vbox
.
add_child
(
hbox
)
# --- 1. Archmage (대마법사) 카드 ---
var
card_arch
:
VBoxContainer
=
VBoxContainer
.
new
()
card_arch
.
name
=
"ArchmageCard"
card_arch
.
add_theme_constant_override
(
"separation"
,
10
)
hbox
.
add_child
(
card_arch
)
var
port_arch
:
TextureRect
=
TextureRect
.
new
()
port_arch
.
custom_minimum_size
=
Vector2
(
150
,
150
)
port_arch
.
expand_mode
=
TextureRect
.
EXPAND_IGNORE_SIZE
port_arch
.
stretch_mode
=
TextureRect
.
STRETCH_KEEP_ASPECT_COVERED
var
arch_path
:
String
=
"res://assets/ui/summoner/portraits/default_summoner.png"
if
ResourceLoader
.
exists
(
arch_path
):
port_arch
.
texture
=
load
(
arch_path
)
as
Texture2D
card_arch
.
add_child
(
port_arch
)
var
name_arch
:
Label
=
Label
.
new
()
name_arch
.
text
=
"Archmage"
name_arch
.
horizontal_alignment
=
HORIZONTAL_ALIGNMENT_CENTER
name_arch
.
add_theme_font_size_override
(
"font_size"
,
16
)
card_arch
.
add_child
(
name_arch
)
var
desc_arch
:
Label
=
Label
.
new
()
desc_arch
.
text
=
"Max Mana: 100
\n
Mana Regen: 3.0 / s
\n
Spells: Haste, Smite"
desc_arch
.
horizontal_alignment
=
HORIZONTAL_ALIGNMENT_CENTER
desc_arch
.
add_theme_font_size_override
(
"font_size"
,
11
)
card_arch
.
add_child
(
desc_arch
)
var
btn_arch
:
Button
=
Button
.
new
()
btn_arch
.
name
=
"SelectArchmage"
btn_arch
.
text
=
"Select Archmage"
btn_arch
.
custom_minimum_size
=
Vector2
(
120
,
35
)
btn_arch
.
pressed
.
connect
(
func
()
->
void
:
var
data
:
SummonerData
=
SummonerData
.
new
()
data
.
summoner_name
=
"Archmage"
data
.
max_mana
=
100.0
data
.
mana_regen
=
3.0
if
ResourceLoader
.
exists
(
arch_path
):
data
.
portrait_texture
=
load
(
arch_path
)
as
Texture2D
selection_overlay
.
queue_free
()
_on_summoner_selected
(
data
)
)
card_arch
.
add_child
(
btn_arch
)
# --- 2. Warlock (흑마법사) 카드 ---
var
card_war
:
VBoxContainer
=
VBoxContainer
.
new
()
card_war
.
name
=
"WarlockCard"
card_war
.
add_theme_constant_override
(
"separation"
,
10
)
hbox
.
add_child
(
card_war
)
var
port_war
:
TextureRect
=
TextureRect
.
new
()
port_war
.
custom_minimum_size
=
Vector2
(
150
,
150
)
port_war
.
expand_mode
=
TextureRect
.
EXPAND_IGNORE_SIZE
port_war
.
stretch_mode
=
TextureRect
.
STRETCH_KEEP_ASPECT_COVERED
var
war_path
:
String
=
"res://assets/ui/summoner/portraits/warlock_summoner.png"
if
ResourceLoader
.
exists
(
war_path
):
port_war
.
texture
=
load
(
war_path
)
as
Texture2D
card_war
.
add_child
(
port_war
)
var
name_war
:
Label
=
Label
.
new
()
name_war
.
text
=
"Warlock"
name_war
.
horizontal_alignment
=
HORIZONTAL_ALIGNMENT_CENTER
name_war
.
add_theme_font_size_override
(
"font_size"
,
16
)
card_war
.
add_child
(
name_war
)
var
desc_war
:
Label
=
Label
.
new
()
desc_war
.
text
=
"Max Mana: 130
\n
Mana Regen: 1.8 / s
\n
Spells: Haste, Smite"
desc_war
.
horizontal_alignment
=
HORIZONTAL_ALIGNMENT_CENTER
desc_war
.
add_theme_font_size_override
(
"font_size"
,
11
)
card_war
.
add_child
(
desc_war
)
var
btn_war
:
Button
=
Button
.
new
()
btn_war
.
name
=
"SelectWarlock"
btn_war
.
text
=
"Select Warlock"
btn_war
.
custom_minimum_size
=
Vector2
(
120
,
35
)
btn_war
.
pressed
.
connect
(
func
()
->
void
:
var
data
:
SummonerData
=
SummonerData
.
new
()
data
.
summoner_name
=
"Warlock"
data
.
max_mana
=
130.0
data
.
mana_regen
=
1.8
if
ResourceLoader
.
exists
(
war_path
):
data
.
portrait_texture
=
load
(
war_path
)
as
Texture2D
selection_overlay
.
queue_free
()
_on_summoner_selected
(
data
)
)
card_war
.
add_child
(
btn_war
)
## 소환사 선택 완료 후 UI 구성 및 스테이지 타이머 시작
func
_on_summoner_selected
(
data
:
SummonerData
)
->
void
:
# 1. 소환사 데이터 실 셋업 가동
_summoner
.
setup_summoner
(
data
)
# 2. 인게임 소환사 UI 런타임 조립하여 BottomBar에 배치
var
bottom_bar
:
Control
=
get_node_or_null
(
"BottomBar"
)
as
Control
if
bottom_bar
:
var
summoner_ui
:
HBoxContainer
=
HBoxContainer
.
new
()
summoner_ui
.
name
=
"SummonerUI"
summoner_ui
.
add_theme_constant_override
(
"separation"
,
10
)
# 소환사 초상화
var
portrait
:
TextureRect
=
TextureRect
.
new
()
portrait
.
name
=
"SummonerPortrait"
portrait
.
custom_minimum_size
=
Vector2
(
48
,
48
)
portrait
.
expand_mode
=
TextureRect
.
EXPAND_IGNORE_SIZE
portrait
.
stretch_mode
=
TextureRect
.
STRETCH_KEEP_ASPECT_COVERED
if
data
.
portrait_texture
:
portrait
.
texture
=
data
.
portrait_texture
summoner_ui
.
add_child
(
portrait
)
# 마나 정보 그룹
var
info_group
:
VBoxContainer
=
VBoxContainer
.
new
()
info_group
.
name
=
"InfoGroup"
info_group
.
custom_minimum_size
=
Vector2
(
110
,
48
)
var
mana_bar
:
ProgressBar
=
ProgressBar
.
new
()
mana_bar
.
name
=
"ManaBar"
mana_bar
.
show_percentage
=
false
mana_bar
.
custom_minimum_size
=
Vector2
(
110
,
14
)
var
sb_bg
:
StyleBoxFlat
=
StyleBoxFlat
.
new
()
sb_bg
.
bg_color
=
Color
(
0.12
,
0.12
,
0.12
,
0.8
)
sb_bg
.
set_corner_radius_all
(
2
)
var
sb_fg
:
StyleBoxFlat
=
StyleBoxFlat
.
new
()
sb_fg
.
bg_color
=
Color
(
0.15
,
0.45
,
0.85
,
1.0
)
sb_fg
.
set_corner_radius_all
(
2
)
mana_bar
.
add_theme_stylebox_override
(
"background"
,
sb_bg
)
mana_bar
.
add_theme_stylebox_override
(
"fill"
,
sb_fg
)
info_group
.
add_child
(
mana_bar
)
var
mana_label
:
Label
=
Label
.
new
()
mana_label
.
name
=
"ManaLabel"
mana_label
.
text
=
"MP:
%.1f
/
%.1f
"
%
[
data
.
max_mana
,
data
.
max_mana
]
mana_label
.
add_theme_font_size_override
(
"font_size"
,
10
)
info_group
.
add_child
(
mana_label
)
summoner_ui
.
add_child
(
info_group
)
# 스펠 주문 버튼 그룹
var
spell_group
:
HBoxContainer
=
HBoxContainer
.
new
()
spell_group
.
name
=
"SpellGroup"
spell_group
.
add_theme_constant_override
(
"separation"
,
5
)
var
btn_haste
:
Button
=
Button
.
new
()
btn_haste
.
name
=
"SpellHaste"
btn_haste
.
text
=
"Haste (40 MP)"
var
haste_icon_path
:
String
=
"res://assets/ui/summoner/spells/spell_haste.png"
if
ResourceLoader
.
exists
(
haste_icon_path
):
btn_haste
.
icon
=
load
(
haste_icon_path
)
as
Texture2D
btn_haste
.
expand_icon
=
true
btn_haste
.
custom_minimum_size
=
Vector2
(
90
,
45
)
else
:
btn_haste
.
custom_minimum_size
=
Vector2
(
90
,
30
)
btn_haste
.
pressed
.
connect
(
func
()
->
void
:
_summoner
.
cast_spell
(
"haste"
))
spell_group
.
add_child
(
btn_haste
)
var
btn_smite
:
Button
=
Button
.
new
()
btn_smite
.
name
=
"SpellSmite"
btn_smite
.
text
=
"Smite (50 MP)"
var
smite_icon_path
:
String
=
"res://assets/ui/summoner/spells/spell_smite.png"
if
ResourceLoader
.
exists
(
smite_icon_path
):
btn_smite
.
icon
=
load
(
smite_icon_path
)
as
Texture2D
btn_smite
.
expand_icon
=
true
btn_smite
.
custom_minimum_size
=
Vector2
(
90
,
45
)
else
:
btn_smite
.
custom_minimum_size
=
Vector2
(
90
,
30
)
btn_smite
.
pressed
.
connect
(
func
()
->
void
:
_summoner
.
cast_spell
(
"smite"
))
spell_group
.
add_child
(
btn_smite
)
summoner_ui
.
add_child
(
spell_group
)
# UI 조립
bottom_bar
.
add_child
(
summoner_ui
)
bottom_bar
.
move_child
(
summoner_ui
,
0
)
# 신호 연결
_summoner
.
mana_changed
.
connect
(
func
(
current
:
float
,
max_v
:
float
)
->
void
:
mana_bar
.
max_value
=
max_v
mana_bar
.
value
=
current
mana_label
.
text
=
"MP:
%.1f
/
%.1f
"
%
[
current
,
max_v
]
btn_haste
.
disabled
=
(
current
<
40.0
or
_summoner
.
spell_cooldowns
.
get
(
"haste"
,
0.0
)
>
0.0
)
btn_smite
.
disabled
=
(
current
<
50.0
or
_summoner
.
spell_cooldowns
.
get
(
"smite"
,
0.0
)
>
0.0
)
)
_summoner
.
spell_cooldown_changed
.
connect
(
func
(
spell_name
:
String
,
remaining
:
float
,
duration
:
float
)
->
void
:
var
btn
:
Button
=
btn_haste
if
spell_name
==
"haste"
else
btn_smite
var
label_text
:
String
=
"Haste"
if
spell_name
==
"haste"
else
"Smite"
var
cost
:
float
=
40.0
if
spell_name
==
"haste"
else
50.0
if
remaining
>
0.0
:
btn
.
text
=
"
%s
\n
(
%.1f
s)"
%
[
label_text
,
remaining
]
btn
.
disabled
=
true
else
:
btn
.
text
=
"
%s
(
%d
MP)"
%
[
label_text
,
cost
]
if
_summoner
.
current_mana
>=
cost
:
btn
.
disabled
=
false
)
# 강제 GUI 갱신용으로 신호 1회 수동 트리거
_summoner
.
mana_changed
.
emit
(
_summoner
.
current_mana
,
_summoner
.
max_mana
)
# 3. StageManager를 찾아 수동 게임 시작 기동
var
stage_mgr
:
Node
=
get_node_or_null
(
"../StageManager"
)
if
stage_mgr
and
stage_mgr
.
has_method
(
"start_game"
):
stage_mgr
.
call
(
"start_game"
)
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