콘텐츠로 이동

Unity Multiplayer Services — Lobby, Relay, Netcode 연동

Unity Gaming Services(UGS)는 별도 서버 없이 멀티플레이어를 구현할 수 있는 클라우드 서비스 묶음입니다. Lobby(방 목록/매칭), Relay(NAT 우회 P2P), Netcode for GameObjects(동기화)를 조합해 완전한 멀티플레이어 흐름을 만들 수 있습니다.


Package Manager:
- com.unity.services.lobby
- com.unity.services.relay
- com.unity.netcode.gameobjects
- com.unity.services.authentication

using Unity.Services.Core;
using Unity.Services.Authentication;
using UnityEngine;
public class UGSInitializer : MonoBehaviour
{
async void Start()
{
await UnityServices.InitializeAsync();
if (!AuthenticationService.Instance.IsSignedIn)
await AuthenticationService.Instance.SignInAnonymouslyAsync();
Debug.Log($"플레이어 ID: {AuthenticationService.Instance.PlayerId}");
}
}

using Unity.Services.Lobbies;
using Unity.Services.Lobbies.Models;
public class LobbyManager : MonoBehaviour
{
private Lobby _currentLobby;
// 방 생성
public async Task<Lobby> CreateLobbyAsync(string lobbyName, int maxPlayers)
{
var options = new CreateLobbyOptions
{
IsPrivate = false,
Data = new Dictionary<string, DataObject>
{
["map"] = new DataObject(
DataObject.VisibilityOptions.Public, "Forest")
}
};
_currentLobby = await LobbyService.Instance
.CreateLobbyAsync(lobbyName, maxPlayers, options);
// 호스트는 하트비트로 로비 유지
StartCoroutine(HeartbeatLobby(_currentLobby.Id));
return _currentLobby;
}
// 방 목록 조회 및 참가
public async Task JoinPublicLobbyAsync()
{
var lobbies = await LobbyService.Instance.QueryLobbiesAsync();
if (lobbies.Results.Count == 0) return;
_currentLobby = await LobbyService.Instance
.JoinLobbyByIdAsync(lobbies.Results[0].Id);
}
IEnumerator HeartbeatLobby(string lobbyId)
{
while (true)
{
yield return new WaitForSeconds(15f);
LobbyService.Instance.SendHeartbeatPingAsync(lobbyId);
}
}
}

using Unity.Services.Relay;
using Unity.Services.Relay.Models;
using Unity.Netcode;
using Unity.Netcode.Transports.UTP;
public class RelayManager : MonoBehaviour
{
// 호스트: Relay 할당 생성
public async Task<string> StartHostWithRelayAsync(int maxConnections = 4)
{
var allocation = await RelayService.Instance
.CreateAllocationAsync(maxConnections);
string joinCode = await RelayService.Instance
.GetJoinCodeAsync(allocation.AllocationId);
var transport = NetworkManager.Singleton.GetComponent<UnityTransport>();
transport.SetHostRelayData(
allocation.RelayServer.IpV4,
(ushort)allocation.RelayServer.Port,
allocation.AllocationIdBytes,
allocation.Key,
allocation.ConnectionData);
NetworkManager.Singleton.StartHost();
return joinCode;
}
// 클라이언트: Join Code로 연결
public async Task JoinWithRelayAsync(string joinCode)
{
var allocation = await RelayService.Instance
.JoinAllocationAsync(joinCode);
var transport = NetworkManager.Singleton.GetComponent<UnityTransport>();
transport.SetClientRelayData(
allocation.RelayServer.IpV4,
(ushort)allocation.RelayServer.Port,
allocation.AllocationIdBytes,
allocation.Key,
allocation.ConnectionData,
allocation.HostConnectionData);
NetworkManager.Singleton.StartClient();
}
}

public class MultiplayerManager : MonoBehaviour
{
public async Task HostGameAsync()
{
// 1. Relay 할당 → Join Code 획득
string joinCode = await relayManager.StartHostWithRelayAsync();
// 2. Lobby에 Join Code 저장
var updateOptions = new UpdateLobbyOptions
{
Data = new Dictionary<string, DataObject>
{
["joinCode"] = new DataObject(
DataObject.VisibilityOptions.Member, joinCode)
}
};
await LobbyService.Instance.UpdateLobbyAsync(
_currentLobby.Id, updateOptions);
}
public async Task JoinGameAsync(string lobbyId)
{
// 1. Lobby 참가
var lobby = await LobbyService.Instance.JoinLobbyByIdAsync(lobbyId);
// 2. Lobby에서 Join Code 추출
string joinCode = lobby.Data["joinCode"].Value;
// 3. Relay로 연결
await relayManager.JoinWithRelayAsync(joinCode);
}
}

using Unity.Netcode;
public class PlayerHealth : NetworkBehaviour
{
private NetworkVariable<int> _health =
new(100, NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server);
public override void OnNetworkSpawn()
{
_health.OnValueChanged += (oldVal, newVal) =>
UpdateHealthUI(newVal);
}
[ServerRpc]
public void TakeDamageServerRpc(int damage)
{
_health.Value = Mathf.Max(0, _health.Value - damage);
}
}

Lobby로 방을 관리하고 Relay로 NAT 우회 P2P 연결을 수립한 뒤 Netcode for GameObjects로 상태를 동기화하는 것이 UGS 멀티플레이어의 표준 흐름입니다. Join Code를 Lobby 데이터에 저장해 두 서비스를 연결하는 패턴이 핵심입니다.