//tips
//Dictionaryクラス
Dictionaryクラスの使い方を簡単なスクリプトを使用することで理解していく。Listのように複数の値を扱える点は同じだが、値ごとにキーを設定してアクセスすることができる機能は特有のものとなる。
foreach文を使うことで要素全てにアクセスする方法がよく用いられる。
まずは簡単なDictionary型を作成する。
using System;
using System.Collections.Generic;
using UnityEngine;
class Test1 : MonoBehaviour
{
void Start()
{
var colors = new Dictionary<string, string>()
{ {"a","赤"},
{"b","黄"},
{"c","青"}
};
foreach (var info in colors)
{
Debug.Log(info.Key);// a b c
Debug.Log(info.Value);// 赤 黄 青
}
}
}
これを実行するとコンソールへのアウトプットは
a
赤
b
黄
c
青
となる。
次にstaticも絡めて下記のようなコードに変更する。
using System;
using System.Collections.Generic;
class Test1
{
static void Main()
{
var colors = new Dictionary<string, string>()
{ {"a","赤"},
{"b","黄"},
{"c","青"}
};
foreach (var a in colors)
{
Console.WriteLine(a.Key);// a b c
Console.WriteLine(a.Value);// 赤 黄 青
}
}
}
こうすると何もコンソールに表示されない。
問題点は
・Unity Engineを使用していないこと
・MonoBehaviourが継承されていないこと
・class・メソッド前にpublicがなく他から参照されないこと
・staticで書かれたスクリプト単体ではアクションすることができないこと
が挙げられる。
下記の二つのコードに分けることで、static以下のメソッドを実行することができる。
using System;
using System.Collections.Generic;
using UnityEngine;
public class Test1 //: MonoBehaviour
{
public static void Colors()
{
var colors = new Dictionary<string, string>()
{ {"a","赤"},
{"b","黄"},
{"c","青"}
};
foreach (var info in colors)
{
Debug.Log(info.Key);// a b c
Debug.Log(info.Value);// 赤 黄 青
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TestO : MonoBehaviour
{
void Start()
{
Test1.Colors();
}
}
次にDictionary型の値に関数を設置してみる。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Test2 : MonoBehaviour
{
void Start()
{
// 値の型は自前のクラスであるParameter型
Dictionary<string, Parameter> charas = new Dictionary<string, Parameter>();
// キーに値をセット
charas["Ethan"] = new Parameter(150, 50);
charas["Sai"] = new Parameter();
// キャラのデータを出力
foreach (var key in charas.Keys)
{
Debug.Log(key + " : " + charas[key].hp + " : " + charas[key].attackPower);
}
}
private class Parameter
{
//new Parameter();とした場合のデフォルト値の設定
public int hp;
public int attackPower;
public Parameter(int hp = 20, int attackPower = 10)
{
this.hp = hp;
this.attackPower = attackPower;
}
}
}
こうすると
Ethan : 150 : 50
Sai : 20 : 10
というアウトプットが得られる。
事前にDictionary<string, Parameter> charas = new Dictionary<string, Parameter>();のように型の中身を説明しておけば、宣言したDirectory型の変数の後ろ[]にkeyを入れてイコール=で値とつなぐことでkey-valueを一度に設定できる。
private Dictionary<string, InputfieldButton> activeEntries = new Dictionary<string, InputfieldButton>();
Dictionary型で宣言されたactiveEntriesについて考えていく。
宣言された段階ではリストは保持していないので、コードでList<RoomInfo> roomListの情報をベースにしてDictionary型に変換していく形となる。
まず、activeEntriesへの項目追加は
activeEntries.Add(info.Name, entry);
で行われる。
これは、activeEntriesに何も含まれていない初期の段階で、activeEntries.TryGetValue(info.Name, out entry)は見つからないため、else ifの処理内容の中に含まれている。
ここでkeyに名前、valueにentry(RoomListEntryクラス or InputfieldButtonクラス)をリストとして蓄えている。
OnRoomListUpdateが呼ばれた際には、ここで蓄えた情報をもとにkeyで検索をかけ、valueとなるボタンentryを抽出し、playerCounterなどの情報更新を行う。
このvalueとして吐き出されるentryは、ヒエラルキー上で実体化したボタンのように思われる。
では、一体いつこのクラスにnewがつけられてインスタンス化されていたかと探すも見つからないので、[SerializeField]にPrefabがアタッチされていることに何か手がかりがあるのかもしれないと考える。
すると、最後の箇所にinactiveEntries.Count > 0の条件が偽の場合、Instantiate(roomListEntryPrefab, scrollRect.content);が実施されるように書かれている。
このスクリプトでelse if の部分が初期時点で実行されることになるので、ルームがあるが、ボタンが一つもない場合、entryに生成されたprefabが代入されることになっている。
これによりインスタンス化されたボタンがkeyにより抽出できるようになる。entry.Activate(info);でボタンに情報が付与されているため探しやすくなり、Directory型リストにもactiveEntries.Add(info.Name, entry);として、名前とインスタンス情報が登録される。
entryインスタンスは今後続々と生まれるため見分けがつかなくなり情報が錯乱しそうと考えてしまったが、そもそもの情報がroomlistとして、photonが保持しているので、各entryインスタンスに名前さえついていれば、瞬時にphotonが保有するroomlist情報をもとに更新できるので、これで問題なさそうである。
public override void OnRoomListUpdate(List<RoomInfo> roomList)
{
foreach (var info in roomList)
{
RoomListEntry entry;
if (activeEntries.TryGetValue(info.Name, out entry))
{
if (!info.RemovedFromList)
{
// リスト要素を更新する
entry.Activate(info);
}
else
{
// リスト要素を削除する
activeEntries.Remove(info.Name);
entry.Deactivate();
inactiveEntries.Push(entry);
}
}
else if (!info.RemovedFromList)
{
// リスト要素を追加する
entry = (inactiveEntries.Count > 0)
? inactiveEntries.Pop().SetAsLastSibling()
: Instantiate(roomListEntryPrefab, scrollRect.content);
entry.Activate(info);
activeEntries.Add(info.Name, entry);
}
}
//Stackの使用
配列に入れて順番に処理したい場合にstackを使用する。Pushでデータをリストに加え、Popで取り出す。これにより管理リストを作る。
簡単な例を下記に記載する。
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class StackTest : MonoBehaviour
{
void Start()
{
Stack<string> stack = new Stack<string>() { }; //管理リストの作成
//Push でデータを入れる
stack.Push("ko");
stack.Push("hi");
stack.Push("runa");
stack.Push("ma");
Debug.Log("stack.Count:" + stack.Count); //stack.Count:4
foreach (string name in stack)
{
Debug.Log("name:" + name); //最後に入れたname:maから順に表示
}
while (stack.Count > 0)
{
//Popで後から入れたものを取り出す
Debug.Log("stack.Pop():" + stack.Pop()); //最後に入れたmaから順に取り出し
}
Debug.Log("stack.Count:" + stack.Count); //stack.Count:0の表示
}
}
管理リストを作成するためどこでPushしているか、Popしているか元のコードで確認していく。
private Stack<RoomListEntry> inactiveEntries = new Stack<RoomListEntry>();
このStackによる管理リストは削除項目の管理で登場しており、roominfoとインスタンスボタンとの橋渡しをするDirectoryリストから削除され、ボタンも非表示となった段階で、不要な項目リストとして、inactiveEntries.Push(entry);が行われる。
なぜここで不要な項目リストが必要なのかというと、Instantiateを無限に行い続けるのではなく、削除ボタンとして一旦非表示にしたものを貯めておいて、次の機会にリサイクルしてボタンを活用するという意味でinactiveEntries.Pop().SetAsLastSibling()の箇所が追加されている。
これにより負荷の削減ができるのだ。
activeEntries.Remove(info.Name);
entry.Deactivate();
inactiveEntries.Push(entry);
entry = (inactiveEntries.Count > 0)
? inactiveEntries.Pop().SetAsLastSibling()
ここからは理解できたDirectoryとStackの内容をもとに、ロビーボタンクリックからの同期・遷移を正常に行われるように修正していく。