グーグルアドセンス

広告

UnityとPhoton Pun2で作るオンラインアクションゲーム制作その8 チャットの導入

2020年3月29日

チャットの導入手順

チャットPhoton PUNではチャットスクリプトが用意されていましたが、PUN2では用意されていないので、自作する必要があります。とはいえ、簡単にできるので、安心してください。

導入手順は以下の通りです。

  1. 空のGameObjectを作成し、ChatManagerと名付けます
  2. ChatManagerにInRoomChatスクリプトをアタッチする

ちなみにPUNのInRoomChat.csをPUN2で使えるようにするには

  • Photon.MonoBehaviourをMonoBehaviourPunCallbacksに変更
  • PhotonNetwork.inRoomをPhotonNetwork.InRoomに変更
  • photonView.RPC(“Chat”, PhotonTargets.All,inputLine);をphotonView.RPC(“Chat”, RpcTarget.All,inputLine);に変更
  • PhotonMessageInfoの要素のsenderをSenderに、IDをUserIDに変更
  • sender.ID→Sender.UserIdに変更

することでPUNのInRoomChat.csのソースコードをPUN2でも使うことができます。

InRoomChat.cs

using System.Collections.Generic;
using UnityEngine;
using System.Collections;
using Photon.Pun;
using Photon.Realtime;

[RequireComponent(typeof(PhotonView))]
public class InRoomChat : MonoBehaviourPunCallbacks
{
    public Rect GuiRect = new Rect(0, 0, 250, 300);
    public bool IsVisible = true;
    public bool AlignBottom = false;
    public List<string> messages = new List<string>();
    private string inputLine = "";
    private Vector2 scrollPos = Vector2.zero;

    public static readonly string ChatRPC = "Chat";

    public void Start()
    {
        if (this.AlignBottom)
        {
            this.GuiRect.y = Screen.height - this.GuiRect.height;
        }
    }

    public void OnGUI()
    {
        if (!this.IsVisible || !PhotonNetwork.InRoom)
        {
            return;
        }

        if (Event.current.type == EventType.KeyDown && (Event.current.keyCode == KeyCode.KeypadEnter || Event.current.keyCode == KeyCode.Return))
        {
            if (!string.IsNullOrEmpty(this.inputLine))
            {
                this.photonView.RPC("Chat", RpcTarget.All, this.inputLine);
                this.inputLine = "";
                GUI.FocusControl("");
                return; // printing the now modified list would result in an error. to avoid this, we just skip this single frame
            }
            else
            {
                GUI.FocusControl("ChatInput");
            }
        }

        GUI.SetNextControlName("");
        GUILayout.BeginArea(this.GuiRect);

        scrollPos = GUILayout.BeginScrollView(scrollPos);
        GUILayout.FlexibleSpace();
        for (int i = messages.Count - 1; i >= 0; i--)
        {
            GUILayout.Label(messages[i]);
        }
        GUILayout.EndScrollView();

        GUILayout.BeginHorizontal();
        GUI.SetNextControlName("ChatInput");
        inputLine = GUILayout.TextField(inputLine);
        if (GUILayout.Button("Send", GUILayout.ExpandWidth(false)))
        {
            this.photonView.RPC("Chat", RpcTarget.All, this.inputLine);
            this.inputLine = "";
            GUI.FocusControl("");
        }
        GUILayout.EndHorizontal();
        GUILayout.EndArea();
    }

    [PunRPC]
    public void Chat(string newLine, PhotonMessageInfo mi)
    {
        string senderName = "anonymous";

        if (mi.Sender != null)
        {
            if (!string.IsNullOrEmpty(mi.Sender.NickName))
            {
                senderName = mi.Sender.NickName;
            }
            else
            {
                senderName = "player " + mi.Sender.UserId;
            }
        }

        this.messages.Add(senderName + ": " + newLine);
    }

    public void AddLine(string newLine)
    {
        this.messages.Add(newLine);
    }
}

これで以下の様なチャット機能が実装されます。

しかしこのままだと背景色によってはチャットの文字が非常に見づらいのでInRoomChatスクリプトを改変して、簡易なチャットUIを作成しましょう。

簡易チャットUIの実装

InRoomChatを以下のように書き換えましょう。

CInRoomChat.cs (新)

using System.Collections.Generic;
using UnityEngine;
using System.Collections;
using Photon.Pun;
using Photon.Realtime;

[RequireComponent(typeof(PhotonView))]
public class CInRoomChat : MonoBehaviourPunCallbacks
{
    #region 変数宣言
    //範囲チャット実装のためのオブジェクト、変数定義
    GameObject[] players;   //全てのプレイヤーキャラ取得用
    GameObject sender;      //送信キャラ取得用
    //GameObject myPlayer;    //自分のキャラ取得用
    GUIStyle ChatStyle = new GUIStyle();    //範囲チャットStyle
    GUIStyleState ChatStyleState = new GUIStyleState();
    GUIStyle AllChatStyle = new GUIStyle(); //全体チャットStyle
    GUIStyleState AllChatStyleState = new GUIStyleState();
    public Rect GuiRect = new Rect(0, 0, 300, 200); //チャットUIの大きさ設定用
    public bool IsVisible = true;   //チャットUI表示非表示フラグ
    public bool AlignBottom = true;
    public List<string> messages = new List<string>();  //チャットログ格納用List
    public List<int> chatKind = new List<int>(); //チャットログの種類格納用(範囲チャor全チャ)
    public string inputLine = "";//入力文章格納用String
    private Vector2 scrollPos = Vector2.zero;   //スクロールバー位置
    #endregion

    public void Start()
    {
        //myPlayerオブジェクト取得(範囲チャット発言時にpositionとmyPM使う)
        //GetmyPlayer();
        //範囲チャットの場合
        ChatStyleState.textColor = Color.blue;
        //文字がUIからあふれた場合は折り返す設定
        ChatStyle.normal = ChatStyleState;
        ChatStyle.wordWrap = true;

        //全体チャットの場合
        AllChatStyleState.textColor = Color.white;
        //文字がUIからあふれた場合は折り返す設定
        AllChatStyle.normal = AllChatStyleState;
        AllChatStyle.wordWrap = true;
    }
    public void Update()
    {
        //ChatUIの位置を調整
        this.GuiRect.y = Screen.height - this.GuiRect.height;
        //ChatUIの大きさ調整
        GuiRect.width = Screen.width / 3;
        GuiRect.height = Screen.height / 3;
    }
    public void OnGUI()
    {
        if (!this.IsVisible || !PhotonNetwork.InRoom)   //表示フラグがOFFまたはphotonにつながっていないとき
        {
            //UI非表示
            return;
        }
        //ChatUIの作成開始
        //チャットUI生成 Begin&EndAreaでチャットUIの位置と大きさを設定 
        GUILayout.Window(0, GuiRect, ChatUIWindow, "");   //チャットUIウインドウを作成
                                                          //Enterを押すと
        if (Event.current.type == EventType.KeyDown && (Event.current.keyCode == KeyCode.KeypadEnter || Event.current.keyCode == KeyCode.Return))
        {
            //チャット入力待ち状態にする
            GUI.FocusControl("ChatInput");
        }
    }

    int ChatType = 0; // 0 全体チャット、1 範囲チャット
    // チャットUI生成
    void ChatUIWindow(int windowID)
    {
        //FocusがチャットUIに乗ってるときにEnterを押すとチャット発言が実行される
        if (Event.current.type == EventType.KeyDown && (Event.current.keyCode == KeyCode.KeypadEnter || Event.current.keyCode == KeyCode.Return))
        {
            if (!string.IsNullOrEmpty(this.inputLine))  //チャット入力欄がNullやEmptyでない場合
            {
                //チャット送信関数実行
                SendChat();
                return;
            }
        }
        //垂直のコントロールグループ開始
        GUILayout.BeginVertical();
        //スクロールビュー開始位置
        scrollPos = GUILayout.BeginScrollView(scrollPos);
        //チャットログ表示用フレキシブルスペース生成
        GUILayout.FlexibleSpace();
        //フレキシブルスペースにチャットログを表示
        //for (int i = 0; i <= messages.Count - 1; i++) 下から新しいログを表示したいならこちら
        for (int i = messages.Count - 1; i >= 0; i--)
        {
            switch (chatKind[i])
            {
                case 0://全チャットであれば
                    GUILayout.Label(messages[i], AllChatStyle);
                    break;
                case 1://範囲チャットであれば
                    GUILayout.Label(messages[i], ChatStyle);
                    break;
            }
        }
        //スクロールビュー終了
        GUILayout.EndScrollView();

        //水平のコントロールグループ開始
        GUILayout.BeginHorizontal();
        //入力テキストフィールド生成、Focusが乗った状態をChatInputと命名
        GUI.SetNextControlName("ChatInput");
        inputLine = GUILayout.TextField(inputLine, 200);
        string ButtounName = string.Empty;
        switch (ChatType)
        {
            case 0:
                ButtounName = "all";
                break;
            case 1:
                ButtounName = "party";
                break;
        }
        //「Send」ボタンを生成かつ押したときにはチャット送信
        if (GUILayout.Button(ButtounName, GUILayout.ExpandWidth(false)))
        {
            //チャット送信関数実行
            SendChat();
        }
        //チャットの種類を切り替える
        if (GUILayout.Button("Change", GUILayout.ExpandWidth(false)))
        {
            ChatType = (ChatType + 1) % 2;
        }
        //水平のコントロールグループ終了
        GUILayout.EndHorizontal();
        //垂直のコントロールグループ終了
        GUILayout.EndVertical();
    }

    // チャット送信関数
    void SendChat()
    {
        //全体チャットに変更
        if (inputLine == "/s")
        {
            ChatType = 0;
            inputLine = "";
            return;
        }
        //パーティーチャットに変更
        else if (inputLine == "/p")
        {
            ChatType = 1;
            inputLine = "";
            return;
        }//ささやきチャットに変更
        else if (inputLine == "/w")
        {
            //ChatType = 2;
        }

        //chatRPC
        photonView.RPC("Chat", RpcTarget.All,
            //myPlayer.transform.position, GetmyPlayer関数を使う時にはここもコメントアウトを解除すること
            inputLine, ChatType);
        //送信後、入力欄を空にし、スクロール最下位置に移動
        inputLine = "";
    }

    // ChatRPC RPC呼出側:送信者 RPC受信側:受信者
    [PunRPC]
    public void Chat(
        //Vector3 senderposition,
        string newLine, int chat_type, PhotonMessageInfo mi)
    {
        if (messages.Count >= 100)          //チャットログが多くなって来たらログを削除してから受信
        {
            messages.Clear();               //全てのチャットログを削除
            chatKind.Clear();               //全てのチャットの種類情報削除
        }
        switch (chat_type)
        {
            case 0://全チャとして受信
                   //chat受信
                ReceiveChat(newLine, chat_type, mi);
                break;
            case 1://範囲チャとして受信
                   //myPlayerとsenderの距離から受信するか判断
                   //if (Vector3.Distance(myPlayer.transform.position, senderposition) < 10)
                {
                    //chat受信
                    ReceiveChat(newLine, chat_type, mi);
                }
                break;
        }

        //受信したときはスクロール最下位置
        //scrollPos.y = Mathf.Infinity;
    }
    // チャット受信関数
    void ReceiveChat(string _newLine, int chat_type, PhotonMessageInfo _mi)
    {
        //送信者の名前用変数
        string senderName = "anonymous";
        if (_mi.Sender != null)
        {
            //送信者の名前があれば
            if (!string.IsNullOrEmpty(_mi.Sender.NickName))
            {
                senderName = _mi.Sender.NickName;
            }
            else
            {
                senderName = "player " + _mi.Sender.UserId;
            }
        }
        //受信したチャットをログに追加
        this.messages.Add(senderName + ": " + _newLine);
        this.chatKind.Add(chat_type);
        return;
    }

    public void AddLine(string newLine)
    {
        this.messages.Add(newLine);
    }
}

書き換え後のChatUI構造

プログラムソースコードだけではどのようにUI構造を作成しているか分かりにくいかもしれないので以下の画像を使って少し説明します。

赤色で囲んだ部分がチャットUIウインドウの垂直コントロールグループになります。つまりUIの構造要素を順番に縦に並べるグループとなります。

青色で囲んだ部分がチャットログ表示するためのスクロールビューとフレキシブルスペースとなります。ここに受信したチャットの内容が流れます。

黄色で囲んだ部分が水平のコントロールグループでUIの構造要素を順番に横に並べます。ここでは入力フィールドと範囲・全体チャットのボタンを横に並べています。

照らし合わせながら、順番を変えて表示していただくと、より構造がわかると思いますのでやってみてください。

プログラムソースコードだけではどのようにUI構造を作成しているか分かりにくいかもしれないので以下の画像を使って少し説明します。

赤色で囲んだ部分がチャットUIウインドウの垂直コントロールグループになります。つまりUIの構造要素を順番に縦に並べるグループとなります。

青色で囲んだ部分がチャットログ表示するためのスクロールビューとフレキシブルスペースとなります。ここに受信したチャットの内容が流れます。

黄色で囲んだ部分が水平のコントロールグループでUIの構造要素を順番に横に並べます。ここでは入力フィールドと範囲・全体チャットのボタンを横に並べています。

照らし合わせながら、順番を変えて表示していただくと、より構造がわかると思いますのでやってみてください。

実行結果

準備中

チャットのシステムがうまく機能していることがわかります。

ちなみに青い文字のチャットログが近くにいる人にしか受信されない範囲チャットで、白文字は同じ部屋にいる全ての人に受信される全体チャットです。

バグが発生する時の注意点

この回で私自身が発生したバグで、In Room ChatスクリプトのGetmyPlayerで呼び出しはされてるのに、プレイヤーのオブジェクトが取得できないというものがありました。

In Room ChatスクリプトがGameManagerScriptより先に実行されてるのが原因でした。

GameManagerScript→ In Room Chatの順で呼ばれるようにすれば、解決します。詳しくは以下を参照。