GameLift RealTimeServerで遊んでみよう for Unity（Unity編）

# はじめに

### 前回からのつづき
前回はこちら
[GameLift RealTimeServerで遊んでみよう for Unity（AWS設定編）](https://qiita.com/nimitsu/items/61cf2b177c10b59ebd3f)

### 対象者
* AWSのアカウントを持っている方
* GameLiftでとりあえず遊んでみたいと考えているUnityエンジニアの方
* AWSの無料利用枠がまだある方、**もしくは使用金額を払ってでもやりたい方（この辺りは自己責任で）**
* 今回作成するものをきちんとクリーンアップできる方

### 試した環境

* MacBook Pro (13-inch, 2017)
* OSバージョン10.14.6
* Unity2019.2.15f


#このページで行うこと
Unity設定をし、RealTimeServerでのデータの送受信まで。
***「誰かが送ったメッセージをトリガーにし、UDPでつながっているすべてのユーザーにデータを送る」***
ことをやってみる。


####Unityでの設定

* パッケージのインストール


####Unityでの実装

* AmazonGameLiftClientクラスの初期化をする
* ルームを作成
* ルームを検索
* ルームへの参加
* データ送受信を確認

##パッケージのインストール

###AWS.NET SDK

**AWSSDK.Core（3.3.104を使用）**
**AWSSDK.GameLift(3.3.104.18を使用)**

ともに[こちら](https://aws.amazon.com/jp/sdk-for-net/)からダウンロード。
（自分はNuGetの方を選択）
.Net4.5対応のものを使ってください。

###GameLift Realtime Client SDK
[こちら](https://aws.amazon.com/jp/gamelift/getting-started/)のRealtime Client SDKをダウンロードしVisualStudioなどでビルド


上記で入手したライブラリをUnityのPluginsフォルダ以下に配置
<img width="290" alt="スクリーンショット 2019-12-08 15.27.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532557/9ccbb70c-62fc-9f32-acb2-1ae384470693.png">

UnityのApi Compatibility Levelを.Net 4.xにするのを忘れないように
<img width="501" alt="スクリーンショット 2019-12-08 15.50.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532557/6fc27e84-d63e-7b0d-c5c3-7f34b0d1c16c.png">
Build Setting -> Player Settings -> Player -> Configuration -> Other Settings -> API Compatibility Level



#今回説明するスクリプト
主要となるスクリプトを載せております。
UIに関しては各々で実装していただければ。
###クライアント側
[こちら](https://docs.aws.amazon.com/ja_jp/gamelift/latest/developerguide/realtime-client.html)のサンプルを元に作成
<details><summary>Lobby.cs</summary><div>

```csharp
using System;
using System.Linq;
using System.Diagnostics;
using System.Collections.Generic;
using System.Net.NetworkInformation;
using UnityEngine;
using Amazon;
using Amazon.GameLift;
using Amazon.GameLift.Model;
using Aws.GameLift.Realtime.Types;
public class Lobby : MonoBehaviour
{
    class GameLiftConfig
    {
        public RegionEndpoint RegionEndPoint { get; set; }
        public string AccessKeyId { get; set; }
        public string SecretAccessKey { get; set; }
        public string GameLiftAliasId { get; set; }
    }
    GameLiftConfig config;
    AmazonGameLiftClient gameLiftClient;
    RealTimeClient realTimeClient;

    [SerializeField]
    LobbyUI ui;
    // Start is called before the first frame update
    void Start()
    {
        UnityEngine.Debug.Log("start");
        initialize();
    }

    void initialize()
    {
        config = new GameLiftConfig
        {
            RegionEndPoint = RegionEndpoint.APNortheast1, //東京の場合
            AccessKeyId = "", // ダウンロードしたcsvのAccess key IDの値
            SecretAccessKey = "", // ダウンロードしたcsvのSecret access keyの値
            GameLiftAliasId = "" // 作成したAliasのID alias- から始まるID
        };

        // AmazonGameLiftClientクラスの初期化
        gameLiftClient = new AmazonGameLiftClient(config.AccessKeyId, config.SecretAccessKey, config.RegionEndPoint);

        ui.CreateRoomButton.onClick.AddListener(() =>
        {
            CreateRoom();
        });

        ui.SearchRoomButton.onClick.AddListener(() =>
       {
           var sessions = SearchRooms();
           ui.ClearAllPanels();
           ui.CreateSessionPanels(sessions, JoinRoom);
       });

        ui.SendTest1Button.onClick.AddListener(() =>
        {
            if (realTimeClient != null) realTimeClient.SendMessage(DeliveryIntent.Reliable, "test");
        });
        ui.SendTest2Button.onClick.AddListener(() =>
        {
            if (realTimeClient != null) realTimeClient.SendEvent(RealTimeClient.OpCode.SendTest2);
        });
    }

    // ルームの作成
    void CreateRoom(string roomName = "")
    {
        UnityEngine.Debug.Log("CreateRoom");
        if (string.IsNullOrEmpty(roomName)) roomName = Guid.NewGuid().ToString();
        var request = new CreateGameSessionRequest
        {
            AliasId = config.GameLiftAliasId,
            MaximumPlayerSessionCount = 2,
            Name = roomName
        };
        var response = gameLiftClient.CreateGameSession(request);
        ui.InfoText.text += "CreateRoom\n";
    }

    //ルームの検索
    public List<GameSession> SearchRooms()
    {
        UnityEngine.Debug.Log("SearchRooms");
        var response = gameLiftClient.SearchGameSessions(new SearchGameSessionsRequest
        {
            AliasId = config.GameLiftAliasId,
        });
        ui.InfoText.text += "SearchRoom\n";
        return response.GameSessions;
    }

    // ルームへの参加
    void JoinRoom(string sessionId)
    {
        UnityEngine.Debug.Log("JoinRoom");
        var response = gameLiftClient.CreatePlayerSession(new CreatePlayerSessionRequest
        {
            GameSessionId = sessionId,
            PlayerId = SystemInfo.deviceUniqueIdentifier,
        });
        var playerSession = response.PlayerSession;

        ushort DefaultUdpPort = 7777;
        var udpPort = SearchAvailableUdpPort(DefaultUdpPort, DefaultUdpPort + 100);
        realTimeClient = new RealTimeClient(
            playerSession.IpAddress,
            playerSession.Port,
            udpPort,
            ConnectionType.RT_OVER_WS_UDP_UNSECURED,
            playerSession.PlayerSessionId,
            null);

        ui.InfoText.text += "JoinRoom\n";
        realTimeClient.OnDataReceivedCallback = OnDataReceivedCallback;
    }

    public void OnDataReceivedCallback(object sender, Aws.GameLift.Realtime.Event.DataReceivedEventArgs e)
    {
        if (ui.InfoText != null)
        {
            ui.InfoText.text += $"{e.OpCode}\n";
        }
    }

    int SearchAvailableUdpPort(int from = 1024, int to = ushort.MaxValue)
    {
        from = Mathf.Clamp(from, 1, ushort.MaxValue);
        to = Mathf.Clamp(to, 1, ushort.MaxValue);
#if UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX
        var set = LsofUdpPorts(from, to);
#else
        var set = GetActiveUdpPorts();
#endif
        for (int port = from; port <= to; port++)
            if (!set.Contains(port))
                return port;
        return -1;
    }

    HashSet<int> LsofUdpPorts(int from, int to)
    {
        var set = new HashSet<int>();
        string command = string.Join(" | ",
            $"lsof -nP -iUDP:{from.ToString()}-{to.ToString()}",
            "sed -E 's/->[0-9.:]+$//g'",
            @"grep -Eo '\d+$'");
        var process = Process.Start(new ProcessStartInfo
        {
            FileName = "/bin/bash",
            Arguments = $"-c \"{command}\"",
            RedirectStandardOutput = true,
            UseShellExecute = false,
        });
        if (process != null)
        {
            process.WaitForExit();
            var stream = process.StandardOutput;
            while (!stream.EndOfStream)
                if (int.TryParse(stream.ReadLine(), out int port))
                    set.Add(port);
        }
        return set;
    }

    HashSet<int> GetActiveUdpPorts()
    {
        return new HashSet<int>(IPGlobalProperties.GetIPGlobalProperties()
            .GetActiveUdpListeners().Select(listener => listener.Port));
    }
}



```
</div></details>

<details><summary>RealTimeClient.cs</summary><div>

```csharp

using System;
using System.Text;
using Aws.GameLift.Realtime;
using Aws.GameLift.Realtime.Event;
using Aws.GameLift.Realtime.Types;
public class RealTimeClient
{
    public Aws.GameLift.Realtime.Client Client { get; private set; }

    public Action<object, DataReceivedEventArgs> OnDataReceivedCallback { get; set; }
    // An opcode defined by client and your server script that represents a custom message type
    public static class OpCode
    {
        public const int SendTest1 = 10;
        public const int SendTest2 = 11;

        public const int RecieveTest1 = 31;
        public const int RecieveTest2 = 32;

    }

    /// Initialize a client for GameLift Realtime and connect to a player session.
    /// <param name="endpoint">The DNS name that is assigned to Realtime server</param>
    /// <param name="remoteTcpPort">A TCP port for the Realtime server</param>
    /// <param name="listeningUdpPort">A local port for listening to UDP traffic</param>
    /// <param name="connectionType">Type of connection to establish between client and the Realtime server</param>
    /// <param name="playerSessionId">The player session ID that is assigned to the game client for a game session </param>
    /// <param name="connectionPayload">Developer-defined data to be used during client connection, such as for player authentication</param>
    public RealTimeClient(string endpoint, int remoteTcpPort, int listeningUdpPort, ConnectionType connectionType,
                 string playerSessionId, byte[] connectionPayload)
    {
        // Create a client configuration to specify a secure or unsecure connection type
        // Best practice is to set up a secure connection using the connection type RT_OVER_WSS_DTLS_TLS12.
        ClientConfiguration clientConfiguration = new ClientConfiguration()
        {
            // C# notation to set the field ConnectionType in the new instance of ClientConfiguration
            ConnectionType = connectionType
        };

        // Create a Realtime client with the client configuration            
        Client = new Client(clientConfiguration);

        // Initialize event handlers for the Realtime client
        Client.ConnectionOpen += OnOpenEvent;
        Client.ConnectionClose += OnCloseEvent;
        Client.GroupMembershipUpdated += OnGroupMembershipUpdate;
        Client.DataReceived += OnDataReceived;

        // Create a connection token to authenticate the client with the Realtime server
        // Player session IDs can be retrieved using AWS SDK for GameLift
        ConnectionToken connectionToken = new ConnectionToken(playerSessionId, connectionPayload);

        // Initiate a connection with the Realtime server with the given connection information
        Client.Connect(endpoint, remoteTcpPort, listeningUdpPort, connectionToken);
    }

    public void Disconnect()
    {
        if (Client.Connected)
        {
            Client.Disconnect();
        }
    }

    public bool IsConnected()
    {
        return Client.Connected;
    }

    /// <summary>
    /// Example of sending to a custom message to the server.
    /// 
    /// Server could be replaced by known peer Id etc.
    /// </summary>
    /// <param name="intent">Choice of delivery intent ie Reliable, Fast etc. </param>
    /// <param name="payload">Custom payload to send with message</param>
    public void SendMessage(DeliveryIntent intent, string payload)
    {
        UnityEngine.Debug.Log("SendMessage");
        Client.SendMessage(Client.NewMessage(OpCode.SendTest1)
            .WithDeliveryIntent(intent)
            .WithTargetPlayer(Constants.PLAYER_ID_SERVER)
            .WithPayload(StringToBytes(payload)));
    }

    /**
     * Handle connection open events
     */
    public void OnOpenEvent(object sender, EventArgs e)
    {
        UnityEngine.Debug.Log("OnOpenEvent");
    }

    /**
     * Handle connection close events
     */
    public void OnCloseEvent(object sender, EventArgs e)
    {
        UnityEngine.Debug.Log("OnCloseEvent");
    }

    /**
     * Handle Group membership update events 
     */
    public void OnGroupMembershipUpdate(object sender, GroupMembershipEventArgs e)
    {
        UnityEngine.Debug.Log("OnGroupMembershipUpdate");
    }

    /**
     *  Handle data received from the Realtime server 
     */
    public virtual void OnDataReceived(object sender, DataReceivedEventArgs e)
    {
        UnityEngine.Debug.Log("OnDataReceived");
        UnityEngine.Debug.Log($"OpCode = {e.OpCode}");
        switch (e.OpCode)
        {
            // handle message based on OpCode
            default:
                break;
        }

        if (OnDataReceivedCallback != null) OnDataReceivedCallback(sender, e);
    }

    /**
     * Helper method to simplify task of sending/receiving payloads.
     */
    public static byte[] StringToBytes(string str)
    {
        return Encoding.UTF8.GetBytes(str);
    }

    /**
     * Helper method to simplify task of sending/receiving payloads.
     */
    public static string BytesToString(byte[] bytes)
    {
        return Encoding.UTF8.GetString(bytes);
    }

    public void SendEvent(int opCode)
    {
        UnityEngine.Debug.Log("SendEvent");
        if (!IsConnected()) return;
        Client.SendEvent(opCode);
    }
}



```
</div></details>

[リファレンスはこちら](https://docs.aws.amazon.com/ja_jp/gamelift/latest/developerguide/realtime-sdk-csharp-ref.html)


###サーバー側
[こちら](https://docs.aws.amazon.com/ja_jp/gamelift/latest/developerguide/realtime-script.html)のサンプルを元に作成
<details><summary>server.js</summary><div>

```javascript

'use strict';
const util = require('util');

const tickTime = 1000;
const minimumElapsedTime = 120;

const SendTest1 = 10;
const SendTest2 = 11;

const RecieveTest1 = 31;
const RecieveTest2 = 32;

// The Realtime server session object
var session;

var logger;
var activePlayers = 0;              // Records the number of connected players

var startTime;                      // Records the time the process started

function init(rtSession) {
    session = rtSession;
    logger = session.getLogger();

}


// A simple tick loop example
// Checks to see if a minimum amount of time has passed before seeing if the game has ended
async function tickLoop() {
    const elapsedTime = getTimeInS() - startTime;
    logger.info("Tick... " + elapsedTime + " activePlayers: " + activePlayers);

    // In Tick loop - see if all players have left early after a minimum period of time has passed
    // Call processEnding() to terminate the process and quit
    if ((activePlayers == 0) && (elapsedTime > minimumElapsedTime)) {
        logger.info("All players disconnected. Ending game");
        const outcome = await session.processEnding();
        logger.info("Completed process ending with: " + outcome);
        process.exit(0);
    }
    else {
        setTimeout(tickLoop, tickTime);
    }
}

// Calculates the current time in seconds
function getTimeInS() {
    return Math.round(new Date().getTime() / 1000);
}

function onProcessStarted(args) {
    logger.info(`[onProcessStarted]`);
    return true;
}
function onStartGameSession(gameSession) {
    // Complete any game session set-up
    logger.info(`[onStartGameSession]`);
    // tryDelayExit();
    startTime = getTimeInS();
    tickLoop();
}

// Handle process termination if the process is being terminated by GameLift
// You do not need to call ProcessEnding here
function onProcessTerminate() {
    // Perform any clean up
}

// On Player Connect is called when a player has passed initial validation
// Return true if player should connect, false to reject
function onPlayerConnect(connectMsg) {
    logger.info(`[onPlayerConnect]`);
    return true;
}

// Called when a Player is accepted into the game
function onPlayerAccepted(player) {
    logger.info(`[onPlayerAccepted]`);
    activePlayers++;
}

// On Player Disconnect is called when a player has left or been forcibly terminated
// Is only called for players that actually connected to the server and not those rejected by validation
// This is called before the player is removed from the player list
function onPlayerDisconnect(peerId) {
    logger.info(`[onPlayerDisconnect]`);
    activePlayers--;
    // tryDelayExit();
}

// Return true if the player is allowed to join the group
function onPlayerJoinGroup(groupId, peerId) {
    return true;
}

// Return true if the player is allowed to leave the group
function onPlayerLeaveGroup(groupId, peerId) {
    return true;
}

// Return true if the send should be allowed
function onSendToPlayer(gameMessage) {
    return true;
}

// Return true if the send to group should be allowed
// Use gameMessage.getPayloadAsText() to get the message contents
function onSendToGroup(gameMessage) {
    logger.info(`[onSendToGroup]`);

    return true;
}

// Handle a message to the server
function onMessage(gameMessage) {
    logger.info(`[onMessage]`);
    switch (gameMessage.opCode) {
        case SendTest1: {
            // do operation 1 with gameMessage.payload for example sendToGroup
            const outMessage = session.newTextGameMessage(RecieveTest1, session.getServerId(), gameMessage.payload);
            session.sendGroupMessage(outMessage, -1);
            break;
        }
        case SendTest2: {
            // do operation 1 with gameMessage.payload for example sendToGroup
            const outMessage = session.newTextGameMessage(RecieveTest2, session.getServerId(), gameMessage.payload);
            session.sendGroupMessage(outMessage, -1);
            break;
        }
    }
}

// Return true if the process is healthy
function onHealthCheck() {
    return true;
}

exports.ssExports = {
    init: init,
    onProcessStarted: onProcessStarted,
    onStartGameSession: onStartGameSession,
    onProcessTerminate: onProcessTerminate,
    onPlayerConnect: onPlayerConnect,
    onPlayerAccepted: onPlayerAccepted,
    onPlayerDisconnect: onPlayerDisconnect,
    onPlayerJoinGroup: onPlayerJoinGroup,
    onPlayerLeaveGroup: onPlayerLeaveGroup,
    onSendToPlayer: onSendToPlayer,
    onSendToGroup: onSendToGroup,
    onMessage: onMessage,
    onHealthCheck: onHealthCheck
};


```
</div></details>

[リファレンスはこちら](https://docs.aws.amazon.com/ja_jp/gamelift/latest/developerguide/realtime-script-ref.html)
前回作成したものに追記いただければ。
server.jsを更新する際はGameLiftのスクリプトを編集しアップロードしてください

![スクリーンショット 2019-12-11 18.34.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532557/88c054ed-54f2-bf93-e136-407128a0dc42.png)


## AmazonGameLiftClientクラスの初期化をする

```csharp:Lobby.cs
         config = new GameLiftConfig
        {
            RegionEndPoint = RegionEndpoint.APNortheast1, //東京の場合
            AccessKeyId = "", // ダウンロードしたcsvのAccess key IDの値
            SecretAccessKey = "", // ダウンロードしたcsvのSecret access keyの値
            GameLiftAliasId = "" // 作成したAliasのID alias- から始まるID
        };

        // AmazonGameLiftClientクラスの初期化
        gameLiftClient = new AmazonGameLiftClient(config.AccessKeyId, config.SecretAccessKey, config.RegionEndPoint);
```

#####引っかかりやすいポイント
 AmazonGameLiftClientクラスの初期化に使用する値を間違えないように
**※リリースするようなものであれば、コードに直に書いたりはしないよう注意**

## ルームの作成

```csharp:Lobby.cs
    // ルームの作成
    void CreateRoom(string roomName = "")
    {
        UnityEngine.Debug.Log("CreateRoom");
        if (string.IsNullOrEmpty(roomName)) roomName = Guid.NewGuid().ToString();
        var request = new CreateGameSessionRequest
        {
            AliasId = config.GameLiftAliasId,
            MaximumPlayerSessionCount = 2,
            Name = roomName
        };
        var response = gameLiftClient.CreateGameSession(request);
        ui.InfoText.text += "CreateRoom\n";
    }
```
CreateGameSessionRequestを作成しCreateGameSessionに渡せば簡単に作成できる。

##ルームの検索
```csharp:Lobby.cs
    //ルームの検索
    public List<GameSession> SearchRooms()
    {
        UnityEngine.Debug.Log("SearchRooms");
        var response = gameLiftClient.SearchGameSessions(new SearchGameSessionsRequest
        {
            AliasId = config.GameLiftAliasId,
        });
        ui.InfoText.text += "SearchRoom\n";
        return response.GameSessions;
    }
```
SearchGameSessionsRequestをAliasIdを引数とし作成し
SearchGameSessionsに渡せば簡単に検索できる。

##ルームへの参加
```csharp:Lobby.cs
    // ルームへの参加
    void JoinRoom(string sessionId)
    {
        UnityEngine.Debug.Log("JoinRoom");
        var response = gameLiftClient.CreatePlayerSession(new CreatePlayerSessionRequest
        {
            GameSessionId = sessionId,
            PlayerId = SystemInfo.deviceUniqueIdentifier,
        });
        var playerSession = response.PlayerSession;

        ushort DefaultUdpPort = 7777;
        var udpPort = SearchAvailableUdpPort(DefaultUdpPort, DefaultUdpPort + 100);
        realTimeClient = new RealTimeClient(
            playerSession.IpAddress,
            playerSession.Port,
            udpPort,
            ConnectionType.RT_OVER_WS_UDP_UNSECURED,
            playerSession.PlayerSessionId,
            null);

        ui.InfoText.text += "JoinRoom\n";
        realTimeClient.OnDataReceivedCallback = OnDataReceivedCallback;
    }

```

```RealTimeClient.cs
   /// Initialize a client for GameLift Realtime and connect to a player session.
    /// <param name="endpoint">The DNS name that is assigned to Realtime server</param>
    /// <param name="remoteTcpPort">A TCP port for the Realtime server</param>
    /// <param name="listeningUdpPort">A local port for listening to UDP traffic</param>
    /// <param name="connectionType">Type of connection to establish between client and the Realtime server</param>
    /// <param name="playerSessionId">The player session ID that is assigned to the game client for a game session </param>
    /// <param name="connectionPayload">Developer-defined data to be used during client connection, such as for player authentication</param>
    public RealTimeClient(string endpoint, int remoteTcpPort, int listeningUdpPort, ConnectionType connectionType,
                 string playerSessionId, byte[] connectionPayload)
    {
        // Create a client configuration to specify a secure or unsecure connection type
        // Best practice is to set up a secure connection using the connection type RT_OVER_WSS_DTLS_TLS12.
        ClientConfiguration clientConfiguration = new ClientConfiguration()
        {
            // C# notation to set the field ConnectionType in the new instance of ClientConfiguration
            ConnectionType = connectionType
        };

        // Create a Realtime client with the client configuration            
        Client = new Client(clientConfiguration);

        // Initialize event handlers for the Realtime client
        Client.ConnectionOpen += OnOpenEvent;
        Client.ConnectionClose += OnCloseEvent;
        Client.GroupMembershipUpdated += OnGroupMembershipUpdate;
        Client.DataReceived += OnDataReceived;

        // Create a connection token to authenticate the client with the Realtime server
        // Player session IDs can be retrieved using AWS SDK for GameLift
        ConnectionToken connectionToken = new ConnectionToken(playerSessionId, connectionPayload);

        // Initiate a connection with the Realtime server with the given connection information
        Client.Connect(endpoint, remoteTcpPort, listeningUdpPort, connectionToken);
    }

```

[Client](https://docs.aws.amazon.com/ja_jp/gamelift/latest/developerguide/realtime-sdk-csharp-ref-actions.html#realtime-sdk-csharp-ref-actions-client)で新しいクライアントを初期化し
[Client.Connect](https://docs.aws.amazon.com/ja_jp/gamelift/latest/developerguide/realtime-sdk-csharp-ref-actions.html#realtime-sdk-csharp-ref-actions-connect)でゲームセッションをホストしているサーバープロセスへの接続をリクエストしルームへ参加する。
MacだとIPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners()
でうまく有効なUDPポートとれなかったので、別実装で取得

##データ送受信の確認
送るイベントのクライアント側定義

```csharp:RealTimeClient.cs
        public const int SendTest1 = 10;
        public const int SendTest2 = 11;

        public const int RecieveTest1 = 31;
        public const int RecieveTest2 = 32
```

送るイベントのサーバー側定義

```javascript:server.js
const SendTest1 = 10;
const SendTest2 = 11;

const RecieveTest1 = 31;
const RecieveTest2 = 32

```

* イベントやメッセージの送信処理

```RealTimeClient.cs
    /// <summary>
    /// Example of sending to a custom message to the server.
    /// 
    /// Server could be replaced by known peer Id etc.
    /// </summary>
    /// <param name="intent">Choice of delivery intent ie Reliable, Fast etc. </param>
    /// <param name="payload">Custom payload to send with message</param>
    public void SendMessage(DeliveryIntent intent, string payload)
    {
        UnityEngine.Debug.Log("SendMessage");
        Client.SendMessage(Client.NewMessage(OpCode.SendTest1)
            .WithDeliveryIntent(intent)
            .WithTargetPlayer(Constants.PLAYER_ID_SERVER)
            .WithPayload(StringToBytes(payload)));
    }

    public void SendEvent(int opCode)
    {
        UnityEngine.Debug.Log("SendEvent");
        if (!IsConnected()) return;
        Client.SendEvent(opCode);
    }
```
[SendMessage](https://docs.aws.amazon.com/ja_jp/gamelift/latest/developerguide/realtime-sdk-csharp-ref-actions.html#realtime-sdk-csharp-ref-actions-sendmessage)とSendEventで試してみる。

* サーバー側ハンドリング処理

```javascript:server.js
// Handle a message to the server
function onMessage(gameMessage) {
    logger.info(`[onMessage]`);
    switch (gameMessage.opCode) {
        case SendTest1: {
            // do operation 1 with gameMessage.payload for example sendToGroup
            const outMessage = session.newTextGameMessage(RecieveTest1, session.getServerId(), gameMessage.payload);
            session.sendGroupMessage(outMessage, -1);
            break;
        }
        case SendTest2: {
            // do operation 1 with gameMessage.payload for example sendToGroup
            const outMessage = session.newTextGameMessage(RecieveTest2, session.getServerId(), gameMessage.payload);
            session.sendGroupMessage(outMessage, -1);
            break;
        }
    }
}
```
* クライアント受信処理

```csharp:RealTimeClient.cs
     /**
     *  Handle data received from the Realtime server 
     */
    public virtual void OnDataReceived(object sender, DataReceivedEventArgs e)
    {
        UnityEngine.Debug.Log("OnDataReceived");
        UnityEngine.Debug.Log($"OpCode = {e.OpCode}");
        switch (e.OpCode)
        {
            // handle message based on OpCode
            default:
                break;
        }

        if (OnDataReceivedCallback != null) OnDataReceivedCallback(sender, e);
    }
```

別途UIを作成しビルド。
以下は別々のアプリで起動し
ルーム作成→ルーム検索→ルームへ参加→イベントを送る
と試したところ
![test.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532557/7de89c0c-0d91-830b-8260-40a66faadda7.gif)
見栄えよくないですが
「誰かが送ったメッセージをトリガーにし、UDPでつながっているすべてのユーザーにデータを送る」
の目標は達成したので今回はここまで

##再度の注意点
作成した

* スクリプト
* フリート
* エイリアス

に関して使わないときは削除するようにしてください。
でないと***お金がかかっちゃう***ので。

##おわりに
走った説明になっちゃいました。
所感としては確かに簡単にリアルタイム通信ができると感じたが、
無料で気軽に試せないのがつらいところ

これから遊びの部分を作って行こうと思いましたが、金額がかかっちゃうので気が向いた時にでも

明日は @e73ryo さんの[UIElementsで開発するときの問題と解決](https://qiita.com/e73ryo/items/f85688bb0ef1d6f8d21f)です！