【Unity】Twitchチャットと連携するシンプルなゲームを作った

![SimpleTwitchGame.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107527/b1417a2f-cae0-ce11-b40c-a2bb2fe73d9e.gif)


[Twitch.tv](https://www.twitch.tv/)のチャンネルチャットと連携して動くめちゃくちゃシンプルなゲーム（？）を作りました。
!loginとチャットで入力すると自キャラ（球体）がゲームに参加します（シーン内ランダムな位置に配置される）
!changecolorで自キャラの色が変わります。
!logoutでゲームから退場します。
ゲームって呼べるかどうかも微妙な程シンプルですが応用すればTwitchのユーザー参加型ゲーム作り等に活用できると思います。
Githubにプロジェクトを公開しましたのでよければお試しください。

https://github.com/mojopon/SimpleGameWithTwitch

# 利用方法
1. プロジェクト( https://github.com/mojopon/SimpleGameWithTwitch.git )をクローン。
2. Unityでプロジェクトを開き、SampleSceneを開く。
3. シーンの中、TwitchInputというオブジェクトにアタッチされたTwitchInputクラスのフィールドに、Twitchのアクセストークン、ご自身のTwitchユーザー名、連携したいチャンネル名等の必要な情報を入力（※Twitchのアクセストークンの取得方法等については[こちらの記事](https://qiita.com/mojomojopon/items/54184c88a32501561cd4)をご覧ください）
4. シーン再生でTwitchチャットチャンネルと連携が開始します。
5. 連携先のチャットチャンネルに「!login」と入力でゲーム参加（発言者の名前を持つキャラが作成されます）、「!changecolor」で自キャラ色変更、「!logout」でゲームから退場します。
# 環境
本プロジェクトではTwitchチャットとの連携を行う上で、UPMパッケージの「[UniTwitchClient](https://github.com/mojopon/UniTwitchClient)」を利用しています。
サンプルプロジェクトには既にパッケージがインストールされていますが、本記事の手順でTwitch連携を行う場合には、あらかじめ「UniTwitchClient」及びその依存ライブラリのインストールが必要となりますのでご注意ください。
環境構築方法については[こちらの記事](https://qiita.com/mojomojopon/items/54184c88a32501561cd4)にまとめてありますので、ご必要な方はご覧ください。

# 内容解説
### PlayerManager.cs
```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerManager : MonoBehaviour
{
    public GameObject PlayerPrefab;

    private Dictionary<string, Player> playerDictionary = new Dictionary<string, Player>();
    private Area area;

    private void Awake()
    {
        area = FindObjectOfType<Area>();
    }

    public void SpawnPlayer(string id, string playerName) 
    {
        if (string.IsNullOrEmpty(id)) { return; }
        if (IsPlayerExist(id)) { return; }

        var position = area.GetRandomPosition();
        var playerObj = Instantiate(PlayerPrefab, position, Quaternion.identity);
        var player = playerObj.GetComponent<Player>();
        player.SetPlayerName(playerName);

        playerDictionary.Add(id, player);
    }

    public void DeletePlayer(string id) 
    {
        if(!IsPlayerExist(id)) { return; }

        var player = playerDictionary[id];
        Destroy(player.gameObject);

        playerDictionary.Remove(id);
    }

    public void ChangePlayerColor(string id) 
    {
        if (!IsPlayerExist(id)) { return; }

        var player = playerDictionary[id];
        player.ChangePlayerColor();
    }

    private bool IsPlayerExist(string id) 
    {
        return playerDictionary.ContainsKey(id);
    }
}
```
プレイヤーをシーンに配置したりChangeColorの操作をしたりなど、プレイヤーの管理を受け持つクラスです。
!logoutや!loginコマンドを受けた時のプレイヤーの配置及び退場処理、!changecolorコマンドを受けた時のプレイヤーへの色変更処理実行の伝達などを行っています。

### Player.cs
```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{
    public TextMesh NameTextMesh;

    private MeshRenderer meshRenderer;
    private List<Color> colorList = new List<Color>()
    {
        Color.white,
        Color.red,
        Color.yellow,
        Color.green,
        Color.blue,
        Color.cyan,
        Color.magenta,
    };
    private int currentColor = 0;

    private void Awake()
    {
        meshRenderer = GetComponent<MeshRenderer>();
    }

    public void SetPlayerName(string playerName) 
    {
        NameTextMesh.text = playerName;
    }

    [ContextMenu("Change Player Color")]
    public void ChangePlayerColor() 
    {
        currentColor++;
        if (currentColor >= colorList.Count) 
        {
            currentColor = 0;
        }

        meshRenderer.material.color = colorList[currentColor];
    }
}
```
プレイヤーの状態変化などの処理を行うクラスです。
今回はシンプルにメッシュのマテリアルの色を変更して!changecolorコマンド入力の色変更を実現しています。

### Area.cs
```
using UnityEngine;

public class Area : MonoBehaviour
{
    public float Width;
    public float Height;

    public Vector3 GetRandomPosition()
    {
        return new Vector3(Random.Range(-(Width / 2), (Width / 2)), Random.Range(-(Height / 2), (Height / 2)));
    }

    private void OnDrawGizmosSelected()
    {
        Gizmos.DrawWireCube(transform.position, new Vector3(Width, Height, 1));
    }
}
```
!loginコマンドでプレイヤーを配置する際の配置場所を定義するクラスです。
SceneビューでAreaオブジェクトを選択することで配置範囲のギズモが確認できます。

### TwitchInput.cs
```
using UniRx;
using UniTwitchClient.Chat;
using UniTwitchClient.Chat.Models;
using UnityEngine;

public class TwitchInput : MonoBehaviour
{
    public string AccessToken;
    public string UserName;
    public string ChannelName;

    private TwitchChatClient client;
    private PlayerManager playerManager;

    private void Awake()
    {
        playerManager = FindObjectOfType<PlayerManager>();
    }

    private void Start()
    {
        if (string.IsNullOrEmpty(AccessToken) || string.IsNullOrEmpty(UserName) || string.IsNullOrEmpty(ChannelName)) { return; }

        client = new TwitchChatClient(new TwitchIrcCredentials(AccessToken, UserName));
        client.TwitchChatMessageAsObservable.Subscribe(HandleTwitchChatMessage).AddTo(this);
        client.Connect(ChannelName);
    }

    private void OnDestroy()
    {
        client?.Close();
        client?.Dispose();
    }

    private void HandleTwitchChatMessage(TwitchChatMessage twitchChatMessage)
    {
        var command = twitchChatMessage.BotCommand;
        var id = twitchChatMessage.UserId;
        var playerName = twitchChatMessage.DisplayName;
        if (command == "login")
        {
            Login(id, playerName);
        }
        else if (command == "logout")
        {
            Logout(id);
        }
        else if (command == "changecolor")
        {
            ChangeColor(id);
        }
    }

    private void Login(string id, string playerName)
    {
        playerManager.SpawnPlayer(id, playerName);
    }

    public void Logout(string id)
    {
        playerManager.DeletePlayer(id);
    }

    public void ChangeColor(string id)
    {
        playerManager.ChangePlayerColor(id);
    }
}
```
UniTwitchClientライブラリのTwitchChatClientクラスを利用してTwitchのチャットと連携を行い、!loginや!changecolorなどのコマンド入力があればそれを受け取ってゲームへの入力を行うクラスです。
HandleTwitchChatMessageメソッドで、Twitchから受け取ったメッセージに応じて処理を行っています。
```
private void HandleTwitchChatMessage(TwitchChatMessage twitchChatMessage)
    {
        var command = twitchChatMessage.BotCommand;
        var id = twitchChatMessage.UserId;
        var playerName = twitchChatMessage.DisplayName;
        if (command == "login")
        {
            Login(id, playerName);
        }
        else if (command == "logout")
        {
            Logout(id);
        }
        else if (command == "changecolor")
        {
            ChangeColor(id);
        }
    }
```
TwitchChatMessage.BotCommandにはエクスクラメーションマーク(!)から始まるコマンドが格納されています（!loginというメッセージの場合はloginが格納される）
ここで入力されたコマンドに応じてPlayerManagerに入力を行うことで、チャットに!loginと入力された場合はプレイヤー配置を、!changecolorと入力された場合は色変更等の処理を実現しています。
TwitchChatMessage.UserIdには発言者のTwitchアカウントのユーザーId、TwitchChatMessage.DisplayNameには発言者のユーザーネームが格納されます。
これをプレイヤーキャラのIdと名前として利用しています。

### UIInput.cs
```
using UnityEngine;
using UnityEngine.UI;

public class UIInput : MonoBehaviour
{
    public InputField IdInputField;
    public InputField PlayerNameInputField;

    private PlayerManager playerManager;

    private void Awake()
    {
        playerManager = FindObjectOfType<PlayerManager>();
    }

    public void Login()
    {
        var id = IdInputField.text;
        var playerName = PlayerNameInputField.text;

        playerManager.SpawnPlayer(id, playerName);
    }

    public void Logout()
    {
        var id = IdInputField.text;

        playerManager.DeletePlayer(id);
    }

    public void ChangeColor()
    {
        var id = IdInputField.text;

        playerManager.ChangePlayerColor(id);
    }
}
```
Twitchチャットとの連携をせずにUnityサイドのみでゲームの動作確認をするためのクラスです。
SampleSceneに配置されているUIInputオブジェクトを表示することで動作確認ができます。
中身は、TwitchInputと同様にPlayerManager.csへの入力を行うのみの内容となっています。

# あとがき
以上がプロジェクトの解説となります。
Twitchとの連携で動作するゲームという今回のプロジェクト作成で意識した点ですが、ネットワーク（Twitchチャットとの連携）からの入力と実際のゲーム部分のロジックの分離を意識して作成しました。
今回のプロジェクトでは、Twitchチャット連携を意識したクラスはTwitchInput.csのみとなっています。
他のクラスはTwitchのチャットの仕組みには全く依存していません。
ネットワークとゲームロジックの部分が密結合してしまうと入力がオンライン入力に依存してしまい、簡単なプロジェクトならいいですが、複雑化するにつれて動作確認が非常に困難となってしまいます。
そこで、ゲームとゲームに対するインプットという構造でクラスや処理を独立したものとして定義しておき、インプット方法の一つとしてTwitchチャットからの入力を受け付ける、という仕組みにすることで、オンライン入力に依存しない動作確認が可能となっています（UIInput.csからもTwitchInput.csと同様の動作確認が可能）