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>
This commit is contained in:
147
Assets/_Game/Scripts/Network/LobbyManager.cs
Normal file
147
Assets/_Game/Scripts/Network/LobbyManager.cs
Normal file
@@ -0,0 +1,147 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/_Game/Scripts/Network/LobbyManager.cs.meta
Normal file
2
Assets/_Game/Scripts/Network/LobbyManager.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8db7d99e81050fd4d8768e2fa31483a0
|
||||
24
Assets/_Game/Scripts/Network/LobbyPlayerData.cs
Normal file
24
Assets/_Game/Scripts/Network/LobbyPlayerData.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Unity.Collections;
|
||||
using Unity.Netcode;
|
||||
|
||||
namespace Colosseum.Network
|
||||
{
|
||||
public struct LobbyPlayerData : INetworkSerializable, System.IEquatable<LobbyPlayerData>
|
||||
{
|
||||
public ulong ClientId;
|
||||
public FixedString32Bytes PlayerName;
|
||||
public bool IsReady;
|
||||
|
||||
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
|
||||
{
|
||||
serializer.SerializeValue(ref ClientId);
|
||||
serializer.SerializeValue(ref PlayerName);
|
||||
serializer.SerializeValue(ref IsReady);
|
||||
}
|
||||
|
||||
public bool Equals(LobbyPlayerData other) =>
|
||||
ClientId == other.ClientId &&
|
||||
PlayerName == other.PlayerName &&
|
||||
IsReady == other.IsReady;
|
||||
}
|
||||
}
|
||||
2
Assets/_Game/Scripts/Network/LobbyPlayerData.cs.meta
Normal file
2
Assets/_Game/Scripts/Network/LobbyPlayerData.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b07f5fad09b348b4387a5824c72f802f
|
||||
Reference in New Issue
Block a user