ChatChatChat ~シンプルなチャットを作る~
UnityのWebGL出力に簡単に無料でグローバルランキングを実装できる仕組みを考えてみた の続きになります。
まず、 https://github.com/divide-by-zero/GSSA/ をチェックアウトし、
projects/Assets/Samples/Empty/Sample1_ChatChatChat/ChatChatChat.unity を開きます。
※こちら、ある程度作ってあるけれど重要なところが空のプロジェクトです。 完成品はCompleteフォルダ配下にあるので、チマチマと講座なんてみるより完成品見た方が早い! というかたはそちらを見てくれればいいと思います。
0.下準備
- 元記事にもあったように、GoogleSpreadSheet側の準備と、WebアプリケーションとしてのURL取得、SpreadSheetSettingsのinspectorへのURLのセットが済んでいる事を前提に進めていきます。
初期のソースコードは
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using GSSA;
using UnityEngine;
using UnityEngine.UI;
public class ChatChatChat : MonoBehaviour
{
[SerializeField] private Text logText;
[SerializeField] private InputField nameInputField;
[SerializeField] private InputField messageInputField;
void Start ()
{
messageInputField.onEndEdit.AddListener(SendChatMessage);
StartCoroutine(GetChatLogIterator());
}
private void SendChatMessage(string s)
{
}
private IEnumerator GetChatLogIterator()
{
while (true)
{
yield return new WaitForSeconds(5.0f);
}
}
}
徐々に処理を追加していきます。
1.メッセージでエンターを押したらGSSA経由で保存する
NameのInputFieldとMessageのInputFieldは用意してありonEndEdit
にAddListener
でSendChatMessage
を呼ぶところまではできています。 が、中身が空なので、何もできていない状態です。
ここで、onEndEdit=Enterキー押下 のたびに名前とメッセージをSpreadSheetに登録するよう処理を書きます。
private void SendChatMessage(string s)
{
var so = new SpreadSheetObject("Chat");//Chatシート用のSpreadSheetObject作成
so["name"] = nameInputField.text;//"name"列に名前用InputFieldのtextをセット
so["message"] = s;//"message"列にはメッセージ用InputFieldのtext = onEndEditで渡される文字列(s)をセット
so.SaveAsync();//SpreadSheetへ保存
messageInputField.text = "";//サーバーに送信したので、手元の文字はクリア
}
この時点で名前を入れて、メッセージを入力してエンターを押すたびに、サーバに1行データが保存されるようになるはずです。
<EX>
2.一定時間間隔で、ログ取得する
すでにサーバーにはデータがたまっているので、たまったデータを取得して、画面に表示すればチャットの大枠の出来上がりです。
上の何もない空間がTextField(logText)
なので、ここにサーバーから取得したログを表示いれてあげます。
private IEnumerator GetChatLogIterator()
{
while (true)
{
var query = new SpreadSheetQuery("Chat");//Chatシート用のQueryオブジェクト作成
query.OrderByDescending("createTime").Limit(20);//生成日時の大きい順にソートして、最新20件を取得
yield return query.FindAsync();//取得処理(yield returnで、サーバーから返却されるまで待機する)
logText.text = "";
foreach (var so in query.Result.Reverse())//取得できた結果をReverseで逆転(↑で生成日時の新しい順でソートしてしまっているので)
{
logText.text += so["name"] + ">" + so["message"] + "\n";
}
yield return new WaitForSeconds(5.0f);//5秒待ってあげてから、またサーバーからデータ取得
}
}
キモは、queryにOrderByDescendingでcreateTimeを指定して、生成日時の大きい=新しいデータ順にしているところ。
+Limitに20を指定して、最新データ20だけに抑えているところです。
これをしないと、表示しきれないほどのデータを毎回サーバーから取得しようとするので、通信量がなかなかな事になりかねません。
しかし、これでもまだ無駄が多いです。
SpreadSheetSettingのIs Debug Log Outputにチェックを入れて、通信ログを取得するとわかるのですが
常に最新データ上20件を取得するので、まったくデータが更新されていなくても同じデータが5秒に1回来てしまいます。
これは・・・あかんですよ・・・。
3.既に取得しているログはいらない
そんなわけで、↑これに尽きるんですが、どうやってすでに取得したものかどうかを判断するか。
今回の例だと、自動で生成されるcreateTimeを保持しておいて、Queryオブジェクトに条件を追加すればよさそうです。
最後に取得した時間(long)を取っておくための変数
private long lastGetTime;
をフィールド変数として用意し、
Queryオブジェクトに、保持しているlastGetTimeよりも新しいデータだけほしいというWhereを追加してあげます。
query.OrderByDescending("createTime").Limit(20);
→query.OrderByDescending("createTime").Where("createTime",">",lastGetTime).Limit(20);
加えて、この変更によって、Queryで返却される件数が0の場合があります(データ更新が無い場合、lastGetTimeよりも新しいデータが無いため)
そのためのif追加や、Logを継ぎ足していくだけではなく、表示しきれない範囲分は消していかなくてはいけないため、いきなりlogTextにログを追加するのではなく、Listの形で表示分の文字列リストを作成してからlogTextにセットするようにします。
private List<string> chatLogList = new List<string>();
private long lastGetTime;
private IEnumerator GetChatLogIterator()
{
while (true)
{
var query = new SpreadSheetQuery("Chat");
query.OrderByDescending("createTime").Where("createTime",">",lastGetTime).Limit(20);//ローカルで保持していない最新データ上限20件に絞る
yield return query.FindAsync();
if (query.Count > 0)//有効なデータが無い場合は何もしない
{
foreach (var so in query.Result.Reverse())
{
chatLogList.Add(so["name"] + ">" + so["message"]);//いきなりlogTextに入れるのではなく、一旦Listに追加
if (chatLogList.Count > 17) chatLogList.RemoveAt(0);//画面に収まりきらない部分(17以上)からは古いデータを削除
}
logText.text = string.Join("\n", chatLogList.ToArray());//Listを改行でjoinしてlogTextにセット
lastGetTime = (long)query.Result.First()["createTime"];//createTimeの新しい順に並んでいるので、先頭(First)のcreateTimeを保持しておく。
}
yield return new WaitForSeconds(5.0f);
}
}
※要注意!:createTimeは実際にはlongでデータが入っていますが、SpreadSheetObject上ではobject型なので、よしなにキャストしてあげる必要があります
この修正により、ローカルにあるデータは来なくなるので、通信量の削減につながります。
最後に
たかが、チャット。 しかも5秒に1回しか更新しない、プル型のチャットですが、いろいろな基本が詰まっており、これが出来れば大体のことができるんじゃないかと思ってます。
次回は、実際にゲームにランキング機能を付けて行きたいと思います。