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
24121fe1
Commit
24121fe1
authored
Jul 27, 2026
by
Gavin An
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
유닛 선택 시, context 메뉴 뜨도록 하기
parent
9123dec3
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
247 additions
and
102 deletions
+247
-102
docs/README.md
docs/README.md
+2
-1
docs/agents.md
docs/agents.md
+1
-3
src/core/main.tscn
src/core/main.tscn
+1
-0
src/core/unit_controller.gd
src/core/unit_controller.gd
+243
-98
No files found.
docs/README.md
View file @
24121fe1
...
@@ -26,7 +26,8 @@ L-Defense 게임 개요
...
@@ -26,7 +26,8 @@ L-Defense 게임 개요
-
유닛 개념
-
유닛 개념
*
유닛 생성시 마다 골드가 소모되며, 모두 무작위 확률로 생성
*
유닛 생성시 마다 골드가 소모되며, 모두 무작위 확률로 생성
*
생성된 유닛은 스테이지의 격자(Tile)에 각각 배치 가능. 고정되어 있으며 자동 공격함.
*
생성된 유닛은 스테이지의 격자(Tile)에 각각 배치 가능.
*
유닛을 선택하면, 이동 / 팔기 / 업그레이드(유닛 3개로 선택하면) 선택 가능
*
체력 / 공격력 / 공격속도 / 이동속도
*
체력 / 공격력 / 공격속도 / 이동속도
*
유닛의 등급은 일반 / 고급 / 희귀 / 전설 / 신화 / 유일 등급
*
유닛의 등급은 일반 / 고급 / 희귀 / 전설 / 신화 / 유일 등급
*
일반 ~ 희귀까지는 고유 스킬이 없고 일반 공격만 가능(근거리 / 원거리 구분 존재)
*
일반 ~ 희귀까지는 고유 스킬이 없고 일반 공격만 가능(근거리 / 원거리 구분 존재)
...
...
docs/agents.md
View file @
24121fe1
...
@@ -7,9 +7,7 @@
...
@@ -7,9 +7,7 @@
-
**주력 언어:**
GDScript
-
**주력 언어:**
GDScript
-
**주요 아키텍처:**
싱글톤(AutoLoad), 노드 기반 트리 구조, 신호(Signal) 시스템 활용
-
**주요 아키텍처:**
싱글톤(AutoLoad), 노드 기반 트리 구조, 신호(Signal) 시스템 활용
-
**핵심 폴더 구조:**
-
**핵심 폴더 구조:**
-
`res://scenes/`
: 게임 씬 (.tscn) 파일
-
`res://assets/`
: 스프라이트, 텍스쳐, 오디오, 폰트 등 리소스
-
`res://scripts/`
: GDScript (.gd) 파일
-
`res://assets/`
: 스프라이트, 오디오, 폰트 등 리소스
-
`res://addons/`
: 플러그인
-
`res://addons/`
: 플러그인
## 2. GDScript 코딩 스타일 및 규칙
## 2. GDScript 코딩 스타일 및 규칙
...
...
src/core/main.tscn
View file @
24121fe1
...
@@ -39,6 +39,7 @@ shadow_enabled = true
...
@@ -39,6 +39,7 @@ shadow_enabled = true
shadow_blur = 5.0
shadow_blur = 5.0
directional_shadow_mode = 0
directional_shadow_mode = 0
directional_shadow_max_distance = 30.0
directional_shadow_max_distance = 30.0
directional_shadow_pancake_size = 15.0
[node name="NavigationRegion3D" type="NavigationRegion3D" parent="." unique_id=161846999]
[node name="NavigationRegion3D" type="NavigationRegion3D" parent="." unique_id=161846999]
navigation_mesh = SubResource("NavigationMesh_map")
navigation_mesh = SubResource("NavigationMesh_map")
...
...
src/core/unit_controller.gd
View file @
24121fe1
extends
Control
extends
Control
## RTS 유닛 선택 및 이동을 통제하는 컨트롤러 (UnitController)
## RTS 유닛 선택 및 이동을 통제하는 컨트롤러 (UnitController)
## 화면 전체를 덮는 Control 노드로 작동하여
드래그 박스를 렌더링하고 마우스 입력
을 처리합니다.
## 화면 전체를 덮는 Control 노드로 작동하여
마우스 입력과 선택 유닛 액션 버튼
을 처리합니다.
# 마우스 및 드래그 상태 관리
const
UNIT_SELECTION_SCREEN_RADIUS
:
float
=
54.0
var
_is_dragging
:
bool
=
false
const
UNIT_ACTION_BUTTON_SIZE
:
Vector2
=
Vector2
(
86.0
,
34.0
)
var
_drag_start
:
Vector2
=
Vector2
.
ZERO
const
UNIT_ACTION_BUTTON_GAP
:
float
=
8.0
var
_drag_end
:
Vector2
=
Vector2
.
ZERO
const
UNIT_ACTION_BUTTON_WORLD_HEIGHT
:
float
=
1.55
const
UNIT_SELL_REFUND_RATIO
:
float
=
0.5
const
UNIT_UPGRADE_COST
:
int
=
75
# 현재 선택된 유닛 리스트
## 현재 선택된 유닛 리스트입니다.
## 예전 다중 선택 API를 참조하는 코드가 있을 수 있어 배열 형태는 유지하지만, 실제로는 항상 0개 또는 1개만 담습니다.
var
selected_units
:
Array
[
Node
]
=
[]
var
selected_units
:
Array
[
Node
]
=
[]
# 화면 드래그 박스 렌더링 색상
var
_selected_unit
:
Node3D
=
null
@
export
var
box_color
:
Color
=
Color
(
0.0
,
1.0
,
0.0
,
0.2
)
# 연녹색 반투명
var
_is_waiting_for_move_destination
:
bool
=
false
@
export
var
border_color
:
Color
=
Color
(
0.0
,
1.0
,
0.0
,
0.8
)
# 녹색 테두리
var
_action_button_container
:
HBoxContainer
=
null
@
export
var
border_width
:
float
=
1.5
var
_sell_button
:
Button
=
null
var
_move_button
:
Button
=
null
var
_upgrade_button
:
Button
=
null
func
_ready
()
->
void
:
func
_ready
()
->
void
:
# 화면 전체를 덮도록 설정
# 화면 전체를 덮도록 설정
anchor_right
=
1.0
anchor_right
=
1.0
anchor_bottom
=
1.0
anchor_bottom
=
1.0
mouse_filter
=
MOUSE_FILTER_IGNORE
# UI가 아닌 unhandled input으로 동작하게 통과 처리
mouse_filter
=
MOUSE_FILTER_IGNORE
# UI가 아닌 unhandled input으로 동작하게 통과 처리
_create_unit_action_buttons
()
# 런타임에 NavigationRegion3D를 찾아 맵을 베이크시킴 (4.2 구현 보완)
# 런타임에 NavigationRegion3D를 찾아 맵을 베이크시킴 (4.2 구현 보완)
var
nav_region
:
NavigationRegion3D
=
get_node_or_null
(
"../NavigationRegion3D"
)
as
NavigationRegion3D
var
nav_region
:
NavigationRegion3D
=
get_node_or_null
(
"../NavigationRegion3D"
)
as
NavigationRegion3D
if
nav_region
:
if
nav_region
:
nav_region
.
bake_navigation_mesh
()
nav_region
.
bake_navigation_mesh
()
func
_process
(
_delta
:
float
)
->
void
:
_update_unit_action_buttons_position
()
func
_unhandled_input
(
event
:
InputEvent
)
->
void
:
func
_unhandled_input
(
event
:
InputEvent
)
->
void
:
if
GameManager
.
is_game_over
:
if
GameManager
.
is_game_over
:
return
return
...
@@ -35,21 +44,14 @@ func _unhandled_input(event: InputEvent) -> void:
...
@@ -35,21 +44,14 @@ func _unhandled_input(event: InputEvent) -> void:
if
not
camera
:
if
not
camera
:
return
return
# 마우스 좌클릭 (선택/드래그 시작)
if
event
is
InputEventMouseButton
:
if
event
is
InputEventMouseButton
:
var
mouse_event
:
InputEventMouseButton
=
event
as
InputEventMouseButton
var
mouse_event
:
InputEventMouseButton
=
event
as
InputEventMouseButton
# 마우스 왼쪽 버튼 클릭
if
mouse_event
.
button_index
==
MOUSE_BUTTON_LEFT
and
mouse_event
.
is_pressed
():
if
mouse_event
.
button_index
==
MOUSE_BUTTON_LEFT
:
if
_is_waiting_for_move_destination
:
if
mouse_event
.
is_pressed
():
_perform_selected_unit_move
(
mouse_event
.
position
)
_is_dragging
=
true
_drag_start
=
mouse_event
.
position
_drag_end
=
_drag_start
else
:
else
:
# 클릭 해제 시 선택 처리
_perform_selection
(
mouse_event
.
position
)
_is_dragging
=
false
_perform_selection
(
_drag_start
,
mouse_event
.
position
)
queue_redraw
()
# 마우스 오른쪽 버튼 클릭 (이동 및 공격 지시)
# 마우스 오른쪽 버튼 클릭 (이동 및 공격 지시)
elif
mouse_event
.
button_index
==
MOUSE_BUTTON_RIGHT
and
mouse_event
.
is_pressed
():
elif
mouse_event
.
button_index
==
MOUSE_BUTTON_RIGHT
and
mouse_event
.
is_pressed
():
...
@@ -61,69 +63,78 @@ func _unhandled_input(event: InputEvent) -> void:
...
@@ -61,69 +63,78 @@ func _unhandled_input(event: InputEvent) -> void:
if
key_event
.
is_pressed
()
and
not
key_event
.
is_echo
()
and
key_event
.
keycode
==
KEY_H
:
if
key_event
.
is_pressed
()
and
not
key_event
.
is_echo
()
and
key_event
.
keycode
==
KEY_H
:
_perform_hold_position
()
_perform_hold_position
()
# 마우스 드래그 중인 상태
## 단일 유닛 선택을 수행합니다.
elif
event
is
InputEventMouseMotion
and
_is_dragging
:
## 드래그 선택과 Shift 누적 선택은 제거하고, 새 유닛을 선택할 때마다 기존 선택은 항상 해제합니다.
var
motion_event
:
InputEventMouseMotion
=
event
as
InputEventMouseMotion
func
_perform_selection
(
mouse_pos
:
Vector2
)
->
void
:
_drag_end
=
motion_event
.
position
var
unit_node
:
Node3D
=
_find_selectable_unit_from_mouse
(
mouse_pos
)
queue_redraw
()
if
unit_node
:
_select_single_unit
(
unit_node
)
func
_draw
()
->
void
:
else
:
# 드래그 영역 상자 그리기
_clear_selected_unit
()
if
_is_dragging
and
_drag_start
.
distance_to
(
_drag_end
)
>
5.0
:
var
rect
:
Rect2
=
Rect2
(
_drag_start
,
_drag_end
-
_drag_start
)
.
abs
()
## 마우스 위치 주변에서 선택 가능한 유닛을 찾습니다.
draw_rect
(
rect
,
box_color
,
true
)
## 먼저 물리 Raycast로 직접 맞은 유닛을 찾고, 실패하면 화면상 가까운 유닛을 찾아 선택 반경을 넓게 느끼도록 합니다.
draw_rect
(
rect
,
border_color
,
false
,
border_width
)
func
_find_selectable_unit_from_mouse
(
mouse_pos
:
Vector2
)
->
Node3D
:
var
ray_result
:
Dictionary
=
_raycast_from_mouse
(
mouse_pos
)
## 다중 및 단일 선택 수행
if
ray_result
.
has
(
"collider"
):
func
_perform_selection
(
start
:
Vector2
,
end
:
Vector2
)
->
void
:
var
hit_collider
:
Node
=
ray_result
[
"collider"
]
as
Node
var
raycast_unit
:
Node
=
_get_unit_node
(
hit_collider
)
if
raycast_unit
and
raycast_unit
.
is_in_group
(
"units"
)
and
raycast_unit
is
Node3D
:
return
raycast_unit
as
Node3D
return
_find_nearest_unit_on_screen
(
mouse_pos
)
## 화면 좌표 기준으로 마우스 근처의 가장 가까운 유닛을 반환합니다.
## 작은 모델이나 콜라이더가 빗나가도 선택이 되도록, 유닛 월드 위치를 카메라 화면 좌표로 투영해 거리 판정을 보강합니다.
func
_find_nearest_unit_on_screen
(
mouse_pos
:
Vector2
)
->
Node3D
:
var
camera
:
Camera3D
=
get_viewport
()
.
get_camera_3d
()
var
camera
:
Camera3D
=
get_viewport
()
.
get_camera_3d
()
if
not
camera
:
if
not
camera
:
return
return
null
# Shift 키 입력 체크
var
nearest_unit
:
Node3D
=
null
var
is_shift
:
bool
=
Input
.
is_key_pressed
(
KEY_SHIFT
)
var
nearest_screen_distance
:
float
=
UNIT_SELECTION_SCREEN_RADIUS
var
units
:
Array
[
Node
]
=
get_tree
()
.
get_nodes_in_group
(
"units"
)
for
unit
:
Node
in
units
:
if
not
is_instance_valid
(
unit
)
or
not
unit
is
Node3D
:
continue
# Shift가 눌려있지 않은 경우에만 기존 선택 유닛들 초기화
var
unit_3d
:
Node3D
=
unit
as
Node3D
if
not
is_shift
:
if
camera
.
is_position_behind
(
unit_3d
.
global_position
):
for
unit
:
Node
in
selected_units
:
continue
if
is_instance_valid
(
unit
)
and
unit
.
has_method
(
"deselect"
):
unit
.
call
(
"deselect"
)
selected_units
.
clear
()
var
drag_dist
:
float
=
start
.
distance_to
(
end
)
var
screen_pos
:
Vector2
=
camera
.
unproject_position
(
unit_3d
.
global_position
)
var
screen_distance
:
float
=
screen_pos
.
distance_to
(
mouse_pos
)
if
screen_distance
<=
nearest_screen_distance
:
nearest_screen_distance
=
screen_distance
nearest_unit
=
unit_3d
return
nearest_unit
## 선택 유닛을 하나로 고정합니다.
## 기존 선택 유닛이 있다면 먼저 deselect()를 호출해 선택 링과 상태를 확실히 정리합니다.
func
_select_single_unit
(
unit_node
:
Node3D
)
->
void
:
if
_selected_unit
==
unit_node
:
_show_unit_action_buttons
()
return
# 1. 단일 선택 처리 (Raycast 이용)
_clear_selected_unit
()
if
drag_dist
<=
5.0
:
_selected_unit
=
unit_node
var
ray_result
:
Dictionary
=
_raycast_from_mouse
(
start
)
selected_units
.
clear
()
if
ray_result
.
has
(
"collider"
):
var
hit_collider
:
Node
=
ray_result
[
"collider"
]
as
Node
var
unit_node
:
Node
=
_get_unit_node
(
hit_collider
)
if
unit_node
and
unit_node
.
is_in_group
(
"units"
):
# Shift 추가 선택 모드 시 이미 선택 리스트에 없는 경우에만 누적
if
not
selected_units
.
has
(
unit_node
):
selected_units
.
append
(
unit_node
)
selected_units
.
append
(
unit_node
)
_is_waiting_for_move_destination
=
false
if
unit_node
.
has_method
(
"select"
):
if
unit_node
.
has_method
(
"select"
):
unit_node
.
call
(
"select"
)
unit_node
.
call
(
"select"
)
_show_unit_action_buttons
()
# 2. 드래그 다중 선택 처리 (Unproject 투영 이용)
## 현재 선택을 해제하고 유닛 액션 버튼도 숨깁니다.
else
:
func
_clear_selected_unit
()
->
void
:
var
selection_rect
:
Rect2
=
Rect2
(
start
,
end
-
start
)
.
abs
()
if
_selected_unit
and
is_instance_valid
(
_selected_unit
)
and
_selected_unit
.
has_method
(
"deselect"
):
var
all_units
:
Array
[
Node
]
=
get_tree
()
.
get_nodes_in_group
(
"units"
)
_selected_unit
.
call
(
"deselect"
)
var
newly_selected_count
:
int
=
0
_selected_unit
=
null
selected_units
.
clear
()
for
unit
:
Node
in
all_units
:
_is_waiting_for_move_destination
=
false
if
is_instance_valid
(
unit
)
and
unit
is
Node3D
:
_hide_unit_action_buttons
()
var
unit_3d
:
Node3D
=
unit
as
Node3D
var
screen_pos
:
Vector2
=
camera
.
unproject_position
(
unit_3d
.
global_position
)
# 화면 뷰포트 영역 내부 및 드래그 사각형 안에 포함되는지 판정
if
selection_rect
.
has_point
(
screen_pos
):
# 중복 선택 추가 방지
if
not
selected_units
.
has
(
unit
):
selected_units
.
append
(
unit
)
if
unit
.
has_method
(
"select"
):
unit
.
call
(
"select"
)
newly_selected_count
+=
1
## 우클릭 액션 (이동 목적지 지정 또는 적 공격 타겟팅)
## 우클릭 액션 (이동 목적지 지정 또는 적 공격 타겟팅)
func
_perform_action
(
mouse_pos
:
Vector2
)
->
void
:
func
_perform_action
(
mouse_pos
:
Vector2
)
->
void
:
...
@@ -142,7 +153,7 @@ func _perform_action(mouse_pos: Vector2) -> void:
...
@@ -142,7 +153,7 @@ func _perform_action(mouse_pos: Vector2) -> void:
var
monster_node
:
Node
=
_get_monster_node
(
hit_collider
)
var
monster_node
:
Node
=
_get_monster_node
(
hit_collider
)
if
monster_node
and
monster_node
.
is_in_group
(
"monsters"
):
if
monster_node
and
monster_node
.
is_in_group
(
"monsters"
):
# 선택된 유닛
들
에게 공격 타겟 명령 하달
# 선택된 유닛에게 공격 타겟 명령 하달
for
unit
:
Node
in
selected_units
:
for
unit
:
Node
in
selected_units
:
if
is_instance_valid
(
unit
)
and
unit
.
has_method
(
"attack_target"
):
if
is_instance_valid
(
unit
)
and
unit
.
has_method
(
"attack_target"
):
unit
.
call
(
"attack_target"
,
monster_node
)
unit
.
call
(
"attack_target"
,
monster_node
)
...
@@ -150,26 +161,9 @@ func _perform_action(mouse_pos: Vector2) -> void:
...
@@ -150,26 +161,9 @@ func _perform_action(mouse_pos: Vector2) -> void:
# 공격 지시 클릭 표시 (빨간색) 스폰
# 공격 지시 클릭 표시 (빨간색) 스폰
_spawn_click_indicator
(
monster_node
.
global_position
,
true
)
_spawn_click_indicator
(
monster_node
.
global_position
,
true
)
else
:
else
:
# 바닥 또는 일반 지형인 경우 해당 좌표로 이동 명령 하달
var
selected_unit
:
Node
=
selected_units
[
0
]
# 다중 유닛 이동 시 겹침 방지를 위해 약간의 분산(Offset)을 주면 좋습니다.
if
is_instance_valid
(
selected_unit
)
and
selected_unit
.
has_method
(
"move_to"
):
var
unit_count
:
int
=
selected_units
.
size
()
selected_unit
.
call
(
"move_to"
,
hit_pos
)
var
spacing
:
float
=
1.5
var
rows
:
int
=
int
(
ceil
(
sqrt
(
unit_count
)))
for
i
:
int
in
range
(
unit_count
):
var
unit
:
Node
=
selected_units
[
i
]
if
is_instance_valid
(
unit
)
and
unit
.
has_method
(
"move_to"
):
# 격자 정렬 오프셋 계산
var
row
:
int
=
i
/
rows
var
col
:
int
=
i
%
rows
var
offset
:
Vector3
=
Vector3
(
(
col
-
(
rows
-
1
)
/
2.0
)
*
spacing
,
0.0
,
(
row
-
(
rows
-
1
)
/
2.0
)
*
spacing
)
var
target_destination
:
Vector3
=
hit_pos
+
offset
unit
.
call
(
"move_to"
,
target_destination
)
# 지면 이동 클릭 표시 (초록색) 스폰
# 지면 이동 클릭 표시 (초록색) 스폰
_spawn_click_indicator
(
hit_pos
,
false
)
_spawn_click_indicator
(
hit_pos
,
false
)
...
@@ -255,3 +249,154 @@ func _perform_hold_position() -> void:
...
@@ -255,3 +249,154 @@ func _perform_hold_position() -> void:
if
is_instance_valid
(
unit
)
and
unit
.
has_method
(
"hold_position"
):
if
is_instance_valid
(
unit
)
and
unit
.
has_method
(
"hold_position"
):
unit
.
call
(
"hold_position"
)
unit
.
call
(
"hold_position"
)
## 선택된 유닛 주변에 표시할 3개 액션 버튼을 생성합니다.
## 버튼은 UnitController 자식 Control로 두어 매 프레임 선택 유닛의 월드 위치를 화면 좌표로 투영해 따라다니게 합니다.
func
_create_unit_action_buttons
()
->
void
:
_action_button_container
=
HBoxContainer
.
new
()
_action_button_container
.
name
=
"SelectedUnitActionButtons"
_action_button_container
.
visible
=
false
_action_button_container
.
mouse_filter
=
Control
.
MOUSE_FILTER_STOP
_action_button_container
.
add_theme_constant_override
(
"separation"
,
int
(
UNIT_ACTION_BUTTON_GAP
))
add_child
(
_action_button_container
)
_sell_button
=
_create_unit_action_button
(
"SellUnitButton"
,
"팔기"
,
Color
(
0.48
,
0.08
,
0.08
,
0.94
),
Color
(
1.0
,
0.32
,
0.22
,
0.86
))
_move_button
=
_create_unit_action_button
(
"MoveUnitButton"
,
"이동"
,
Color
(
0.08
,
0.22
,
0.42
,
0.94
),
Color
(
0.35
,
0.72
,
1.0
,
0.86
))
_upgrade_button
=
_create_unit_action_button
(
"UpgradeUnitButton"
,
"업그레이드"
,
Color
(
0.18
,
0.20
,
0.08
,
0.94
),
Color
(
1.0
,
0.82
,
0.24
,
0.86
))
_sell_button
.
pressed
.
connect
(
_on_sell_selected_unit_pressed
)
_move_button
.
pressed
.
connect
(
_on_move_selected_unit_pressed
)
_upgrade_button
.
pressed
.
connect
(
_on_upgrade_selected_unit_pressed
)
## 유닛 액션 버튼 하나를 생성합니다.
## HUD 버튼처럼 보이되 월드 선택 메뉴로 읽히도록 크기를 작게 고정하고, 입력은 반드시 버튼이 먼저 소비하게 합니다.
func
_create_unit_action_button
(
button_name
:
String
,
button_text
:
String
,
bg_color
:
Color
,
border_color
:
Color
)
->
Button
:
var
button
:
Button
=
Button
.
new
()
button
.
name
=
button_name
button
.
text
=
button_text
button
.
custom_minimum_size
=
UNIT_ACTION_BUTTON_SIZE
button
.
mouse_filter
=
Control
.
MOUSE_FILTER_STOP
button
.
focus_mode
=
Control
.
FOCUS_NONE
button
.
add_theme_font_size_override
(
"font_size"
,
13
)
button
.
add_theme_stylebox_override
(
"normal"
,
_create_action_button_style
(
bg_color
,
border_color
))
button
.
add_theme_stylebox_override
(
"hover"
,
_create_action_button_style
(
bg_color
.
lightened
(
0.10
),
border_color
.
lightened
(
0.12
)))
button
.
add_theme_stylebox_override
(
"pressed"
,
_create_action_button_style
(
bg_color
.
darkened
(
0.08
),
border_color
))
_action_button_container
.
add_child
(
button
)
return
button
## 선택 액션 버튼의 공통 카드형 배경을 생성합니다.
## 둥근 정도는 과하지 않게 두어 기존 HUD와 어울리게 하고, 테두리 색으로 버튼 성격을 구분합니다.
func
_create_action_button_style
(
bg_color
:
Color
,
border_color
:
Color
)
->
StyleBoxFlat
:
var
style
:
StyleBoxFlat
=
StyleBoxFlat
.
new
()
style
.
bg_color
=
bg_color
style
.
border_color
=
border_color
style
.
border_width_left
=
1
style
.
border_width_top
=
1
style
.
border_width_right
=
1
style
.
border_width_bottom
=
1
style
.
corner_radius_top_left
=
6
style
.
corner_radius_top_right
=
6
style
.
corner_radius_bottom_left
=
6
style
.
corner_radius_bottom_right
=
6
style
.
content_margin_left
=
8.0
style
.
content_margin_right
=
8.0
style
.
content_margin_top
=
4.0
style
.
content_margin_bottom
=
4.0
return
style
## 선택 유닛이 살아 있다면 액션 버튼을 표시합니다.
func
_show_unit_action_buttons
()
->
void
:
if
_action_button_container
:
_action_button_container
.
visible
=
_selected_unit
!=
null
and
is_instance_valid
(
_selected_unit
)
_update_unit_action_buttons_position
()
## 선택이 없거나 유닛이 사라진 경우 액션 버튼을 숨깁니다.
func
_hide_unit_action_buttons
()
->
void
:
if
_action_button_container
:
_action_button_container
.
visible
=
false
## 선택 유닛의 머리 위 위치를 화면 좌표로 변환해 버튼 묶음을 따라다니게 합니다.
## 카메라 뒤로 사라지거나 유닛이 삭제되면 선택 상태도 정리해 잘못된 버튼 클릭을 막습니다.
func
_update_unit_action_buttons_position
()
->
void
:
if
not
_action_button_container
or
not
_action_button_container
.
visible
:
return
if
not
_selected_unit
or
not
is_instance_valid
(
_selected_unit
):
_clear_selected_unit
()
return
var
camera
:
Camera3D
=
get_viewport
()
.
get_camera_3d
()
if
not
camera
:
_hide_unit_action_buttons
()
return
var
anchor_world_position
:
Vector3
=
_selected_unit
.
global_position
+
Vector3
(
0.0
,
UNIT_ACTION_BUTTON_WORLD_HEIGHT
,
0.0
)
if
camera
.
is_position_behind
(
anchor_world_position
):
_action_button_container
.
visible
=
false
return
var
screen_position
:
Vector2
=
camera
.
unproject_position
(
anchor_world_position
)
var
container_size
:
Vector2
=
_action_button_container
.
get_combined_minimum_size
()
_action_button_container
.
size
=
container_size
_action_button_container
.
position
=
screen_position
-
Vector2
(
container_size
.
x
*
0.5
,
container_size
.
y
+
10.0
)
## 팔기 버튼 처리입니다.
## 아직 개별 유닛 구매가를 저장하지 않으므로, 현재 기본 소환 비용의 50%를 환불 기준으로 사용합니다.
func
_on_sell_selected_unit_pressed
()
->
void
:
if
not
_selected_unit
or
not
is_instance_valid
(
_selected_unit
):
_clear_selected_unit
()
return
var
refund_amount
:
int
=
int
(
float
(
GameManager
.
unit_spawn_cost
)
*
UNIT_SELL_REFUND_RATIO
)
if
refund_amount
>
0
:
GameManager
.
add_gold
(
refund_amount
)
var
unit_to_sell
:
Node3D
=
_selected_unit
_clear_selected_unit
()
unit_to_sell
.
queue_free
()
## 이동 버튼 처리입니다.
## 버튼을 누른 뒤 다음 좌클릭 위치를 선택 유닛의 이동 목적지로 사용합니다.
func
_on_move_selected_unit_pressed
()
->
void
:
if
not
_selected_unit
or
not
is_instance_valid
(
_selected_unit
):
_clear_selected_unit
()
return
_is_waiting_for_move_destination
=
true
## 이동 버튼 후속 좌클릭 처리입니다.
## 바닥을 찍으면 선택 유닛에 move_to()를 호출하고, 클릭 인디케이터를 표시한 뒤 이동 대기 상태를 해제합니다.
func
_perform_selected_unit_move
(
mouse_pos
:
Vector2
)
->
void
:
if
not
_selected_unit
or
not
is_instance_valid
(
_selected_unit
):
_clear_selected_unit
()
return
var
ray_result
:
Dictionary
=
_raycast_from_mouse
(
mouse_pos
)
if
ray_result
.
is_empty
()
or
not
ray_result
.
has
(
"position"
):
return
var
hit_pos
:
Vector3
=
ray_result
[
"position"
]
if
_selected_unit
.
has_method
(
"move_to"
):
_selected_unit
.
call
(
"move_to"
,
hit_pos
)
_spawn_click_indicator
(
hit_pos
,
false
)
_is_waiting_for_move_destination
=
false
_show_unit_action_buttons
()
## 업그레이드 버튼 처리입니다.
## UnitData가 템플릿 Resource로 공유될 수 있으므로 선택 유닛에만 복제본을 할당한 뒤 능력치를 올립니다.
func
_on_upgrade_selected_unit_pressed
()
->
void
:
if
not
_selected_unit
or
not
is_instance_valid
(
_selected_unit
):
_clear_selected_unit
()
return
if
not
_selected_unit
is
BaseUnit
:
return
var
selected_base_unit
:
BaseUnit
=
_selected_unit
as
BaseUnit
if
not
selected_base_unit
.
unit_data
:
return
if
not
GameManager
.
spend_gold
(
UNIT_UPGRADE_COST
):
return
selected_base_unit
.
unit_data
=
selected_base_unit
.
unit_data
.
duplicate
(
true
)
as
UnitData
selected_base_unit
.
unit_data
.
damage
*=
1.18
selected_base_unit
.
unit_data
.
attack_range
*=
1.04
selected_base_unit
.
unit_data
.
cooldown
=
maxf
(
0.15
,
selected_base_unit
.
unit_data
.
cooldown
*
0.94
)
selected_base_unit
.
unit_data
.
move_speed
*=
1.04
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