211 lines
9.2 KiB
C#
211 lines
9.2 KiB
C#
|
|
using UnityEngine; // 유니티 엔진 기본 네임스페이스
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 무기(활, 화살 등)를 런타임에 손 뼈에 부착하는 컴포넌트
|
||
|
|
/// FBX에 포함된 무기는 애니메이션이 Transform을 덮어씌우므로
|
||
|
|
/// 반드시 별도 프리팹으로 분리 후 이 스크립트로 런타임 부착해야 함
|
||
|
|
/// Player 오브젝트에 부착하여 사용
|
||
|
|
/// </summary>
|
||
|
|
public class WeaponAttacher : MonoBehaviour // MonoBehaviour를 상속받아 컴포넌트로 사용
|
||
|
|
{
|
||
|
|
[Header("=== 활 설정 ===")] // 인스펙터에서 활 관련 섹션 구분
|
||
|
|
|
||
|
|
[Tooltip("활 프리팹 (Project에서 드래그)")] // 인스펙터 툴팁
|
||
|
|
[SerializeField] private GameObject bowPrefab; // 활 프리팹 참조 (인스펙터에서 할당)
|
||
|
|
|
||
|
|
[Tooltip("활을 붙일 뼈 이름")] // 인스펙터 툴팁
|
||
|
|
[SerializeField] private string bowBoneName = "RogueHooded_ArmLeft"; // 활을 부착할 뼈 이름
|
||
|
|
|
||
|
|
[Tooltip("활 위치 오프셋")] // 인스펙터 툴팁
|
||
|
|
[SerializeField] private Vector3 bowPositionOffset = Vector3.zero; // 활 위치 보정값
|
||
|
|
|
||
|
|
[Tooltip("활 회전 오프셋")] // 인스펙터 툴팁
|
||
|
|
[SerializeField] private Vector3 bowRotationOffset = Vector3.zero; // 활 회전 보정값
|
||
|
|
|
||
|
|
[Tooltip("활 크기 배율")] // 인스펙터 툴팁
|
||
|
|
[SerializeField] private Vector3 bowScale = Vector3.one; // 활 크기 보정값 (기본 1,1,1)
|
||
|
|
|
||
|
|
[Header("=== 화살 설정 ===")] // 인스펙터에서 화살 관련 섹션 구분
|
||
|
|
|
||
|
|
[Tooltip("화살 프리팹 (Project에서 드래그)")] // 인스펙터 툴팁
|
||
|
|
[SerializeField] private GameObject arrowPrefab; // 화살 프리팹 참조 (인스펙터에서 할당)
|
||
|
|
|
||
|
|
[Tooltip("화살을 붙일 뼈 이름 (활과 같은 손)")] // 인스펙터 툴팁
|
||
|
|
[SerializeField] private string arrowBoneName = "RogueHooded_ArmLeft"; // 화살을 부착할 뼈 이름
|
||
|
|
|
||
|
|
[Tooltip("화살 위치 오프셋")] // 인스펙터 툴팁
|
||
|
|
[SerializeField] private Vector3 arrowPositionOffset = Vector3.zero; // 화살 위치 보정값
|
||
|
|
|
||
|
|
[Tooltip("화살 회전 오프셋")] // 인스펙터 툴팁
|
||
|
|
[SerializeField] private Vector3 arrowRotationOffset = Vector3.zero; // 화살 회전 보정값
|
||
|
|
|
||
|
|
[Header("=== FBX 원본 무기 비활성화 ===")] // 인스펙터 섹션 구분
|
||
|
|
|
||
|
|
[Tooltip("FBX에 포함된 원본 활 오브젝트 이름 (자동으로 비활성화됨)")] // 인스펙터 툴팁
|
||
|
|
[SerializeField] private string originalBowObjectName = "bow.x"; // FBX 내 원본 활 이름
|
||
|
|
|
||
|
|
// --- 내부 변수 (캐싱용) ---
|
||
|
|
private GameObject bowInstance; // 생성된 활 인스턴스
|
||
|
|
private GameObject arrowInstance; // 생성된 화살 인스턴스
|
||
|
|
private Transform bowBone; // 활 부착 뼈 Transform 캐싱
|
||
|
|
private Transform arrowBone; // 화살 부착 뼈 Transform 캐싱
|
||
|
|
private bool isInitialized; // 초기화 완료 플래그
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 게임 시작 시 무기 초기화
|
||
|
|
/// </summary>
|
||
|
|
private void Start() // 게임 시작 시 1회 호출
|
||
|
|
{
|
||
|
|
Initialize(); // 무기 초기화 실행
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 무기 초기화 — FBX 원본 비활성화 + 프리팹 부착
|
||
|
|
/// </summary>
|
||
|
|
private void Initialize() // 전체 초기화를 담당하는 함수
|
||
|
|
{
|
||
|
|
// --- 1. FBX에 포함된 원본 활 비활성화 ---
|
||
|
|
DisableOriginalBow(); // 원본 활을 찾아서 꺼줌
|
||
|
|
|
||
|
|
// --- 2. 활 부착 ---
|
||
|
|
if (bowPrefab != null) // 활 프리팹이 할당되어 있으면
|
||
|
|
{
|
||
|
|
bowBone = FindBoneRecursive(transform, bowBoneName); // 활 부착할 뼈 탐색
|
||
|
|
|
||
|
|
if (bowBone != null) // 뼈를 찾았으면
|
||
|
|
{
|
||
|
|
bowInstance = Instantiate(bowPrefab, bowBone); // 활 프리팹을 뼈의 자식으로 생성
|
||
|
|
bowInstance.name = "Bow_Runtime"; // 인스턴스 이름 설정
|
||
|
|
bowInstance.transform.localPosition = bowPositionOffset; // 위치 오프셋 적용
|
||
|
|
bowInstance.transform.localRotation = Quaternion.Euler(bowRotationOffset); // 회전 오프셋 적용
|
||
|
|
bowInstance.transform.localScale = bowScale; // 크기 적용
|
||
|
|
|
||
|
|
Debug.Log($"[WeaponAttacher] 활이 '{bowBoneName}'에 부착되었습니다."); // 성공 로그
|
||
|
|
}
|
||
|
|
else // 뼈를 못 찾았으면
|
||
|
|
{
|
||
|
|
Debug.LogError($"[WeaponAttacher] '{bowBoneName}' 뼈를 찾을 수 없습니다!"); // 에러 로그
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// --- 3. 화살 부착 (기본 비활성화) ---
|
||
|
|
if (arrowPrefab != null) // 화살 프리팹이 할당되어 있으면
|
||
|
|
{
|
||
|
|
arrowBone = FindBoneRecursive(transform, arrowBoneName); // 화살 부착할 뼈 탐색
|
||
|
|
|
||
|
|
if (arrowBone != null) // 뼈를 찾았으면
|
||
|
|
{
|
||
|
|
arrowInstance = Instantiate(arrowPrefab, arrowBone); // 화살 프리팹을 뼈의 자식으로 생성
|
||
|
|
arrowInstance.name = "Arrow_Runtime"; // 인스턴스 이름 설정
|
||
|
|
arrowInstance.transform.localPosition = arrowPositionOffset; // 위치 오프셋 적용
|
||
|
|
arrowInstance.transform.localRotation = Quaternion.Euler(arrowRotationOffset); // 회전 오프셋 적용
|
||
|
|
arrowInstance.SetActive(false); // 기본 비활성화 (쏠 때만 보이게)
|
||
|
|
|
||
|
|
Debug.Log($"[WeaponAttacher] 화살이 '{arrowBoneName}'에 부착되었습니다 (비활성화 상태)."); // 성공 로그
|
||
|
|
}
|
||
|
|
else // 뼈를 못 찾았으면
|
||
|
|
{
|
||
|
|
Debug.LogError($"[WeaponAttacher] '{arrowBoneName}' 뼈를 찾을 수 없습니다!"); // 에러 로그
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
isInitialized = true; // 초기화 완료 표시
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// FBX에 포함된 원본 활 오브젝트를 찾아서 비활성화
|
||
|
|
/// 이걸 안 하면 원본 활이 T-포즈 위치에 고정된 채로 보임
|
||
|
|
/// </summary>
|
||
|
|
private void DisableOriginalBow() // 원본 활 비활성화 함수
|
||
|
|
{
|
||
|
|
if (string.IsNullOrEmpty(originalBowObjectName)) return; // 이름이 비어있으면 스킵
|
||
|
|
|
||
|
|
Transform originalBow = FindBoneRecursive(transform, originalBowObjectName); // 원본 활 탐색
|
||
|
|
|
||
|
|
if (originalBow != null) // 찾았으면
|
||
|
|
{
|
||
|
|
originalBow.gameObject.SetActive(false); // 비활성화
|
||
|
|
Debug.Log($"[WeaponAttacher] FBX 원본 '{originalBowObjectName}'를 비활성화했습니다."); // 로그
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 화살 표시 — 차징/발사 시작할 때 호출
|
||
|
|
/// </summary>
|
||
|
|
public void ShowArrow() // 외부에서 호출하여 화살 활성화
|
||
|
|
{
|
||
|
|
if (arrowInstance != null) // 화살 인스턴스가 유효하면
|
||
|
|
{
|
||
|
|
arrowInstance.SetActive(true); // 화살 보이게
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 화살 숨기기 — 발사 완료 후 호출
|
||
|
|
/// </summary>
|
||
|
|
public void HideArrow() // 외부에서 호출하여 화살 비활성화
|
||
|
|
{
|
||
|
|
if (arrowInstance != null) // 화살 인스턴스가 유효하면
|
||
|
|
{
|
||
|
|
arrowInstance.SetActive(false); // 화살 숨기기
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 활 표시/숨기기 (무기 교체 시스템에 사용 가능)
|
||
|
|
/// </summary>
|
||
|
|
/// <param name="show">true면 보이기, false면 숨기기</param>
|
||
|
|
public void SetBowVisible(bool show) // 활의 가시성을 제어하는 함수
|
||
|
|
{
|
||
|
|
if (bowInstance != null) // 활 인스턴스가 유효하면
|
||
|
|
{
|
||
|
|
bowInstance.SetActive(show); // 파라미터에 따라 활성화/비활성화
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 재귀적으로 자식 Transform을 탐색하여 이름이 일치하는 뼈를 찾음
|
||
|
|
/// </summary>
|
||
|
|
/// <param name="parent">탐색 시작할 부모 Transform</param>
|
||
|
|
/// <param name="boneName">찾을 뼈 이름</param>
|
||
|
|
/// <returns>찾은 뼈의 Transform, 없으면 null</returns>
|
||
|
|
private Transform FindBoneRecursive(Transform parent, string boneName) // 재귀 뼈 탐색 함수
|
||
|
|
{
|
||
|
|
if (parent.name == boneName) // 이름 일치하면
|
||
|
|
{
|
||
|
|
return parent; // 반환
|
||
|
|
}
|
||
|
|
|
||
|
|
for (int i = 0; i < parent.childCount; i++) // 모든 자식 순회
|
||
|
|
{
|
||
|
|
Transform found = FindBoneRecursive(parent.GetChild(i), boneName); // 재귀 탐색
|
||
|
|
|
||
|
|
if (found != null) // 찾았으면
|
||
|
|
{
|
||
|
|
return found; // 반환
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return null; // 못 찾으면 null
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 인스펙터에서 오프셋 변경 시 실시간 반영
|
||
|
|
/// </summary>
|
||
|
|
private void OnValidate() // 인스펙터 값 변경 시 자동 호출
|
||
|
|
{
|
||
|
|
if (bowInstance != null) // 활 인스턴스가 있으면
|
||
|
|
{
|
||
|
|
bowInstance.transform.localPosition = bowPositionOffset; // 활 위치 업데이트
|
||
|
|
bowInstance.transform.localRotation = Quaternion.Euler(bowRotationOffset); // 활 회전 업데이트
|
||
|
|
bowInstance.transform.localScale = bowScale; // 활 크기 업데이트
|
||
|
|
}
|
||
|
|
|
||
|
|
if (arrowInstance != null) // 화살 인스턴스가 있으면
|
||
|
|
{
|
||
|
|
arrowInstance.transform.localPosition = arrowPositionOffset; // 화살 위치 업데이트
|
||
|
|
arrowInstance.transform.localRotation = Quaternion.Euler(arrowRotationOffset); // 화살 회전 업데이트
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|