Files
WhiteMan_Unity2D/Assets/02_Scripts/Combat/PlayerWeaponInventory.cs

134 lines
5.2 KiB
C#

using System;
using System.Collections.Generic;
using UnityEngine;
// ============================================================================
// PlayerWeaponInventory
// ----------------------------------------------------------------------------
// 플레이어가 보유한 무기 목록 + 현재 장착 무기 관리.
// 씬 전환 시에도 데이터를 유지하기 위해 DontDestroyOnLoad 싱글톤으로 동작.
// PlayerController가 OnWeaponChanged 이벤트를 구독해서 애니메이션/공격 콤보 교체.
//
// 사용법:
// - 어디서든 PlayerWeaponInventory.Instance 또는 EnsureInstance()로 접근.
// - 인스턴스가 없으면 EnsureInstance()가 새 GameObject로 자동 생성 → 첫 씬에
// 수동 배치하지 않아도 됨.
// - Player 프리팹에는 이 컴포넌트를 더 이상 붙이지 않는다 (중복 시엔 자동 폐기되긴 하지만,
// 프리팹과 같은 GameObject가 영속화되는 부작용을 피하려면 제거 권장).
//
// 슬롯 매핑 (외부 코드의 EquipSlot 인덱스):
// -1 → 맨손 (CurrentWeapon == null)
// 0 → 첫 번째로 픽업한 무기
// 1 → 두 번째로 픽업한 무기
// ...
//
// 입력 키 매핑 (PlayerController에서 처리):
// Key 1 → EquipUnarmed()
// Key 2 → EquipSlot(0)
// Key 3 → EquipSlot(1)
// ============================================================================
public class PlayerWeaponInventory : MonoBehaviour
{
// ─── 싱글톤 ──────────────────────────────────────────────────────────
public static PlayerWeaponInventory Instance { get; private set; }
// 어디서든 호출하면 인스턴스를 보장한다. 없으면 새 GameObject로 생성.
// (PlayerController.Awake에서 호출해 첫 진입을 보장하는 용도)
public static PlayerWeaponInventory EnsureInstance()
{
if (Instance != null) return Instance;
GameObject go = new GameObject(nameof(PlayerWeaponInventory));
return go.AddComponent<PlayerWeaponInventory>();
// AddComponent가 Awake를 동기 호출 → Instance 세팅 + DontDestroyOnLoad 적용 완료 후 반환.
}
private readonly List<WeaponData> _weapons = new();
private int _currentIndex = -1; // -1 = 맨손
private void Awake()
{
// 중복 생성 방지 — 이미 Instance가 있으면 이 컴포넌트만 폐기.
// (GameObject 전체를 지우지 않는 이유: Player 프리팹 등에 잘못 붙어 있어도 Player가 사라지면 안 되니까.)
if (Instance != null && Instance != this)
{
Destroy(this);
return;
}
Instance = this;
}
private void OnDestroy()
{
// 앱 종료 등으로 영속 인스턴스가 파괴될 때 정적 참조 정리.
if (Instance == this) Instance = null;
}
// 무기 교체 시 발화. null이면 맨손.
public event Action<WeaponData> OnWeaponChanged;
// 인벤토리 상태(목록 추가·장착 변경)가 바뀔 때마다 발화. UI 갱신용.
public event Action OnInventoryChanged;
public WeaponData CurrentWeapon =>
_currentIndex >= 0 && _currentIndex < _weapons.Count
? _weapons[_currentIndex]
: null;
public bool IsArmed => CurrentWeapon != null;
public int Count => _weapons.Count;
// UI 등 외부에서 읽기 전용으로 목록/장착 인덱스 조회 (UI는 데이터를 소유하지 않는다).
public IReadOnlyList<WeaponData> Weapons => _weapons;
public int CurrentIndex => _currentIndex;
// 무기 추가. 이미 보유 중이면 false 반환 (중복 픽업 방지).
// 첫 픽업 시 자동 장착할지는 호출자가 OnWeaponChanged를 보고 결정.
public bool Pickup(WeaponData weapon)
{
if (weapon == null) return false;
if (_weapons.Contains(weapon)) return false;
_weapons.Add(weapon);
// 첫 픽업이면 자동으로 장착해주면 사용자 편의 ↑.
if (_weapons.Count == 1 && _currentIndex == -1)
{
_currentIndex = 0;
OnWeaponChanged?.Invoke(_weapons[0]);
}
OnInventoryChanged?.Invoke();
return true;
}
// 맨손 상태로 전환.
public void EquipUnarmed()
{
if (_currentIndex == -1) return;
_currentIndex = -1;
OnWeaponChanged?.Invoke(null);
OnInventoryChanged?.Invoke();
}
// 슬롯 번호로 직접 장착. 슬롯이 비어있으면 무시.
public void EquipSlot(int slotIndex)
{
if (slotIndex < 0 || slotIndex >= _weapons.Count) return;
if (_currentIndex == slotIndex) return;
_currentIndex = slotIndex;
OnWeaponChanged?.Invoke(_weapons[slotIndex]);
OnInventoryChanged?.Invoke();
}
public void ClearWeaponInven()
{
_weapons.Clear();
OnInventoryChanged?.Invoke();
}
// 디버그용: 슬롯에 해당 무기가 있는지 확인.
public bool HasWeaponInSlot(int slotIndex)
{
return slotIndex >= 0 && slotIndex < _weapons.Count && _weapons[slotIndex] != null;
}
}