- WeaponTrait enum 추가 (Melee/TwoHanded/Defense/Magic/Ranged) - SkillData에 allowedWeaponTraits 필드 및 MatchesWeaponTrait() 검증 메서드 추가 - WeaponEquipment에 보조손(오프핸드) 슬롯 지원 (장착/해제/스탯 보너스/네트워크 동기화) - EquippedWeaponTraits 프로퍼티로 메인+보조 무기 특성 합산 제공 - PlayerSkillInput 4곳(OnSkillInput/RPC/CanUseSkill/DebugExecute)에 무기 trait 제약 검증 추가 - SkillSlotUI에 무기 불호환 시 회색 표시(weaponIncompatibleColor) 지원 - 기존 검 에셋에 weaponTrait=Melee 설정
663 lines
23 KiB
C#
663 lines
23 KiB
C#
using System;
|
|
|
|
using UnityEngine;
|
|
using Unity.Netcode;
|
|
|
|
using Colosseum.Stats;
|
|
|
|
namespace Colosseum.Weapons
|
|
{
|
|
/// <summary>
|
|
/// 무기 장착을 관리하는 컴포넌트.
|
|
/// 무기 장착 시 스탯 보너스를 적용하고 배율을 제공하며, 무기 외형을 표시합니다.
|
|
/// 메시 이름으로 소켓을 자동 검색합니다.
|
|
/// </summary>
|
|
public class WeaponEquipment : NetworkBehaviour
|
|
{
|
|
[Header("References")]
|
|
[Tooltip("CharacterStats 컴포넌트 (없으면 자동 검색)")]
|
|
[SerializeField] private CharacterStats characterStats;
|
|
|
|
[Header("Socket Names (메시 이름)")]
|
|
[Tooltip("오른손 메시 이름")]
|
|
[SerializeField] private string rightHandName = "Hand_R";
|
|
[Tooltip("왼손 메시 이름")]
|
|
[SerializeField] private string leftHandName = "Hand_L";
|
|
[Tooltip("등 메시 이름")]
|
|
[SerializeField] private string backName = "Spine";
|
|
[Tooltip("허리 메시 이름")]
|
|
[SerializeField] private string hipName = "Hip";
|
|
[Tooltip("양손 메시 이름 (기본값: 오른손 사용)")]
|
|
[SerializeField] private string twoHandedName = "";
|
|
|
|
[Header("Starting Weapon")]
|
|
[Tooltip("시작 무기 (선택)")]
|
|
[SerializeField] private WeaponData startingWeapon;
|
|
|
|
[Header("Starting Offhand")]
|
|
[Tooltip("시작 보조 무기 (선택)")]
|
|
[SerializeField] private WeaponData startingOffhandWeapon;
|
|
|
|
[Header("네트워크 동기화")]
|
|
[Tooltip("이 장착 시스템이 사용하는 모든 WeaponData 목록. 서버→클라이언트 무기 동기화에 사용됩니다.")]
|
|
[SerializeField] private System.Collections.Generic.List<WeaponData> registeredWeapons = new();
|
|
|
|
[Header("네트워크 동기화 (보조손)")]
|
|
[Tooltip("보조손 WeaponData 목록. 서버→클라이언트 동기화에 사용됩니다.")]
|
|
[SerializeField] private System.Collections.Generic.List<WeaponData> registeredOffhands = new();
|
|
|
|
// 캐싱된 소켓 Transform들
|
|
private Transform rightHandSocket;
|
|
private Transform leftHandSocket;
|
|
private Transform backSocket;
|
|
private Transform hipSocket;
|
|
private Transform twoHandedSocket;
|
|
|
|
// 현재 장착 중인 무기
|
|
private WeaponData currentWeapon;
|
|
|
|
// 현재 생성된 무기 인스턴스
|
|
private GameObject currentWeaponInstance;
|
|
|
|
// 현재 적용된 스탯 수정자들 (해제 시 제거용)
|
|
private readonly System.Collections.Generic.Dictionary<StatType, StatModifier> activeModifiers
|
|
= new System.Collections.Generic.Dictionary<StatType, StatModifier>();
|
|
|
|
// 보조손 무기
|
|
private WeaponData currentOffhandWeapon;
|
|
private GameObject currentOffhandWeaponInstance;
|
|
private readonly System.Collections.Generic.Dictionary<StatType, StatModifier> activeOffhandModifiers
|
|
= new System.Collections.Generic.Dictionary<StatType, StatModifier>();
|
|
|
|
// 보조손 네트워크 동기화 (registeredOffhands 인덱스, -1 = 없음)
|
|
private NetworkVariable<int> equippedOffhandId = new NetworkVariable<int>(-1);
|
|
|
|
// 무기 장착 상태 동기화 (registeredWeapons 인덱스, -1 = 없음)
|
|
private NetworkVariable<int> equippedWeaponId = new NetworkVariable<int>(-1);
|
|
|
|
public WeaponData CurrentWeapon => currentWeapon;
|
|
public bool HasWeaponEquipped => currentWeapon != null;
|
|
public GameObject CurrentWeaponInstance => currentWeaponInstance;
|
|
|
|
// 배율 프로퍼티 (무기 없으면 기본값 1.0)
|
|
public float DamageMultiplier => currentWeapon != null ? currentWeapon.DamageMultiplier : 1f;
|
|
public float RangeMultiplier => currentWeapon != null ? currentWeapon.RangeMultiplier : 1f;
|
|
public float ManaCostMultiplier => currentWeapon != null ? currentWeapon.ManaCostMultiplier : 1f;
|
|
|
|
// 보조손 프로퍼티
|
|
public WeaponData CurrentOffhandWeapon => currentOffhandWeapon;
|
|
public bool HasOffhandEquipped => currentOffhandWeapon != null;
|
|
|
|
/// <summary>
|
|
/// 현재 장착된 무기(메인+보조)의 특성 합산
|
|
/// </summary>
|
|
public WeaponTrait EquippedWeaponTraits
|
|
{
|
|
get
|
|
{
|
|
WeaponTrait traits = currentWeapon != null ? currentWeapon.WeaponTrait : WeaponTrait.None;
|
|
if (currentOffhandWeapon != null)
|
|
traits |= currentOffhandWeapon.WeaponTrait;
|
|
return traits;
|
|
}
|
|
}
|
|
|
|
// 이벤트
|
|
public event Action<WeaponData> OnWeaponEquipped;
|
|
public event Action<WeaponData> OnWeaponUnequipped;
|
|
public event Action<WeaponData> OnOffhandWeaponEquipped;
|
|
public event Action<WeaponData> OnOffhandWeaponUnequipped;
|
|
|
|
private void Awake()
|
|
{
|
|
// CharacterStats 참조 확인
|
|
if (characterStats == null)
|
|
{
|
|
characterStats = GetComponent<CharacterStats>();
|
|
}
|
|
|
|
// 소켓 자동 검색
|
|
CacheSockets();
|
|
}
|
|
|
|
public override void OnNetworkSpawn()
|
|
{
|
|
equippedWeaponId.OnValueChanged += HandleEquippedWeaponChanged;
|
|
equippedOffhandId.OnValueChanged += HandleEquippedOffhandChanged;
|
|
|
|
if (IsServer && startingWeapon != null)
|
|
{
|
|
EquipWeapon(startingWeapon);
|
|
}
|
|
else if (!IsServer && equippedWeaponId.Value >= 0)
|
|
{
|
|
// 늦게 접속한 클라이언트: 현재 장착된 무기 시각화
|
|
SpawnWeaponVisualsLocal(equippedWeaponId.Value);
|
|
}
|
|
|
|
if (IsServer && startingOffhandWeapon != null)
|
|
{
|
|
EquipOffhandWeapon(startingOffhandWeapon);
|
|
}
|
|
else if (!IsServer && equippedOffhandId.Value >= 0)
|
|
{
|
|
SpawnOffhandVisualsLocal(equippedOffhandId.Value);
|
|
}
|
|
}
|
|
|
|
public override void OnNetworkDespawn()
|
|
{
|
|
equippedWeaponId.OnValueChanged -= HandleEquippedWeaponChanged;
|
|
equippedOffhandId.OnValueChanged -= HandleEquippedOffhandChanged;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 메시 이름으로 소켓 Transform 캐싱
|
|
/// </summary>
|
|
private void CacheSockets()
|
|
{
|
|
rightHandSocket = FindDeepChild(rightHandName);
|
|
leftHandSocket = FindDeepChild(leftHandName);
|
|
backSocket = FindDeepChild(backName);
|
|
hipSocket = FindDeepChild(hipName);
|
|
|
|
// 양손은 별도 이름 없으면 오른손 사용
|
|
if (!string.IsNullOrEmpty(twoHandedName))
|
|
{
|
|
twoHandedSocket = FindDeepChild(twoHandedName);
|
|
}
|
|
else
|
|
{
|
|
twoHandedSocket = rightHandSocket;
|
|
}
|
|
|
|
Debug.Log($"[WeaponEquipment] Sockets cached - R:{rightHandSocket != null}, L:{leftHandSocket != null}, Back:{backSocket != null}, Hip:{hipSocket != null}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 이름으로 자식 Transform 재귀 검색
|
|
/// </summary>
|
|
private Transform FindDeepChild(string name)
|
|
{
|
|
if (string.IsNullOrEmpty(name)) return null;
|
|
|
|
// BFS로 검색
|
|
var queue = new System.Collections.Generic.Queue<Transform>();
|
|
queue.Enqueue(transform);
|
|
|
|
while (queue.Count > 0)
|
|
{
|
|
Transform current = queue.Dequeue();
|
|
|
|
if (current.name == name)
|
|
{
|
|
return current;
|
|
}
|
|
|
|
foreach (Transform child in current)
|
|
{
|
|
queue.Enqueue(child);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private void HandleEquippedWeaponChanged(int oldValue, int newValue)
|
|
{
|
|
if (IsServer) return; // 서버는 EquipWeapon/UnequipWeapon에서 직접 처리
|
|
|
|
if (newValue == -1)
|
|
UnequipWeaponInternal();
|
|
else
|
|
SpawnWeaponVisualsLocal(newValue);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 무기 장착 (서버에서만 호출)
|
|
/// </summary>
|
|
public void EquipWeapon(WeaponData weapon)
|
|
{
|
|
if (weapon == null)
|
|
{
|
|
Debug.LogWarning("[WeaponEquipment] EquipWeapon called with null weapon");
|
|
return;
|
|
}
|
|
|
|
if (!IsServer)
|
|
{
|
|
Debug.LogWarning("[WeaponEquipment] EquipWeapon can only be called on server");
|
|
return;
|
|
}
|
|
|
|
// 기존 무기 해제
|
|
if (currentWeapon != null)
|
|
{
|
|
UnequipWeapon();
|
|
}
|
|
|
|
currentWeapon = weapon;
|
|
|
|
// 스탯 보너스 적용
|
|
ApplyStatBonuses(weapon);
|
|
|
|
// 무기 외형 생성 및 부착
|
|
SpawnWeaponVisuals(weapon);
|
|
|
|
// registeredWeapons 인덱스로 동기화
|
|
equippedWeaponId.Value = registeredWeapons.IndexOf(weapon);
|
|
if (equippedWeaponId.Value < 0)
|
|
Debug.LogWarning($"[WeaponEquipment] '{weapon.WeaponName}' is not in registeredWeapons. Add it to sync to clients.");
|
|
|
|
// 이벤트 발생
|
|
OnWeaponEquipped?.Invoke(weapon);
|
|
|
|
Debug.Log($"[WeaponEquipment] Equipped: {weapon.WeaponName} at {weapon.WeaponSlot}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 무기 해제 (서버에서만 호출)
|
|
/// </summary>
|
|
public void UnequipWeapon()
|
|
{
|
|
if (currentWeapon == null) return;
|
|
|
|
if (!IsServer)
|
|
{
|
|
Debug.LogWarning("[WeaponEquipment] UnequipWeapon can only be called on server");
|
|
return;
|
|
}
|
|
|
|
WeaponData previousWeapon = currentWeapon;
|
|
|
|
// 스탯 보너스 제거
|
|
RemoveStatBonuses();
|
|
|
|
// 무기 외형 제거
|
|
DespawnWeaponVisuals();
|
|
|
|
currentWeapon = null;
|
|
equippedWeaponId.Value = -1;
|
|
|
|
// 이벤트 발생
|
|
OnWeaponUnequipped?.Invoke(previousWeapon);
|
|
|
|
Debug.Log($"[WeaponEquipment] Unequipped: {previousWeapon.WeaponName}");
|
|
}
|
|
|
|
#region 보조손
|
|
|
|
private void HandleEquippedOffhandChanged(int oldValue, int newValue)
|
|
{
|
|
if (IsServer) return;
|
|
|
|
if (newValue == -1)
|
|
UnequipOffhandWeaponInternal();
|
|
else
|
|
SpawnOffhandVisualsLocal(newValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 보조손 무기 장착 (서버에서만 호출)
|
|
/// </summary>
|
|
public void EquipOffhandWeapon(WeaponData weapon)
|
|
{
|
|
if (weapon == null)
|
|
{
|
|
Debug.LogWarning("[WeaponEquipment] EquipOffhandWeapon called with null weapon");
|
|
return;
|
|
}
|
|
|
|
if (!IsServer)
|
|
{
|
|
Debug.LogWarning("[WeaponEquipment] EquipOffhandWeapon can only be called on server");
|
|
return;
|
|
}
|
|
|
|
if (currentOffhandWeapon != null)
|
|
{
|
|
UnequipOffhandWeapon();
|
|
}
|
|
|
|
currentOffhandWeapon = weapon;
|
|
|
|
ApplyOffhandStatBonuses(weapon);
|
|
SpawnOffhandVisuals(weapon);
|
|
|
|
equippedOffhandId.Value = registeredOffhands.IndexOf(weapon);
|
|
if (equippedOffhandId.Value < 0)
|
|
Debug.LogWarning($"[WeaponEquipment] '{weapon.WeaponName}' is not in registeredOffhands. Add it to sync to clients.");
|
|
|
|
OnOffhandWeaponEquipped?.Invoke(weapon);
|
|
|
|
Debug.Log($"[WeaponEquipment] Equipped offhand: {weapon.WeaponName}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 보조손 무기 해제 (서버에서만 호출)
|
|
/// </summary>
|
|
public void UnequipOffhandWeapon()
|
|
{
|
|
if (currentOffhandWeapon == null) return;
|
|
|
|
if (!IsServer)
|
|
{
|
|
Debug.LogWarning("[WeaponEquipment] UnequipOffhandWeapon can only be called on server");
|
|
return;
|
|
}
|
|
|
|
WeaponData previousWeapon = currentOffhandWeapon;
|
|
|
|
RemoveOffhandStatBonuses();
|
|
DespawnOffhandVisuals();
|
|
|
|
currentOffhandWeapon = null;
|
|
equippedOffhandId.Value = -1;
|
|
|
|
OnOffhandWeaponUnequipped?.Invoke(previousWeapon);
|
|
|
|
Debug.Log($"[WeaponEquipment] Unequipped offhand: {previousWeapon.WeaponName}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 보조손 내부 해제 로직 (클라이언트 동기화용)
|
|
/// </summary>
|
|
private void UnequipOffhandWeaponInternal()
|
|
{
|
|
if (currentOffhandWeaponInstance == null && currentOffhandWeapon == null) return;
|
|
|
|
WeaponData previousWeapon = currentOffhandWeapon;
|
|
DespawnOffhandVisualsLocal();
|
|
|
|
OnOffhandWeaponUnequipped?.Invoke(previousWeapon);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 보조손 무기의 스탯 보너스 적용
|
|
/// </summary>
|
|
private void ApplyOffhandStatBonuses(WeaponData weapon)
|
|
{
|
|
if (characterStats == null) return;
|
|
|
|
foreach (StatType statType in System.Enum.GetValues(typeof(StatType)))
|
|
{
|
|
int bonus = weapon.GetStatBonus(statType);
|
|
if (bonus != 0)
|
|
{
|
|
var stat = characterStats.GetStat(statType);
|
|
if (stat != null)
|
|
{
|
|
var modifier = new StatModifier(bonus, StatModType.Flat, weapon);
|
|
stat.AddModifier(modifier);
|
|
activeOffhandModifiers[statType] = modifier;
|
|
|
|
Debug.Log($"[WeaponEquipment] Applied offhand {statType} +{bonus}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 보조손 무기의 스탯 보너스 제거
|
|
/// </summary>
|
|
private void RemoveOffhandStatBonuses()
|
|
{
|
|
if (characterStats == null) return;
|
|
|
|
foreach (StatType statType in System.Enum.GetValues(typeof(StatType)))
|
|
{
|
|
var stat = characterStats.GetStat(statType);
|
|
if (stat != null && activeOffhandModifiers.TryGetValue(statType, out StatModifier modifier))
|
|
{
|
|
stat.RemoveModifier(modifier);
|
|
Debug.Log($"[WeaponEquipment] Removed offhand {statType} modifier");
|
|
}
|
|
}
|
|
|
|
activeOffhandModifiers.Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 보조손 무기 외형 생성 및 부착 (서버). 항상 왼손에 장착합니다.
|
|
/// </summary>
|
|
private void SpawnOffhandVisuals(WeaponData weapon)
|
|
{
|
|
if (weapon == null || weapon.WeaponPrefab == null) return;
|
|
|
|
if (leftHandSocket == null)
|
|
{
|
|
Debug.LogWarning("[WeaponEquipment] No left hand socket found for offhand weapon");
|
|
return;
|
|
}
|
|
|
|
currentOffhandWeaponInstance = Instantiate(weapon.WeaponPrefab, leftHandSocket);
|
|
currentOffhandWeaponInstance.transform.localPosition = weapon.PositionOffset;
|
|
currentOffhandWeaponInstance.transform.localRotation = Quaternion.Euler(weapon.RotationOffset);
|
|
currentOffhandWeaponInstance.transform.localScale = weapon.Scale;
|
|
|
|
Debug.Log($"[WeaponEquipment] Spawned offhand visual: {weapon.WeaponName}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 클라이언트: registeredOffhands 인덱스로 보조손 무기 외형 생성
|
|
/// </summary>
|
|
private void SpawnOffhandVisualsLocal(int weaponIndex)
|
|
{
|
|
if (weaponIndex < 0 || weaponIndex >= registeredOffhands.Count || registeredOffhands[weaponIndex] == null)
|
|
{
|
|
Debug.LogWarning($"[WeaponEquipment] Offhand weapon index {weaponIndex} not found in registeredOffhands.");
|
|
return;
|
|
}
|
|
|
|
var weapon = registeredOffhands[weaponIndex];
|
|
if (weapon.WeaponPrefab == null) return;
|
|
|
|
DespawnOffhandVisualsLocal();
|
|
|
|
if (leftHandSocket == null)
|
|
{
|
|
Debug.LogWarning("[WeaponEquipment] No left hand socket found for offhand weapon");
|
|
return;
|
|
}
|
|
|
|
currentOffhandWeaponInstance = Instantiate(weapon.WeaponPrefab, leftHandSocket);
|
|
currentOffhandWeaponInstance.transform.localPosition = weapon.PositionOffset;
|
|
currentOffhandWeaponInstance.transform.localRotation = Quaternion.Euler(weapon.RotationOffset);
|
|
currentOffhandWeaponInstance.transform.localScale = weapon.Scale;
|
|
currentOffhandWeapon = weapon;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 보조손 무기 외형 제거 (서버)
|
|
/// </summary>
|
|
private void DespawnOffhandVisuals()
|
|
{
|
|
if (currentOffhandWeaponInstance == null) return;
|
|
Destroy(currentOffhandWeaponInstance);
|
|
currentOffhandWeaponInstance = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 보조손 무기 외형 제거 (클라이언트)
|
|
/// </summary>
|
|
private void DespawnOffhandVisualsLocal()
|
|
{
|
|
if (currentOffhandWeaponInstance == null) return;
|
|
Destroy(currentOffhandWeaponInstance);
|
|
currentOffhandWeaponInstance = null;
|
|
currentOffhandWeapon = null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// 내부 해제 로직 (클라이언트 동기화용)
|
|
/// </summary>
|
|
private void UnequipWeaponInternal()
|
|
{
|
|
if (currentWeaponInstance == null && currentWeapon == null) return;
|
|
|
|
WeaponData previousWeapon = currentWeapon;
|
|
DespawnWeaponVisualsLocal();
|
|
|
|
OnWeaponUnequipped?.Invoke(previousWeapon);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 무기의 스탯 보너스 적용
|
|
/// </summary>
|
|
private void ApplyStatBonuses(WeaponData weapon)
|
|
{
|
|
if (characterStats == null) return;
|
|
|
|
// 모든 스탯 타입에 대해 보너스 적용
|
|
foreach (StatType statType in System.Enum.GetValues(typeof(StatType)))
|
|
{
|
|
int bonus = weapon.GetStatBonus(statType);
|
|
if (bonus != 0)
|
|
{
|
|
var stat = characterStats.GetStat(statType);
|
|
if (stat != null)
|
|
{
|
|
var modifier = new StatModifier(bonus, StatModType.Flat, weapon);
|
|
stat.AddModifier(modifier);
|
|
activeModifiers[statType] = modifier;
|
|
|
|
Debug.Log($"[WeaponEquipment] Applied {statType} +{bonus}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 무기의 스탯 보너스 제거
|
|
/// </summary>
|
|
private void RemoveStatBonuses()
|
|
{
|
|
if (characterStats == null) return;
|
|
|
|
// 각 스탯에서 무기로부터 추가된 수정자 제거
|
|
foreach (StatType statType in System.Enum.GetValues(typeof(StatType)))
|
|
{
|
|
var stat = characterStats.GetStat(statType);
|
|
if (stat != null && activeModifiers.TryGetValue(statType, out StatModifier modifier))
|
|
{
|
|
stat.RemoveModifier(modifier);
|
|
Debug.Log($"[WeaponEquipment] Removed {statType} modifier");
|
|
}
|
|
}
|
|
|
|
activeModifiers.Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 무기 외형 생성 및 부착 (서버)
|
|
/// </summary>
|
|
private void SpawnWeaponVisuals(WeaponData weapon)
|
|
{
|
|
if (weapon == null || weapon.WeaponPrefab == null) return;
|
|
|
|
Transform socket = GetSocketForSlot(weapon.WeaponSlot);
|
|
if (socket == null)
|
|
{
|
|
Debug.LogWarning($"[WeaponEquipment] No socket found for slot: {weapon.WeaponSlot}");
|
|
return;
|
|
}
|
|
|
|
currentWeaponInstance = Instantiate(weapon.WeaponPrefab, socket);
|
|
currentWeaponInstance.transform.localPosition = weapon.PositionOffset;
|
|
currentWeaponInstance.transform.localRotation = Quaternion.Euler(weapon.RotationOffset);
|
|
currentWeaponInstance.transform.localScale = weapon.Scale;
|
|
|
|
Debug.Log($"[WeaponEquipment] Spawned weapon visual: {weapon.WeaponName}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 클라이언트: registeredWeapons 인덱스로 무기 외형 생성
|
|
/// </summary>
|
|
private void SpawnWeaponVisualsLocal(int weaponIndex)
|
|
{
|
|
if (weaponIndex < 0 || weaponIndex >= registeredWeapons.Count || registeredWeapons[weaponIndex] == null)
|
|
{
|
|
Debug.LogWarning($"[WeaponEquipment] Weapon index {weaponIndex} not found in registeredWeapons.");
|
|
return;
|
|
}
|
|
|
|
var weapon = registeredWeapons[weaponIndex];
|
|
if (weapon.WeaponPrefab == null) return;
|
|
|
|
DespawnWeaponVisualsLocal();
|
|
|
|
Transform socket = GetSocketForSlot(weapon.WeaponSlot);
|
|
if (socket == null)
|
|
{
|
|
Debug.LogWarning($"[WeaponEquipment] No socket found for slot: {weapon.WeaponSlot}");
|
|
return;
|
|
}
|
|
|
|
currentWeaponInstance = Instantiate(weapon.WeaponPrefab, socket);
|
|
currentWeaponInstance.transform.localPosition = weapon.PositionOffset;
|
|
currentWeaponInstance.transform.localRotation = Quaternion.Euler(weapon.RotationOffset);
|
|
currentWeaponInstance.transform.localScale = weapon.Scale;
|
|
currentWeapon = weapon;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 무기 외형 제거 (서버)
|
|
/// </summary>
|
|
private void DespawnWeaponVisuals()
|
|
{
|
|
if (currentWeaponInstance == null) return;
|
|
Destroy(currentWeaponInstance);
|
|
currentWeaponInstance = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 무기 외형 제거 (클라이언트)
|
|
/// </summary>
|
|
private void DespawnWeaponVisualsLocal()
|
|
{
|
|
if (currentWeaponInstance == null) return;
|
|
Destroy(currentWeaponInstance);
|
|
currentWeaponInstance = null;
|
|
currentWeapon = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 슬롯 타입에 맞는 소켓 Transform 반환
|
|
/// </summary>
|
|
private Transform GetSocketForSlot(WeaponSlot slot)
|
|
{
|
|
return slot switch
|
|
{
|
|
WeaponSlot.RightHand => rightHandSocket,
|
|
WeaponSlot.LeftHand => leftHandSocket,
|
|
WeaponSlot.Back => backSocket,
|
|
WeaponSlot.Hip => hipSocket,
|
|
WeaponSlot.TwoHanded => twoHandedSocket != null ? twoHandedSocket : rightHandSocket,
|
|
_ => rightHandSocket,
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 서버에 무기 장착 요청
|
|
/// </summary>
|
|
[Rpc(SendTo.Server)]
|
|
public void RequestEquipWeaponRpc(int weaponInstanceId)
|
|
{
|
|
// TODO: WeaponDatabase에서 ID로 WeaponData 조회 후 EquipWeapon 호출
|
|
Debug.Log($"[WeaponEquipment] Client requested weapon equip: {weaponInstanceId}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 서버에 무기 해제 요청
|
|
/// </summary>
|
|
[Rpc(SendTo.Server)]
|
|
public void RequestUnequipWeaponRpc()
|
|
{
|
|
UnequipWeapon();
|
|
}
|
|
}
|
|
}
|