Commit 9d46295f authored by Gavin An's avatar Gavin An

소환사 선택 화면

parent c35090ea
[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
[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
[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
[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
...@@ -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/ # 메인 게임 루프 및 관리자 스크립트
......
...@@ -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)
## 다음 스테이지 준비 대기 시간 시작 ## 다음 스테이지 준비 대기 시간 시작
......
[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"]
......
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()
)
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"]
...@@ -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
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment