ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Unity] 유니티 멀티플레이를 위한 Lobby, 플레이어 매치메이킹
    쾌락없는 책임 (공부)/Unity 2023. 1. 30. 18:48
    반응형

    개요

    친구와 함께 플젝을 시작하기 전, 최근 유니티에서 퍼스트파티로 네트워크 서비스를 제공하는 걸 접했습니다. Unity NetCode, Lobby, Relay들이 그 주인공들인데 최신 버전들은 이 혜택을 받을 수 있습니다.

    그래서 이번에 Lobby를 통해서 플레이어 매치메이킹을 만든 과정에 대해서 한번 적어보려고 합니다.

     

     

    Lobby는 매치메이킹 용이지 멀티플레이 기능은 아닙니다.

    일단 여러 플레이어가 CO-OP을 하거나 VS를 하는 기능과 Lobby는 다른 기능입니다. Lobby는 이름대로 매치메이킹을 위한 도구로 이후 Relay와 더불어서 플레이어 연결에 사용되는 기능입니다. 때문에 '멀티플레이' 자체를 보고 싶다면 NetCode 부분을 알아보시면 됩니다.

    (추후 글을 작성할 예정입니다)

     

    NetCode

    Unity의 최신 퍼스트 파티 멀티플레이어 네트워킹 솔루션 NetCode를 살펴보고 멀티플레이어 게임을 제작하세요.

    unity.com

     

     

    Lobby를 위해 준비할 것들

    일단 Window > Package Manager에서 Lobby를 설치해야 합니다. 

    이후 Edit > Project Settings > Services 에서 로그인을 해 줘야 합니다. 단순 로그인 하고 새 Org를 만드는 것이니 크게 어렵지 않습니다.

     

     

    Unity Gaming Services

     

    dashboard.unity3d.com

    그런 다음 Unity Gaming Services에 가서 왼쪽 대쉬보드에서 Multiplayer > Lobby에서 로비를 활성화해주면 됩니다. Setup Guide 메뉴가 있으니 이를 활용해서 진행하면 됩니다. 또한 매달 10GB 정도를 무료로 제공해 줍니다.

    10GB가 넘어간다면 1GB당 0.09~0.16달러라고 하니 참고하셔서 관리해주는게 좋습니다. 테스트를 진행하면서 보는건 테스트의 경우는 생각보다 많은 양이 들지 않아 켜고 끄는걸 반복해서 한 1~5MB 정도 쓴 것 같네요.

    다 마무리 되면 이제 본격적으로 로비 서비스를 통해서 매치메이킹을 할 준비가 되었습니다.

     

     

    코드 배경 지식

    제가 코드 쓰는 과정을 보여줄 수 있다면 해설이 다 되겠지만 블로그 특성상 보여주기가 힘들어 제 코드에 대한 배경 지식을 설명하고 가겠습니다. (ex) Lobby와 관련 없는 부분들, 클래스 필드)

    using System;
    using System.Collections.Generic;
    using UnityEngine;
    using Unity.Services.Core;
    using Unity.Services.Lobbies;
    using Unity.Services.Lobbies.Models;
    using Unity.Services.Authentication;

    필요한 namespace들은 위와 같습니다. 그리고 그 외 코드 설명은 아래와 같습니다.

    public class LobbySingleton : MonoBehaviour
    {
        public static LobbySingleton instance { get; private set; }
        // 로비 검색 목록이 변경되면 UI에게 알려 UI에서 목록을 갱신하는 이벤트
        public event Action<List<Lobby>> lobbyListChangedEvent;
        // 로비에 들어오면(로비 갱신에도 사용) 호출할 이벤트로 로비 관련 UI함수들이 구독중
        public event Action<Lobby> joinLobbyEvent;
        // 로비를 나갈때 UI를 제어해줄 이벤트
        public event Action leaveLobbyEvent; 
        // 강퇴되었을때 UI를 제어해줄 이벤트
        public event Action kickedFromLobbyEvent;
        // 게임이 시작되면 호출될 이벤트로 Relay와 관련
        public event Action gameStartEvent;
        // 현재 참가한 로비를 저장할 클래스 필드
        private Lobby joinedLobby; 
        // 인증에 사용한 플레이어 이름 저장
        private string playerName;
        // 아래 설명할 로비 관련 타이머들
        private float lobbyMaintainTimer = 0.0f;
    
        private float lobbyInfomationUpdateTimer = 0.0f;
    
        private void Awake()
        {
        // 기능만 있어서 싱글토으로 제작
            instance = this;
        }
        
        //... 이 아래는 아래 설명에
        // 전체 코드는 본문 아래

    추가적으로 NetworkConstants.~~ 는 단순 readonly string, float 을 저장하는 클래스일 뿐입니다. 관련 변수들은 아래 더보기를 눌러보시면 됩니다.

    더보기
    namespace AG.Network
    {
        public class NetworkConstants
        {
            public static readonly float LOBBY_INFO_UPDATE_TIME = 1.1f;
    
            public static readonly float LOBBY_MAINTAIN_TIME = 25.0f;
    
            public static readonly string GAMEMODE_KEY = "GameMode";
    
            public static readonly string PLAYERNAME_KEY = "PlayerName";
    
            public static readonly string GAMESTART_KEY = "GameStart";
    
            public static readonly string GAMESTART_KEY_DEFAULT = "DEFAULT";
        }
    }

     

     

    인증으로 시작, 로비 만들기, 로비의 특징들

    일단 유니티 게임 서비스를 사용하는 것이니 로그인을 먼저 해야 합니다.

    public async void Authenticate(string playerName)
    {
        this.playerName = playerName;  // 클래스 안 변수로 저장
        InitializationOptions initializationOptions = new InitializationOptions();
        initializationOptions.SetProfile(playerName); // 프로필을 설정하고
    
        await UnityServices.InitializeAsync(initializationOptions);
        await AuthenticationService.Instance.SignInAnonymouslyAsync();
    }

    이런 함수를 통해서 인증을 하면 됩니다. 전부 비동기 프로그래밍으로 진행되는데 그것에 대한 설명은 논제를 벗어나니 생략하겠습니다. 

     

    이제 인증이 되었으니 로비를 만들 수 있습니다.

    public async void CreateLobby(string lobbyName, int maxPlayers, bool isPrivate)
    {
        CreateLobbyOptions createOptions = new CreateLobbyOptions{
            Player = GetPlayer(),
            IsPrivate = isPrivate,
            Data = new Dictionary<string, DataObject>{
                { NetworkConstants.GAMEMODE_KEY, new DataObject(DataObject.VisibilityOptions.Public, "DefaultGameMode") },
                { NetworkConstants.GAMESTART_KEY, new DataObject(DataObject.VisibilityOptions.Member, NetworkConstants.GAMESTART_KEY_DEFAULT) }
            }
        };
    
        var lobby = await LobbyService.Instance.CreateLobbyAsync(lobbyName, maxPlayers, createOptions);
        joinedLobby = lobby;  // 클래스에서 로비에 대한 정보를 가지고 있는게 좋다 (Lobby 타입)
    
        joinLobbyEvent?.Invoke(joinedLobby);  // 로비 만들어지고 이벤트 있으면 호출
    }
    
    private Player GetPlayer()
    {
        return new Player{
            Data = new Dictionary<string, PlayerDataObject>{
                {NetworkConstants.PLAYERNAME_KEY, new PlayerDataObject(PlayerDataObject.VisibilityOptions.Member, playerName)}
            }
        };
    }

    위 코드에서 NetworkConstants.~~ 는 단순 readonly string 들의 모음입니다.

    CreateLobbyOptions createOptions = new CreateLobbyOptions{
        Player = GetPlayer(),
        IsPrivate = isPrivate,
        Data = new Dictionary<string, DataObject>{
            { NetworkConstants.GAMEMODE_KEY, new DataObject(DataObject.VisibilityOptions.Public, "DefaultGameMode") },
            { NetworkConstants.GAMESTART_KEY, new DataObject(DataObject.VisibilityOptions.Member, NetworkConstants.GAMESTART_KEY_DEFAULT) }
        }
    };

    중요한 부분은 이 부분인데 Player, IsPrivate는 플레이어 정보,  Lobby가 비밀방(방 코드를 알아야 올 수 있는)여부를 뜻합니다.

    Data의 경우 직접 정의하는 Dictionary인데 저같은 경우 게임 모드와 GAMESTART_KEY가 들어갑니다. 이후 알 수 있겠지만 로비에서 정보를 업데이트하면서 게임이 시작되었는지 알아보는 용도입니다. 

     

    그런데 이렇게 로비를 만들었는데 알아야 할 점이 있습니다. 여기서 유니티 게임 서비스의 로비는 30초 지나면 자동으로 사라지게 됩니다. 때문에 이 30초가 되기 전 로비를 계속 활성화 해줘야 합니다. 저 같은 경우 20초 초반으로 설정하고 아래 함수를 호출해 줬습니다.

    private void Update()
    {
        MaintainLobbyAlive();  // 코루틴을 사용하는 방법이 있겠지만 안정성을 위해서 Update에서 사용했습니다.
    }
    
    public async void MaintainLobbyAlive()
    {
        if(!IsLobbyhost())  return;
    
        lobbyMaintainTimer += Time.deltaTime;
        if(lobbyMaintainTimer < NetworkConstants.LOBBY_MAINTAIN_TIME) return;
    
        lobbyMaintainTimer = 0.0f;
        // 시간이 다 되면 아래 함수를 통해 로비가 유지될 수 있게 해줍니다
        await LobbyService.Instance.SendHeartbeatPingAsync(joinedLobby.Id);
    }
    
    private bool IsLobbyhost()
    {
        return joinedLobby != null && joinedLobby.HostId == AuthenticationService.Instance.PlayerId;
    }

    물론 이 작업은 호스트가 아니면 할 이유가 없기에 먼저 호스트 여부를 체크해 줍니다. 그런 다음 시간이 되면 LobbyService의 SendHeartbeatPingAsync 함수를 통해서 로비를 유지해 줍니다. 이렇게 해야 30초가 지나도 로비를 계속 유지할 수 있는 것입니다. 이 부분을 꼭 넣어서 로비가 계속 검색될 수 있게해줍시다.

     

     

    만들어진 로비들 검색하기

    이제 다른 사용자가 만드는 로비를 검색해야 합니다. 이런 로비들의 List를 반환하는 

    public async void GetLobbyList()
    {
        try
        {
            QueryLobbiesOptions options = new QueryLobbiesOptions{
                Count = 25,
                Filters = new List<QueryFilter>{
                    new QueryFilter(
                        field: QueryFilter.FieldOptions.AvailableSlots,
                        op: QueryFilter.OpOptions.GT,
                        value: "0"
                    )
                },
                Order = new List<QueryOrder>{
                    new QueryOrder(
                        asc: false,
                        field: QueryOrder.FieldOptions.Created
                    )
                }
            };
    
            QueryResponse lobbyListQueryResponse = await Lobbies.Instance.QueryLobbiesAsync();
            // QueryResponse.Results에 로비 List가 저장되어 있습니다
            lobbyListChangedEvent?.Invoke(lobbyListQueryResponse.Results);  // List<Lobby> 를 이벤트로 전달
        }
        catch (LobbyServiceException e)
        {
            Debug.Log($"{e}");
        }
    }

    위 함수를 통해서 호출할 수 있습니다. 질의(QueryLobbiesOption)에 다양한 옵션들을 줄 수 있습니다.

    namespace Unity.Services.Lobbies
    {
        public class QueryLobbiesOptions
        {
            public QueryLobbiesOptions();
    
            public int Count { get; set; }
            public int Skip { get; set; }
            public bool SampleResults { get; set; }
            public List<QueryFilter> Filters { get; set; }
            public List<QueryOrder> Order { get; set; }
            public string ContinuationToken { get; set; }
        }
    }
    Count 최대 검색할 로비의 수 (Default : 10, 1~100 가능)
    Skip (제보 바랍니다)
    SampleResults 필터 결과값에서 랜덤 하게 방을 반환할건지 여부
    Filters 검색 필터 설정 (아래 QueryFilter 링크 참고)
    Order 순서를 어떻게 할지에 대한 설정 (아래 QueryOrder 링크 참고)
    ContinuationToken 다음 검색결과 페이지를 위한 토큰

    옵션들에 대한 설명은 위와 같은데 대부분의 경우 제 코드처럼 Count, Filters, Order를 설정해 주면 됩니다.

    QueryOrder와 QueryFilter 관련 설명은 아래 링크에 자세히 나와 있습니다. 너무 지나치게 파고들어 글이 길어질까 봐 링크로 대체합니다.

     

    Class QueryFilter | Lobby | 1.0.3

    Class QueryFilter Inheritance QueryFilter Assembly : solution.dll Syntax [Preserve] public class QueryFilter Constructors QueryFilter(QueryFilter.FieldOptions, String, QueryFilter.OpOptions) A filter for an individual field that is applied to a query. Decl

    docs.unity3d.com

     

     

    Class QueryOrder | Lobby | 1.0.3

    Class QueryOrder Inheritance QueryOrder Assembly : solution.dll Syntax [Preserve] public class QueryOrder Constructors QueryOrder(Boolean, QueryOrder.FieldOptions) An order for an individual field that is applied to a query. Declaration [Preserve] public Q

    docs.unity3d.com

     

     

     

    로비에 참가하기 (+ 퀵 매칭)

    이제 로비 리스트를 뽑을 수 있게 되었으니 로비에 참가를 할 수 있어야 합니다. 일단 저 같은 경우 위 로비 List를 UI로 뽑아내는 작업을 했습니다.

    // UI 에서 사용할 함수 
    // Lobby를 인자로 넘겨주면 됩니다.
    public async void JoinLobbyByUI(Lobby lobby)
    {
        var joinOption = new JoinLobbyByIdOptions{ Player = GetPlayer() };
        joinedLobby = await LobbyService.Instance.JoinLobbyByIdAsync(lobby.Id, joinOption);
    
        joinLobbyEvent?.Invoke(joinedLobby);
    }
    
    // 로비 코드를 통해서 들어가는 함수
    public async void JoinLobbyByCode(string lobbyCode)
    {
        var joinOption = new JoinLobbyByCodeOptions{ Player = GetPlayer() };
        joinedLobby = await LobbyService.Instance.JoinLobbyByCodeAsync(lobbyCode, joinOption);
    
        joinLobbyEvent?.Invoke(joinedLobby);
    }

    UI로 리스트를 뽑는 게 준비되었다면 위 JoinLobbyByUI 함수를 활용해서 로비에 접근할 수 있게 됩니다. 만일 아직 UI가 없거나 비밀방에 코드로 참가하기 위해서는 아래 JoinLobbyByCode 함수에 로비 코드를 넘겨주면 들어갈 수 있게 됩니다.

     

    // 퀵 매치
    public async void QuickMatch()
    {
        try {
            QuickJoinLobbyOptions options = new QuickJoinLobbyOptions{ Player = GetPlayer() };
            joinedLobby = await LobbyService.Instance.QuickJoinLobbyAsync(options);
    
            joinLobbyEvent?.Invoke(joinedLobby);
        } 
        catch (LobbyServiceException e) 
        {
            if(e.Reason == LobbyExceptionReason.NoOpenLobbies)
            {
                Debug.Log($"No Open Lobbies");
            }
            Debug.Log(e);
        }
    }

    퀵 매치의 경우 위 코드를 사용하면 되며 LobbyServiceException에서 로비가 없는 오류가 많이 검출되므로 해당 오류에 대한 처리를 해주면 좋습니다.

    그리고 여기서 주의할 점은 QuickJoinLobbyOptions에 인자로 플레이어 정보를 넘겨줘야 한다는 점인데요. 해외에서 레퍼런스를 많이 찾는 도중 대부분 이곳에 인자를 주지 않아 이후 오류가 나는 모습을 볼 수 있습니다. 때문에 꼭 플레이어 정보를 넘겨줘야 합니다.

    // 꼭 Player를 넘겨줍시다!
    QuickJoinLobbyOptions options = new QuickJoinLobbyOptions{ Player = GetPlayer() };
    joinedLobby = await LobbyService.Instance.QuickJoinLobbyAsync(options);

     

     

    플레이어가 들어온 거는 어떻게 알 수 있을까?

    이제 로비 만들기, 접속까지 했으니 접속을 했을 때 모든 로비에 있는 사람들이 새 사람이 들어온 것, 나간 것을 알 수 있어야 합니다. 안타깝게도 제가 짧은 지식으로 찾은 결과 이걸 이벤트로 할 방법은 없고 위 로비 유지 함수처럼 Update에서 계속 로비를 갱신하는 방법으로 진행해야 했습니다.

    private void Update()
    {
        MaintainLobbyAlive();
        // 이 함수가 추가되었다
        RefreshLobbyInfomation();
    }
    
    public async void RefreshLobbyInfomation()
    {
        if(joinedLobby == null) return;
    
        lobbyInfomationUpdateTimer += Time.deltaTime;
        if(lobbyInfomationUpdateTimer < NetworkConstants.LOBBY_INFO_UPDATE_TIME)   return;
    
        lobbyInfomationUpdateTimer = 0.0f;
        // 시간이 되면 로비를 다시 갱신함
        var lobby = await LobbyService.Instance.GetLobbyAsync(joinedLobby.Id);
        joinedLobby = lobby;
    
        if(!IsPlayerInLobby())  // 플레이어가 로비에 없는 경우 처리
        {
            joinedLobby = null;
            kickedFromLobbyEvent?.Invoke();
            return;
        }
        // 위 언급한 게임 시작 키가 변경되었다면 게임 시작이라는 뜻!
        if(joinedLobby.Data[NetworkConstants.GAMESTART_KEY].Value != NetworkConstants.GAMESTART_KEY_DEFAULT)
        {
            if(!IsLobbyhost())
            {
                // 이 경우 Relay 관련 내용이라 Lobby만 준비했다면 넘겨도 됩니다.
                RelaySingleton.JoinRelay(joinedLobby.Data[NetworkConstants.GAMESTART_KEY].Value);
            }
    
            joinedLobby = null;
    
            gameStartEvent?.Invoke();
            return;
        }
    
        // 위 두가지 경우가 해당되지 않는다면 로비 정보 갱신!
        joinLobbyEvent?.Invoke(joinedLobby);
    }
    
    // 플레이어가 로비에 있는지 확인하는 함수
    private bool IsPlayerInLobby()
    {
        if(joinedLobby == null || joinedLobby.Players == null)  return false;
    
        foreach(var player in joinedLobby.Players)
        {
            if(player.Id != AuthenticationService.Instance.PlayerId)    continue;
            return true;
        }
    
        return false;
    }

     

     

    플레이어 강퇴(제거) 및 호스트 넘겨주기

    public async void KickPlayer(string playerId)
    {
        if(!IsLobbyhost())  return;  // 호스트가 아니면 권한이 없음
        if(playerId == AuthenticationService.Instance.PlayerId) return; // 호스트 스스로 강퇴 불가능
    
        try
        {
            await LobbyService.Instance.RemovePlayerAsync(joinedLobby.Id, playerId);
        }
        catch (LobbyServiceException e) 
        {
            Debug.Log($"{e}");
        }
    } 
    
    private async void MigrateHost()
    {
    // 로비 호스트가 아니거나 플레이어가 호스트밖에 없으면 이 함수는 실행 X
        if(!IsLobbyhost() || joinedLobby.Players.Count <= 1)  return;
        try
        {
            joinedLobby = await Lobbies.Instance.UpdateLobbyAsync(joinedLobby.Id, new UpdateLobbyOptions{
                HostId = joinedLobby.Players[1].Id
            });
        }
        catch (LobbyServiceException e)
        {
            Debug.Log($"{e}");
        }
    }

    위와 같은 코드를 바탕으로 진행하면 됩니다. 코드가 크게 어려운 부분은 없습니다.


     

    전체 코드

    전체 코드의 경우 Relay 관련 부분이 들어가 있습니다. 때문에 이 부분을 감안하고 보셔야 합니다.

    더보기
    using System;
    using System.Collections.Generic;
    using UnityEngine;
    using Unity.Services.Core;
    using Unity.Services.Lobbies;
    using Unity.Services.Lobbies.Models;
    using Unity.Services.Authentication;
    using AG.Network.AGRelay;
    
    namespace AG.Network.AGLobby
    {
        public class LobbySingleton : MonoBehaviour
        {
            public static LobbySingleton instance { get; private set; }
    
            public event Action<List<Lobby>> lobbyListChangedEvent;
    
            public event Action<Lobby> joinLobbyEvent;
    
            public event Action leaveLobbyEvent;
    
            public event Action kickedFromLobbyEvent;
    
            public event Action gameStartEvent;
    
            private Lobby joinedLobby;
    
            private string playerName;
    
            private float lobbyMaintainTimer = 0.0f;
    
            private float lobbyInfomationUpdateTimer = 0.0f;
    
            private void Awake()
            {
                instance = this;
            }
    
            private void Update()
            {
                MaintainLobbyAlive();
                RefreshLobbyInfomation();
            }
    
            public async void Authenticate(string playerName)
            {
                this.playerName = playerName;
                InitializationOptions initializationOptions = new InitializationOptions();
                initializationOptions.SetProfile(playerName);
    
                await UnityServices.InitializeAsync(initializationOptions);
                await AuthenticationService.Instance.SignInAnonymouslyAsync();
            }
    
            public async void CreateLobby(string lobbyName, int maxPlayers, bool isPrivate)
            {
                CreateLobbyOptions createOptions = new CreateLobbyOptions{
                    Player = GetPlayer(),
                    IsPrivate = isPrivate,
                    Data = new Dictionary<string, DataObject>{
                        { NetworkConstants.GAMEMODE_KEY, new DataObject(DataObject.VisibilityOptions.Public, "DefaultGameMode") },
                        { NetworkConstants.GAMESTART_KEY, new DataObject(DataObject.VisibilityOptions.Member, NetworkConstants.GAMESTART_KEY_DEFAULT) }
                    }
                };
    
                var lobby = await LobbyService.Instance.CreateLobbyAsync(lobbyName, maxPlayers, createOptions);
                joinedLobby = lobby;
    
                joinLobbyEvent?.Invoke(joinedLobby);
            }
            
            public async void MaintainLobbyAlive()
            {
                if(!IsLobbyhost())  return;
    
                lobbyMaintainTimer += Time.deltaTime;
                if(lobbyMaintainTimer < NetworkConstants.LOBBY_MAINTAIN_TIME) return;
    
                lobbyMaintainTimer = 0.0f;
                await LobbyService.Instance.SendHeartbeatPingAsync(joinedLobby.Id);
            }
    
            public async void RefreshLobbyInfomation()
            {
                if(joinedLobby == null) return;
    
                lobbyInfomationUpdateTimer += Time.deltaTime;
                if(lobbyInfomationUpdateTimer < NetworkConstants.LOBBY_INFO_UPDATE_TIME)   return;
    
                lobbyInfomationUpdateTimer = 0.0f;
                var lobby = await LobbyService.Instance.GetLobbyAsync(joinedLobby.Id);
                joinedLobby = lobby;
    
                if(!IsPlayerInLobby())
                {
                    joinedLobby = null;
                    kickedFromLobbyEvent?.Invoke();
                    return;
                }
                if(joinedLobby.Data[NetworkConstants.GAMESTART_KEY].Value != NetworkConstants.GAMESTART_KEY_DEFAULT)
                {
                    if(!IsLobbyhost())
                    {
                        RelaySingleton.JoinRelay(joinedLobby.Data[NetworkConstants.GAMESTART_KEY].Value);
                    }
    
                    joinedLobby = null;
    
                    gameStartEvent?.Invoke();
                    return;
                }
                
                joinLobbyEvent?.Invoke(joinedLobby);
            }
    
            public async void JoinLobbyByUI(Lobby lobby)
            {
                var joinOption = new JoinLobbyByIdOptions{ Player = GetPlayer() };
                joinedLobby = await LobbyService.Instance.JoinLobbyByIdAsync(lobby.Id, joinOption);
    
                joinLobbyEvent?.Invoke(joinedLobby);
            }
    
            public async void JoinLobbyByCode(string lobbyCode)
            {
                var joinOption = new JoinLobbyByCodeOptions{ Player = GetPlayer() };
                joinedLobby = await LobbyService.Instance.JoinLobbyByCodeAsync(lobbyCode, joinOption);
    
                joinLobbyEvent?.Invoke(joinedLobby);
            }
    
            public async void QuickMatch()
            {
                try {
                    QuickJoinLobbyOptions options = new QuickJoinLobbyOptions{ Player = GetPlayer() };
                    joinedLobby = await LobbyService.Instance.QuickJoinLobbyAsync(options);
    
                    joinLobbyEvent?.Invoke(joinedLobby);
                } 
                catch (LobbyServiceException e) 
                {
                    if(e.Reason == LobbyExceptionReason.NoOpenLobbies)
                    {
                        Debug.Log($"No Open Lobbies");
                    }
                    Debug.Log(e);
                }
            }
    
            public async void GetLobbyList()
            {
                try
                {
                    QueryLobbiesOptions options = new QueryLobbiesOptions{
                        Count = 25,
                        Filters = new List<QueryFilter>{
                            new QueryFilter(
                                field: QueryFilter.FieldOptions.AvailableSlots,
                                op: QueryFilter.OpOptions.GT,
                                value: "0"
                            )
                        },
                        Order = new List<QueryOrder>{
                            new QueryOrder(
                                asc: false,
                                field: QueryOrder.FieldOptions.Created
                            )
                        }
                    };
    
                    QueryResponse lobbyListQueryResponse = await Lobbies.Instance.QueryLobbiesAsync();
                    lobbyListChangedEvent?.Invoke(lobbyListQueryResponse.Results);
                }
                catch (LobbyServiceException e)
                {
                    Debug.Log($"{e}");
                }
            }
    
            public async void LeaveLobby()
            {
                if(joinedLobby == null) return;
    
                try
                {
                    MigrateHost();
                    await LobbyService.Instance.RemovePlayerAsync(joinedLobby.Id, AuthenticationService.Instance.PlayerId);
                    joinedLobby = null;
    
                    leaveLobbyEvent?.Invoke();
                }
                catch (LobbyServiceException e)
                {
                    Debug.Log($"{e}");
                }
            }
    
            public async void KickPlayer(string playerId)
            {
                if(!IsLobbyhost())  return;
                if(playerId == AuthenticationService.Instance.PlayerId) return;
    
                try
                {
                    await LobbyService.Instance.RemovePlayerAsync(joinedLobby.Id, playerId);
                }
                catch (LobbyServiceException e) 
                {
                    Debug.Log($"{e}");
                }
            } 
    
            public async void StartGame()
            {
                if(!IsLobbyhost())  return;
    
                try
                {
                    string relayCode = await RelaySingleton.CreateRelay();
    
                    Lobby lobby = await Lobbies.Instance.UpdateLobbyAsync(joinedLobby.Id, new UpdateLobbyOptions{
                        Data = new Dictionary<string, DataObject>{
                            { NetworkConstants.GAMESTART_KEY, new DataObject(DataObject.VisibilityOptions.Member, relayCode) }
                        }
                    });
    
                    joinedLobby = lobby;
                }
                catch (LobbyServiceException e)
                {
                    Debug.Log($"{e}");
                }
            }
    
            private Player GetPlayer()
            {
                return new Player{
                    Data = new Dictionary<string, PlayerDataObject>{
                        {NetworkConstants.PLAYERNAME_KEY, new PlayerDataObject(PlayerDataObject.VisibilityOptions.Member, playerName)}
                    }
                };
            }
    
            private bool IsPlayerInLobby()
            {
                if(joinedLobby == null || joinedLobby.Players == null)  return false;
    
                foreach(var player in joinedLobby.Players)
                {
                    if(player.Id != AuthenticationService.Instance.PlayerId)    continue;
                    return true;
                }
    
                return false;
            }
    
            private async void MigrateHost()
            {
                if(!IsLobbyhost() || joinedLobby.Players.Count <= 1)  return;
                try
                {
                    joinedLobby = await Lobbies.Instance.UpdateLobbyAsync(joinedLobby.Id, new UpdateLobbyOptions{
                        HostId = joinedLobby.Players[1].Id
                    });
                }
                catch (LobbyServiceException e)
                {
                    Debug.Log($"{e}");
                }
            }
    
            private bool IsLobbyhost()
            {
                return joinedLobby != null && joinedLobby.HostId == AuthenticationService.Instance.PlayerId;
            }
    
            public Lobby GetJoinedLobby()
            {
                return joinedLobby;
            }
        }
    }

    참고 자료

    시간이 된다면 영상으로 남기고 싶은데 아쉽게도 여유가 없어 공식 문서를 링크로 남깁니다. 추후 영상을 찍는다면 이 글을 업데이트하도록 하겠습니다.

     

     

    Namespace Unity.Services.Lobbies | Lobby | 1.0.3

    Namespace Unity.Services.Lobbies Classes Optional parameter class for Lobby creation requests. Optional parameter class for Lobby Join By Code requests Optional parameter class for Lobby Join By ID requests This class is marked for deprecation. Please use

    docs.unity3d.com

     

    반응형

    댓글

Designed by Tistory.