[Unity] Netcode for GameObjects(NGO) (4)

조경민·2026년 4월 21일

씬 전환

씬 전환 방식

방식API호출 주체사용 시점
일반 씬 전환SceneManager.LoadScene()누구나네트워크 연결 전
네트워크 씬 전환NetworkManager.SceneManager.LoadScene()Host 만네트워크 연결 후
  • 일반 씬 전환은 자기 자신만 다른 씬으로 이동
  • 네트워크 씬 전환은 Host 가 호출하면 연결된 모든 클라이언트가 함께 같은 씬으로 이동

NetworkSceneManager

  • NetworkManager 가 시작된 이후에만 SceneManager 프로퍼티에 접근할 수 있음
    NetworkManager.Singleton.SceneManager.LoadScene("GameScene", LoadSceneMode.Single);
  • NetworkManager Inspector 의 Enable Scene Management 체크박스가 활성화되어 있어야 네트워크 씬 전환이 동작

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 필드를 비움 (연결 해제)
    • 비우지 않으면 2배로 소환됨
  • 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;    // NetworkObject 가 붙은 플레이어 프리팹
    [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
{
    // 명시적으로 : byte 를 지정해 두면 네트워크 트래픽을 조금이라도 줄일 수 있음
    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)
        {
            // 서버는 게임씬에서 스폰된 직후 Playing 상태로 전환
            _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)
    {
        // 자기 자신이 해제된 경우 = 서버와의 연결이 끊김 = Host 이탈
        if (clientId != NetworkManager.Singleton.LocalClientId) return;

        Debug.Log("[Disconnect] 서버와의 연결이 끊겼습니다. 연결 씬으로 복귀합니다.");

        NetworkManager.Singleton.Shutdown();
        SceneManager.LoadScene("BootScene");
    }
}
profile
안녕하세요

0개의 댓글