feat: 무기 장착 시스템 핵심 구현

- WeaponData: 무기 스탯 보너스, 배율, 슬롯, 프리팹 정보를 담은 ScriptableObject
- WeaponEquipment: 무기 장착/해제, 스탯 보너스 적용, 메시 이름 기반 소켓 검색, 스케일 보정
- WeaponSlot enum: RightHand, LeftHand, Back, Hip, TwoHanded 슬롯 지원
- GetInstanceID() 음수 버그 수정 (>= 0 → == -1 체크)
- 소켓 스케일 보정으로 0.01 스케일 메시에서도 무기가 올바른 크기로 표시됨

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-03-12 19:55:27 +09:00
parent 2079e1b232
commit df064c4eaf
3 changed files with 516 additions and 0 deletions

View File

@@ -0,0 +1,107 @@
using UnityEngine;
using Colosseum.Stats;
namespace Colosseum.Weapons
{
/// <summary>
/// 무기 장착 위치
/// </summary>
public enum WeaponSlot
{
RightHand, // 오른손
LeftHand, // 왼손
Back, // 등
Hip, // 허리
TwoHanded, // 양손
}
/// <summary>
/// 무기 데이터. 무기의 기본 정보, 스탯 보너스, 배율, 외형을 관리합니다.
/// </summary>
[CreateAssetMenu(fileName = "NewWeapon", menuName = "Colosseum/Weapon")]
public class WeaponData : ScriptableObject
{
[Header("기본 정보")]
[SerializeField] private string weaponName;
[TextArea(2, 4)]
[SerializeField] private string description;
[SerializeField] private Sprite icon;
[Header("장착 설정")]
[Tooltip("무기가 장착될 슬롯")]
[SerializeField] private WeaponSlot weaponSlot = WeaponSlot.RightHand;
[Tooltip("무기 프리팹 (메시, 콜라이더 등 포함)")]
[SerializeField] private GameObject weaponPrefab;
[Tooltip("장착 시 위치 오프셋")]
[SerializeField] private Vector3 positionOffset = Vector3.zero;
[Tooltip("장착 시 회전 오프셋 (오일러 각도)")]
[SerializeField] private Vector3 rotationOffset = Vector3.zero;
[Tooltip("장착 시 스케일")]
[SerializeField] private Vector3 scale = Vector3.one;
[Header("스탯 보너스 (Flat)")]
[Tooltip("힘 보너스")]
[SerializeField] private int strengthBonus = 0;
[Tooltip("민첩 보너스")]
[SerializeField] private int dexterityBonus = 0;
[Tooltip("지능 보너스")]
[SerializeField] private int intelligenceBonus = 0;
[Tooltip("활력 보너스")]
[SerializeField] private int vitalityBonus = 0;
[Tooltip("지혜 보너스")]
[SerializeField] private int wisdomBonus = 0;
[Tooltip("정신 보너스")]
[SerializeField] private int spiritBonus = 0;
[Header("배율")]
[Tooltip("데미지 배율 (1.0 = 100%, 1.5 = 150%)")]
[Min(0f)] [SerializeField] private float damageMultiplier = 1f;
[Tooltip("사거리 배율 (1.0 = 100%, 1.2 = 120%)")]
[Min(0f)] [SerializeField] private float rangeMultiplier = 1f;
[Tooltip("마나 소모 배율 (1.0 = 100%, 0.8 = 80%)")]
[Min(0f)] [SerializeField] private float manaCostMultiplier = 1f;
// Properties - 기본 정보
public string WeaponName => weaponName;
public string Description => description;
public Sprite Icon => icon;
// Properties - 장착 설정
public WeaponSlot WeaponSlot => weaponSlot;
public GameObject WeaponPrefab => weaponPrefab;
public Vector3 PositionOffset => positionOffset;
public Vector3 RotationOffset => rotationOffset;
public Vector3 Scale => scale;
// Properties - 스탯 보너스
public int StrengthBonus => strengthBonus;
public int DexterityBonus => dexterityBonus;
public int IntelligenceBonus => intelligenceBonus;
public int VitalityBonus => vitalityBonus;
public int WisdomBonus => wisdomBonus;
public int SpiritBonus => spiritBonus;
// Properties - 배율
public float DamageMultiplier => damageMultiplier;
public float RangeMultiplier => rangeMultiplier;
public float ManaCostMultiplier => manaCostMultiplier;
/// <summary>
/// 스탯 타입에 해당하는 보너스 값 반환
/// </summary>
public int GetStatBonus(StatType statType)
{
return statType switch
{
StatType.Strength => strengthBonus,
StatType.Dexterity => dexterityBonus,
StatType.Intelligence => intelligenceBonus,
StatType.Vitality => vitalityBonus,
StatType.Wisdom => wisdomBonus,
StatType.Spirit => spiritBonus,
_ => 0,
};
}
}
}