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
17e73e22
Commit
17e73e22
authored
Jul 20, 2026
by
Gavin An
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
영웅의 공격방식 변경
parent
48b5d744
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
295 additions
and
11 deletions
+295
-11
.DS_Store
.DS_Store
+0
-0
assets/map/desert/Heightmap_0_0.res
assets/map/desert/Heightmap_0_0.res
+0
-0
assets/map/desert/Splatmap_0_0_0.res
assets/map/desert/Splatmap_0_0_0.res
+0
-0
assets/map/desert/Water_0_0.res
assets/map/desert/Water_0_0.res
+0
-0
src/.DS_Store
src/.DS_Store
+0
-0
src/entities/units/hero/base_hero.gd
src/entities/units/hero/base_hero.gd
+226
-0
src/entities/units/hero/dwarf/dwarf_hero.gd
src/entities/units/hero/dwarf/dwarf_hero.gd
+9
-0
src/map/background/desert.tscn
src/map/background/desert.tscn
+6
-6
src/ui/virtual_joystick/virtual_joystick.gd
src/ui/virtual_joystick/virtual_joystick.gd
+54
-5
No files found.
.DS_Store
View file @
17e73e22
No preview for this file type
assets/map/desert/Heightmap_0_0.res
View file @
17e73e22
No preview for this file type
assets/map/desert/Splatmap_0_0_0.res
View file @
17e73e22
No preview for this file type
assets/map/desert/Water_0_0.res
View file @
17e73e22
No preview for this file type
src/.DS_Store
View file @
17e73e22
No preview for this file type
src/entities/units/hero/base_hero.gd
View file @
17e73e22
...
@@ -12,6 +12,22 @@ signal skill_cooldown_changed(skill_slot: String, remaining: float, duration: fl
...
@@ -12,6 +12,22 @@ signal skill_cooldown_changed(skill_slot: String, remaining: float, duration: fl
# 영웅 능력치 한계 상수
# 영웅 능력치 한계 상수
const
MAX_HERO_LEVEL
:
int
=
20
const
MAX_HERO_LEVEL
:
int
=
20
## 근접 영웅 기본 수동 공격 범위입니다.
## 각 영웅 스크립트에서 값을 덮어써 영웅마다 다른 전방 공격 반경을 줄 수 있습니다.
@
export
var
manual_attack_distance
:
float
=
3.0
## 근접 영웅 수동 공격의 전방 판정 각도입니다.
## 110도면 영웅 정면을 중심으로 좌우 55(110 / 2)도 안의 몬스터가 피해를 받습니다.
@
export
var
manual_attack_angle_degrees
:
float
=
110.0
## 근접 영웅 공격 범위 표시가 화면에 남아 있는 시간입니다.
## 공격 판정 순간만 확인할 수 있도록 짧게 표시합니다.
@
export
var
manual_attack_indicator_duration
:
float
=
0.28
## 근접 영웅 공격 범위 표시 색상입니다.
## 투명도를 포함한 Color 값이므로 영웅과 몬스터를 가리지 않는 반투명 색을 사용합니다.
@
export
var
manual_attack_indicator_color
:
Color
=
Color
(
1.0
,
0.25
,
0.08
,
0.34
)
# 레벨 및 경험치 상태 변수
# 레벨 및 경험치 상태 변수
var
level
:
int
=
1
:
var
level
:
int
=
1
:
set
(
value
):
set
(
value
):
...
@@ -43,6 +59,8 @@ var skill_mana_costs: Dictionary = {
...
@@ -43,6 +59,8 @@ var skill_mana_costs: Dictionary = {
# 3D 이중 선택 링 캐싱
# 3D 이중 선택 링 캐싱
@
onready
var
_selection_ring_outer
:
MeshInstance3D
=
$
SelectionRingOuter
@
onready
var
_selection_ring_outer
:
MeshInstance3D
=
$
SelectionRingOuter
var
_manual_attack_indicator_material
:
StandardMaterial3D
=
null
func
_ready
()
->
void
:
func
_ready
()
->
void
:
super
.
_ready
()
super
.
_ready
()
# 영웅 그룹에 별도로 등록
# 영웅 그룹에 별도로 등록
...
@@ -69,6 +87,39 @@ func _physics_process(delta: float) -> void:
...
@@ -69,6 +87,39 @@ func _physics_process(delta: float) -> void:
if
_selection_ring_outer
:
if
_selection_ring_outer
:
_selection_ring_outer
.
rotate_y
(
-
delta
*
2.0
)
_selection_ring_outer
.
rotate_y
(
-
delta
*
2.0
)
## 영웅 전용 수동 공격 진입점입니다.
## 일반 유닛은 자동으로 사거리 안의 적을 공격하지만, 근접 영웅은 Attack 버튼을 누를 때만 이 함수로 공격합니다.
func
manual_attack
()
->
void
:
if
GameManager
.
is_game_over
or
not
unit_data
:
return
if
_attack_timer
>
0.0
:
return
if
unit_data
.
is_ranged
:
_manual_ranged_attack
()
else
:
_manual_melee_area_attack
()
## 근접 영웅은 일반 유닛처럼 자동 타겟을 찾지 않습니다.
## 원거리 영웅이 추가될 경우에는 기존 BaseUnit 자동 공격 탐색을 그대로 사용합니다.
func
_find_nearest_target
()
->
void
:
if
unit_data
and
unit_data
.
is_ranged
:
super
.
_find_nearest_target
()
else
:
_target_monster
=
null
## 근접 영웅은 타겟을 잡고 자동 공격 로직에 들어가지 않습니다.
## 이동 명령/조이스틱 이동은 유지하되, 공격은 manual_attack()에서만 발생하게 분리합니다.
func
_process_attack_logic
(
delta
:
float
)
->
void
:
if
unit_data
and
unit_data
.
is_ranged
:
super
.
_process_attack_logic
(
delta
)
return
_target_monster
=
null
_is_currently_attacking
=
false
_process_normal_logic
(
delta
)
## 경험치 획득 처리 함수
## 경험치 획득 처리 함수
func
gain_xp
(
amount
:
int
)
->
void
:
func
gain_xp
(
amount
:
int
)
->
void
:
if
level
>=
MAX_HERO_LEVEL
:
if
level
>=
MAX_HERO_LEVEL
:
...
@@ -112,6 +163,181 @@ func _play_level_up_visual_effect() -> void:
...
@@ -112,6 +163,181 @@ func _play_level_up_visual_effect() -> void:
tween
.
tween_property
(
mat
,
"emission_energy_multiplier"
,
10.0
,
0.2
)
tween
.
tween_property
(
mat
,
"emission_energy_multiplier"
,
10.0
,
0.2
)
tween
.
tween_property
(
mat
,
"emission_energy_multiplier"
,
2.0
,
0.5
)
tween
.
tween_property
(
mat
,
"emission_energy_multiplier"
,
2.0
,
0.5
)
## 근접 영웅 수동 공격 처리입니다.
## 영웅 정면 기준 manual_attack_radius 안에 있고, manual_attack_angle_degrees 부채꼴 안에 들어온 모든 몬스터에게 피해를 줍니다.
func
_manual_melee_area_attack
()
->
void
:
var
attack_origin
:
Vector3
=
global_position
var
attack_forward
:
Vector3
=
_get_manual_attack_forward
()
var
hit_monsters
:
Array
[
Node3D
]
=
_get_monsters_in_manual_attack_arc
(
attack_origin
,
attack_forward
)
_show_manual_attack_range_indicator
(
attack_origin
,
attack_forward
)
if
hit_monsters
.
is_empty
():
_play_manual_attack_motion
()
_lock_manual_attack_movement_until_hit
()
_start_manual_attack_cooldown
()
return
# 첫 번째 피격 몬스터는 스킬 Q 같은 타겟 기반 스킬 참조용으로만 기록합니다.
# 공격 방향은 버튼을 누른 순간 영웅이 바라보던 방향을 그대로 사용하며, 몬스터 쪽으로 자동 회전하지 않습니다.
_target_monster
=
hit_monsters
[
0
]
_play_manual_attack_motion
()
_lock_manual_attack_movement_until_hit
()
_start_manual_attack_cooldown
()
var
damage_delay
:
float
=
_get_attack_damage_delay
(
_attack_lock_timer
)
var
damage_amount
:
float
=
unit_data
.
damage
if
damage_delay
>
0.0
:
get_tree
()
.
create_timer
(
damage_delay
)
.
timeout
.
connect
(
func
()
->
void
:
_apply_manual_attack_damage
(
hit_monsters
,
damage_amount
)
)
else
:
_apply_manual_attack_damage
(
hit_monsters
,
damage_amount
)
## 원거리 영웅이 추가될 경우를 위한 기본 수동 공격 fallback입니다.
## 원거리 타입은 기존 자동 공격 구조를 유지하므로, 버튼 입력 시 가장 가까운 적을 즉시 타겟으로 지정하는 정도만 수행합니다.
func
_manual_ranged_attack
()
->
void
:
_find_nearest_target
()
if
_target_monster
:
_process_attack_logic
(
0.0
)
## 수동 공격의 애니메이션 락/재생 상태를 설정합니다.
func
_play_manual_attack_motion
()
->
void
:
var
anim_length
:
float
=
_get_attack_animation_length
()
var
play_speed
:
float
=
_get_attack_timing_play_speed
()
_attack_lock_timer
=
_get_attack_lock_duration
(
anim_length
,
play_speed
)
_is_currently_attacking
=
true
_is_moving
=
false
_nav_agent
.
target_position
=
global_position
velocity
.
x
=
0.0
velocity
.
z
=
0.0
_update_animations
()
## 근접 수동 공격의 타격 확정 전까지 이동을 막습니다.
## 조이스틱이 계속 move_to()를 보내더라도 BaseUnit.move_to()가 이동 명령을 큐에 보관하고,
## 데미지 판정 시점이 지나 후딜레이가 시작되면 큐된 이동을 적용해 자연스럽게 빠져나가게 합니다.
func
_lock_manual_attack_movement_until_hit
()
->
void
:
var
damage_delay
:
float
=
_get_attack_damage_delay
(
_attack_lock_timer
)
_set_melee_move_lock_until_hit
(
damage_delay
)
## 수동 공격 쿨다운을 시작합니다.
## 기존 BaseUnit 공격 쿨다운 필드를 공유해 일반 공격/수동 공격 타이밍 계산을 한곳에서 유지합니다.
func
_start_manual_attack_cooldown
()
->
void
:
_attack_timer
=
unit_data
.
cooldown
## 현재 영웅의 수동 공격 정면 방향을 반환합니다.
## 표시와 피해 판정이 같은 방향을 쓰도록 공격 순간 한 번만 계산해 두는 데 사용합니다.
func
_get_manual_attack_forward
()
->
Vector3
:
var
forward
:
Vector3
=
-
global_transform
.
basis
.
z
forward
.
y
=
0.0
if
forward
.
length
()
<=
0.01
:
return
Vector3
.
FORWARD
return
forward
.
normalized
()
## 전방 부채꼴 안의 몬스터 목록을 거리순으로 반환합니다.
## attack_origin과 attack_forward는 표시용 Mesh에도 그대로 전달되어 시각 범위와 실제 판정이 완전히 같은 기준을 씁니다.
func
_get_monsters_in_manual_attack_arc
(
attack_origin
:
Vector3
,
attack_forward
:
Vector3
)
->
Array
[
Node3D
]:
var
hit_monsters
:
Array
[
Node3D
]
=
[]
var
min_dot
:
float
=
cos
(
deg_to_rad
(
manual_attack_angle_degrees
*
0.5
))
for
monster
:
Node
in
get_tree
()
.
get_nodes_in_group
(
"monsters"
):
if
not
is_instance_valid
(
monster
)
or
not
monster
is
Node3D
:
continue
if
monster
.
get
(
"is_dead"
)
==
true
:
continue
var
monster_3d
:
Node3D
=
monster
as
Node3D
var
monster_position
:
Vector3
=
monster_3d
.
global_position
var
to_monster
:
Vector3
=
monster_position
-
attack_origin
to_monster
.
y
=
0.0
var
distance
:
float
=
to_monster
.
length
()
if
distance
>
manual_attack_distance
:
continue
if
distance
<=
0.01
:
hit_monsters
.
append
(
monster_3d
)
continue
var
direction_to_monster
:
Vector3
=
to_monster
.
normalized
()
if
attack_forward
.
dot
(
direction_to_monster
)
>=
min_dot
:
hit_monsters
.
append
(
monster_3d
)
hit_monsters
.
sort_custom
(
func
(
a
:
Node3D
,
b
:
Node3D
)
->
bool
:
return
attack_origin
.
distance_squared_to
(
a
.
global_position
)
<
attack_origin
.
distance_squared_to
(
b
.
global_position
)
)
return
hit_monsters
## 수동 공격에 적중한 몬스터들에게 실제 피해를 적용합니다.
func
_apply_manual_attack_damage
(
hit_monsters
:
Array
[
Node3D
],
damage_amount
:
float
)
->
void
:
for
monster
:
Node3D
in
hit_monsters
:
if
is_instance_valid
(
monster
)
and
not
monster
.
get
(
"is_dead"
)
and
monster
.
has_method
(
"take_damage"
):
monster
.
call
(
"take_damage"
,
damage_amount
)
## 근접 영웅의 실제 공격 판정 범위를 바닥에 잠깐 표시합니다.
## 판정 함수와 같은 radius/angle 값을 사용해 플레이어가 "어디까지 맞는지" 시각적으로 확인할 수 있게 합니다.
func
_show_manual_attack_range_indicator
(
attack_origin
:
Vector3
,
attack_forward
:
Vector3
)
->
void
:
var
indicator
:
MeshInstance3D
=
MeshInstance3D
.
new
()
indicator
.
name
=
"ManualAttackRangeIndicator"
indicator
.
mesh
=
_create_manual_attack_indicator_mesh
(
attack_forward
)
indicator
.
material_override
=
_get_manual_attack_indicator_material
()
indicator
.
cast_shadow
=
GeometryInstance3D
.
SHADOW_CASTING_SETTING_OFF
var
parent_node
:
Node
=
get_tree
()
.
current_scene
if
get_tree
()
.
current_scene
else
self
parent_node
.
add_child
(
indicator
)
indicator
.
global_position
=
attack_origin
+
Vector3
(
0.0
,
0.045
,
0.0
)
var
tween
:
Tween
=
create_tween
()
tween
.
tween_interval
(
manual_attack_indicator_duration
)
tween
.
tween_property
(
indicator
,
"transparency"
,
1.0
,
0.12
)
tween
.
finished
.
connect
(
func
()
->
void
:
if
is_instance_valid
(
indicator
):
indicator
.
queue_free
()
)
## 전방 부채꼴 공격 범위 Mesh를 생성합니다.
## attack_forward 기준으로 월드 방향 버텍스를 직접 만들기 때문에 표시와 피해 판정이 같은 방향을 공유합니다.
func
_create_manual_attack_indicator_mesh
(
attack_forward
:
Vector3
)
->
ArrayMesh
:
var
mesh
:
ArrayMesh
=
ArrayMesh
.
new
()
var
vertices
:
PackedVector3Array
=
PackedVector3Array
()
var
indices
:
PackedInt32Array
=
PackedInt32Array
()
var
segment_count
:
int
=
max
(
8
,
int
(
manual_attack_angle_degrees
/
6.0
))
var
half_angle
:
float
=
deg_to_rad
(
manual_attack_angle_degrees
*
0.5
)
var
forward_2d
:
Vector2
=
Vector2
(
attack_forward
.
x
,
attack_forward
.
z
)
.
normalized
()
vertices
.
append
(
Vector3
.
ZERO
)
for
index
:
int
in
range
(
segment_count
+
1
):
var
ratio
:
float
=
float
(
index
)
/
float
(
segment_count
)
var
angle
:
float
=
lerpf
(
-
half_angle
,
half_angle
,
ratio
)
var
rotated_direction
:
Vector2
=
forward_2d
.
rotated
(
angle
)
var
point
:
Vector3
=
Vector3
(
rotated_direction
.
x
*
manual_attack_distance
,
0.0
,
rotated_direction
.
y
*
manual_attack_distance
)
vertices
.
append
(
point
)
for
index
:
int
in
range
(
1
,
segment_count
+
1
):
indices
.
append
(
0
)
indices
.
append
(
index
)
indices
.
append
(
index
+
1
)
var
arrays
:
Array
=
[]
arrays
.
resize
(
Mesh
.
ARRAY_MAX
)
arrays
[
Mesh
.
ARRAY_VERTEX
]
=
vertices
arrays
[
Mesh
.
ARRAY_INDEX
]
=
indices
mesh
.
add_surface_from_arrays
(
Mesh
.
PRIMITIVE_TRIANGLES
,
arrays
)
return
mesh
## 공격 범위 표시용 머티리얼을 반환합니다.
## 매 공격마다 머티리얼을 새로 만들지 않도록 최초 1회 생성 후 재사용합니다.
func
_get_manual_attack_indicator_material
()
->
StandardMaterial3D
:
if
_manual_attack_indicator_material
:
return
_manual_attack_indicator_material
_manual_attack_indicator_material
=
StandardMaterial3D
.
new
()
_manual_attack_indicator_material
.
transparency
=
BaseMaterial3D
.
TRANSPARENCY_ALPHA
_manual_attack_indicator_material
.
shading_mode
=
BaseMaterial3D
.
SHADING_MODE_UNSHADED
_manual_attack_indicator_material
.
albedo_color
=
manual_attack_indicator_color
_manual_attack_indicator_material
.
cull_mode
=
BaseMaterial3D
.
CULL_DISABLED
_manual_attack_indicator_material
.
no_depth_test
=
true
return
_manual_attack_indicator_material
## 키보드 스킬 단축키 입력 수신 처리
## 키보드 스킬 단축키 입력 수신 처리
func
_unhandled_input
(
event
:
InputEvent
)
->
void
:
func
_unhandled_input
(
event
:
InputEvent
)
->
void
:
if
GameManager
.
is_game_over
or
not
is_selected
:
if
GameManager
.
is_game_over
or
not
is_selected
:
...
...
src/entities/units/hero/dwarf/dwarf_hero.gd
View file @
17e73e22
...
@@ -21,6 +21,15 @@ const DWARF_ATTACK_DAMAGE_RATIO: float = 0.48
...
@@ -21,6 +21,15 @@ const DWARF_ATTACK_DAMAGE_RATIO: float = 0.48
const
DWARF_MIN_ATTACK_LOCK
:
float
=
0.65
const
DWARF_MIN_ATTACK_LOCK
:
float
=
0.65
const
DWARF_MIN_ATTACK_PLAY_SPEED
:
float
=
1.0
const
DWARF_MIN_ATTACK_PLAY_SPEED
:
float
=
1.0
const
DWARF_MAX_ATTACK_PLAY_SPEED
:
float
=
4.0
const
DWARF_MAX_ATTACK_PLAY_SPEED
:
float
=
4.0
const
DWARF_MANUAL_ATTACK_DISTANCE
:
float
=
5.2
const
DWARF_MANUAL_ATTACK_ANGLE_DEGREES
:
float
=
115.0
func
_ready
()
->
void
:
# 드워프는 도끼를 크게 휘두르는 근접 영웅이므로, Attack 버튼 입력 시 넓은 전방 부채꼴을 공격합니다.
# BaseHero의 기본값을 여기에서 덮어써 영웅별 수동 공격 반경을 분리합니다.
manual_attack_distance
=
DWARF_MANUAL_ATTACK_DISTANCE
manual_attack_angle_degrees
=
DWARF_MANUAL_ATTACK_ANGLE_DEGREES
super
.
_ready
()
func
_setup_unit_visuals
()
->
void
:
func
_setup_unit_visuals
()
->
void
:
# 자식 노드 중 드워프 3D 모델 노드 이름을 명시하여 인스턴스를 확보합니다.
# 자식 노드 중 드워프 3D 모델 노드 이름을 명시하여 인스턴스를 확보합니다.
...
...
src/map/background/desert.tscn
View file @
17e73e22
...
@@ -59,7 +59,7 @@ void fragment() {
...
@@ -59,7 +59,7 @@ void fragment() {
}
}
"
"
[sub_resource type="PlaceholderTexture2DArray" id="PlaceholderTexture2DArray_
b0gws
"]
[sub_resource type="PlaceholderTexture2DArray" id="PlaceholderTexture2DArray_
lkjvc
"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_lkjvc"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_lkjvc"]
render_priority = 0
render_priority = 0
...
@@ -69,7 +69,7 @@ shader_parameter/OffsetPosition = Vector3(0, 0, 0)
...
@@ -69,7 +69,7 @@ shader_parameter/OffsetPosition = Vector3(0, 0, 0)
shader_parameter/ZonesSize = 0.0
shader_parameter/ZonesSize = 0.0
shader_parameter/Resolution = 0.0
shader_parameter/Resolution = 0.0
shader_parameter/NumberOfZones = 0.0
shader_parameter/NumberOfZones = 0.0
shader_parameter/WaterTextures = SubResource("PlaceholderTexture2DArray_
b0gws
")
shader_parameter/WaterTextures = SubResource("PlaceholderTexture2DArray_
lkjvc
")
shader_parameter/WaterFactor = 0.0
shader_parameter/WaterFactor = 0.0
shader_parameter/WaterInnerOffset = 0.3
shader_parameter/WaterInnerOffset = 0.3
shader_parameter/WaterColor = Color(0, 0, 0, 1)
shader_parameter/WaterColor = Color(0, 0, 0, 1)
...
@@ -99,20 +99,20 @@ waterColorDeep = Color(0.79508156, 0.8459231, 1, 1)
...
@@ -99,20 +99,20 @@ waterColorDeep = Color(0.79508156, 0.8459231, 1, 1)
waterColorShallow = Color(0.14232409, 5.7756904e-07, 0.853347, 1)
waterColorShallow = Color(0.14232409, 5.7756904e-07, 0.853347, 1)
customShader = SubResource("ShaderMaterial_lkjvc")
customShader = SubResource("ShaderMaterial_lkjvc")
[sub_resource type="ZoneResource" id="ZoneResource_
vhovy
"]
[sub_resource type="ZoneResource" id="ZoneResource_
tdk5o
"]
heightMapImage = ExtResource("1_6h2jq")
heightMapImage = ExtResource("1_6h2jq")
splatmapsImage = Array[Image]([ExtResource("2_8a6xj")])
splatmapsImage = Array[Image]([ExtResource("2_8a6xj")])
waterImage = ExtResource("9_lkjvc")
waterImage = ExtResource("9_lkjvc")
[sub_resource type="ZonesResource" id="ZonesResource_
aj3fk
"]
[sub_resource type="ZonesResource" id="ZonesResource_
b0gws
"]
zones = Array[ZoneResource]([SubResource("ZoneResource_
vhovy
")])
zones = Array[ZoneResource]([SubResource("ZoneResource_
tdk5o
")])
[node name="DesertBackground" type="TerraBrush" unique_id=568763585]
[node name="DesertBackground" type="TerraBrush" unique_id=568763585]
dataPath = "res://assets/map/desert"
dataPath = "res://assets/map/desert"
zonesSize = 64
zonesSize = 64
textureSets = SubResource("TextureSetsResource_207lx")
textureSets = SubResource("TextureSetsResource_207lx")
waterDefinition = SubResource("WaterResource_gbjga")
waterDefinition = SubResource("WaterResource_gbjga")
terrainZones = SubResource("ZonesResource_
aj3fk
")
terrainZones = SubResource("ZonesResource_
b0gws
")
physics_interpolation_mode = 2
physics_interpolation_mode = 2
top_level = true
top_level = true
metadata/_edit_lock_ = false
metadata/_edit_lock_ = false
src/ui/virtual_joystick/virtual_joystick.gd
View file @
17e73e22
...
@@ -12,6 +12,7 @@ const MOVE_COMMAND_INTERVAL: float = 0.08
...
@@ -12,6 +12,7 @@ const MOVE_COMMAND_INTERVAL: float = 0.08
var
_hero
:
Node3D
=
null
var
_hero
:
Node3D
=
null
var
_move_vector
:
Vector2
=
Vector2
.
ZERO
var
_move_vector
:
Vector2
=
Vector2
.
ZERO
var
_keyboard_move_vector
:
Vector2
=
Vector2
.
ZERO
var
_is_dragging_move_stick
:
bool
=
false
var
_is_dragging_move_stick
:
bool
=
false
var
_move_command_timer
:
float
=
0.0
var
_move_command_timer
:
float
=
0.0
...
@@ -33,12 +34,28 @@ func _ready() -> void:
...
@@ -33,12 +34,28 @@ func _ready() -> void:
visible
=
false
visible
=
false
set_process
(
false
)
set_process
(
false
)
## 키보드 공격 입력을 처리합니다.
## A 키는 화면의 ATK 버튼과 완전히 같은 _on_attack_pressed() 경로를 사용해 입력별 동작 차이를 없앱니다.
func
_unhandled_input
(
event
:
InputEvent
)
->
void
:
if
not
visible
or
not
is_instance_valid
(
_hero
)
or
_is_game_over
():
return
if
event
is
InputEventKey
:
var
key_event
:
InputEventKey
=
event
as
InputEventKey
if
key_event
.
pressed
and
not
key_event
.
echo
and
key_event
.
keycode
==
KEY_A
:
_on_attack_pressed
()
get_viewport
()
.
set_input_as_handled
()
func
_process
(
delta
:
float
)
->
void
:
func
_process
(
delta
:
float
)
->
void
:
if
not
is_instance_valid
(
_hero
)
or
_is_game_over
():
if
not
is_instance_valid
(
_hero
)
or
_is_game_over
():
visible
=
false
visible
=
false
set_process
(
false
)
set_process
(
false
)
return
return
_update_keyboard_move_vector
()
if
not
_is_dragging_move_stick
:
_apply_keyboard_vector_to_stick
()
_move_command_timer
-=
delta
_move_command_timer
-=
delta
if
_move_vector
.
length
()
>
JOYSTICK_DEADZONE
and
_move_command_timer
<=
0.0
:
if
_move_vector
.
length
()
>
JOYSTICK_DEADZONE
and
_move_command_timer
<=
0.0
:
_move_command_timer
=
MOVE_COMMAND_INTERVAL
_move_command_timer
=
MOVE_COMMAND_INTERVAL
...
@@ -153,7 +170,10 @@ func _on_move_area_gui_input(event: InputEvent) -> void:
...
@@ -153,7 +170,10 @@ func _on_move_area_gui_input(event: InputEvent) -> void:
_is_dragging_move_stick
=
mouse_event
.
pressed
_is_dragging_move_stick
=
mouse_event
.
pressed
_update_move_stick
(
mouse_event
.
position
if
mouse_event
.
pressed
else
_move_area
.
size
*
0.5
)
_update_move_stick
(
mouse_event
.
position
if
mouse_event
.
pressed
else
_move_area
.
size
*
0.5
)
if
not
mouse_event
.
pressed
:
if
not
mouse_event
.
pressed
:
_reset_move_stick
()
if
_keyboard_move_vector
.
length
()
>
JOYSTICK_DEADZONE
:
_apply_keyboard_vector_to_stick
()
else
:
_reset_move_stick
()
elif
event
is
InputEventMouseMotion
and
_is_dragging_move_stick
:
elif
event
is
InputEventMouseMotion
and
_is_dragging_move_stick
:
var
motion_event
:
InputEventMouseMotion
=
event
as
InputEventMouseMotion
var
motion_event
:
InputEventMouseMotion
=
event
as
InputEventMouseMotion
_update_move_stick
(
motion_event
.
position
)
_update_move_stick
(
motion_event
.
position
)
...
@@ -169,14 +189,14 @@ func _apply_hero_move_vector() -> void:
...
@@ -169,14 +189,14 @@ func _apply_hero_move_vector() -> void:
if
_hero
.
has_method
(
"move_to"
):
if
_hero
.
has_method
(
"move_to"
):
_hero
.
call
(
"move_to"
,
target_position
)
_hero
.
call
(
"move_to"
,
target_position
)
## 공격 버튼을 누르면 가장 가까운 살아있는 몬스터를 찾아 영웅의 공격 대상으로 지정합니다.
## 공격 버튼을 누르면 영웅 전용 수동 공격을 실행합니다.
## 근접 영웅은 BaseHero.manual_attack()에서 전방 범위 공격을 하고, 원거리 타입은 기존 자동 타겟 공격 흐름을 사용합니다.
func
_on_attack_pressed
()
->
void
:
func
_on_attack_pressed
()
->
void
:
if
not
is_instance_valid
(
_hero
):
if
not
is_instance_valid
(
_hero
):
return
return
var
target
:
Node3D
=
_find_nearest_monster
()
if
_hero
.
has_method
(
"manual_attack"
):
if
target
and
_hero
.
has_method
(
"attack_target"
):
_hero
.
call
(
"manual_attack"
)
_hero
.
call
(
"attack_target"
,
target
)
## 스킬 버튼을 누르면 BaseHero.use_skill()에 그대로 위임합니다.
## 스킬 버튼을 누르면 BaseHero.use_skill()에 그대로 위임합니다.
func
_on_skill_pressed
(
slot
:
String
)
->
void
:
func
_on_skill_pressed
(
slot
:
String
)
->
void
:
...
@@ -199,6 +219,35 @@ func _reset_move_stick() -> void:
...
@@ -199,6 +219,35 @@ func _reset_move_stick() -> void:
if
_move_area
and
_move_knob
:
if
_move_area
and
_move_knob
:
_move_knob
.
position
=
_move_area
.
size
*
0.5
-
_move_knob
.
size
*
0.5
_move_knob
.
position
=
_move_area
.
size
*
0.5
-
_move_knob
.
size
*
0.5
## 키보드 화살표 입력을 매 프레임 읽어 조이스틱 방향 벡터로 변환합니다.
## 왼쪽/오른쪽은 x축, 위/아래는 조이스틱 y축과 동일하게 대응하며 대각선은 normalized 처리합니다.
func
_update_keyboard_move_vector
()
->
void
:
var
new_vector
:
Vector2
=
Vector2
.
ZERO
if
Input
.
is_key_pressed
(
KEY_LEFT
):
new_vector
.
x
-=
1.0
if
Input
.
is_key_pressed
(
KEY_RIGHT
):
new_vector
.
x
+=
1.0
if
Input
.
is_key_pressed
(
KEY_UP
):
new_vector
.
y
-=
1.0
if
Input
.
is_key_pressed
(
KEY_DOWN
):
new_vector
.
y
+=
1.0
_keyboard_move_vector
=
new_vector
.
normalized
()
if
new_vector
.
length
()
>
1.0
else
new_vector
## 방향키 입력을 실제 조이스틱 상태에 반영합니다.
## knob 위치와 _move_vector를 같은 함수 경로로 갱신해, 키보드와 터치 조이스틱이 완전히 같은 이동 로직을 탑니다.
func
_apply_keyboard_vector_to_stick
()
->
void
:
if
not
_move_area
:
return
if
_keyboard_move_vector
.
length
()
<=
JOYSTICK_DEADZONE
:
_reset_move_stick
()
return
var
center
:
Vector2
=
_move_area
.
size
*
0.5
var
stick_position
:
Vector2
=
center
+
_keyboard_move_vector
*
JOYSTICK_RADIUS
_update_move_stick
(
stick_position
)
## 현재 필드에서 영웅과 가장 가까운 살아있는 몬스터를 찾습니다.
## 현재 필드에서 영웅과 가장 가까운 살아있는 몬스터를 찾습니다.
func
_find_nearest_monster
()
->
Node3D
:
func
_find_nearest_monster
()
->
Node3D
:
var
nearest_monster
:
Node3D
=
null
var
nearest_monster
:
Node3D
=
null
...
...
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