Amazon GameLift In C# 10: より良いチャットクライアントを作ります(Part1)

Amazon GameLift Walkthrough in C# (with Unity) Tutorial 10

概要

前回の記事まではより良いチャットサーバーのソースコード解説でしたが、これからより良いチャットクライアントのソースコードを分析します。

マシン環境

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を保存するメンバーです。プレイヤーが接続すべきサーバーのIPPortなどを持っています。
  • 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()構造体です。
プログラムの存続フラグIsAlivetrueにします。
そしてGuid.NewGuid().ToString()でプレイヤーの唯一IDを生成します。
Identity Pool IDRegionEndpointCognitoAWSCredentials を生成し、検証情報と地域情報を持っている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()で作ったrequestFleet 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()で請求を作成します。FleetIdCreatorIdMaximumPlayerSessionCountを記入し、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()で請求を作って、GameSessionIdPlayerIdを記入しgameLiftClient.CreatePlayerSessionAsync(request)で請求を送ります。

無事にresponse.PlayerSessionPlayerSessionの情報が返してくれたら、ローカルのメンバーplayerSessionに保存します。

PlayerSessionIPPort情報があるので、それをTcpClientに渡して、サーバーと通信ができます。

 

最後

より良いチャットクライアントPart1はここで完了です。
今回の記事は接続部分ソースコードの説明でしたので、次回の記事は受送信のソースコードを解説します。