- 로비 씬 추가 및 LobbyManager/LobbyUI/LobbySceneBuilder 구현 - NetworkPrefabsList로 플레이어 프리팹 등록 (PlayerPrefab 자동스폰 비활성화) - PlayerMovement 서버 권한 이동 아키텍처로 전환 - NetworkVariable<Vector2>로 클라이언트 입력 → 서버 전달 - 점프 JumpRequestRpc로 서버 검증 후 실행 - 보스 프리팹에 NetworkTransform/NetworkAnimator 추가 (서버 권한) - SkillController를 NetworkBehaviour로 전환 - PlaySkillClipClientRpc로 클립 override + 재생 원자적 동기화 - OnEffect/OnSkillEnd 클라이언트 실행 차단 - WeaponEquipment 클라이언트 무기 시각화 동기화 수정 - registeredWeapons 인덱스 기반 NetworkVariable 동기화 - SpawnWeaponVisualsLocal로 클라이언트 무기 생성 - 중복 Instantiate 버그 수정 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
232 lines
8.9 KiB
C#
232 lines
8.9 KiB
C#
using System.Collections.Generic;
|
|
using Colosseum.Network;
|
|
using TMPro;
|
|
using Unity.Netcode;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
|
|
namespace Colosseum.UI
|
|
{
|
|
/// <summary>
|
|
/// 로비 씬 UI 제어.
|
|
/// 두 패널 구조:
|
|
/// connectPanel — IP/포트 입력 후 Host 또는 Join
|
|
/// lobbyPanel — 대기실 (플레이어 목록, 준비, 게임 시작)
|
|
/// </summary>
|
|
public class LobbyUI : MonoBehaviour
|
|
{
|
|
// ─── Connect Panel ────────────────────────────────────────
|
|
[Header("Connect Panel")]
|
|
[SerializeField] private GameObject connectPanel;
|
|
[SerializeField] private TMP_InputField ipInput;
|
|
[SerializeField] private TMP_InputField portInput;
|
|
[SerializeField] private Button hostButton;
|
|
[SerializeField] private Button joinButton;
|
|
[SerializeField] private TextMeshProUGUI connectStatusText;
|
|
|
|
// ─── Lobby Panel ──────────────────────────────────────────
|
|
[Header("Lobby Panel")]
|
|
[SerializeField] private GameObject lobbyPanel;
|
|
[SerializeField] private Transform playerListParent;
|
|
[SerializeField] private GameObject playerSlotPrefab; // TextMeshProUGUI 하나 포함
|
|
[SerializeField] private Button readyButton;
|
|
[SerializeField] private Button startButton; // 호스트만 표시
|
|
[SerializeField] private Button disconnectButton;
|
|
|
|
// ─── 내부 ─────────────────────────────────────────────────
|
|
private readonly List<GameObject> _slots = new();
|
|
private bool _isReady;
|
|
|
|
// ─── 초기화 ───────────────────────────────────────────────
|
|
private void Start()
|
|
{
|
|
ipInput.text = "127.0.0.1";
|
|
portInput.text = "7777";
|
|
|
|
hostButton.onClick.AddListener(OnHostClicked);
|
|
joinButton.onClick.AddListener(OnJoinClicked);
|
|
readyButton.onClick.AddListener(OnReadyClicked);
|
|
startButton.onClick.AddListener(OnStartClicked);
|
|
disconnectButton.onClick.AddListener(OnDisconnectClicked);
|
|
|
|
ShowConnectPanel();
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
if (NetworkManager.Singleton == null) return;
|
|
NetworkManager.Singleton.OnClientConnectedCallback += OnConnectionEvent;
|
|
NetworkManager.Singleton.OnClientDisconnectCallback += OnConnectionEvent;
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
if (NetworkManager.Singleton != null)
|
|
{
|
|
NetworkManager.Singleton.OnClientConnectedCallback -= OnConnectionEvent;
|
|
NetworkManager.Singleton.OnClientDisconnectCallback -= OnConnectionEvent;
|
|
}
|
|
}
|
|
|
|
// ─── Connect Panel 버튼 ───────────────────────────────────
|
|
private void OnHostClicked()
|
|
{
|
|
if (!TryGetAddress(out string ip, out ushort port)) return;
|
|
LobbyManager.Instance.StartHost(ip, port);
|
|
LobbyManager.Instance.OnPlayersChanged += RefreshPlayerList;
|
|
ShowLobbyPanel();
|
|
}
|
|
|
|
private void OnJoinClicked()
|
|
{
|
|
if (!TryGetAddress(out string ip, out ushort port)) return;
|
|
SetConnectStatus("Connecting...");
|
|
joinButton.interactable = false;
|
|
hostButton.interactable = false;
|
|
|
|
LobbyManager.Instance.StartClient(ip, port);
|
|
|
|
// 연결 성공/실패 대기
|
|
NetworkManager.Singleton.OnClientConnectedCallback += OnJoinSuccess;
|
|
NetworkManager.Singleton.OnTransportFailure += OnJoinFailed;
|
|
}
|
|
|
|
private void OnJoinSuccess(ulong clientId)
|
|
{
|
|
if (clientId != NetworkManager.Singleton.LocalClientId) return;
|
|
|
|
NetworkManager.Singleton.OnClientConnectedCallback -= OnJoinSuccess;
|
|
NetworkManager.Singleton.OnTransportFailure -= OnJoinFailed;
|
|
|
|
LobbyManager.Instance.OnPlayersChanged += RefreshPlayerList;
|
|
ShowLobbyPanel();
|
|
}
|
|
|
|
private void OnJoinFailed()
|
|
{
|
|
NetworkManager.Singleton.OnClientConnectedCallback -= OnJoinSuccess;
|
|
NetworkManager.Singleton.OnTransportFailure -= OnJoinFailed;
|
|
|
|
SetConnectStatus("Connection failed. Check IP/Port.");
|
|
joinButton.interactable = true;
|
|
hostButton.interactable = true;
|
|
}
|
|
|
|
// ─── Lobby Panel 버튼 ─────────────────────────────────────
|
|
private void OnReadyClicked()
|
|
{
|
|
_isReady = !_isReady;
|
|
LobbyManager.Instance.SetReadyRpc(_isReady);
|
|
readyButton.GetComponentInChildren<TextMeshProUGUI>().text =
|
|
_isReady ? "Ready!" : "Ready";
|
|
}
|
|
|
|
private void OnStartClicked()
|
|
{
|
|
LobbyManager.Instance.StartGame();
|
|
}
|
|
|
|
private void OnDisconnectClicked()
|
|
{
|
|
LobbyManager.Instance.OnPlayersChanged -= RefreshPlayerList;
|
|
LobbyManager.Instance.Disconnect();
|
|
_isReady = false;
|
|
ShowConnectPanel();
|
|
}
|
|
|
|
// ─── 패널 전환 ────────────────────────────────────────────
|
|
private void ShowConnectPanel()
|
|
{
|
|
connectPanel.SetActive(true);
|
|
lobbyPanel.SetActive(false);
|
|
SetConnectStatus("");
|
|
hostButton.interactable = true;
|
|
joinButton.interactable = true;
|
|
}
|
|
|
|
private void ShowLobbyPanel()
|
|
{
|
|
connectPanel.SetActive(false);
|
|
lobbyPanel.SetActive(true);
|
|
|
|
bool isHost = NetworkManager.Singleton.IsHost;
|
|
startButton.gameObject.SetActive(isHost);
|
|
readyButton.gameObject.SetActive(!isHost);
|
|
|
|
RefreshPlayerList();
|
|
}
|
|
|
|
// ─── 플레이어 목록 갱신 ───────────────────────────────────
|
|
private void OnConnectionEvent(ulong _) => RefreshPlayerList();
|
|
|
|
private void RefreshPlayerList()
|
|
{
|
|
if (LobbyManager.Instance == null) return;
|
|
|
|
// 슬롯 수 맞추기
|
|
int count = LobbyManager.Instance.PlayerCount;
|
|
while (_slots.Count < count)
|
|
{
|
|
var slot = Instantiate(playerSlotPrefab, playerListParent);
|
|
_slots.Add(slot);
|
|
}
|
|
while (_slots.Count > count)
|
|
{
|
|
Destroy(_slots[^1]);
|
|
_slots.RemoveAt(_slots.Count - 1);
|
|
}
|
|
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
var data = LobbyManager.Instance.GetPlayer(i);
|
|
var label = _slots[i].GetComponentInChildren<TextMeshProUGUI>();
|
|
if (label != null)
|
|
label.text = $"{data.PlayerName} {(data.IsReady ? "[Ready]" : "")}";
|
|
}
|
|
|
|
// 호스트: 모두 준비됐을 때만 시작 활성화
|
|
if (NetworkManager.Singleton.IsHost)
|
|
{
|
|
bool allReady = AllPlayersReady();
|
|
startButton.interactable = allReady && count >= 1;
|
|
}
|
|
}
|
|
|
|
private bool AllPlayersReady()
|
|
{
|
|
if (LobbyManager.Instance == null) return false;
|
|
int count = LobbyManager.Instance.PlayerCount;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
// 호스트 본인은 준비 체크 면제
|
|
if (LobbyManager.Instance.GetPlayer(i).ClientId ==
|
|
NetworkManager.Singleton.LocalClientId) continue;
|
|
|
|
if (!LobbyManager.Instance.GetPlayer(i).IsReady) return false;
|
|
}
|
|
return count > 0;
|
|
}
|
|
|
|
// ─── 유틸 ─────────────────────────────────────────────────
|
|
private bool TryGetAddress(out string ip, out ushort port)
|
|
{
|
|
ip = ipInput.text.Trim();
|
|
if (string.IsNullOrEmpty(ip)) ip = "127.0.0.1";
|
|
|
|
if (!ushort.TryParse(portInput.text.Trim(), out port))
|
|
{
|
|
port = 7777;
|
|
SetConnectStatus("Invalid port number.");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private void SetConnectStatus(string msg)
|
|
{
|
|
if (connectStatusText != null)
|
|
connectStatusText.text = msg;
|
|
}
|
|
}
|
|
}
|