Commit 9168d476 authored by Gavin An's avatar Gavin An

스킨 입혀보기

parent 9af0b9d6
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://ddqbm4y6brray"
path="res://.godot/imported/striker.fbx-bc1f4af7c3cd42cf4653a106a2b4cd60.scn"
[deps]
source_file="res://assets/models/units/striker.fbx"
dest_files=["res://.godot/imported/striker.fbx-bc1f4af7c3cd42cf4653a106a2b4cd60.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
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ccilxf3k1hhpb"
path.s3tc="res://.godot/imported/striker_0.png-33c061e5427709c43bfd101c9c356d76.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
generator_parameters={
"md5": "4bf449ff8612570375bc5092fac3ec8b"
}
[deps]
source_file="res://assets/models/units/striker_0.png"
dest_files=["res://.godot/imported/striker_0.png-33c061e5427709c43bfd101c9c356d76.s3tc.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
...@@ -17,4 +17,15 @@ ldefense/ ...@@ -17,4 +17,15 @@ ldefense/
│ │ └── units/ # 플레이어 유닛 관련 씬 및 스크립트 │ │ └── units/ # 플레이어 유닛 관련 씬 및 스크립트
│ ├── ui/ # UI 관련 씬 및 스크립트 │ ├── ui/ # UI 관련 씬 및 스크립트
│ └── utils/ # 기타 유틸리티 함수 │ └── utils/ # 기타 유틸리티 함수
├── assets/
│ ├── models/ # 3D 모델 (.glb, .gltf, .obj) 및 애니메이션
│ │ ├── units/ # 플레이어 유닛 3D 애셋 (Striker, Ranger 등)
│ │ ├── monsters/ # 몬스터 3D 애셋
│ │ └── environment/ # 지형 장애물, 소품, 타일 등
│ ├── textures/ # 3D 맵핑용 재질 텍스처, 노이즈 맵, 하늘(Skybox) HDRI
│ ├── ui/ # 버튼, 테두리, 아이콘 등 2D 스프라이트 이미지 (.png)
│ ├── audio/ # 사운드 파일
│ │ ├── sfx/ # 효과음 (즉시 재생을 위해 .wav 확장자 권장)
│ │ └── music/ # 배경음악 (스트리밍 재생을 위해 .ogg 또는 .mp3 권장)
│ └── fonts/ # UI 텍스트용 외부 폰트 (.ttf, .otf)
└── project.godot └── project.godot
# 프로젝트 개발 체크리스트 (todo.md) # 프로젝트 개발 및 개선 종합 체크리스트 (docs/agents_docs/todo.md)
이 문서는 본 3D 무작위 유닛 디펜스 게임의 전체 개발 로드맵과 각 단계별 진척 상황을 관리합니다. 이 문서는 본 3D 무작위 유닛 디펜스 게임의 기본 프레임워크 설계부터 시작하여, 여태까지 진행된 핵심 8단계 구현 사항, 디버깅 내역, AI/비주얼 개선 내역 및 향후 확장 계획을 정리한 개발 관리 문서입니다.
## [ ] 1단계: 기본 맵과 3D 카메라 셋업 ---
- [ ] 프로젝트 기본 폴더 구조 생성 (`src/core`, `src/autoload`, `src/entities`, `src/ui`, `src/utils` 등)
- [ ] 메인 게임 씬 (`src/core/main.tscn`) 생성 ## ▣ 개발 완료 및 버그 수정 완료 내역
- [ ] 3D 기본 지형 구축 (지상 바닥 및 조명, 환경 설정)
- 맵은 해상도에 맞게 최대로 만들어짐 ### [x] 1단계: 기본 맵과 3D 카메라 셋업
- [ ] 몬스터 이동 경로 (`Path3D`) 배치 (맵 가장자리를 도는 루프 형태) - [x] 프로젝트 기본 폴더 구조 생성 (`src/core`, `src/autoload`, `src/entities/monsters`, `src/entities/units`, `src/ui`, `src/utils`)
- [ ] RTS 스타일 3D 카메라 구현 (`src/core/rts_camera.gd`) - [x] 메인 게임 씬 (`src/core/main.tscn`) 설계
- 마우스 휠을 통한 줌인/줌아웃 - [x] 3D 지형 바닥(Ground) 및 실외 환경 조명 설정
- 맵이 줌인 되었을 때 WASD / 방향키를 통한 화면 이동 - [x] 맵 가장자리를 회전하는 사각형 루프 경로 (`Path3D`) 배치
- 맵이 최대로 줌 아웃 되었을 때는 화면 이동 없음 - [x] RTS 3D 탑다운 카메라 컨트롤러 구현 (`src/core/rts_camera.gd`)
- 화면 가장자리에 마우스 위치 시 카메라 스크롤 (선택 사항) - WASD 키보드 입력 및 화면 가장자리(Screen Edge) 마우스 스크롤 대응
- 마우스 휠을 활용한 고도 기반 줌인/줌아웃 (피치 60도 고정각 보정)
## [ ] 2단계: 글로벌 게임 매니저와 스테이지 시스템
- [ ] 글로벌 게임 매니저 싱글톤 (`src/autoload/game_manager.gd`) 구현 ### [x] 2단계: 글로벌 게임 매니저와 스테이지 시스템
- `player_gold`, `unit_spawn_cost`, `current_stage`, `active_monster_count` 등 상태 변수 관리 - [x] 글로벌 게임 매니저 AutoLoad 싱글톤 (`src/autoload/game_manager.gd`) 구현
- 게임오버(200마리 초과) 및 승리 조건(20스테이지 클리어) 체크 루프 - `player_gold` (초기 300), `unit_spawn_cost` (100), `current_stage` (1), `active_monster_count` 실시간 추적
- [ ] 스테이지 제어 로직 구현 (`src/core/stage_manager.gd`) - 몬스터 200마리 초과 시 게임 패배 및 20스테이지 생존 성공 시 게임 승리 감지
- 1초 간격 몬스터 스폰 타이머 설정 - [x] 스테이지 제어 엔진 (`src/core/stage_manager.gd`) 작성
- 스테이지별 몬스터 제원 (HP, 골드 보상 등) 스케일링 공식 적용 - 1초 간격 몬스터 스폰 타이머 관리 및 스폰 수량 통제
- 스테이지별 몬스터 체력 및 처치 골드 스케일링 공식 구현
## [ ] 3단계: 몬스터 시스템 구현
- [ ] 기본 몬스터 씬 (`src/entities/monsters/base_monster.tscn`) 및 스크립트 작성 ### [x] 3단계: 몬스터 시스템 구현
- [ ] 몬스터가 `PathFollow3D`를 따라 이동하게 하고, 끝에 도달하면 시작 지점으로 다시 루프하도록 설정 - [x] 몬스터 캐릭터 씬 및 비주얼 구체 메쉬 생성 (`src/entities/monsters/base_monster.tscn`)
- [ ] 몬스터 피격, 체력 소진 시 소멸 및 골드 보상 획득 로직 구현 - [x] 몬스터 `PathFollow3D` 상속 스크립트 구현 (`src/entities/monsters/base_monster.gd`)
- 경로를 자동으로 순환 비행하며 끝부분(Progress 1.0) 도달 시 0.0으로 리셋되어 무한 루프 회전
## [ ] 4단계: RTS식 유닛 선택 및 이동 (Navigation) - 피해 적용(`take_damage()`), 체력 소진 시 사망 처리 및 플레이어 골드 보상 증여 로직 구현
- [ ] 네비게이션 메시 (`NavigationRegion3D`) 맵에 추가 및 구워내기 (Bake)
- [ ] 마우스 클릭 및 드래그 박스(`Area3D` 또는 스크린 투 월드 투영 기법)를 활용한 유닛 멀티 선택 기능 구현 ### [x] 4단계: RTS식 유닛 선택 및 이동 (Navigation)
- [ ] 선택된 유닛이 맵 바닥 우클릭 시 네비게이션 에이전트(`NavigationAgent3D`)를 활용하여 목적지로 이동하도록 구현 - [x] 런타임 내비게이션 맵 생성을 위한 `NavigationRegion3D` 탑재 및 자동 베이킹
- [x] RTS 드래그 다중 선택 제어기 구현 (`src/core/unit_controller.gd`)
## [ ] 5단계: 유닛 클래스 정의 및 전투 구현 - 화면 마우스 좌클릭 드래그 박스 2D 렌더링 및 3D 공간 투영(Unproject) 다중 유닛 수집
- [ ] 유닛 기본 스키마 (`src/entities/units/base_unit.gd`) 작성 (정적 타이핑 적용) - [x] 우클릭 지점 대한 유닛 분산 격자 정렬 이동 및 몬스터 대상 타겟 어택 명령 하달 구현
- [ ] 5종의 유닛(Striker, Guardian, Ranger, Slayer, Archmage) 스펙 정의 (커스텀 리소스 사용 또는 스크립트 분기)
- [ ] 적 감지 범위 (`Area3D`) 내의 몬스터를 자동 검색 및 추격, 사거리 진입 시 쿨타임에 따른 자동 공격 구현 ### [x] 5단계: 유닛 클래스 정의 및 전투 구현
- [ ] 투사체(Ranger, Archmage용) 또는 근접 공격 이펙트 처리 - [x] 5종 유닛(Striker, Guardian, Ranger, Slayer, Archmage) 제원을 정의하는 커스텀 리소스 스키마 (`src/entities/units/unit_data.gd`) 작성
- [x] 유닛 뼈대 캐릭터 씬 및 선택 링 하이라이트 생성 (`src/entities/units/base_unit.tscn`)
## [ ] 6단계: UI 및 소환 시스템 - [x] 유닛 행동 및 전투 인공지능 스크립트 작성 (`src/entities/units/base_unit.gd`)
- [ ] 메인 HUD UI 씬 (`src/ui/hud.tscn`) 구성 - 네비게이션 에이전트(`NavigationAgent3D`)를 활용한 맵 경로 이동
- 상단: 스테이지 정보, 몬스터 마릿수(0/200), 현재 골드 상태 - 정지 상태 중 사거리 내의 가까운 적 자동 탐지 및 쿨타임 공격 연산 루프 구현
- 하단: 유닛 소환 버튼 (100골드 소모) 및 선택된 유닛 정보 창
- [ ] 소환 버튼 클릭 시 맵 중앙의 소환 구역(Spawn Zone)에 20% 확률로 5종 유닛 중 무작위 소환 ### [x] 6단계: UI 및 소환 시스템
- [ ] 게임 오버 및 게임 승리 결과 팝업 UI 구현 - [x] HUD UI 인터페이스 구축 (`src/ui/hud.tscn`)
- 화면 상단 골드, 현재 스테이지, 몬스터 마릿수 한도(200) 라벨 배치
- 화면 중앙 결과(승리/패배) 알림 Panel 및 재시작 Button 설정
- [x] HUD 스크립트 제어 (`src/ui/hud.gd`)
- 유닛 소환 버튼 클릭 시 100G 차감 및 중앙 영역 무작위 분산 스폰
- 5종 유닛 20% 동일 확률 주입 초기화 장치 마련
- 재시작 클릭 시 씬 내 잔존 개체 정리 및 매니저 값 초기화 연동
### [x] 7단계 & 8단계: Striker 전용 씬 분리 및 스킨 연동 리팩토링
- [x] `src/entities/units/striker/` 전용 폴더 및 `striker_unit.tscn` 씬 파일 생성
- [x] `striker_unit.tscn` 내부에 `striker.fbx` 모델을 서브씬 인스턴스로 바인딩하고 스케일을 0.01로 조정하여 저장
- [x] `base_unit.gd`를 리팩토링하여 불필요한 런타임 FBX 로더 및 런타임 스케일 세팅 코드를 모두 제거하고, 씬에 구성된 노드를 인계받는 구조로 슬림화
- [x] `hud.gd`에 소환 씬 분기 기능을 도입하여, 소환 시 ID에 맞춰 Striker 유닛일 때 전용 `striker_unit.tscn`을 소환하도록 구현
- [x] 유닛이 스폰될 때 3D 모델 하위 메쉬에 `striker_0.png` 텍스처를 씌우는 오버라이드 매핑 기능을 작동시켜 모델 렌더 텍스처 출력 완료
---
### [x] 추가 디버깅 및 고도화 개선 내역
- [x] **구문 컴파일 오류 정비**
- `unit_controller.gd` 내부의 존재하지 않는 `ceilf()`, `sqrtf()` 수학 함수를 Godot 4.x 내장 표준 함수인 `ceil()`, `sqrt()`로 교정 완료.
- `base_monster.gd` 내부에서 Godot 4.x에서 폐기된 `rotates` 프로퍼티 주석 라인을 완전히 삭제하여 컴파일 실패 차단.
- [x] **몬스터 감지 및 수동 조작 마비 버그 픽스**
- `base_monster.gd` 초기화 시 `add_to_group("monsters")`를 추가하여 유닛 자동 전투 AI가 적을 감지할 수 있도록 연계.
- `unit_controller.gd` 내의 `_get_monster_node()``_get_unit_node()` 판정 방식을 단순 그룹 대조에서 클래스 타입 캐스팅 대조(`is BaseMonster`, `is BaseUnit`)로 강화하여 우클릭 공격 지정이 유실되지 않도록 정밀 수정.
- [x] **UI 입력 가로챔 차단 및 드래그 박스 정비**
- 화면 전체를 덮는 `HUD` 씬의 루트 컨트롤 `mouse_filter``2` (IGNORE)로 수정하여 마우스 좌/우클릭이 맵 바닥까지 안전하게 통과하도록 조치.
- `UnitController` 마우스 필터 역시 IGNORE로 조율하고 입력 방식을 `_gui_input`에서 `_unhandled_input`으로 전면 변경하여 UI 클릭(예: 소환 버튼)과 순수 필드 클릭의 경계를 분리.
- [x] **RTS 전투 인공지능(AI) 고도화**
- 유닛이 목적지로 이동하는 중이라도 주변에 몬스터가 포착되면 즉시 자동 추격/공격을 개시하도록 타겟팅 주기 튜닝.
- 수동 긴급 대피(Flee) 시 즉시 복귀해 버리는 충돌을 막기 위해, 수동 이동 시 **1.5초간 어그로 인지를 임시 정지**시키는 쿨다운 장치 탑재.
- [x] **모바일(스마트폰) 스펙 축소 리스케일링**
- 스마트폰 세로/가로 컴팩트 비율 가독성을 위해 지형 크기를 `50x50` $\rightarrow$ **`20x20`**으로 대폭 축소.
- 몬스터 이동 경로 포인트를 맵 안쪽 **`(±8.5, ±8.5)`** 스케일로 축소.
- 카메라 줌 조절 범위 **`5.0 ~ 18.0`** (기본 10.0), 스크롤 이탈 경계 **`(±8.0, ±8.0)`**, 유닛 탐지 반경 **`15.0m`**로 소형 맵 최적화 완료.
- [x] **원거리 유닛 3D 발사체 이펙트 시스템 탑재**
- 원거리 유도 비행 발사체 엔티티 스크립트 작성 (`src/entities/units/projectile.gd`).
- Ranger와 Archmage 공격 시, 즉발 대미지 대신 해당 유도 탄환을 어깨 높이에서 생성 발사하며 충돌 시점에 대미지가 연산되도록 구현.
- 유닛 스펙 컬러에 동기화되는 빛 구슬 이펙트 머티리얼 적용. 컴파일 순환 참조 오류 우회를 위해 프리로드 동적 주입 기법 적용.
- [x] **RTS 조작 편의성 추가 (Shift 선택)**
- `UnitController` 선택 루프 시 `Input.is_key_pressed(KEY_SHIFT)`를 확인하여, Shift 입력 상태에서는 선택을 초기화하지 않고 신규 대상을 중복 없이 `append`하는 추가/누적 선택 시스템 구현.
---
## ▣ 향후 계획 (Next Steps)
### [ ] 9단계: 사운드 피드백 및 3D 이펙트 연출 보강
- [ ] 몬스터 피격 시 간단한 3D 파티클 스파크 연출 구현
- [ ] 유닛 소환, 몬스터 타격, 사망, 스테이지 클리어 시 2D/3D 효과 오디오 플레이어 추가
- [ ] 원거리 유닛(Archmage 등)의 마법 광역 빔 또는 낙하 이펙트 추가
### [ ] 10단계: 밸런싱 및 게임 모드 세분화
- [ ] 라운드 증가에 따른 몬스터 외관 다형화 (크기 변형, 색상 변형)
- [ ] 유닛 판매(환급) 및 유닛 합성(조합) 업그레이드 시스템 기반 설계
- [ ] 웨이브 시작 수동 조절 모드 및 배속 플레이 기능 도입
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
[ext_resource type="Script" uid="uid://dd1xgi6hnpuvs" path="res://src/core/rts_camera.gd" id="1_rts_cam"] [ext_resource type="Script" uid="uid://dd1xgi6hnpuvs" path="res://src/core/rts_camera.gd" id="1_rts_cam"]
[ext_resource type="Script" uid="uid://cq7r51whh6po" path="res://src/core/stage_manager.gd" id="2_stage_mgr"] [ext_resource type="Script" uid="uid://cq7r51whh6po" path="res://src/core/stage_manager.gd" id="2_stage_mgr"]
[ext_resource type="PackedScene" path="res://src/entities/monsters/base_monster.tscn" id="3_base_monster"] [ext_resource type="PackedScene" uid="uid://bslqp550s3j0s" path="res://src/entities/monsters/base_monster.tscn" id="3_base_monster"]
[ext_resource type="Script" uid="uid://dkplkt6q8e3b5" path="res://src/core/unit_controller.gd" id="4_unit_ctrl"] [ext_resource type="Script" uid="uid://dkplkt6q8e3b5" path="res://src/core/unit_controller.gd" id="4_unit_ctrl"]
[ext_resource type="PackedScene" uid="uid://bqga1rvcjyydy" path="res://src/ui/hud.tscn" id="5_hud"] [ext_resource type="PackedScene" uid="uid://bqga1rvcjyydy" path="res://src/ui/hud.tscn" id="5_hud"]
......
...@@ -21,6 +21,11 @@ var _auto_target_cooldown: float = 0.0 ...@@ -21,6 +21,11 @@ var _auto_target_cooldown: float = 0.0
@onready var _selection_ring: MeshInstance3D = $SelectionRing @onready var _selection_ring: MeshInstance3D = $SelectionRing
@onready var _visual_mesh: MeshInstance3D = $VisualMesh @onready var _visual_mesh: MeshInstance3D = $VisualMesh
# 3D 모델 및 애니메이션 연동 변수 (정적 타이핑 캐싱)
var _model_instance: Node3D = null
var _model_anim_player: AnimationPlayer = null
var _last_anim_state: String = ""
func _ready() -> void: func _ready() -> void:
# 유닛 그룹에 등록 # 유닛 그룹에 등록
add_to_group("units") add_to_group("units")
...@@ -36,6 +41,17 @@ func _ready() -> void: ...@@ -36,6 +41,17 @@ func _ready() -> void:
if unit_data: if unit_data:
_apply_unit_style() _apply_unit_style()
# 씬 트리에 조립된 Striker 3D 모델 탐색 및 초기화
var striker_node: Node3D = get_node_or_null("striker") as Node3D
if striker_node:
_model_instance = striker_node
_apply_striker_skin(_model_instance)
_model_anim_player = _find_animation_player(_model_instance)
if _model_anim_player:
print("BaseUnit: Linked pre-configured striker model & AnimationPlayer.")
func _physics_process(delta: float) -> void: func _physics_process(delta: float) -> void:
if GameManager.is_game_over: if GameManager.is_game_over:
velocity = Vector3.ZERO velocity = Vector3.ZERO
...@@ -61,6 +77,9 @@ func _physics_process(delta: float) -> void: ...@@ -61,6 +77,9 @@ func _physics_process(delta: float) -> void:
else: else:
_process_normal_logic(delta) _process_normal_logic(delta)
# 4. 3D 애니메이션 재생 업데이트
_update_animations()
## 1. 공격 타겟이 존재할 때의 동작 로직 ## 1. 공격 타겟이 존재할 때의 동작 로직
func _process_attack_logic(_delta: float) -> void: func _process_attack_logic(_delta: float) -> void:
if not _target_monster: if not _target_monster:
...@@ -200,3 +219,54 @@ func deselect() -> void: ...@@ -200,3 +219,54 @@ func deselect() -> void:
is_selected = false is_selected = false
if _selection_ring: if _selection_ring:
_selection_ring.visible = false _selection_ring.visible = false
## FBX 모델의 자식 메쉬에 striker_0.png 텍스처를 씌우는 재귀 함수
func _apply_striker_skin(node: Node) -> void:
if node is MeshInstance3D:
var mesh_inst: MeshInstance3D = node as MeshInstance3D
var texture: Texture2D = load("res://assets/models/units/striker_0.png") as Texture2D
if texture:
var mat: StandardMaterial3D = StandardMaterial3D.new()
mat.albedo_texture = texture
mat.roughness = 0.5
mesh_inst.material_override = mat
for child: Node in node.get_children():
_apply_striker_skin(child)
## 모델 자식 노드 중 AnimationPlayer 탐색
func _find_animation_player(node: Node) -> AnimationPlayer:
if node is AnimationPlayer:
return node as AnimationPlayer
for child: Node in node.get_children():
var res: AnimationPlayer = _find_animation_player(child)
if res:
return res
return null
## 상태(대기, 이동, 공격)별 3D 애니메이션 트랙 제어
func _update_animations() -> void:
if not _model_anim_player:
return
# 사용할 애니메이션 클립 이름
var anim_name: String = "Armature|Armature|Basic_Jump|baselayer"
if not _model_anim_player.has_animation(anim_name):
return
# 1. 공격 중 상태: 공격 타이머 차감 후 쿨타임 구간의 초입 0.4초간은 공격 애니메이션 우선 재생
# Striker의 공격 속도(cooldown)는 0.5초이므로, 공격 직후 0.4초 동안 2.5배속으로 순간 재생하면 활기찬 펀칭 타격을 표현함
if _attack_timer > (unit_data.cooldown - 0.4):
if _last_anim_state != "attack":
_last_anim_state = "attack"
_model_anim_player.play(anim_name, -1, 2.5)
# 2. 이동 중 상태: 속도 벡터가 존재할 때 (이동 상태)
elif velocity.length() > 0.1:
if _last_anim_state != "move":
_last_anim_state = "move"
_model_anim_player.play(anim_name, -1, 1.5)
# 3. 대기(Idle) 상태: 정지 상태일 때 애니메이션 정지 처리 (T-Pose 상태로 대기)
else:
if _last_anim_state != "idle":
_last_anim_state = "idle"
_model_anim_player.stop()
[gd_scene format=3 uid="uid://dwvbc5m3myqbk"]
[ext_resource type="Script" uid="uid://daq4ko8ryry3w" path="res://src/entities/units/base_unit.gd" id="1_base_unit"]
[ext_resource type="PackedScene" uid="uid://ddqbm4y6brray" path="res://assets/models/units/striker.fbx" id="2_striker_fbx"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_body"]
radius = 0.4
height = 1.2
[sub_resource type="CapsuleMesh" id="CapsuleMesh_unit"]
radius = 0.4
height = 1.2
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ring"]
albedo_color = Color(0.098039, 0.901961, 0.098039, 1)
emission_enabled = true
emission = Color(0.098039, 0.901961, 0.098039, 1)
emission_energy_multiplier = 2.0
[sub_resource type="TorusMesh" id="TorusMesh_selection"]
material = SubResource("StandardMaterial3D_ring")
inner_radius = 0.65
outer_radius = 0.7
[node name="StrikerUnit" type="CharacterBody3D" unique_id=1844747242]
script = ExtResource("1_base_unit")
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=332342782]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.6, 0)
shape = SubResource("CapsuleShape3D_body")
[node name="VisualMesh" type="MeshInstance3D" parent="." unique_id=758090532]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.6, 0)
visible = false
mesh = SubResource("CapsuleMesh_unit")
[node name="SelectionRing" type="MeshInstance3D" parent="." unique_id=1378906746]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.02, 0)
mesh = SubResource("TorusMesh_selection")
[node name="NavigationAgent3D" type="NavigationAgent3D" parent="." unique_id=462079662]
[node name="striker" parent="." unique_id=1562791213 instance=ExtResource("2_striker_fbx")]
...@@ -55,7 +55,7 @@ func _initialize_unit_templates() -> void: ...@@ -55,7 +55,7 @@ func _initialize_unit_templates() -> void:
# 2. Guardian (밸런스형 근접 유닛) # 2. Guardian (밸런스형 근접 유닛)
var guardian: UnitData = UnitData.new() var guardian: UnitData = UnitData.new()
guardian.unit_id = "Unit_Guardian" guardian.unit_id = "Unit_Striker"
guardian.unit_name = "Guardian" guardian.unit_name = "Guardian"
guardian.damage = 25.0 guardian.damage = 25.0
guardian.cooldown = 1.0 guardian.cooldown = 1.0
...@@ -67,7 +67,7 @@ func _initialize_unit_templates() -> void: ...@@ -67,7 +67,7 @@ func _initialize_unit_templates() -> void:
# 3. Ranger (원거리 유닛) # 3. Ranger (원거리 유닛)
var ranger: UnitData = UnitData.new() var ranger: UnitData = UnitData.new()
ranger.unit_id = "Unit_Ranger" ranger.unit_id = "Unit_Striker"
ranger.unit_name = "Ranger" ranger.unit_name = "Ranger"
ranger.damage = 18.0 ranger.damage = 18.0
ranger.cooldown = 0.8 ranger.cooldown = 0.8
...@@ -79,7 +79,7 @@ func _initialize_unit_templates() -> void: ...@@ -79,7 +79,7 @@ func _initialize_unit_templates() -> void:
# 4. Slayer (고화력 대형 유닛) # 4. Slayer (고화력 대형 유닛)
var slayer: UnitData = UnitData.new() var slayer: UnitData = UnitData.new()
slayer.unit_id = "Unit_Slayer" slayer.unit_id = "Unit_Striker"
slayer.unit_name = "Slayer" slayer.unit_name = "Slayer"
slayer.damage = 55.0 slayer.damage = 55.0
slayer.cooldown = 1.8 slayer.cooldown = 1.8
...@@ -91,7 +91,7 @@ func _initialize_unit_templates() -> void: ...@@ -91,7 +91,7 @@ func _initialize_unit_templates() -> void:
# 5. Archmage (극단적 극딜 마법 유닛) # 5. Archmage (극단적 극딜 마법 유닛)
var archmage: UnitData = UnitData.new() var archmage: UnitData = UnitData.new()
archmage.unit_id = "Unit_Archmage" archmage.unit_id = "Unit_Striker"
archmage.unit_name = "Archmage" archmage.unit_name = "Archmage"
archmage.damage = 110.0 archmage.damage = 110.0
archmage.cooldown = 3.0 archmage.cooldown = 3.0
...@@ -136,8 +136,14 @@ func _on_spawn_pressed() -> void: ...@@ -136,8 +136,14 @@ func _on_spawn_pressed() -> void:
var random_index: int = randi() % _unit_templates.size() var random_index: int = randi() % _unit_templates.size()
var chosen_template: UnitData = _unit_templates[random_index] var chosen_template: UnitData = _unit_templates[random_index]
# 유닛 객체 스폰 # 유닛 객체 스폰 (Striker 전용 씬 분기 분할)
var new_unit: Node = base_unit_scene.instantiate() var target_scene: PackedScene = base_unit_scene
if chosen_template.unit_id == "Unit_Striker":
var striker_scene: PackedScene = load("res://src/entities/units/striker/striker_unit.tscn") as PackedScene
if striker_scene:
target_scene = striker_scene
var new_unit: Node = target_scene.instantiate()
var parent_scene: Node = get_tree().current_scene var parent_scene: Node = get_tree().current_scene
# 맵 중앙 스폰 존(0, 0) 기준 분산 스폰 좌표 지정 # 맵 중앙 스폰 존(0, 0) 기준 분산 스폰 좌표 지정
......
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