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:
2026-03-17 20:46:45 +09:00
parent b470aa4f8a
commit e5ef94da85
24 changed files with 5150 additions and 116 deletions

View File

@@ -27,6 +27,10 @@ namespace Colosseum
/// </summary>
public class GameManager : NetworkBehaviour
{
[Header("Player")]
[Tooltip("플레이어 프리팹 (NetworkObject 포함)")]
[SerializeField] private GameObject playerPrefab;
[Header("UI Prefabs")]
[Tooltip("게임 오버 UI 프리팹")]
[SerializeField] private GameObject gameOverUIPrefab;
@@ -107,33 +111,62 @@ namespace Colosseum
if (loadSceneMode == LoadSceneMode.Single)
{
if (debugMode)
{
Debug.Log($"[GameManager] Scene loaded: {sceneName}");
}
// 씬 로드 완료 시 플레이어 리스폰
if (IsServer)
StartCoroutine(SpawnPlayersAndRespawn(clientsCompleted));
}
}
private IEnumerator SpawnPlayersAndRespawn(List<ulong> clientsCompleted)
{
if (playerPrefab == null)
{
Debug.LogWarning("[GameManager] playerPrefab이 설정되지 않았습니다. 인스펙터에서 할당하세요.");
RespawnAllPlayersClientRpc();
yield break;
}
Debug.Log($"[GameManager] SpawnPlayersAndRespawn: clientsCompleted=[{string.Join(",", clientsCompleted)}]");
// 씬 로드를 완료한 클라이언트마다 플레이어 스폰
foreach (ulong clientId in clientsCompleted)
{
var go = Instantiate(playerPrefab);
var no = go.GetComponent<NetworkObject>();
if (no != null)
{
RespawnAllPlayersClientRpc();
no.SpawnAsPlayerObject(clientId, true);
Debug.Log($"[GameManager] Spawned player for clientId={clientId}");
}
else
{
Debug.LogError($"[GameManager] playerPrefab에 NetworkObject가 없습니다!");
Destroy(go);
}
}
// 클라이언트가 스폰된 오브젝트를 받을 시간 여유
yield return new WaitForSeconds(0.5f);
RespawnAllPlayersClientRpc();
}
[Rpc(SendTo.ClientsAndHost)]
private void RespawnAllPlayersClientRpc()
{
// 모든 플레이어 리스폰
// 서버: 모든 플레이어 체력/상태 리셋
var players = FindObjectsByType<PlayerNetworkController>(FindObjectsSortMode.None);
foreach (var player in players)
{
player.Respawn();
}
// 카메라 재설정
var playerMovement = FindObjectsByType<PlayerMovement>(FindObjectsSortMode.None);
foreach (var movement in playerMovement)
// 카메라 재설정 — 자신이 소유한 플레이어만
var playerMovements = FindObjectsByType<PlayerMovement>(FindObjectsSortMode.None);
foreach (var movement in playerMovements)
{
movement.RefreshCamera();
if (movement.IsOwner)
movement.RefreshCamera();
}
}