using UnityEngine;
using WL.Combat;
using WL.UI;
namespace WL.Player
{
///
/// 캐릭터 교체기 (PD #711).
///
/// 목록의 캐릭터를 **전부 씬에 배치해 두고 활성/비활성으로 교체**한다(생성·파괴하지 않는다 — 교체 지연·GC 회피).
/// 교체 시 위치·회전·체력·카메라 모드를 그대로 넘기고, 바깥에서 플레이어를 참조하는 것들을 다시 묶는다.
/// · 카메라 추적 대상 (FollowCamera.SetTarget)
/// · 몬스터 추적 대상 (EnemyController.RebindPlayer) — 교체된 쪽은 파괴되지 않아 참조가 null 이 되지 않는다
/// · 가상패드 UI (VirtualPadView.RebindPlayer)
///
/// 값은 CharacterRoster(ScriptableObject)에서 읽는다(C45).
///
public class PlayerSwitcher : MonoBehaviour
{
[Header("설정 (ScriptableObject)")]
[SerializeField] private CharacterRoster roster;
[Header("씬 인스턴스 (roster 순서와 1:1)")]
[Tooltip("비어 있으면 Awake 에서 roster 의 프리팹을 씬에 만든다")]
[SerializeField] private PlayerController[] instances = new PlayerController[0];
[Header("참조 (비우면 씬에서 찾는다)")]
[SerializeField] private FollowCamera followCamera;
[SerializeField] private VirtualPadView virtualPad;
[Header("현재 상태 (읽기 전용)")]
[SerializeField] private int currentIndex;
/// 현재 조종 중인 캐릭터의 목록 번호.
public int CurrentIndex { get { return currentIndex; } }
/// 현재 조종 중인 캐릭터.
public PlayerController Current { get { return Get(currentIndex); } }
/// 목록 길이.
public int Count { get { return instances != null ? instances.Length : 0; } }
/// 현재 캐릭터의 표시 이름.
public string CurrentDisplayName
{
get
{
var e = roster != null ? roster.Get(currentIndex) : null;
return e != null && !string.IsNullOrEmpty(e.displayName) ? e.displayName : "Player " + (currentIndex + 1);
}
}
/// 현재 캐릭터의 더미 아이콘 색.
public Color CurrentIconColor
{
get { var e = roster != null ? roster.Get(currentIndex) : null; return e != null ? e.iconColor : Color.white; }
}
/// 교체가 일어날 때마다 알린다(HUD 라벨 갱신용).
public event System.Action Switched;
public CharacterRoster Roster { get { return roster; } }
private PlayerController Get(int i)
{
if (instances == null || instances.Length == 0) return null;
return instances[Mathf.Clamp(i, 0, instances.Length - 1)];
}
private void Awake()
{
if (followCamera == null) followCamera = Object.FindFirstObjectByType();
if (virtualPad == null) virtualPad = Object.FindFirstObjectByType(FindObjectsInactive.Include);
EnsureInstances();
}
/// 씬에 인스턴스가 없으면 roster 프리팹으로 만든다. 0 번만 켠 채 시작한다.
private void EnsureInstances()
{
if (roster == null || roster.Count == 0) return;
if (instances == null || instances.Length != roster.Count)
{
var grown = new PlayerController[roster.Count];
for (int i = 0; i < grown.Length; i++)
grown[i] = (instances != null && i < instances.Length) ? instances[i] : null;
instances = grown;
}
Vector3 spawnPos = Vector3.zero;
Quaternion spawnRot = Quaternion.identity;
for (int i = 0; i < instances.Length; i++)
if (instances[i] != null) { spawnPos = instances[i].transform.position; spawnRot = instances[i].transform.rotation; break; }
for (int i = 0; i < instances.Length; i++)
{
if (instances[i] != null) continue;
var e = roster.Get(i);
if (e == null || e.prefab == null) continue;
var go = Instantiate(e.prefab, spawnPos, spawnRot);
go.name = e.prefab.name;
instances[i] = go.GetComponent();
}
for (int i = 0; i < instances.Length; i++)
{
if (instances[i] == null) continue;
bool on = (i == currentIndex);
if (!on) instances[i].SetPlayIntroSequence(false); // 켤 때 시작 시퀀스를 다시 돌리지 않는다
instances[i].gameObject.SetActive(on);
}
}
private void Start()
{
Rebind(Current);
if (Switched != null) Switched(currentIndex);
}
/// 다음 캐릭터로 순환 교체한다. 교체한 번호를 돌려준다.
public int SwitchNext()
{
if (Count <= 1) return currentIndex;
return SwitchTo((currentIndex + 1) % Count);
}
/// 지정한 번호로 교체한다.
public int SwitchTo(int index)
{
if (Count == 0) return currentIndex;
index = Mathf.Clamp(index, 0, Count - 1);
var from = Get(currentIndex);
var to = Get(index);
if (to == null || to == from) return currentIndex;
// 위치·회전·체력 인계
Vector3 pos = from != null ? from.transform.position : to.transform.position;
Quaternion rot = from != null ? from.transform.rotation : to.transform.rotation;
float hp = from != null ? from.CurrentHp : -1f;
if (from != null) from.gameObject.SetActive(false);
// CharacterController 가 켜져 있으면 위치 대입이 무시된다 — 껐다 켜며 옮긴다
var cc = to.GetComponent();
if (cc != null) cc.enabled = false;
to.transform.SetPositionAndRotation(pos, rot);
if (roster == null || roster.playIntroOnlyOnce) to.SetPlayIntroSequence(false);
to.gameObject.SetActive(true);
if (cc != null) cc.enabled = true;
if (hp >= 0f) to.SetHp(Mathf.Min(hp, to.MaxHp));
to.LockInput(roster != null ? roster.switchInputLockSeconds : 0.2f);
currentIndex = index;
Rebind(to);
if (Switched != null) Switched(currentIndex);
Debug.Log("[PlayerSwitcher] 캐릭터 교체 -> " + (currentIndex + 1) + " " + CurrentDisplayName
+ " pos=" + pos.ToString("F2") + " hp=" + to.CurrentHp.ToString("F0")
+ " 카메라모드=" + (followCamera != null ? (followCamera.ModeIndex + 1).ToString() : "-"));
return currentIndex;
}
/// 바깥에서 플레이어를 참조하는 것들을 새 캐릭터로 다시 묶는다.
private void Rebind(PlayerController pc)
{
if (pc == null) return;
// 카메라 — 모드는 유지된다(SetTarget 은 모드를 바꾸지 않는다)
if (followCamera != null) followCamera.SetTarget(pc.transform);
// 몬스터
var enemies = Object.FindObjectsByType(FindObjectsSortMode.None);
for (int i = 0; i < enemies.Length; i++) enemies[i].RebindPlayer(pc);
// 가상패드 UI
if (virtualPad != null) virtualPad.RebindPlayer(pc);
}
}
}