씬 전환
씬 전환 방식
| 방식 | API | 호출 주체 | 사용 시점 |
|---|
| 일반 씬 전환 | SceneManager.LoadScene() | 누구나 | 네트워크 연결 전 |
| 네트워크 씬 전환 | NetworkManager.SceneManager.LoadScene() | Host 만 | 네트워크 연결 후 |
- 일반 씬 전환은 자기 자신만 다른 씬으로 이동
- 네트워크 씬 전환은 Host 가 호출하면 연결된 모든 클라이언트가 함께 같은 씬으로 이동
NetworkSceneManager
LoadSceneMode
- LoadSceneMode.Single: 현재 활성 씬과 모든 Additive 씬을 언로드한 후 새 씬을 로드
- LoadSceneMode.Additive: 기존 씬을 유지한 채 새 씬을 추가로 로드
- 미니맵 · UI 오버레이 · 월드 스트리밍 등에 사용
씬 전환 완료 콜백
- 모든 클라이언트가 씬 로드를 마친 시점을 감지하려면
OnLoadEventCompleted 콜백을 구독
- 게임 시작 로직 (예: 플레이어 스폰) 은 반드시 이 콜백에서 실행해야 안전
NetworkManager.Singleton.SceneManager.OnLoadEventCompleted += OnSceneLoadCompleted;
private void OnSceneLoadCompleted(
string sceneName,
LoadSceneMode loadSceneMode,
List<ulong> clientsCompleted,
List<ulong> clientsTimedOut)
{
Debug.Log($"[Scene] {sceneName} 로드 완료. 완료: {clientsCompleted.Count}명");
}
LoadComplete : 각 피어가 자기 로컬 씬 로드를 끝냈을 때 호출
- 이 시점에는 아직 다른 피어가 로딩 중일 수 있음
LoadEventCompleted : 모든 피어가 로드를 완료했을 때 호출
- 게임 시작 · 첫 플레이어 스폰 같은 초기화 로직은 반드시 여기서 실행
예제 코드
using UnityEngine;
using UnityEngine.SceneManagement;
using Unity.Netcode;
public class SessionLauncher : NetworkBehaviour
{
private const int MIN_PLAYERS_TO_START = 2;
private bool _gameStarted;
public override void OnNetworkSpawn()
{
if (!IsServer) return;
NetworkManager.Singleton.OnClientConnectedCallback += OnClientConnected;
}
public override void OnNetworkDespawn()
{
if (!IsServer) return;
if (NetworkManager.Singleton == null) return;
NetworkManager.Singleton.OnClientConnectedCallback -= OnClientConnected;
}
private void OnClientConnected(ulong clientId)
{
if (_gameStarted) return;
int count = NetworkManager.Singleton.ConnectedClientsIds.Count;
Debug.Log($"[Session] 현재 접속자: {count}/{MIN_PLAYERS_TO_START}");
if (count >= MIN_PLAYERS_TO_START)
{
_gameStarted = true;
NetworkManager.Singleton.SceneManager.LoadScene(
"GameScene", LoadSceneMode.Single);
}
}
}
런타임 플레이어 스폰
- NetworkManager Inspector에서
Player Prefab 필드를 비움 (연결 해제)
- GameScene에 스폰할 플레이어 프리팹을 NetworkManager의
Network Prefabs 목록에 등록
Spawn API
| 메서드 | 동작 |
|---|
| Spawn() | 서버 소유로 스폰. 플레이어 객체로 지정되지 않음. (몬스터) |
| SpawnWithOwnership(clientId) | 특정 클라이언트 소유로 스폰. 플레이어 객체로 지정되지 않음. |
| SpawnAsPlayerObject(clientId) | 특정 클라이언트의 플레이어 객체로 스폰. 플레이어 캐릭터에 사용. |
- 모든 Spawn 계열 메서드는 서버에서만 호출할 수 있음
예제 코드
using UnityEngine;
using Unity.Netcode;
public class PlayerSpawnManager : NetworkBehaviour
{
[SerializeField] private GameObject _playerPrefab;
[SerializeField] private Transform[] _spawnPoints;
public override void OnNetworkSpawn()
{
if (!IsServer) return;
SpawnAllPlayers();
}
private void SpawnAllPlayers()
{
int index = 0;
foreach (ulong clientId in NetworkManager.Singleton.ConnectedClientsIds)
{
Transform sp = _spawnPoints[index % _spawnPoints.Length];
GameObject instance = Instantiate(_playerPrefab, sp.position, sp.rotation);
instance.GetComponent<NetworkObject>().SpawnAsPlayerObject(clientId);
Debug.Log($"[Spawn] Player {clientId} → {sp.position}");
index++;
}
}
}
게임 상태 관리
- 게임의 전체 흐름을
NetworkVariable<enum> 으로 동기화하면 모든 피어가 같은 상태를 공유하고, OnValueChanged 콜백으로 상태 전이 시점을 감지해 UI 나 입력을 제어할 수 있음
using UnityEngine;
using Unity.Netcode;
public class GameManager : NetworkBehaviour
{
public enum GameState : byte
{
Waiting,
Playing,
Finished
}
private NetworkVariable<GameState> _currentState = new NetworkVariable<GameState>(
GameState.Waiting,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server);
public override void OnNetworkSpawn()
{
_currentState.OnValueChanged += OnGameStateChanged;
if (IsServer)
{
_currentState.Value = GameState.Playing;
}
}
public override void OnNetworkDespawn()
{
_currentState.OnValueChanged -= OnGameStateChanged;
}
private void OnGameStateChanged(GameState previous, GameState current)
{
Debug.Log($"[GameState] {previous} → {current}");
}
}
인게임 Host 이탈 처리
- Host 는 Server 와 Client 를 겸하므로, Host 가 나가면 서버 자체가 사라져 모든 클라이언트의 연결이 끊김.
| 전략 | 동작 |
|---|
| 안전한 종료 | 이탈 감지 → 클라이언트가 연결 씬으로 복귀 |
| Host Migration | 새 Host 선출 + 게임 상태 복원 후 게임 계속 |
| Dedicated Server | 별도 서버가 항상 운영되어 이탈 영향 없음 |
안전한 종료 방식
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
using Unity.Netcode;
public class DisconnectHandler : MonoBehaviour
{
private void Awake()
{
DontDestroyOnLoad(gameObject);
}
private void OnEnable()
{
NetworkManager.Singleton.OnClientDisconnectCallback += OnClientDisconnect;
}
private void OnDisable()
{
NetworkManager.Singleton.OnClientDisconnectCallback -= OnClientDisconnect;
}
private void OnClientDisconnect(ulong clientId)
{
if (clientId != NetworkManager.Singleton.LocalClientId) return;
Debug.Log("[Disconnect] 서버와의 연결이 끊겼습니다. 연결 씬으로 복귀합니다.");
NetworkManager.Singleton.Shutdown();
SceneManager.LoadScene("BootScene");
}
}