SDK の実装 - フレンドとプレイ - (Unity モジュール)
注釈:本資料はAI技術を用いて翻訳されています。
このページは Session Essentials チュートリアルモジュールで実装した関数に依存しているため、先に完了してから進めてください。
ラッパーを開く
Byte Wars は、AccelByte Gaming Services (AGS) Software Development Kit (SDK) と統合するために PlayingWithFriendsWrapper というラッパークラスを使用しています。このラッパーは、SDK が提供する User、Session、Lobby インターフェースを活用します。このチュートリアルでは、必要な機能をゼロから実装できるスターターバージョンのラッパーを使用します。
スターターパックの内容
このチュートリアルに従うために、PlayingWithFriendsWrapper_Starter という名前のスターターサブシステムクラスが提供されています。リソース セクションで見つけることができます。以下のファイルが含まれています:
- CS ファイル:
Assets/Resources/Modules/Play/PlayingWithFriends/Scripts/PlayingWithFriendsWrapper_Starter.cs
PlayingWithFriendsWrapper_Starter クラスは、Session Essentials モジュールで設定した SessionEssentialsWrapper_Starter から派生しており、さらに AccelByteWarsOnlineSession クラスから派生しています。このラッパーは、継承されたクラスの関数と変数を使用します。
AccelByteWarsOnlineSession クラスには、いくつかの便利なコンポーネントが含まれています:
- キャッシュされた現在のセッションデータ。
public static SessionV2GameSession CachedSession { get; protected set; } = null;
- このページで使用する AGS SDK インターフェースの静的参照。
protected static User User;
protected static Lobby Lobby;
protected static Session Session;
- セッション情報に基づいて Dedicated Server へのクライアント移動を処理する関数。
public virtual void TravelToDS(SessionV2GameSession session, InGameMode gameMode)
{
SessionV2DsInformation dsInfo = session.dsInformation;
if (dsInfo == null)
{
BytewarsLogger.LogWarning("Failed to travel to dedicated server. Dedicated server information not found.");
return;
}
if (NetworkManager.Singleton.IsListening)
{
BytewarsLogger.LogWarning("Failed to travel to dedicated server. The instance is running as listen server.");
return;
}
string ip = dsInfo.server.ip;
ushort port = (ushort)dsInfo.server.port;
InitialConnectionData initialData = new InitialConnectionData()
{
sessionId = string.Empty,
inGameMode = gameMode,
serverSessionId = session.id,
userId = GameData.CachedPlayerState.PlayerId
};
GameManager.Instance.ShowTravelingLoading(() =>
{
BytewarsLogger.Log("Travel to dedicated server as client.");
GameManager.Instance.StartAsClient(ip, port, initialData);
});
}
- セッション情報に基づいて P2P ホストへのクライアント移動を処理する関数。この関数は AccelByte のネットワーキングプラグイン
AccelByteNetworkTransportManagerを使用します。
public virtual void TravelToP2PHost(SessionV2GameSession session, InGameMode gameMode)
{
AccelByteNetworkTransportManager transportManager = NetworkManager.Singleton.GetComponent<AccelByteNetworkTransportManager>();
if (transportManager == null)
{
transportManager = NetworkManager.Singleton.gameObject.AddComponent<AccelByteNetworkTransportManager>();
transportManager.Initialize(AccelByteSDK.GetClientRegistry().GetApi());
transportManager.OnTransportEvent += GameManager.Instance.OnTransportEvent;
}
InitialConnectionData initialData = new InitialConnectionData()
{
inGameMode = gameMode,
serverSessionId = session.id,
userId = GameData.CachedPlayerState.PlayerId
};
NetworkManager.Singleton.NetworkConfig.ConnectionData = GameUtility.ToByteArray(initialData);
NetworkManager.Singleton.NetworkConfig.NetworkTransport = transportManager;
bool isHost = session.leaderId == GameData.CachedPlayerState.PlayerId;
GameManager.Instance.ShowTravelingLoading(() =>
{
GameManager.Instance.ResetCache();
GameData.ServerType = ServerType.OnlinePeer2Peer;
if (isHost)
{
BytewarsLogger.Log("Start as P2P host");
GameData.ServerSessionID = session.id;
NetworkManager.Singleton.StartHost();
}
else
{
BytewarsLogger.Log($"Start as P2P client. Target host: {session.leaderId}");
transportManager.SetTargetHostUserId(session.leaderId);
NetworkManager.Singleton.StartClient();
}
},
isHost ? StartingAsHostMessage : WaitingHostMessage);
}
SessionEssentialsWrapper_Starter クラスには、いくつかの便利なコンポーネントが含まれています:
- ゲームセッション招待を送信する関数。
public override void SendGameSessionInvite(
string sessionId,
string inviteeUserId,
ResultCallback onComplete)
{
Session.InviteUserToGameSession(
sessionId,
inviteeUserId,
(result) =>
{
if (result.IsError)
{
BytewarsLogger.LogWarning(
$"Failed to send game session invite to {inviteeUserId}. " +
$"Error {result.Error.Code}: {result.Error.Message}");
}
else
{
BytewarsLogger.Log($"Success to send game session invite to {inviteeUserId}");
}
onComplete?.Invoke(result);
});
}
- ゲームセッションに参加する関数。
public override void JoinGameSession(
string sessionId,
ResultCallback<SessionV2GameSession> onComplete)
{
// Leave the existing session before joining a new session.
if (CachedSession != null)
{
LeaveGameSession(CachedSession.id, (leaveResult) =>
{
// Abort only if there's an error and it's not due to a missing session.
if (leaveResult.IsError && leaveResult.Error.Code != ErrorCode.SessionIdNotFound)
{
BytewarsLogger.LogWarning($"Failed to join game session. Error {leaveResult.Error.Code}: {leaveResult.Error.Message}");
onComplete?.Invoke(Result<SessionV2GameSession>.CreateError(leaveResult.Error));
return;
}
JoinGameSession(sessionId, onComplete);
});
return;
}
// Join a new session.
Session.JoinGameSession(sessionId, (result) =>
{
if (result.IsError)
{
BytewarsLogger.LogWarning($"Failed to join game session. Error {result.Error.Code}: {result.Error.Message}");
}
else
{
BytewarsLogger.Log($"Success to join game session. Session id: {result.Value.id}");
}
CachedSession = result.Value;
onComplete?.Invoke(result);
});
}
- ゲームセッション招待を拒否する関数。
public override void RejectGameSessionInvite(
string sessionId,
ResultCallback onComplete)
{
Session.RejectGameSessionInvitation(sessionId, (result) =>
{
if (result.IsError)
{
BytewarsLogger.LogWarning(
$"Failed to reject game session with ID: {sessionId}. " +
$"Error {result.Error.Code}: {result.Error.Message}");
}
else
{
BytewarsLogger.Log($"Success to reject game session. Session ID: {sessionId}");
}
onComplete?.Invoke(result);
});
}
このラッパーは、Assets/Resources/Modules/Play/OnlineSessionUtils/AccelByteWarsOnlineSessionModels.cs にあるモデルクラスを使用して、現在のセッションのゲームモードを判定します。Byte Wars は、セッション名を使用して特定のセッションのゲームモードを判定します。
public static InGameMode GetGameSessionGameMode(SessionV2GameSession session)
{
if (session == null)
{
return InGameMode.None;
}
bool isMatchmaking = string.IsNullOrEmpty(session.matchPool);
switch (session.configuration.name)
{
case EliminationDSSessionTemplateName:
case EliminationDSAMSSessionTemplateName:
case EliminationP2PSessionTemplateName:
return isMatchmaking ? InGameMode.MatchmakingElimination : InGameMode.CreateMatchElimination;
case TeamDeathmatchDSSessionTemplateName:
case TeamDeathmatchDSAMSSessionTemplateName:
case TeamDeathmatchP2PSessionTemplateName:
return isMatchmaking ? InGameMode.MatchmakingTeamDeathmatch : InGameMode.CreateMatchTeamDeathmatch;
default:
return InGameMode.None;
}
}
また、Assets/Resources/Modules/Play/PlayingWithFriends/Scripts/PlayingWithFriendsModels.cs にあるモデルクラスもあり、通知で使用されるテキストを保存しています。
public static readonly string SendGameSessionInviteErrorNotInSession = "Not currently in any game session.";
public static readonly string SendGameSessionInviteSuccess = "Invitation sent.";
public static readonly string SendGameSessionInviteError = "Fail to send invite.";
public static readonly string InviteReceived = " invites to join game session.";
public static readonly string InviteRejected = " rejected invitation.";
public static readonly string InviteAccept = "Accept";
public static readonly string InviteReject = "Reject";
招待送信の実装
PlayingWithFriendsWrapper_Starter クラスを開き、以下のコードを使用して新しい関数を作成します。この関数は、指定されたユーザー ID に招待リクエストを送信し、通知を表示し、onComplete デリゲートをトリガーします。他のセッションモジュールによって設定されたキャッシュされたセッションを使用します。このセッション ID は、セッション作成またはセッション参加のレスポンスから取得できます。
public void SendInviteToCurrentGameSession(string userId, ResultCallback onComplete)
{
SendGameSessionInvite(CachedSession.id, userId, (Result result) =>
{
// Display notification.
MenuManager.Instance.PushNotification(new PushNotificationModel
{
Message = result.IsError ? PlayingWithFriendsModels.SendGameSessionInviteError : PlayingWithFriendsModels.SendGameSessionInviteSuccess,
UseDefaultIconOnEmpty = true
});
onComplete?.Invoke(result);
});
}
招待受信の実装
-
以下のコードを使用して新しい関数を作成します。この関数は、受信したゲームセッション招待を処理します。招待ペイロードにはセッション ID のみが含まれていますが、Byte Wars はポップアップに送信者の表示名も表示する必要があります。これを実現するために、2 つのリクエストを順番に行う必要があります: セッション詳細を取得して送信者のユーザー ID を取得し、ユーザーの公開情報を取得して送信者の表示名を取得します。その後、招待を受け入れるか拒否するかのポップアップが表示されます。招待の受け入れは、セッション参加を呼び出すことで行われることに注意してください。
private void OnGameSessionInviteReceived(Result<SessionV2GameInvitationNotification> notif)
{
if (notif.IsError)
{
BytewarsLogger.LogWarning($"Failed to handle received game session invitation. Error {notif.Error.Code}: {notif.Error.Message}");
return;
}
// Get session info.
Session.GetGameSessionDetailsBySessionId(notif.Value.sessionId, (result) =>
{
if (result.IsError)
{
BytewarsLogger.LogWarning($"Failed to get game session details. Error {result.Error.Code}: {result.Error.Message}");
return;
}
SessionV2GameSession gameSession = result.Value;
// Ignore if the invitation is from party leader as it is already handled in the play with party module.
if (CachedParty != null && gameSession.leaderId == CachedParty.leaderId)
{
return;
}
// Get session owner info.
User.GetUserOtherPlatformBasicPublicInfo("ACCELBYTE", new string[] { result.Value.leaderId }, (userInfoResult) =>
{
AccountUserPlatformData senderInfo = userInfoResult.IsError ? null : userInfoResult.Value.Data[0];
if (senderInfo == null)
{
BytewarsLogger.LogWarning($"Failed to get sender info. Error {userInfoResult.Error.Code}: {userInfoResult.Error.Message}");
return;
}
// Prompt notification to join or reject session invitation.
MenuManager.Instance.PushNotification(new PushNotificationModel
{
Message = senderInfo.DisplayName + PlayingWithFriendsModels.InviteReceived,
IconUrl = senderInfo.AvatarUrl,
UseDefaultIconOnEmpty = true,
ActionButtonTexts = new string[]
{
PlayingWithFriendsModels.InviteAccept,
PlayingWithFriendsModels.InviteReject
},
ActionButtonCallback = async (PushNotificationActionResult actionResult) =>
{
switch (actionResult)
{
// Show accept party invitation confirmation.
case PushNotificationActionResult.Button1:
if (await AccelByteWarsOnlineSession.OnValidateToJoinGameSession.Invoke(gameSession))
{
JoinGameSession(notif.Value.sessionId, (Result<SessionV2GameSession> result) =>
{
OnJoinGameSessionCompleted(result, null);
});
}
break;
// Reject party invitation.
case PushNotificationActionResult.Button2:
RejectGameSessionInvite(notif.Value.sessionId, null);
break;
}
}
});
});
});
} -
以下のコードを使用して、セッション参加リクエストのハンドラー関数を作成します。この関数は、プレイヤーを Dedicated Server (DS) または P2P ホストに接続します。DS セッションの場合、ゲームはすぐに接続を試みます。ただし、DS がまだ準備できていない場合、これは失敗する可能性があります。その場合、ゲームは
SessionV2DsStatusChangedイベントをリッスンして、DS が利用可能になったときに再試行します。P2P の場合、ゲームはすぐにホストに接続します。private void OnJoinGameSessionCompleted(Result<SessionV2GameSession> result, ResultCallback<SessionV2GameSession> onComplete = null)
{
if (result.IsError)
{
BytewarsLogger.LogWarning($"Failed to received game session user joined notification. Error {result.Error.Code}: {result.Error.Message}");
onComplete?.Invoke(result);
return;
}
// Update cached session ID.
GameData.ServerSessionID = result.Value.id;
SessionV2GameSession gameSession = result.Value;
switch (gameSession.configuration.type)
{
case SessionConfigurationTemplateType.DS:
if (
gameSession.dsInformation == null ||
gameSession.dsInformation.status != SessionV2DsStatus.AVAILABLE
)
{
// Server is not ready, listen to DS event.
Lobby.SessionV2DsStatusChanged += OnDsStatusChanged;
}
else
{
TravelToDS(gameSession, AccelByteWarsOnlineSessionModels.GetGameSessionGameMode(gameSession));
}
break;
case SessionConfigurationTemplateType.P2P:
TravelToP2PHost(gameSession, AccelByteWarsOnlineSessionModels.GetGameSessionGameMode(gameSession));
break;
default:
break;
}
onComplete?.Invoke(result);
} -
以下のコードを使用して、DS への接続を再試行する関数を作成します。上記で説明したように、DS がまだ準備できていない場合、ゲームは DS の更新ごとに接続を試み続けます。
private void OnDsStatusChanged(Result<SessionV2DsStatusUpdatedNotification> result)
{
Lobby.SessionV2DsStatusChanged -= OnDsStatusChanged;
if (result.IsError)
{
BytewarsLogger.LogWarning($"Dedicated server information received with error. Error {result.Error.Code}: {result.Error.Message}");
return;
}
if (
result.Value.session.dsInformation == null ||
result.Value.session.dsInformation.status != SessionV2DsStatus.AVAILABLE
)
{
TravelToDS(result.Value.session, AccelByteWarsOnlineSessionModels.GetGameSessionGameMode(result.Value.session));
}
else
{
BytewarsLogger.LogWarning("Failed to travel to dedicated server. Dedicated server information not found.");
}
} -
OnEnable()関数を見つけて、以下のコードで既存の関数を置き換え、招待ハンドラーを SDK のイベントにバインドします。private void OnEnable()
{
Lobby.SessionV2InvitedUserToGameSession += OnGameSessionInviteReceived;
} -
OnDisable()関数を見つけて、以下のコードで既存の関数を置き換え、ハンドラーをアンバインドして、クリーンなシャットダウン動作を保証します。private void OnDisable()
{
Lobby.SessionV2InvitedUserToGameSession -= OnGameSessionInviteReceived;
}
招待拒否通知の実装
-
以下のコードを使用して、招待拒否通知のハンドラー関数を作成します。これにより、招待者に招待が拒否されたことを通知します。
private void OnGameSessionInviteRejected(Result<SessionV2GameInvitationRejectedNotification> notif)
{
if (notif.IsError)
{
BytewarsLogger.LogWarning($"Failed to received game session user joined notification. Error {notif.Error.Code}: {notif.Error.Message}");
return;
}
// Construct local function to display push notification.
void OnGetSenderInfoCompleted(Result<AccountUserPlatformInfosResponse> result)
{
AccountUserPlatformData receiverInfo = result.IsError ? null : result.Value.Data[0];
if (receiverInfo == null)
{
BytewarsLogger.LogWarning($"Failed to get sender info. Error {result.Error.Code}: {result.Error.Message}");
return;
}
MenuManager.Instance.PushNotification(new PushNotificationModel
{
Message = receiverInfo.UniqueDisplayName + PlayingWithFriendsModels.InviteRejected,
IconUrl = receiverInfo.AvatarUrl,
UseDefaultIconOnEmpty = true
});
}
// Construct local function to get sender info.
User.GetUserOtherPlatformBasicPublicInfo("ACCELBYTE", new string[] { notif.Value.rejectedId }, OnGetSenderInfoCompleted);
} -
OnEnable()関数を見つけて、以下のコードで既存の関数を置き換え、拒否ハンドラーを SDK のイベントにバインドします。private void OnEnable()
{
Lobby.SessionV2InvitedUserToGameSession += OnGameSessionInviteReceived;
Lobby.SessionV2UserRejectedGameSessionInvitation += OnGameSessionInviteRejected;
} -
OnDisable()関数を見つけて、以下のコードで既存の関数を置き換え、ハンドラーをアンバインドして、クリーンなシャットダウン動作を保証します。private void OnDisable()
{
Lobby.SessionV2InvitedUserToGameSession -= OnGameSessionInviteReceived;
Lobby.SessionV2UserRejectedGameSessionInvitation -= OnGameSessionInviteRejected;
}
リソース
- このチュートリアルで使用されるファイルは、Unity Byte Wars GitHub リポジトリで入手できます。