Amazon GameLift In C# 10: より良いチャットクライアントを作ります(Part1)
概要
前回の記事まではより良いチャットサーバーのソースコード解説でしたが、これからより良いチャットクライアントのソースコードを分析します。
マシン環境
CPU : Intel i7-6920HQ
GPU : AMD Radeon Pro 460
Memory : 16GB
System : Windows 10 (with .NET 5 installed)
IDE : Visual Studio 2019 Community
Editor : Visual Studio Code
Terminal : Windows Terminal (or Command Prompt)
ファイルの構造とクラスの運用
このプロジェクトはGitHubにアップロードしています: AGLW-CSharp-BetterChatClientSample
ソースコードファイル :
- Program.cs (プログラムの入口)
- GameLiftClient.cs (GameLiftとの接続を管理するクラス)
- ChatClient.cs (接続できたものと受送信クラスの管理クラス)
- ManagedConnection.cs (受送信クラス)
GameLiftClient.cs ソースコード
GameLiftClient.cs
ソースコードを貼り付けます。
using System; using System.Threading; using System.Threading.Tasks; using System.Net.Sockets; using Amazon; using Amazon.GameLift; using Amazon.GameLift.Model; using Amazon.CognitoIdentity; namespace AGLW_CSharp_BetterChatClientSample { class GameLiftClient { // All the GameLift related things will be processed by this AmazonGameLiftClient private AmazonGameLiftClient gameLiftClient = null; // Sessions contain general information private GameSession gameSession = null; private PlayerSession playerSession = null; private string playerId = string.Empty; // A instance's status flag of this class public bool IsAlive { get; private set; } = false; public GameLiftClient() { IsAlive = true; playerId = Guid.NewGuid().ToString(); Console.WriteLine($"Client : playerId {playerId}"); // gameLiftClient = new AmazonGameLiftClient("fake", "fake", new AmazonGameLiftConfig() { ServiceURL = "http://localhost:9080" }); CognitoAWSCredentials credentials = new CognitoAWSCredentials( "Your-Identity-Pool-ID", // Identity pool ID RegionEndpoint.APNortheast1 // Region ); gameLiftClient = new AmazonGameLiftClient(credentials, RegionEndpoint.APNortheast1); } public void Start() { // SearchGameSessionAsync(async) -> Create GameSession(async) -> Create PlayerSession(async) -> Connect () CreateSessionsAndConnect(); } async private Task CreateSessionsAndConnect() { await SearchGameSessionAsync(); await CreateGameSessionAsync(); await CreatePlayerSessionAsync(); // Connect to the IP provided by PlayerSession Connect(); } // Search if there's available session to join async private Task SearchGameSessionAsync() { Console.WriteLine($"Client : SearchGameSessionAsync() start"); var request = new DescribeGameSessionsRequest(); request.FleetId = "fleet-id"; Console.WriteLine($"Client : Sending request and await"); var response = await gameLiftClient.DescribeGameSessionsAsync(request); Console.WriteLine($"Client : request sent"); foreach (var session in response.GameSessions) { if (session.MaximumPlayerSessionCount > session.CurrentPlayerSessionCount && session.Status == GameSessionStatus.ACTIVE) { gameSession = session; break; } } } // Create a GameSession async private Task CreateGameSessionAsync() { if (gameSession != null) return; Console.WriteLine($"Client : CreateGameSessionAsync() start"); var request = new CreateGameSessionRequest(); request.FleetId = "fleet-id"; request.CreatorId = playerId; request.MaximumPlayerSessionCount = 2; Console.WriteLine($"Client : Sending request and await"); var response = await gameLiftClient.CreateGameSessionAsync(request); Console.WriteLine($"Client : request sent"); if (response.GameSession != null) { Console.WriteLine($"Client : GameSession Created!"); Console.WriteLine($"Client : GameSession ID {response.GameSession.GameSessionId}!"); gameSession = response.GameSession; } else { Console.Error.WriteLine($"Client : Failed creating GameSession!"); IsAlive = false; } } // Create a PlayerSession from a GameSession async private Task CreatePlayerSessionAsync() { Console.WriteLine($"Client : CreatePlayerSessionAsync() start"); if (gameSession == null) return; while (gameSession.Status != GameSessionStatus.ACTIVE) { Console.WriteLine($"Client : Wait for GameSession to be ACTIVE, Sleep for a while"); Thread.Sleep(100); await UpdateGameSession(); } var request = new CreatePlayerSessionRequest(); request.GameSessionId = gameSession.GameSessionId; request.PlayerId = playerId; Console.WriteLine($"Client : Sending request and await"); var response = await gameLiftClient.CreatePlayerSessionAsync(request); Console.WriteLine($"Client : request sent"); if (response.PlayerSession != null) { Console.WriteLine($"Client : PlayerSession Created!"); Console.WriteLine($"Client : PlayerSession ID {response.PlayerSession.PlayerSessionId}!"); playerSession = response.PlayerSession; } else { Console.Error.WriteLine($"Client : Failed creating PlayerSession!"); IsAlive = false; } } // Update GameSession until it's active async private Task UpdateGameSession() { DescribeGameSessionsRequest request = new DescribeGameSessionsRequest(); request.GameSessionId = gameSession.GameSessionId; Console.WriteLine($"Client : Describe GameSession {gameSession.GameSessionId}..."); DescribeGameSessionsResponse response = await gameLiftClient.DescribeGameSessionsAsync(request); if (response != null) { if (response.GameSessions.Count == 1) { // Update GameSession gameSession = response.GameSessions[0]; } else { Console.WriteLine($"Client : Warning, describe GameSession count {response.GameSessions.Count}"); } } else { Console.WriteLine($"Client : Warning, describe GameSession no response"); } } // Connect to the IP which PlayerSession provides // When client connects : // 1) Receive the msg sent by server // 2) Send another msg back // 3) Close the connection private void Connect() { Console.WriteLine($"Client : Connect() start"); if (playerSession == null) return; Console.WriteLine($"Client : Try to connect {playerSession.IpAddress}:{playerSession.Port}"); TcpClient client = new TcpClient(playerSession.IpAddress, playerSession.Port); if (client.Connected) { Console.WriteLine($"Client : Connected"); ChatClient chatClient = new ChatClient(client); chatClient.StartClient(); } } } }
GameLiftClient.cs メンバー変数の解説
// All the GameLift related things will be processed by this AmazonGameLiftClient private AmazonGameLiftClient gameLiftClient = null; // Sessions contain general information private GameSession gameSession = null; private PlayerSession playerSession = null; private string playerId = string.Empty; // A instance's status flag of this class public bool IsAlive { get; private set; } = false;
- AmazonGameLiftClient :
gameLiftClient
検証情報と地域情報を持っているGameLiftとの接続の担当です。 - GameSession :
gameSession
GameSession情報を保存するメンバーです。 - PlayerSession :
playerSession
PlayerSessionを保存するメンバーです。プレイヤーが接続すべきサーバーのIP
とPort
などを持っています。 - string :
playerId
GameSessionとPlayerSessionなどの請求情報に登録する、プレイヤー唯一のIDです。 - bool :
IsAlive
プログラムまだ生きてるかどうかを記録するフラグです(今後よく使うかもしれません。例えば、接続失敗したらこのフラグをfalse
にするとか)。
ConnectedClient.cs 関数の解説
public GameLiftClient() { IsAlive = true; playerId = Guid.NewGuid().ToString(); Console.WriteLine($"Client : playerId {playerId}"); // gameLiftClient = new AmazonGameLiftClient("fake", "fake", new AmazonGameLiftConfig() { ServiceURL = "http://localhost:9080" }); CognitoAWSCredentials credentials = new CognitoAWSCredentials( "Your-Identity-Pool-ID", // Identity pool ID RegionEndpoint.APNortheast1 // Region ); gameLiftClient = new AmazonGameLiftClient(credentials, RegionEndpoint.APNortheast1); }
まずはGameLiftClient()
構造体です。
プログラムの存続フラグIsAlive
をtrue
にします。
そしてGuid.NewGuid().ToString()
でプレイヤーの唯一IDを生成します。
Identity Pool ID
と RegionEndpoint
で CognitoAWSCredentials
を生成し、検証情報と地域情報を持っているGameLiftとの接続の担当gameLiftClient
を作成します。
// Search if there's available session to join async private Task SearchGameSessionAsync() { Console.WriteLine($"Client : SearchGameSessionAsync() start"); var request = new DescribeGameSessionsRequest(); request.FleetId = "fleet-id"; Console.WriteLine($"Client : Sending request and await"); var response = await gameLiftClient.DescribeGameSessionsAsync(request); Console.WriteLine($"Client : request sent"); foreach (var session in response.GameSessions) { if (session.MaximumPlayerSessionCount > session.CurrentPlayerSessionCount && session.Status == GameSessionStatus.ACTIVE) { gameSession = session; break; } } }
SearchGameSessionAsync()
で現在利用できるGameSession
を検索します。new DescribeGameSessionsRequest()
で作ったrequest
にFleet IDを入れて、gameLiftClient.DescribeGameSessionsAsync(request)
でセッション検索請求を送ります。
response
に利用できるGameSession
情報に返してくれます。session.CurrentPlayerSessionCount
で現在のプレイヤー人数を取得し、入れるかどうかを判断します。そしてsession.Status
でそのセッションが働いてるかどうか確認できます。
条件に満足したsession
はメンバーのgameSession
に保存します。
// Looping in a sender thread async private Task CreateGameSessionAsync() { if (gameSession != null) return; Console.WriteLine($"Client : CreateGameSessionAsync() start"); var request = new CreateGameSessionRequest(); request.FleetId = "fleet-id"; request.CreatorId = playerId; request.MaximumPlayerSessionCount = 2; Console.WriteLine($"Client : Sending request and await"); var response = await gameLiftClient.CreateGameSessionAsync(request); Console.WriteLine($"Client : request sent"); if (response.GameSession != null) { Console.WriteLine($"Client : GameSession Created!"); Console.WriteLine($"Client : GameSession ID {response.GameSession.GameSessionId}!"); gameSession = response.GameSession; } else { Console.Error.WriteLine($"Client : Failed creating GameSession!"); IsAlive = false; } }
CreateGameSessionAsync()
でGameSession
を作ります。
new CreateGameSessionRequest()
で請求を作成します。FleetId
、CreatorId
、MaximumPlayerSessionCount
を記入し、gameLiftClient.CreateGameSessionAsync(request)
で請求を送ります。
セッション情報がresponse.GameSession
に返したら、ローカルのメンバーgameSession
に保存します。
// Create a PlayerSession from a GameSession async private Task CreatePlayerSessionAsync() { Console.WriteLine($"Client : CreatePlayerSessionAsync() start"); if (gameSession == null) return; while (gameSession.Status != GameSessionStatus.ACTIVE) { Console.WriteLine($"Client : Wait for GameSession to be ACTIVE, Sleep for a while"); Thread.Sleep(100); await UpdateGameSession(); } var request = new CreatePlayerSessionRequest(); request.GameSessionId = gameSession.GameSessionId; request.PlayerId = playerId; Console.WriteLine($"Client : Sending request and await"); var response = await gameLiftClient.CreatePlayerSessionAsync(request); Console.WriteLine($"Client : request sent"); if (response.PlayerSession != null) { Console.WriteLine($"Client : PlayerSession Created!"); Console.WriteLine($"Client : PlayerSession ID {response.PlayerSession.PlayerSessionId}!"); playerSession = response.PlayerSession; } else { Console.Error.WriteLine($"Client : Failed creating PlayerSession!"); IsAlive = false; } }
CreatePlayerSessionAsync()
で指定したGameSession
に接続するPlayerSession
を作ります。
GameSession
が準備できていないこともあるので、UpdateGameSession()
でGameSession
の最新情報を取得します。gameSession.Status == GameSessionStatus.ACTIVE
になってから、次に進めます。
new CreatePlayerSessionRequest()
で請求を作って、GameSessionId
とPlayerId
を記入しgameLiftClient.CreatePlayerSessionAsync(request)
で請求を送ります。
無事にresponse.PlayerSession
にPlayerSession
の情報が返してくれたら、ローカルのメンバーplayerSession
に保存します。
PlayerSession
にIP
とPort
情報があるので、それをTcpClient
に渡して、サーバーと通信ができます。
最後
より良いチャットクライアントPart1はここで完了です。
今回の記事は接続部分ソースコードの説明でしたので、次回の記事は受送信のソースコードを解説します。