Files
Colosseum/Assets/_Game/Scripts/Network/LobbyManager.cs
dal4segno e5ef94da85 feat: 멀티플레이어 네트워크 동기화 구현
- 로비 씬 추가 및 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>
2026-03-17 20:46:45 +09:00

148 lines
5.2 KiB
C#

using System;
using Unity.Collections;
using Unity.Netcode;
using Unity.Netcode.Transports.UTP;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Colosseum.Network
{
/// <summary>
/// 로비 상태 관리. NetworkBehaviour로 플레이어 목록을 모든 클라이언트에 동기화.
/// NetworkManager가 있는 오브젝트와 별개로 배치하거나 NetworkObject 컴포넌트 필요.
/// </summary>
public class LobbyManager : NetworkBehaviour
{
public static LobbyManager Instance { get; private set; }
[Header("Game Scene")]
[SerializeField] private string gameSceneName = "Test";
// 모든 클라이언트에 동기화되는 플레이어 목록
private NetworkList<LobbyPlayerData> _players;
public event Action OnPlayersChanged;
// ─── 초기화 ───────────────────────────────────────────────
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
_players = new NetworkList<LobbyPlayerData>();
}
public override void OnNetworkSpawn()
{
_players.OnListChanged += _ => OnPlayersChanged?.Invoke();
if (IsServer)
{
NetworkManager.Singleton.OnClientConnectedCallback += OnClientConnected;
NetworkManager.Singleton.OnClientDisconnectCallback += OnClientDisconnected;
// 호스트 자신 추가
AddPlayer(NetworkManager.Singleton.LocalClientId);
}
}
public override void OnNetworkDespawn()
{
if (IsServer)
{
NetworkManager.Singleton.OnClientConnectedCallback -= OnClientConnected;
NetworkManager.Singleton.OnClientDisconnectCallback -= OnClientDisconnected;
}
}
// ─── 연결 제어 (클라이언트에서 호출) ─────────────────────
/// <summary>호스트 시작</summary>
public void StartHost(string ip, ushort port)
{
SetTransport(ip, port);
NetworkManager.Singleton.StartHost();
}
/// <summary>클라이언트로 접속</summary>
public void StartClient(string ip, ushort port)
{
SetTransport(ip, port);
NetworkManager.Singleton.StartClient();
}
public void Disconnect()
{
NetworkManager.Singleton?.Shutdown();
}
// ─── 플레이어 목록 ────────────────────────────────────────
public int PlayerCount => _players.Count;
public LobbyPlayerData GetPlayer(int index) => _players[index];
private void OnClientConnected(ulong clientId)
{
AddPlayer(clientId);
}
private void OnClientDisconnected(ulong clientId)
{
for (int i = 0; i < _players.Count; i++)
{
if (_players[i].ClientId == clientId)
{
_players.RemoveAt(i);
return;
}
}
}
private void AddPlayer(ulong clientId)
{
_players.Add(new LobbyPlayerData
{
ClientId = clientId,
PlayerName = new FixedString32Bytes($"Player {clientId}"),
IsReady = false
});
}
// ─── 준비 상태 ────────────────────────────────────────────
[Rpc(SendTo.Server)]
public void SetReadyRpc(bool isReady, RpcParams rpcParams = default)
{
ulong clientId = rpcParams.Receive.SenderClientId;
for (int i = 0; i < _players.Count; i++)
{
if (_players[i].ClientId == clientId)
{
var data = _players[i];
data.IsReady = isReady;
_players[i] = data;
return;
}
}
}
// ─── 게임 시작 ────────────────────────────────────────────
/// <summary>호스트만 호출 가능. 모든 플레이어가 준비되어야 활성화 권장.</summary>
public void StartGame()
{
if (!IsHost) return;
NetworkManager.Singleton.SceneManager.LoadScene(gameSceneName, LoadSceneMode.Single);
}
// ─── 유틸 ─────────────────────────────────────────────────
private void SetTransport(string ip, ushort port)
{
var transport = NetworkManager.Singleton?.GetComponent<UnityTransport>();
if (transport != null)
transport.SetConnectionData(ip, port);
}
}
}