매치메이킹 가이드
누군가와 같이 플레이(또는 상대하여) 하기 위하여 룸에 입장하는 것은 Photon에서 매우 쉽습니다.
기본적으로 3가지의 접근 방식이 있습니다:
서버에게 매칭되는 룸을 찾아 달라고 하는 것, 친구를 따라 룸에 입장하거나 룸의 목록을 얻어 사용자가 하나를 선택하는 것 입니다.
세 가지 변형 모두 Photon에서 지원되며 직접 변경할 수도 있습니다.
저희는 서버 측에서 매치메이킹을 하여 빠르고 어려움 없이 사용하는 것이 현대의 게임에 가장 최적이라고 생각합니다.
따라서 룸 목록을 사용하는 것은 그리 권장하고 있지 않습니다.
아래에는 내가 직접 하려는 생각을 가지고 있다면 기억해야 할 옵션들과 사항에 대해서 설명합니다.
크로스 플랫폼 매치메이킹 체크리스트
설계 사상에 의하여 Photon 클라우드에서는 다른 지역간 게임은 할 수 없습니다.
기기 또는 플랫폼에 관계 없이 동일한 지역에 접속한 플레이어간에만 플레이 할 수 있습니다.
플레이어 매칭에 문제가 발생하고 있다면 다음의 체크리스트로 검토 해보세요:
- 모든 클라이언트들이 동일한
AppId
,AppVersion
과Region
을 이용하고 있는지 확인 합니다. - 이름으로 룸 참여를 시도하기 전에, 생성된 룸인지 아니면
JoinOrCreateRoom
메소드를 사용하였는지 확인 합니다. - 무작위 룸에 참여 하고 있다면 생성 할 때와 동일한 로비(이름과 타입)를 선택 했는지 확인 합니다.
빠른 매치
요즘 대부분의 플레이어들은 매치에 바로 참여하기를 원합니다.
플레이어들은 지금 당장 게임을 하고 싶어 합니다.
그렇기 때문에 대부분의 게임은 빠른 매치를 첫 번째 모드로 제공합니다.
여기에 설명된 제안된 워크플로우는 플레이어가 긴 룸 목록에서 임의로 하나를 선택하도록 요청하지 않고 바로 룸으로 이동합니다.
플레이어를 빨리 룸에 참가시키려면 다음을 수행합니다:
JoinRandomOrCreateRoom
단순하게 JoinRandomOrCreateRoom
(클라이언트 SDK에 따라 정확한 함수 또는 메소드 이름을 다를 수 있습니다)을 호출합니다.
룸이 있으면, 참여할 것이고 그렇지 않으면 새로운 룸이 생성됩니다.
C#
using Photon.Realtime;
using System.Collections.Generic;
public class QuickMatchExample : IMatchmakingCallbacks
{
private LoadBalancingClient loadBalancingClient;
private void QuickMatch()
{
loadBalancingClient.OpJoinRandomOrCreateRoom(null, null);;
}
// do not forget to register callbacks via loadBalancingClient.AddCallbackTarget
// also deregister via loadBalancingClient.RemoveCallbackTarget
#region IMatchmakingCallbacks
void IMatchmakingCallbacks.OnJoinedRoom()
{
// joined a room successfully
}
// [..] Other callbacks implementations are stripped out for brevity, they are empty in this case as not used.
#endif
}
JoinRandomRoom 또는 CreateRoom
JoinRandomRoom
(클라이언트 SDK에 따라 정확한 함수 또는 메소드 이름을 다를 수 있습니다)을 시도합니다.- 최고의 경우에 끝입니다.
클라이언트는 성공적으로 룸에 참여하게 됩니다. - 가장 나쁜 경우에는, 룸이 존재하지 않거나, 참여할 수 있는 룸이 없는 경우입니다 (닫힘, 비공개 또는 가득 참).
- 최고의 경우에 끝입니다.
- 즉시 룸을 찾지 못하면 룸을 생성합니다.
- 룸 이름을 표시하지 않는 경우(그리고 왜 표시해야 하는지), 이름을 만들지 마십시오.
서버에서 이 작업을 수행합니다.
룸을 작성할 때 null 또는 빈 문자열을 "룸 이름"으로 설정합니다.
룸은 고유한 GUID를 가집니다. - "최대 플레이어 수" 값을 적용합니다.
이렇게 하면, 서버는 룸에 플레이어가 가득 차면 플레이어 추가를 중지합니다.
- 룸 이름을 표시하지 않는 경우(그리고 왜 표시해야 하는지), 이름을 만들지 마십시오.
- 클라이언트가 룸에 혼자 있는 경우(클라이언트 수 == 1):
잠깐만요.
상대를 기다리는 화면을 보여줍니다. - 충분한 플레이어가 방에 있을 때 게임을 "시작"할 수 있습니다.
새 플레이어가 들어오지 못하도록 하려면 룸을 "닫습니다".
서버가 아직 가득 차지 않은 경우에도 룸에 플레이어가 입장하는 것을 멈춥니다.- 참고: 방을 닫을 때, 플레이어들이 이미 입장하고 있을 수 있는 짧은 시간이 있습니다.
클라이언트가 방을 닫은 후에 누군가가 들어와도 놀라지 마세요.
- 참고: 방을 닫을 때, 플레이어들이 이미 입장하고 있을 수 있는 짧은 시간이 있습니다.
C#
using Photon.Realtime;
using System.Collections.Generic;
public class QuickMatchExample : IMatchmakingCallbacks
{
[SerializeField]
private maxPlayers = 4;
private LoadBalancingClient loadBalancingClient;
private void CreateRoom()
{
RoomOptions roomOptions = new RoomOptions();
roomOptions.MaxPlayers = maxPlayers;
EnterRoomParams enterRoomParams = new EnterRoomParams();
enterRoomParams.RoomOptions = roomOptions;
loadBalancingClient.OpCreateRoom(enterRoomParams);
}
private void QuickMatch()
{
loadBalancingClient.OpJoinRandomRoom();
}
// do not forget to register callbacks via loadBalancingClient.AddCallbackTarget
// also deregister via loadBalancingClient.RemoveCallbackTarget
#region IMatchmakingCallbacks
void IMatchmakingCallbacks.OnJoinRandomFailed(short returnCode, string message)
{
CreateRoom();
}
void IMatchmakingCallbacks.OnJoinedRoom()
{
// joined a room successfully
}
// [..] Other callbacks implementations are stripped out for brevity, they are empty in this case as not used.
#endif
}
이 작업 흐름을 이용하여, 플레이어가 게임에 참여하는 것은 쉽습니다.
무작위 매치메이킹
때때로 플레이어는 빠른 매치 이상의 것을 원할 때 특정 맵이나 모드(예: 2 대 2 등)를 플레이하기를 원합니다.
임의의 "사용자 지정 룸 속성"을 설정하여 JoinRandomRoom
요청에서 필터로 사용할 수 있습니다(정확한 함수 또는 메소드 이름은 클라이언트 SDK에 따라 다를 수 있습니다).
로비에 일부 속성 노출하기
사용자 정의 룸 속성은 룸의 모든 플레이어와 동기화되며 현재 맵, 게임 모드, 난이도, 턴, 라운드, 시작 시간 등을 추적하는 데 유용할 수 있습니다.
속성은 문자열 키가 있는 해시 테이블로 처리됩니다.
기본적으로 이러한 속성은 룸 내부에서만 접근할 수 있으며, 마스터 서버(로비가 있는 곳)로 전송되지 않습니다.
로비에서 노출할 사용자 정의 룸 속성을 선택할 수 있습니다.
이러한 속성은 임의 일치를 위한 필터로 사용되며 로비에 표시됩니다(룸 목록에서 룸 정보의 일부로 전송되며 기본 유형의 룸 전송 목록만 있음).
예제:
매치메이킹에 "map" 및 "gm" 을 사용하도록 하려면, 룸을 생성할 때 "로비에서 볼 수 있는 룸 특성" 목록을 설정할 수 있습니다.
팁: 간단한 이름이 더 좋으므로 "GameMode" 대신 "gm"을 사용하십시오.
C#
using Photon.Realtime;
using System.Collections.Generic;
using Hashtable = ExitGames.Client.Photon.Hashtable;
public class CreateRoomWithLobbyPropertiesExample : IMatchmakingCallbacks
{
public const string MAP_PROP_KEY = "map";
public const string GAME_MODE_PROP_KEY = "gm";
public const string AI_PROP_KEY = "ai";
private LoadBalancingClient loadBalancingClient;
private void CreateRoom()
{
RoomOptions roomOptions = new RoomOptions();
roomOptions.CustomRoomPropertiesForLobby = { MAP_PROP_KEY, GAME_MODE_PROP_KEY, AI_PROP_KEY };
roomOptions.CustomRoomProperties = new Hashtable { { MAP_PROP_KEY, 1 }, { GAME_MODE_PROP_KEY, 0 } };
EnterRoomParams enterRoomParams = new EnterRoomParams();
enterRoomParams.RoomOptions = roomOptions;
loadBalancingClient.OpCreateRoom(enterRoomParams);
}
// do not forget to register callbacks via loadBalancingClient.AddCallbackTarget
// also deregister via loadBalancingClient.RemoveCallbackTarget
#region IMatchmakingCallbacks
void IMatchmakingCallbacks.OnCreateRoomFailed(short returnCode, string message)
{
// log error message and code
}
void IMatchmakingCallbacks.OnCreatedRoom()
{
}
void IMatchmakingCallbacks.OnJoinedRoom()
{
// joined a room successfully, OpCreateRoom leads here on success
}
// [..] Other callbacks implementations are stripped out for brevity, they are empty in this case as not used.
#endif
}
초기에는 "ai" 값이 없습니다.
룸에 설정될 때까지 로비에 나타나지 않습니다(C# SDK에서는 Room.SetCustomProperties
를 통해 수행됩니다) 설정됩니다.
"map", "gm" 또는 "ai"의 값을 변경하면 로비에서도 약간의 지연으로 업데이트됩니다.
나중에(방 생성 후) 로비에 표시되는 룸 속성 키를 변경(추가 또는 제거) 할 수도 있습니다(C# SDK에서는 Room.PropertiesListedInLobby
를 통해 수행됩니다).
팁: 룸에 참여하거나 로비에 참여할 때 클라이언트 성능이 느려지지 않도록 로비 속성 목록을 짧게 유지합니다(기본 유형의 룸 전송 목록만 해당).
리마인드: 이것을 사용하기 위해 로비에 가입할 필요가 없습니다(그리고 엄청나게 긴 룸 목록을 얻습니다).
로비용으로 설정하면 필터로도 사용할 수 있습니다.
무작위 참여시 룸 프로퍼티 필터링
Filtering Room Properties in Join Random
임의의 룸을 찾으려는 경우 원하는 룸 속성 또는 예상 최대 플레이어 수를 선택할 수 있습니다.
서버가 사용자에게 "적합한"" 룸을 선택할 때 필터로 사용됩니다.
예제:
C#
using Photon.Realtime;
using System.Collections.Generic;
using Hashtable = ExitGames.Client.Photon.Hashtable;
public class RandomMatchmakingExample : IMatchmakingCallbacks
{
public const string MAP_PROP_KEY = "map";
private LoadBalancingClient loadBalancingClient;
public void JoinRandomRoom(byte mapCode, byte expectedMaxPlayers)
{
Hashtable expectedCustomRoomProperties = new Hashtable { { MAP_PROP_KEY, mapCode } };
OpJoinRandomRoomParams opJoinRandomRoomParams = new OpJoinRandomRoomParams();
opJoinRandomRoomParams.ExpectedMaxPlayers = expectedMaxPlayers;
opJoinRandomRoomParams.ExpectedCustomRoomProperties = expectedCustomRoomProperties:
loadBalancingClient.OpJoinRandomRoom();
}
// do not forget to register callbacks via loadBalancingClient.AddCallbackTarget
// also deregister via loadBalancingClient.RemoveCallbackTarget
#region IMatchmakingCallbacks
void IMatchmakingCallbacks.OnJoinRandomFailed(short returnCode, string message)
{
// log error code and message
// here usually you create a new room
}
void IMatchmakingCallbacks.OnJoinedRoom()
{
// joined a room successfully, OpJoinRandomRoom leads here on success
}
// [..] Other callbacks implementations are stripped out for brevity, they are empty in this case as not used.
#endif
}
더 많은 필터링 등록 정보를 전달할 경우, 룸이 필터 등록 정보와 일치할 가능성이 낮아집니다.
선택 사항을 제한하는 것이 좋습니다.
여기와 같이 로비에 보이는 속성을 사용하여 항상 필터링해야 합니다
친구와 플레이
만약 플레이어들이 친구들과 커뮤니케이션을 한다면, 플레이어들은 쉽게 룸 이름을 만들 수 있고, 모든 플레이어들은 JoinOrCreateRoom
(클라이언트 SDK에 따라 함수명이나 메소드 이름이 다를 수 있음)을 사용하여 그 룸에 들어갈 수 있습니다.
예제:
고유한 룸 이름은 다음과 같이 구성할 수 있습니다(예: "친구이름 1 + 친구이름 2 + 무작위 정수").
다른 플레이어의 참여를 방지하려면 다음과 같이 보이지 않는 룸을 작성합니다:
C#
using Photon.Realtime;
using System.Collections.Generic;
public class PrivateRoomExample : IMatchmakingCallbacks
{
private LoadBalancingClient loadBalancingClient;
public void JoinOrCreatePrivateRoom(string nameEveryFriendKnows)
{
RoomOptions roomOptions = new RoomOptions();
roomOptions.IsVisible = false;
EnterRoomParams enterRoomParams = new EnterRoomParams();
enterRoomParams.RoomName = nameEveryFriendKnows;
enterRoomParams.RoomOptions = roomOptions;
loadBalancingClient.OpJoinOrCreateRoom(enterRoomParams);
}
// do not forget to register callbacks via loadBalancingClient.AddCallbackTarget
// also deregister via loadBalancingClient.RemoveCallbackTarget
#region IMatchmakingCallbacks
void IMatchmakingCallbacks.OnJoinRoomFailed(short returnCode, string message)
{
// log error code and message
}
void IMatchmakingCallbacks.OnJoinedRoom()
{
// joined a room successfully, OpJoinOrCreateRoom leads here on success
}
// [..] Other callbacks implementations are stripped out for brevity, they are empty in this case as not used.
#endif
}
고유 UserID를 사용할 경우 FindFriends
(클라이언트 SDK에 따라 정확한 함수 또는 메소드 이름이 다를 수 있음)를 사용하여 친구를 찾을 수도 있습니다.
룸에 UserID들을 게시하기
Photon 은 다양한 장소에서 UserID 를 사용합니다. 예를 들어, 플레이어별로 적절한 UserId 를 통해 친구를 찾을 수 있습니다.
룸에서 플레이어들의 UserID를 알 수 있도록 하는 옵션을 Photon 에 추가했습니다.
이 옵션을 이용하려면 룸을 생성할 때 RoomOptions.PublishUserId
을 true
로 설정하시면 됩니다.
클라이언트에서 PhotonPlayer.UserId
를 이용하면 서버는 UserId 를 제공 해주게 됩니다.
노트:
- UserIDs 는 Photon 의 참여 이벤트에서 플레이어 프로퍼티와 같이 브로드캐스트 됩니다.
- 클라이언트의 UserID 는 세가지 방식으로 설정 될 수 있습니다:
- 연결 전에 클라이언트가
LoadBalancingClient.UserId
를 설정 합니다. - 커스텀 인증을 이용한 외부 웹서비스에서 전달된 값으로 클라이언트가 보낸 값을 덮어 쓰게 될 것 입니다.
- Photon 은 사용자의 UserID를(GUID) 만들게 되며 명시적으로 지정 하지는 않습니다.
- 일반적으로 UserID들은 화면에 표시되는 목적이 아닙니다.
매치메이킹 자리 예약
때로 플레이어가 룸에 참여할 때 친구도 역시 참여할 것이라는 것을 알고 있습니다.
자리 예약으로 Photon 은 자리 블록을 통하여 특정 유저에게 자리를 제공하여 매치메이킹에 사용할 수 있습니다.
자리를 예약하기 위해서는 (JoinRoom
, JoinOrCreateRoom
, JoinRandomRoom
과 CreateRoom
) 메소드 내에 expectedUsers
파라미터를 이용합니다.
C#
EnterRoomParams enterRoomParams = new EnterRoomParams();
enterRoomParams.ExpectedUsers = expectedUsers;
// create room example
loadBalancingClient.OpCreateRoom(enterRoomParams);
// join room example
loadBalancingClient.OpJoinRoom(enterRoomParams);
// join or create room example
loadBalancingClient.OpJoinOrCreateRoom(enterRoomParams);
// join random room example
OpJoinRandomRoomParams opJoinRandomRoomParams = new OpJoinRandomRoomParams();
opJoinRandomRoomParams.ExpectedUsers = expectedUsers;
loadBalancingClient.OpJoinRandomRoom(opJoinRandomRoomParams);
누군가가 참여한다는 것을 알고 있으면 UserID의 배열을 전달합니다.
JoinRandomRoom
에 대해서 서버는 충분한 자리가 있는 룸과 참여가 예상되는 플레이어들(룸 안의 모든 액티브와 이미 참여하고 있는 플레이어들)을 찾을 것입니다.
서버는 룸 안의 클라이언트를 현재 expectedUsers
로 업데이트 시킬 것이고 클라이언트는 변경되어야 합니다.
룸 안에 있는 예상 사용자 목록을 업데이트할 수 있으며(한 명 이상의 사용자 추가 또는 제거), 잘 알려진 룸 속성을 통해 업데이트됩니다.
(C# SDK에서는 Room.ExpectedUsers
를 얻고 설정할 수 있습니다).
슬롯 예약을 지원하기 위해 룸 안으로 UserID를 주는 것을 허용해야 합니다.
유즈 케이스 예:
매치메이킹에서 팀을 지원하기 위해 사용할 수 있습니다.
팀 리더가 실제의 매치메이킹을 수행합니다.
리더는 룸에 참여할 수 있으며 모든 멤버를 위해서 자리를 예약합니다:
무작위 룸을 찾기 위해 시도합니다:
C#
OpJoinRandomRoomParams opJoinRandomRoomParams = new OpJoinRandomRoomParams();
opJoinRandomRoomParams.ExpectedUsers = teamMembersUserIds;
loadBalancingClient.OpJoinRandomRoom(opJoinRandomRoomParams);
아무도 없는 경우 새로운 룸을 생성합니다:
C#
EnterRoomParams enterRoomParams = new EnterRoomParams();
enterRoomParams.ExpectedUsers = teamMembersUserIds;
loadBalancingClient.OpCreateRoom(enterRoomParams);
다른 사람들은 매치메이킹을 할 필요가 없고 대신 반복적으로 호출합니다('주기적 폴링', 몇 프레임/(밀리초)마다):
C#
loadBalancingClient.OpFindFriends(new string[1]{ leaderUserId });
리더가 방에 도착하면 FindFriends
오퍼레이션을 통해 해당 룸의 이름이 표시되고 모든 사용자가 그 방에 참여할 수 있습니다:
C#
EnterRoomParams enterRoomParams = new EnterRoomParams();
enterRoomParams.RoomName = roomNameWhereTheLeaderIs;
loadBalancingClient.OpJoinRoom(enterRoomParams);
로비
Photon 은 룸을 "로비"라고 불리는 곳에 룸을 구성하고 있습니다.
따라서 모든 룸은 로비에 속하게 됩니다.
로비는 로비의 이름과 타입으로 구분됩니다.
이름은 모든 문자열이 될 수 있지만, 3개 유형의 로비만이 존재합니다. 디폴트, SQL 그리고 Async.
각각의 로비는 특정 사용 사례에 적합한 고유한 기능을 가지고 있습니다.
모든 애플리케이션은 디폴트 로비라는 디폴트 로비로 시작합니다.
대부분의 애플리케이션은 다른 로비가 필요하지 않습니다.
하지만, 클라이언트들은 다른 로비를 즉시 만들 수 있습니다.
작업 요청에서 JoinLobby
, CreateRoom
또는 JoinOrCreateRoom
과 같은 새 로비 정의를 지정하면 로비가 나타나기 시작합니다.
룸과 마찬가지로 로비에 참여할 수 있고, 로비를 떠날 수도 있습니다.
로비에서 클라이언트는 해당되는 경우에만 해당 로비의 룸 목록을 가져옵니다.
그 이상 그 이하도 아닙니다.
로비에서는 다른 사람들과 소통할 방법이 없습니다.
클라이언트가 로비에 들어가 있고 명시적으로 로비를 설정하지 않고 룸을 생성(또는 JoinOrCreate
) 하려고 할 때, 생성이 성공/발생하면 룸이 현재 가입된 로비에 추가됩니다.
클라이언트가 로비에 참여하지 않고 로비를 명시적으로 설정하지 않고 룸을 생성(또는 JoinOrCreate
) 하려고 할 때, 생성이 성공/발생하면 룸이 기본 로비에 추가됩니다.
클라이언트가 로비에 들어가 있고 로비를 명시적으로 설정하여 룸을 생성(또는 JoinOrCreate
) 하려고 할 때, 생성이 성공/발생하면 다음과 같습니다:
- 로비 이름이 null이거나 비어 있는 경우: 룸이 현재 가입된 로비에 추가됩니다.
즉, 사용자 정의/다른 사용자 정의 로비에 가입되어 있을 때 기본 로비에 룸을 생성할 수 없습니다. - 로비 이름이 null이거나 비어 있지 않은 경우: 룸 생성 요청에 의해 지정된 로비에 룸이 추가됩니다.
클라이언트가 로비에 참여하고 있고 명시적으로 로비를 설정하지 않고 임의의 룸에 참여하려고 하면 서버는 현재 참여하고 있는 로비에서 룸을 찾습니다.
클라이언트가 로비에 참여하지 않고 로비를 명시적으로 설정하지 않고 임의의 룸에 참여하려고 할 때, 서버는 기본 로비에서 룸을 찾습니다.
클라이언트가 로비에 가입되어 있고 임의의 룸에 가입하려고 할 때, 명시적으로 로비를 설정하여 룸에 가입하려고 합니다:
- 로비 이름이 null이거나 비어 있는 경우: 서버는 현재 참여하고 있는 로비에서 룸을 찾습니다.
즉, 사용자 정의/다른 룸에 조인된 경우 기본 로비에서 임의의 룸에 참여할 수 없습니다. - 로비 이름이 null이거나 비어 있지 않은 경우: 서버는 룸 작성 요청에 의해 지정된 로비에서 룸을 찾습니다.
클라이언트가 로비에 들어가 있고 다른 로비로 전환하려는 경우, 직접 JoinLobby에 전화할 수 있으며 LeaveLobby를 명시적으로 호출하여 첫 번째 로비를 떠날 필요가 없습니다.
디폴트 로비 타입
동기식랜덤 매치메이킹에 가장 적합한 유형입니다.
아마도 덜 세련되고 가장 많이 사용되는 유형일 것입니다.
디폴트 로비 유형에 가입되어 있는 동안 클라이언트는 정기적으로 룸 목록 업데이트를 수신합니다.
클라이언트가 디폴트 유형의 로비에 가입하면, 사용 가능한 룸의 초기 목록을 즉시 가져옵니다.
그런 다음 클라이언트는 정기적으로 룸 목록 업데이트를 받습니다.
목록은 열기 또는 닫기, 전체 또는 그렇지 않음의 두 가지 기준을 사용하여 정렬됩니다.
이 목록은 다음 순서로 세 그룹으로 구성됩니다:
- 첫 번째 그룹: 열려 있고 가득 차지 않습니다(참여 가능).
- 두 번째 그룹: 꽉 찼지만 닫히지 않았습니다(참여 불가).
- 세 번째 그룹: 닫힘(가입 불가, 가득 차거나 그렇지 않을 수 있음)입니다.
각 그룹에서 항목에는 특정 순서(무작위 숫자)가 없습니다.
룸 목록(또는 룸 업데이트)도 개수가 제한됩니다. 로비 제한을 참조하십시오.
C#
using Photon.Realtime;
using System.Collections.Generic;
public class RoomListCachingExample : ILobbyCallbacks, IConnectionCallbacks
{
private TypedLobby customLobby = new TypedLobby("customLobby", LobbyType.Default);
private LoadBalancingClient loadBalancingClient;
private Dictionary<string, RoomInfo> cachedRoomList = new Dictionary<string, RoomInfo>();
public void JoinLobby()
{
loadBalancingClient.JoinLobby(customLobby);
}
private void UpdateCachedRoomList(List<RoomInfo> roomList)
{
for(int i=0; i<roomList.Count; i++)
{
RoomInfo info = roomList[i];
if (info.RemovedFromList)
{
cachedRoomList.Remove(info.Name);
}
else
{
cachedRoomList[info.Name] = info;
}
}
}
// do not forget to register callbacks via loadBalancingClient.AddCallbackTarget
// also deregister via loadBalancingClient.RemoveCallbackTarget
#region ILobbyCallbacks
void ILobbyCallbacks.OnJoinedLobby()
{
cachedRoomList.Clear();
}
void ILobbyCallbacks.OnLeftLobby()
{
cachedRoomList.Clear();
}
void ILobbyCallbacks.OnRoomListUpdate(List<RoomInfo> roomList)
{
// here you get the response, empty list if no rooms found
UpdateCachedRoomList(roomList);
}
// [..] Other callbacks implementations are stripped out for brevity, they are empty in this case as not used.
#endif
#region IConnectionCallbacks
void IConnectionCallbacks.OnDisconnected(DisconnectCause cause)
{
cachedRoomList.Clear();
}
// [..] Other callbacks implementations are stripped out for brevity, they are empty in this case as not used.
#endregion
}
디폴트 로비 타입
이름은 null이고 유형은 디폴트 로비 유형입니다.
C# SDK에서는 TypedLobby.Default
에 정의되어 있습니다.
디폴트 로비의 이름은 예약되어 있습니다:
디폴트 로비만 null
이름을 가질 수 있으며, 다른 모든 로비 이름은 null이 아니거나 비어 있지 않은 문자열 이어야 합니다.
비어 있거나 null 문자열을 로비 이름으로 사용할 경우 지정된 유형에 관계없이 디폴트 로비를 가리킵니다.
권장되는 흐름
꼭 필요한 경우를 제외하고는 로비에 참여하지 않는 건너뛰는 것이 좋습니다.
필요한 경우, 특정 또는 사용자 정의 로비에 룸을 추가하려면, 클라이언트는 새 룸을 작성할 때 로비를 지정할 수 있습니다.
디폴트 유형의 로비에 참여하면 룸 목록이 표시되지만 대부분의 경우 유용하지 않습니다:
- 목록의 항목 사이에 ping의 관점에서 차이가 없습니다
- 보통 플레이어들은 빠른 경기를 원합니다
- 수신 룸 목록은 추가 지연을 추가하고 트래픽을 소비합니다
- 정보가 너무 많은 긴 목록은 사용자 경험에 나쁜 영향을 미칠 수 있습니다
대신 플레이어에게 매치메이킹에 대한 더 많은 권한을 부여하려면 무작위 매치메이킹 필터를 사용하십시오.
다중 로비는 (서버 측) 임의 매치메이킹에도 사용되며 로비 통계를 사용할 수 있기 때문에 여전히 유용할 수 있습니다.
SQL 로비 타입
SQL 로비 타입에서 JoinRandomRoom
의 문자열 필터는 디폴트로 예상되는 로비 속성을 대체합니다.
또한 SQL 로비 타입에서 일치 모드는 FillRoom
(기본값, 0) 하나만 지원됩니다.
또한 "사용자 정의 룸 목록"은 디폴트 로비 타입에만 존재하는 자동 정기 룸 목록을 대체합니다.
이 로비 타입은 완전히 클라이언트 중심인 서버 측 기술 기반 매치메이킹에 사용할 수 있는 보다 정교한 매치메이킹 필터링을 추가합니다.
내부적으로 SQL 로비는 최대 10개의 특수 "SQL 필터링 속성"을 가진 SQLite 테이블에 룸을 저장합니다.
이러한 SQL 속성의 이름은 "C0", "C1"에서 "C9"까지 고정됩니다.
정수형 및 문자열형 값만 허용되며 값이 특정 로비의 열에 할당되면 이 열은 해당 유형의 값으로 잠깁니다.
정적 이름 지정에도 불구하고 클라이언트는 로비에 필요한 이름을 정의해야 합니다.
SQL 속성을 로비 속성으로 정의하거나 값을 설정할 때 대소문자를 구분하지만 SQL 필터 내부에서는 대소문자를 구분하지 않으므로 주의하십시오.
룸을 작성하는 동안 또는 룸에 참여한 후에도 로비에 보이거나 보이지 않는 SQL 속성 이외의 사용자 정의 룸 속성을 사용할 수 있습니다.
그러나 그것들은 매치메이킹을 위해 사용되지 않을 것입니다.
쿼리는 JoinRandomRoom
오퍼레이션으로 전송할 수 있습니다.
필터링 쿼리는 기본적으로 "C0" .. "C9" 값을 기반으로 하는 SQL WHERE 조건입니다.
지원되는 모든 SQLite 연산자 목록과 사용 방법은 여기에서 확인할 수 있습니다.
제외된 키워드를 고려하십시오.
예제:
C#
using Photon.Realtime;
using System.Collections.Generic;
using Hashtable = ExitGames.Client.Photon.Hashtable;
public class RandomMatchmakingExample : IMatchmakingCallbacks
{
public const string ELO_PROP_KEY = "C0";
public const string MAP_PROP_KEY = "C1";
private TypedLobby sqlLobby = new TypedLobby("customSqlLobby", LobbyType.SqlLobby);
private LoadBalancingClient loadBalancingClient;
private void CreateRoom()
{
RoomOptions roomOptions = new RoomOptions();
roomOptions.CustomRoomProperties = new Hashtable { { ELO_PROP_KEY, 400 }, { MAP_PROP_KEY, "Map3" } };
roomOptions.CustomRoomPropertiesForLobby = { ELO_PROP_KEY, MAP_PROP_KEY }; // makes "C0" and "C1" available in the lobby
EnterRoomParams enterRoomParams = new EnterRoomParams();
enterRoomParams.RoomOptions = roomOptions;
enterRoomParams.Lobby = sqlLobby;
loadBalancingClient.OpCreateRoom(enterRoomParams);
}
private void JoinRandomRoom()
{
string sqlLobbyFilter = "C0 BETWEEN 345 AND 475 AND C1 = 'Map2'";
//string sqlLobbyFilter = "C0 > 345 AND C0 < 475 AND (C1 = 'Map2' OR C1 = \"Map3\")";
//string sqlLobbyFilter = "C0 >= 345 AND C0 <= 475 AND C1 IN ('Map1', 'Map2', 'Map3')";
OpJoinRandomRoomParams opJoinRandomRoomParams = new OpJoinRandomRoomParams();
opJoinRandomRoomParams.SqlLobbyFilter = sqlLobbyFilter;
loadBalancingClient.OpJoinRandomRoom(opJoinRandomRoomParams);
}
// do not forget to register callbacks via loadBalancingClient.AddCallbackTarget
// also deregister via loadBalancingClient.RemoveCallbackTarget
#region IMatchmakingCallbacks
void IMatchmakingCallbacks.OnJoinRandomFailed(short returnCode, string message)
{
CreateRoom();
}
void IMatchmakingCallbacks.OnCreateRoomFailed(short returnCode, string message)
{
Debug.LogErrorFormat("Room creation failed with error code {0} and error message {1}", returnCode, message);
}
void IMatchmakingCallbacks.OnJoinedRoom()
{
// joined a room successfully, both JoinRandomRoom or CreateRoom lead here on success
}
// [..] Other callbacks implementations are stripped out for brevity, they are empty in this case as not used.
#endif
}
체인 필터
한 번의 JoinRandomRoom
오퍼레이션으로 최대 3개의 쉼표로 구분된 필터를 한 번에 전송할 수 있습니다.
이러한 필터를 체인 필터라고 합니다.
Photon 서버는 필터를 순서대로 사용하려고 합니다.
필터가 룸과 일치하는 경우 룸이 조인됩니다.
그렇지 않으면 NoMatchFound 오류가 클라이언트에 반환됩니다.
연결된 필터는 매치메이킹 요청을 저장하고 처리 속도를 높이는 데 도움이 될 수 있습니다.
실패한 후 필터를 '완화'해야 하는 기술 기반 매치메이킹에 특히 유용할 수 있습니다.
가능한 필터 문자열 형식은 다음과 같습니다:
- 1 (최소) 필터링 값:
{filter1}
(또는{filter1};
) - 2 필터 값:
{filter1};{filter2}
(또는{filter1};{filter2};
) - 3 (최대) 필터 값:
{filter1};{filter2};{filter3}
(또는{filter1};{filter2};{filter3};
)
예제:
C0 BETWEEN 345 AND 475
C0 BETWEEN 345 AND 475;C0 BETWEEN 475 AND 575
C0 BETWEEN 345 AND 475;C0 BETWEEN 475 AND 575;C0 >= 575
사용자 지정 룸 목록
또한 클라이언트는 SQL 유사 쿼리를 사용하여 SqlLobby에서 사용자 정의 룸 목록을 요청할 수 있습니다.
이 메소드는 조건에 맞는 최대 100개의 룸을 반환합니다.
반환된 룸은 참여 가능하며(즉, 열려 있고 가득 차지 않음) 보여집니다.
C#
using Photon.Realtime;
using System.Collections.Generic;
public class GetCustomRoomListExample : ILobbyCallbacks
{
private TypedLobby sqlLobby = new TypedLobby("customSqlLobby", LobbyType.SqlLobby);
public void GetCustomRoomList(string sqlLobbyFilter)
{
loadBalancingClient.OpGetGameList(sqlLobby, sqlLobbyFilter);
}
// do not forget to register callbacks via loadBalancingClient.AddCallbackTarget
// also deregister via loadBalancingClient.RemoveCallbackTarget
#region ILobbyCallbacks
void ILobbyCallbacks.OnRoomListUpdate(List<RoomInfo> roomList)
{
// here you get the response, empty list if no rooms found
}
// [..] Other callbacks implementations are stripped out for brevity, they are empty in this case as not used.
#endif
}
스킬 기반 매치메이킹
SQL-타입의 로비는 나만의 스킬 기반 매치메이킹을 구현할 때 이용할 수 있습니다.
우선, 룸은 참가할 때 플레이어들이 가지고 있어야 하는 고정된 스킬을 받습니다.
이 값은 변경되지 않아야 하며 변경되면 이전에 매칭된 것은 기본적으로 더 이상 유효하지 않습니다.
일반적으로 플레이어들은 JoinRandomRoom
으로 룸에 입장을 하려고 합니다.
필터는 사용자의 기술을 기반으로 해야 합니다.
클라이언트는 "skill +/- X" 로 룸에 대해서 쉽게 필터 할 수 있습니다.
JoinRandomRoom
은 평소와 같이 즉시 응답을 받지만 곧바로 룸을 찾을 수 없었다면 클라이언트는 수 초간 대기 후에 다시 시도해야 합니다.
원하는 만큼 요청을 할 수 있습니다.
SQL 로비 타입을 사용하고 있는 경우, 체임 필터를 이용할 수도 있습니다.
이 중에서 가장 좋은 것: 클라이언트는 시간이 지남에 따라 필터 규칙을 완화할 수 있습니다.
시간이 경과 후에 필터를 완화시켜주는 것이 중요합니다.
그렇습니다: 룸은 드문 스킬을 가진 플레이어를 참여 시킬 수 있으나 분명히 다른 방들에 비해서는 매칭이 어렵게 되므로 누군가와 플레이하기가 힘들 수 있습니다.
최대 편차 및 타임아웃을 정의할 수 있습니다.
만약 검색된 룸이 없다면 이 클라이언트는 사용자가 가진 스킬로 새로운 룸을 오픈해야 합니다.
그리고 이 클라이언트는 다른 플레이어들을 기다려야 합니다.
명백히, 이 흐름은 사용할 수 있는 룸이 적을 때 시간이 걸릴 수도 있습니다.
"애플리케이션 통계"를 체크하여 사용할 수 있는 룸이 몇 개나 있는지 플레이어가 무작정 기다리지 않도록 할 수 있습니다.
낮은 CCU용 매치메이킹을 참고하세요.
필터와 "100개 미만의 룸"에 대한 타이밍을 조정하고 "100 ~ 1000개의 룸"에 대해 다른 설정을 사용하고 "더 많은 룸"에 대해 다시 사용할 수 있습니다.
제외된 SQL 키워드
SQL 필터들은 다음의 키워드는 허용하지 않습니다:
- ALTER
- CREATE
- DELETE
- DROP
- EXEC
- EXECUTE
- INSERT
- INSERT INTO
- MERGE
- SELECT
- UPDATE
- UNION
- UNION ALL
SQL 필터 문자열에서 이러한 단어를 사용하면 해당 작업이 실패합니다.
비동기 무작위 로비 타입
이 로비는 디폴트 로비 유형과 유사하며 두 가지 주요 차이점이 있습니다:
- 게임 서버에서 룸 항목이 제거된 후 1시간 동안 로비 목록(매치메이킹 가능)에 머무릅니다.
비동기식 매치메이킹에서 고려하려면 룸이 보이고 열려 있어야 합니다. - 룸 목록은 클라이언트에 전송되지 않습니다.
로비 타입 비교
로비 타입 | 룸 목록 업데이트 주기 | SQL 필터 | 최대 플레이어 수 필터 | 사용자 지정 룸 속성 필터 | 매치메이킹 모드 | 제거된 룸 엔트리 TTL (분) |
---|---|---|---|---|---|---|
디폴트 | 0 | |||||
SQL | 0 | |||||
비동기 | 60 |
낮은 CCU용 매치메이킹
정말 좋은 매치메이킹을 위해서, 게임은 온라인으로 수백 명의 플레이어를 필요로 합니다.
온라인에서 플레이어 수가 줄어들면, 가치 있는 상대를 찾는 것이 더 어려워질 것이고 어느 시점에서는 거의 모든 경기를 받아들이는 것이 어려울 수 있습니다.
클라이언트 측에서 보다 정교한 매치메이킹을 구축할 때는 이 점을 고려해야 합니다.
이를 위해 Photon 마스터 서버는 연결된 사용자, 룸 및 플레이어(룸)의 수를 제공하므로 런타임에 클라이언트 기반 매치메이킹을 조정할 수 있습니다.
룸 수는 현재 게임이 얼마나 빈번하게 이루어지는지를 보여주는 좋은 일반적인 지표가 되어야 합니다.
또한 한 룸에 없는 플레이어의 수에 따라 매치메이킹을 미세 조정할 수 있습니다.
룸에 없는 사람은 룸을 찾고 있을 수도 있습니다.
예를 들어, CCU가 낮은 상황에서는 20개 미만의 룸으로 정의할 수 있습니다.
따라서 룸 수가 20개 미만이면 클라이언트는 필터링을 사용하지 않고 빠른 매치 루틴을 실행합니다.
개발 초기에 매치메이킹을 테스트
개발 단계 초기에 매치메이킹을 테스트할 때 두 클라이언트의 랜덤 룸에 거의 동시에 가입하려고 하면 두 클라이언트가 서로 다른 룸에 있게 될 가능성이 있습니다. 이는 랜덤 룸 가입이 두 클라이언트의 일치 항목을 모두 반환하지 않고 각 클라이언트가 발견되지 않은 것처럼 새 룸을 만들 수 있기 때문입니다.
그래서 이것은 충분히 예측되는 상황이며 나쁘지는 않습니다.
이 문제를 방지하려면 JoinRandomRoom 후 CreateRoom을 호출하는 대신 JoinRandomOrCreateRoom(Quick Match 참고)을 사용하십시오.
그렇지 않은 경우, 가능한 해결 방법은 룸에 가입하거나 다시 시도하기 전(또는 후) 임의 지연을 추가하는 것입니다.
또한 응용프로그램 또는 로비 통계를 청취하여 룸이 적어도 존재하는지 또는 생성되었는지 확인할 수 있습니다.
다른 매치메이킹 옵션
직접 매치메이킹을 수행하려면 대부분의 매치메이킹이 서버 측에서 수행되었는지 확인하십시오.
서버 간 룸 목록 업데이트 빈도가 낮기 때문에(~1.2초) 클라이언트는 룸이 얼마나 꽉 찬지에 대한 완벽한 정보를 가지고 있지 않습니다.
수천 명의 플레이어가 있는 경우 여러 명이 동시에 "참여" 요청을 보냅니다.
룸이 빨리 차면 플레이어가 자주 룸에 가입하지 못하고 매치메이킹을 하는 데 시간이 점점 더 오래 걸립니다.
반면, 서버는 플레이어를 완벽하게 분배할 수 있으며, 거의 모든 룸이 가득 차게 되며 필터링을 존중합니다.
당신이 할 수 있는 일은 HTTP 기반 웹 서비스를 통해 Photon 외부에서 매치메이킹을 하는 것입니다. Photon을 사용하여 룸을 만들고 조인(또는 JoinOrCreate
한 번의 호출) 할 수 있습니다.
이러한 매치메이킹 서비스는 Photon의 "네이티브 HTTP 모듈"(사용자 지정 인증/WebRPC/WebHooks) 또는 사용자 지정 플러그인을 사용하여 웹 서비스에 룸 가용성을 보고할 수 있습니다.
매치메이킹을 할 때 고려해야 할 사항(키워드): 지역(그리고 클러스터(해당하는 경우), AppVersion, AppId, UserId, RoomName/GameId, Auth Cookie (사용자 지정 Auth), URL tags (WebHooks) 등.
또 다른 선택사항은 로드 밸런싱 서버 애플리케이션, 특히 마스터 서버, 일치 부분을 수정하는 것입니다.
물론 이 옵션은 자체 호스팅 전용입니다.
즉, 실제 게임 플레이가 시작되기 전에 방을 '로비' 또는 '매치메이킹 장소'로 사용하는 것은 대부분의 경우 인기 있는 게임에 좋은 생각이 아닙니다.
Lobby v1 애플리케이션
무료 20 CCU 요금제에 없는 일부 오래된 Photon 애플리케이션은 로비 동작이 다를 수 있습니다:
- 룸 목록을 보내는 데 사용되는 SQL 로비 유형.
지금은 그렇지 않습니다. - 룸 목록은 제한되지 않았습니다.
이제 룸 목록이 제한되어 있습니다. - 름 목록이 참여 가능한 그룹별로 정렬되지 않았습니다.
이제 참여 가능한 것들이 목록의 첫 번째에 있습니다.
로비 제한
Photon에는 다음과 같이 로비에 디폴트로 제한되어 있습니다:
- 애플리케이션 당 최대 로비 수: 10000.
- 게임 목록 이벤트의 최대 룸 목록 항목 수(디폴트 타입의 로비에 참여할 때 초기 목록): 500개
- GameListUpdate 이벤트에서 업데이트된 최대 룸 항목 수(디폴트 유형의 타입의 가입된 경우): 500개
이 제한은 제거된 룸 항목을 고려하지 않습니다(더 이상 보이지 않거나 단순히 사라진 룸에 해당). - GetGameList 오퍼레이션 응답(SQL Lobby)의 최대 룸 목록 항목 수: 100.
노트:
로비 v2에서는 GameList 또는 GameListUpdate 이벤트에서 디폴트 유형의 동일한 각 로비에 참여한 클라이언트에 전송된 초기/업데이트된 룸 항목의 수가 500개로 제한됩니다.
GameList 이벤트의 경우 클라이언트가 받은 배열의 길이는 500을 초과하지 않습니다.
GameListUpdate 이벤트의 경우 제거된 룸 항목(여기서도 전송됨)의 수를 제한하지 않고 업데이트된 항목만 제한하므로 클라이언트가 받은 배열의 길이가 500개를 초과할 수 있습니다.
로비 v2의 새로운 제한은 서버 측에 영향을 미치지 않습니다. 룸은 여전히 로비에 존재합니다.
우리는 대역폭을 이유로 로비에 참여한 클라이언트에게만 브로드캐스트를 제한하고 사양에 제한이 될 수 있는 클라이언트를 넘어서지 않습니다.
그리고 500은 꽤 큰 숫자입니다. 어떤 플레이어도 전체 목록을 보기 위해 스크롤 하지 않습니다.
게다가 이론적으로 로비에 충분히 오래 머무는 클라이언트는 서버가 모든 업데이트를 대기열에 추가하고 최대 길이 500개 단위로 발송하기 때문에 서버에서 사용 가능한 전체 룸 목록을 갖게 될 수 있습니다.
물론 로비에 룸이 있는데도 한동안 바뀌지 않으면 클라이언트가 놓칠 수도 있습니다.
그래서 서버에서는 로비당 룸 수에 제한이 없습니다.
FindFriends, CreateRoom, JoinRoom 또는 JoinOrCreateRoom은 로비 v2로의 이동의 영향을 받지 않으며 제한되지 않습니다. 즉, 클라이언트는 로비 목록 업데이트에서 클라이언트에게 전송되지 않은 룸에 무제한으로 룸을 만들거나 룸에 참여하거나 친구를 찾을 수 있습니다.