Commit 62e164aa authored by devourer3's avatar devourer3

궁수 투사체 추가

parent f6b882c8
File added
...@@ -4,12 +4,12 @@ importer="scene" ...@@ -4,12 +4,12 @@ importer="scene"
importer_version=1 importer_version=1
type="PackedScene" type="PackedScene"
uid="uid://7aw71ibtccuf" uid="uid://7aw71ibtccuf"
path="res://.godot/imported/azure_blade.fbx-9ebf095140efea605d7beb551dad626d.scn" path="res://.godot/imported/azure_blade.fbx-df4f6fe2f2dcc72b74121c873372fc08.scn"
[deps] [deps]
source_file="res://assets/models/items/weapons/azure_blade.fbx" source_file="res://assets/models/items/weapons/azure_blade/azure_blade.fbx"
dest_files=["res://.godot/imported/azure_blade.fbx-9ebf095140efea605d7beb551dad626d.scn"] dest_files=["res://.godot/imported/azure_blade.fbx-df4f6fe2f2dcc72b74121c873372fc08.scn"]
[params] [params]
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://fye2cyrwwe8o" uid="uid://fye2cyrwwe8o"
path.s3tc="res://.godot/imported/azure_blade_0.png-af3f8658a30e83ce1363b6d1a8c68487.s3tc.ctex" path.s3tc="res://.godot/imported/azure_blade_0.png-e786c7c26e42d49835430751d6064f5d.s3tc.ctex"
metadata={ metadata={
"imported_formats": ["s3tc_bptc"], "imported_formats": ["s3tc_bptc"],
"vram_texture": true "vram_texture": true
...@@ -14,8 +14,8 @@ generator_parameters={ ...@@ -14,8 +14,8 @@ generator_parameters={
[deps] [deps]
source_file="res://assets/models/items/weapons/azure_blade_0.png" source_file="res://assets/models/items/weapons/azure_blade/azure_blade_0.png"
dest_files=["res://.godot/imported/azure_blade_0.png-af3f8658a30e83ce1363b6d1a8c68487.s3tc.ctex"] dest_files=["res://.godot/imported/azure_blade_0.png-e786c7c26e42d49835430751d6064f5d.s3tc.ctex"]
[params] [params]
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://c3qqd8c1bgy5j" uid="uid://c3qqd8c1bgy5j"
path.s3tc="res://.godot/imported/azure_blade_1.png-2df52b6429f1065c06ccd2649e87aaec.s3tc.ctex" path.s3tc="res://.godot/imported/azure_blade_1.png-94b3ef07fb9efbadb1671d84a6dd2216.s3tc.ctex"
metadata={ metadata={
"imported_formats": ["s3tc_bptc"], "imported_formats": ["s3tc_bptc"],
"vram_texture": true "vram_texture": true
...@@ -14,8 +14,8 @@ generator_parameters={ ...@@ -14,8 +14,8 @@ generator_parameters={
[deps] [deps]
source_file="res://assets/models/items/weapons/azure_blade_1.png" source_file="res://assets/models/items/weapons/azure_blade/azure_blade_1.png"
dest_files=["res://.godot/imported/azure_blade_1.png-2df52b6429f1065c06ccd2649e87aaec.s3tc.ctex"] dest_files=["res://.godot/imported/azure_blade_1.png-94b3ef07fb9efbadb1671d84a6dd2216.s3tc.ctex"]
[params] [params]
......
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://uyra5vsbtakn"
path="res://.godot/imported/arrow.glb-1021954cfc8244ae2ad40b00b87d5323.scn"
[deps]
source_file="res://assets/models/items/weapons/azure_bow/arrow.glb"
dest_files=["res://.godot/imported/arrow.glb-1021954cfc8244ae2ad40b00b87d5323.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=false
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={}
gltf/naming_version=2
gltf/embedded_image_handling=1
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ich0m8enn06g"
path.s3tc="res://.godot/imported/arrow_0.jpg-64c6e32502091e099449bd2e131b1bb8.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
generator_parameters={
"md5": "3cc39ffa68bdc9f5375f2e8d36dbb700"
}
[deps]
source_file="res://assets/models/items/weapons/azure_bow/arrow_0.jpg"
dest_files=["res://.godot/imported/arrow_0.jpg-64c6e32502091e099449bd2e131b1bb8.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
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://bpbiksphmyw04"
path="res://.godot/imported/bow.glb-1784fd364296aebc0cced3fd64035baa.scn"
[deps]
source_file="res://assets/models/items/weapons/azure_bow/bow.glb"
dest_files=["res://.godot/imported/bow.glb-1784fd364296aebc0cced3fd64035baa.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=false
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={}
gltf/naming_version=2
gltf/embedded_image_handling=1
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://byow3khisasa"
path.s3tc="res://.godot/imported/bow_0.jpg-7f9db53893217b91e458f6561b73405a.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
generator_parameters={
"md5": "04380a0e0b68e8030ba518cede60b7c1"
}
[deps]
source_file="res://assets/models/items/weapons/azure_bow/bow_0.jpg"
dest_files=["res://.godot/imported/bow_0.jpg-7f9db53893217b91e458f6561b73405a.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
...@@ -4,12 +4,12 @@ importer="scene" ...@@ -4,12 +4,12 @@ importer="scene"
importer_version=1 importer_version=1
type="PackedScene" type="PackedScene"
uid="uid://bablnjk1ikcpt" uid="uid://bablnjk1ikcpt"
path="res://.godot/imported/azure_spear.fbx-32e680e5dbbe4f07d2a1f889ac900890.scn" path="res://.godot/imported/azure_spear.fbx-cac24b0901afcb241e6f8f8927c096fe.scn"
[deps] [deps]
source_file="res://assets/models/items/weapons/azure_spear.fbx" source_file="res://assets/models/items/weapons/azure_spear/azure_spear.fbx"
dest_files=["res://.godot/imported/azure_spear.fbx-32e680e5dbbe4f07d2a1f889ac900890.scn"] dest_files=["res://.godot/imported/azure_spear.fbx-cac24b0901afcb241e6f8f8927c096fe.scn"]
[params] [params]
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://cegmt45qy38d" uid="uid://cegmt45qy38d"
path.s3tc="res://.godot/imported/azure_spear_0.png-6873451d106d749e3d1fc23442844dbb.s3tc.ctex" path.s3tc="res://.godot/imported/azure_spear_0.png-a27c090607ed9636023ae1ea42bc7b3e.s3tc.ctex"
metadata={ metadata={
"imported_formats": ["s3tc_bptc"], "imported_formats": ["s3tc_bptc"],
"vram_texture": true "vram_texture": true
...@@ -14,8 +14,8 @@ generator_parameters={ ...@@ -14,8 +14,8 @@ generator_parameters={
[deps] [deps]
source_file="res://assets/models/items/weapons/azure_spear_0.png" source_file="res://assets/models/items/weapons/azure_spear/azure_spear_0.png"
dest_files=["res://.godot/imported/azure_spear_0.png-6873451d106d749e3d1fc23442844dbb.s3tc.ctex"] dest_files=["res://.godot/imported/azure_spear_0.png-a27c090607ed9636023ae1ea42bc7b3e.s3tc.ctex"]
[params] [params]
......
File added
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
[ext_resource type="Script" uid="uid://daq4ko8ryry3w" path="res://src/entities/units/base_unit.gd" id="1_lmwk6"] [ext_resource type="Script" uid="uid://daq4ko8ryry3w" path="res://src/entities/units/base_unit.gd" id="1_lmwk6"]
[ext_resource type="PackedScene" uid="uid://dh00osxx32ll" path="res://src/entities/units/guardian/guardian.tscn" id="2_soqit"] [ext_resource type="PackedScene" uid="uid://dh00osxx32ll" path="res://src/entities/units/guardian/guardian.tscn" id="2_soqit"]
[ext_resource type="PackedScene" uid="uid://bablnjk1ikcpt" path="res://assets/models/items/weapons/azure_spear.fbx" id="3_pm8px"] [ext_resource type="PackedScene" uid="uid://bablnjk1ikcpt" path="res://assets/models/items/weapons/azure_spear/azure_spear.fbx" id="3_pm8px"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_body"] [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_body"]
radius = 0.4 radius = 0.4
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
[ext_resource type="Script" uid="uid://daq4ko8ryry3w" path="res://src/entities/units/base_unit.gd" id="1_base_unit"] [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://c2aiejcidlihv" path="res://src/entities/units/striker/striker.tscn" id="2_iytoq"] [ext_resource type="PackedScene" uid="uid://c2aiejcidlihv" path="res://src/entities/units/striker/striker.tscn" id="2_iytoq"]
[ext_resource type="PackedScene" uid="uid://7aw71ibtccuf" path="res://assets/models/items/weapons/azure_blade.fbx" id="3_weapon"] [ext_resource type="PackedScene" uid="uid://7aw71ibtccuf" path="res://assets/models/items/weapons/azure_blade/azure_blade.fbx" id="3_weapon"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_body"] [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_body"]
radius = 0.4 radius = 0.4
......
File added
...@@ -44,19 +44,22 @@ func _ready() -> void: ...@@ -44,19 +44,22 @@ func _ready() -> void:
print("BaseMonster: Linked model AnimationPlayer: ", detected_model_name) print("BaseMonster: Linked model AnimationPlayer: ", detected_model_name)
# 모델명_animations.tres 라이브러리를 동적으로 로드하여 등록 (base_unit.gd 방식 준수) # 모델명_animations.tres 라이브러리를 동적으로 로드하여 등록 (base_unit.gd 방식 준수)
# 단, 씬 파일에서 이미 기본 라이브러리("")로 등록된 경우 중복 등록 방지
var anim_lib_path: String = "res://assets/models/monsters/" + detected_model_name + "/" + detected_model_name + "_animations.tres" var anim_lib_path: String = "res://assets/models/monsters/" + detected_model_name + "/" + detected_model_name + "_animations.tres"
if ResourceLoader.exists(anim_lib_path): if ResourceLoader.exists(anim_lib_path):
var lib: AnimationLibrary = load(anim_lib_path) as AnimationLibrary var lib: AnimationLibrary = load(anim_lib_path) as AnimationLibrary
if lib: if lib and not _model_anim_player.has_animation_library(detected_model_name):
_model_anim_player.add_animation_library(detected_model_name, lib) _model_anim_player.add_animation_library(detected_model_name, lib)
print("BaseMonster: Loaded and registered ", detected_model_name, " animations library successfully.") print("BaseMonster: Loaded and registered ", detected_model_name, " animations library successfully.")
else:
print("BaseMonster: Animation library already present (scene-bound or already added).")
# 초기 Idle 애니메이션 재생 시도 # 초기 Idle 애니메이션 재생 시도
var init_idle: String = detected_model_name + "/Idle" # 탐색 순서: 기본 라이브러리("") -> 동적 등록(모델명/) -> FBX 내장
if _model_anim_player.has_animation(init_idle): var init_idle: String = _resolve_anim_name(_model_anim_player, detected_model_name, "Idle")
if init_idle != "":
_model_anim_player.play(init_idle) _model_anim_player.play(init_idle)
elif _model_anim_player.has_animation("Armature|Idle"): print("BaseMonster: Playing initial Idle: ", init_idle)
_model_anim_player.play("Armature|Idle")
## 몬스터 자식 메쉬에 noble_man_0.png 텍스처를 입히는 재귀 함수 ## 몬스터 자식 메쉬에 noble_man_0.png 텍스처를 입히는 재귀 함수
func _apply_noble_man_skin(node: Node) -> void: func _apply_noble_man_skin(node: Node) -> void:
...@@ -169,18 +172,16 @@ func _update_monster_animation() -> void: ...@@ -169,18 +172,16 @@ func _update_monster_animation() -> void:
if not _model_anim_player: if not _model_anim_player:
return return
# 기본 트랙 이름 탐색 순서: 동적 로드된 라이브러리(noble_man/walking) -> FBX 내장 트랙(Armature|Walking)
var anim_name: String = ""
var model_name: String = "" var model_name: String = ""
if _model_instance: if _model_instance:
model_name = _model_instance.name.to_lower() model_name = _model_instance.name
var lib_walking: String = model_name + "/Walking"
if _model_anim_player.has_animation(lib_walking):
anim_name = lib_walking
elif _model_anim_player.has_animation("Armature|Walking"):
anim_name = "Armature|Walking"
# 걷기 애니메이션 탐색 순서:
# 1. 씬 바인딩 기본 라이브러리("") -> "Walking"
# 2. 동적 등록 라이브러리 -> "모델명/Walking"
# 3. FBX 내장 트랙 -> "Armature|Walking"
var anim_name: String = _resolve_anim_name(_model_anim_player, model_name, "Walking")
if anim_name != "": if anim_name != "":
# 걷는 애니메이션 루프 활성화 # 걷는 애니메이션 루프 활성화
var anim: Animation = _model_anim_player.get_animation(anim_name) var anim: Animation = _model_anim_player.get_animation(anim_name)
...@@ -190,6 +191,7 @@ func _update_monster_animation() -> void: ...@@ -190,6 +191,7 @@ func _update_monster_animation() -> void:
if _last_anim_state != "walking": if _last_anim_state != "walking":
_last_anim_state = "walking" _last_anim_state = "walking"
_model_anim_player.play(anim_name, -1, 1.2) # 1.2배속 재생 _model_anim_player.play(anim_name, -1, 1.2) # 1.2배속 재생
print("BaseMonster: Playing Walk animation: ", anim_name)
## 헬퍼: 자식 노드 중 AnimationPlayer 탐색 ## 헬퍼: 자식 노드 중 AnimationPlayer 탐색
func _find_animation_player(node: Node) -> AnimationPlayer: func _find_animation_player(node: Node) -> AnimationPlayer:
...@@ -200,3 +202,19 @@ func _find_animation_player(node: Node) -> AnimationPlayer: ...@@ -200,3 +202,19 @@ func _find_animation_player(node: Node) -> AnimationPlayer:
if res: if res:
return res return res
return null return null
## 헬퍼: 애니메이션 이름 탐색 (기본 라이브러리 -> 모델명 라이브러리 -> FBX 내장 순)
## anim_base_name: "Walking", "Idle", "Running" 등 라이브러리 키 없는 기본 이름
func _resolve_anim_name(player: AnimationPlayer, model_name: String, anim_base_name: String) -> String:
# 1순위: 씬에서 기본 라이브러리("")로 바인딩된 경우 (noble_man.tscn 방식)
if player.has_animation(anim_base_name):
return anim_base_name
# 2순위: 동적으로 등록된 모델명 라이브러리
var lib_anim: String = model_name + "/" + anim_base_name
if player.has_animation(lib_anim):
return lib_anim
# 3순위: FBX 내장 Armature| 접두사 트랙
var fbx_anim: String = "Armature|" + anim_base_name
if player.has_animation(fbx_anim):
return fbx_anim
return ""
...@@ -264,15 +264,28 @@ func _try_attack() -> void: ...@@ -264,15 +264,28 @@ func _try_attack() -> void:
# 데미지 적용 및 발사체 발사 처리 분기 # 데미지 적용 및 발사체 발사 처리 분기
if unit_data.is_ranged: if unit_data.is_ranged:
# 원거리 유닛: 즉발로 유도 발사체(Projectile) 생성하여 발사 # 원거리 유닛: 유도 발사체(Projectile) 생성하여 발사
var projectile_script = preload("res://src/entities/units/projectile.gd") var projectile_script = preload("res://src/entities/units/projectile.gd")
var proj: Node3D = projectile_script.new() as Node3D var proj: Node3D = projectile_script.new() as Node3D
proj.set("damage", unit_data.damage) proj.set("damage", unit_data.damage)
proj.set("target", _target_monster) proj.set("target", _target_monster)
proj.set("color", unit_data.unit_color) proj.set("color", unit_data.unit_color)
# 유닛 중심보다 높은 곳(어깨 높이)에서 발사 처리 # Ranger: arrow.glb 모델 화살 사용
proj.global_position = global_position + Vector3(0.0, 0.7, 0.0) var model_name: String = _model_instance.name.to_lower() if _model_instance else ""
if model_name == "ranger":
proj.set("use_arrow_model", true)
# 발사 위치: Ranger는 활이 장착된 LeftHand 본 위치에서, 나머지는 어깨 높이
var fire_position: Vector3 = global_position + Vector3(0.0, 0.7, 0.0)
if model_name == "ranger" and _model_instance:
var bow_attachment: BoneAttachment3D = UnitHelper.get_bone_attachment(
_model_instance, "LeftHandAttachment"
)
if bow_attachment:
fire_position = bow_attachment.global_position
proj.global_position = fire_position
get_tree().current_scene.add_child(proj) get_tree().current_scene.add_child(proj)
else: else:
# 근접 유닛: 공격 애니메이션 중간 즈음(락아웃 시간의 50% 지점)에 데미지 지연 적용 # 근접 유닛: 공격 애니메이션 중간 즈음(락아웃 시간의 50% 지점)에 데미지 지연 적용
......
...@@ -2,19 +2,64 @@ class_name Projectile ...@@ -2,19 +2,64 @@ class_name Projectile
extends Node3D extends Node3D
## 원거리 유닛이 발사하는 3D 발사체 개체 ## 원거리 유닛이 발사하는 3D 발사체 개체
## 지정된 대상을 향해 날아가며, 충돌 시 데미지를 가하고 소멸합니다. ## - Ranger: azure_bow/arrow.glb 모델을 사용한 화살
## - 기타: 기존 구체 빛 구슬 발사체
var speed: float = 24.0 var speed: float = 24.0
var damage: float = 0.0 var damage: float = 0.0
var target: Node3D = null var target: Node3D = null
var color: Color = Color(1.0, 0.9, 0.2, 1.0) # 기본 노란색 발사체 var color: Color = Color(1.0, 0.9, 0.2, 1.0) # 기본 노란색 발사체
# 화살 모드 여부 (Ranger 전용)
var use_arrow_model: bool = false
# 화살 모델 경로
const ARROW_MODEL_PATH: String = "res://assets/models/items/weapons/azure_bow/arrow.glb"
const ARROW_TEXTURE_PATH: String = "res://assets/models/items/weapons/azure_bow/arrow_0.jpg"
func _ready() -> void: func _ready() -> void:
# 동적 비주얼 메쉬 생성 (구체 형태의 길쭉한 빛 구슬 표현) if use_arrow_model and ResourceLoader.exists(ARROW_MODEL_PATH):
_setup_arrow_visual()
else:
_setup_sphere_visual()
## 화살 모델 비주얼 설정
func _setup_arrow_visual() -> void:
var arrow_scene: PackedScene = load(ARROW_MODEL_PATH) as PackedScene
if not arrow_scene:
_setup_sphere_visual()
return
var arrow_inst: Node3D = arrow_scene.instantiate() as Node3D
# 화살 텍스처 적용 (재귀)
if ResourceLoader.exists(ARROW_TEXTURE_PATH):
var tex: Texture2D = load(ARROW_TEXTURE_PATH) as Texture2D
if tex:
_apply_texture_recursive(arrow_inst, tex)
# 화살 스케일 및 초기 회전 (모델 좌표계에 따라 조정)
arrow_inst.scale = Vector3(0.333, 0.333, 0.333)
add_child(arrow_inst)
## 텍스처 재귀 적용
func _apply_texture_recursive(node: Node, texture: Texture2D) -> void:
if node is MeshInstance3D:
var mesh_inst: MeshInstance3D = node as MeshInstance3D
var mat: StandardMaterial3D = StandardMaterial3D.new()
mat.albedo_texture = texture
mat.roughness = 0.4
mat.metallic = 0.3
mesh_inst.material_override = mat
for child: Node in node.get_children():
_apply_texture_recursive(child, texture)
## 구체 빛 구슬 비주얼 설정 (Ranger 이외 유닛 또는 arrow 로드 실패 시 Fallback)
func _setup_sphere_visual() -> void:
var mesh_instance: MeshInstance3D = MeshInstance3D.new() var mesh_instance: MeshInstance3D = MeshInstance3D.new()
var sphere_mesh: SphereMesh = SphereMesh.new() var sphere_mesh: SphereMesh = SphereMesh.new()
sphere_mesh.radius = 0.15 sphere_mesh.radius = 0.12
sphere_mesh.height = 0.4 sphere_mesh.height = 0.35
var mat: StandardMaterial3D = StandardMaterial3D.new() var mat: StandardMaterial3D = StandardMaterial3D.new()
mat.albedo_color = color mat.albedo_color = color
...@@ -41,17 +86,20 @@ func _physics_process(delta: float) -> void: ...@@ -41,17 +86,20 @@ func _physics_process(delta: float) -> void:
if target.has_node("VisualMesh"): if target.has_node("VisualMesh"):
var vm: Node3D = target.get_node("VisualMesh") as Node3D var vm: Node3D = target.get_node("VisualMesh") as Node3D
target_center.y = vm.global_position.y target_center.y = vm.global_position.y
else:
target_center.y += 0.5
# 방향 계산 및 이동 # 방향 계산 및 이동
var dir: Vector3 = (target_center - global_position).normalized() var dir: Vector3 = (target_center - global_position).normalized()
global_position += dir * speed * delta global_position += dir * speed * delta
# 비행 방향 바라보기 # 비행 방향 바라보기 (화살이 날아가는 방향으로 정렬)
if global_position.distance_to(target_center) > 0.1: var dist_to_target: float = global_position.distance_to(target_center)
if dist_to_target > 0.1:
look_at(target_center, Vector3.UP) look_at(target_center, Vector3.UP)
# 타겟 근접 도달 판정 (0.6m 이내) # 타겟 근접 도달 판정 (0.6m 이내)
if global_position.distance_to(target_center) < 0.6: if dist_to_target < 0.6:
_impact() _impact()
## 적에게 충돌하여 데미지 가함 ## 적에게 충돌하여 데미지 가함
......
...@@ -44,7 +44,7 @@ static func find_skeleton(node: Node) -> Skeleton3D: ...@@ -44,7 +44,7 @@ static func find_skeleton(node: Node) -> Skeleton3D:
return res return res
return null return null
## 유닛의 오른손에 지정된 무기 모델을 런타임 장착 ## 유닛 종류에 따라 해당 본에 무기 모델을 런타임 장착
static func attach_weapon(model_instance: Node3D, unit_data: UnitData) -> void: static func attach_weapon(model_instance: Node3D, unit_data: UnitData) -> void:
if not model_instance or not unit_data: if not model_instance or not unit_data:
return return
...@@ -54,40 +54,55 @@ static func attach_weapon(model_instance: Node3D, unit_data: UnitData) -> void: ...@@ -54,40 +54,55 @@ static func attach_weapon(model_instance: Node3D, unit_data: UnitData) -> void:
if not skeleton: if not skeleton:
return return
# 오른손 본("RightHand")이 존재하는지 확인 # 유닛 종류에 따른 무기 설정 분기
var bone_idx: int = skeleton.find_bone("RightHand") var bone_name: String = "RightHand"
var attachment_name: String = "RightHandAttachment"
var weapon_scene_path: String = ""
var weapon_scale: Vector3 = Vector3.ONE
var weapon_rotation: Vector3 = Vector3.ZERO
var weapon_position: Vector3 = Vector3.ZERO
var texture_path: String = ""
if unit_data.unit_id == "Unit_Guardian":
# 가디언: 오른손에 창 (azure_spear)
bone_name = "RightHand"
attachment_name = "RightHandAttachment"
weapon_scene_path = "res://assets/models/items/weapons/azure_spear/azure_spear.fbx"
texture_path = "res://assets/models/items/weapons/azure_spear/azure_spear_0.png"
elif unit_data.unit_id == "Unit_Striker":
# 스트라이커: 오른손에 검 (azure_blade)
bone_name = "RightHand"
attachment_name = "RightHandAttachment"
weapon_scene_path = "res://assets/models/items/weapons/azure_blade/azure_blade.fbx"
texture_path = "res://assets/models/items/weapons/azure_blade/azure_blade_0.png"
elif unit_data.unit_id == "Unit_Ranger":
# 레인저: 왼손에 활 (azure_bow/bow)
bone_name = "LeftHand"
attachment_name = "LeftHandAttachment"
weapon_scene_path = "res://assets/models/items/weapons/azure_bow/bow.glb"
texture_path = "res://assets/models/items/weapons/azure_bow/bow_0.jpg"
# 활을 왼손에 자연스럽게 맞추는 회전/위치 오프셋
weapon_rotation = Vector3(0.0, 90.0, 0.0)
weapon_scale = Vector3(0.8, 0.8, 0.8)
# 지정된 본이 존재하는지 확인
var bone_idx: int = skeleton.find_bone(bone_name)
if bone_idx == -1: if bone_idx == -1:
print("UnitHelper: RightHand bone not found in skeleton.") print("UnitHelper: Bone '", bone_name, "' not found in skeleton.")
return return
# 이미 장착된 무기가 있는지 확인하여 제거 (중복 장착 방지) # 이미 장착된 무기가 있는지 확인하여 제거 (중복 장착 방지)
var existing_attachment: Node = skeleton.get_node_or_null("RightHandAttachment") var existing_attachment: Node = skeleton.get_node_or_null(attachment_name)
if existing_attachment: if existing_attachment:
existing_attachment.queue_free() existing_attachment.queue_free()
# 1. BoneAttachment3D 노드 동적 생성 및 본 동기화 설정 # BoneAttachment3D 노드 동적 생성 및 본 동기화 설정
var bone_attachment: BoneAttachment3D = BoneAttachment3D.new() var bone_attachment: BoneAttachment3D = BoneAttachment3D.new()
bone_attachment.name = "RightHandAttachment" bone_attachment.name = attachment_name
bone_attachment.bone_name = "RightHand" bone_attachment.bone_name = bone_name
skeleton.add_child(bone_attachment) skeleton.add_child(bone_attachment)
# 2. 유닛 종류에 따른 무기 설정 분기 # 무기 로드 및 인스턴스화
var weapon_scene_path: String = ""
var weapon_scale: Vector3 = Vector3.ONE
var weapon_rotation: Vector3 = Vector3.ZERO
var weapon_position: Vector3 = Vector3.ZERO
var texture_path: String = ""
if unit_data.unit_id == "Unit_Guardian":
# 가디언 창 (azure_spear)
weapon_scene_path = "res://assets/models/items/weapons/azure_spear.fbx"
texture_path = "res://assets/models/items/weapons/azure_spear_0.png"
elif unit_data.unit_id == "Unit_Striker":
# 기본값: Striker 검 (azure_blade)
weapon_scene_path = "res://assets/models/items/weapons/azure_blade.fbx"
texture_path = "res://assets/models/items/weapons/azure_blade_0.png"
# 3. 무기 로드 및 인스턴스화
if ResourceLoader.exists(weapon_scene_path): if ResourceLoader.exists(weapon_scene_path):
var weapon_scene: PackedScene = load(weapon_scene_path) as PackedScene var weapon_scene: PackedScene = load(weapon_scene_path) as PackedScene
if weapon_scene: if weapon_scene:
...@@ -101,6 +116,13 @@ static func attach_weapon(model_instance: Node3D, unit_data: UnitData) -> void: ...@@ -101,6 +116,13 @@ static func attach_weapon(model_instance: Node3D, unit_data: UnitData) -> void:
bone_attachment.add_child(weapon_inst) bone_attachment.add_child(weapon_inst)
print("UnitHelper: Attached weapon successfully. Unit: ", unit_data.unit_name, ", Weapon: ", weapon_scene_path.get_file()) print("UnitHelper: Attached weapon successfully. Unit: ", unit_data.unit_name, ", Weapon: ", weapon_scene_path.get_file())
## 모델 스켈레톤에서 특정 BoneAttachment3D 노드를 반환 (발사 위치 계산 등 외부 활용)
static func get_bone_attachment(model_instance: Node3D, attachment_name: String) -> BoneAttachment3D:
var skeleton: Skeleton3D = find_skeleton(model_instance)
if not skeleton:
return null
return skeleton.get_node_or_null(attachment_name) as BoneAttachment3D
## 애니메이션 내에서 모델의 로컬 정면 방향을 강제 리셋하는 루트(Armature) 트랙 정화 ## 애니메이션 내에서 모델의 로컬 정면 방향을 강제 리셋하는 루트(Armature) 트랙 정화
static func clean_root_animation_tracks(anim_player: AnimationPlayer) -> void: static func clean_root_animation_tracks(anim_player: AnimationPlayer) -> void:
if not anim_player: if not anim_player:
......
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