IndexedSet は UGUI の内部クラスで、同じオブジェクトが存在しないテーブルで、必要に応じてプロジェクトに直接コピーできます。
主に次のコードを見てください。
readonly List<T> m_List = new List<T>();
Dictionary<T, int> m_Dictionary = new Dictionary<T, int>();
public void Add(T item)
{
if (m_Dictionary.ContainsKey(item))
return;
m_List.Add(item);
m_Dictionary.Add(item, m_List.Count - 1);
}
実際には、追加の m_Dictionary が維持され、item がキーとして使用され、item が値として m_List の添字として使用されます。
Add メソッドは非常に簡単ですが、Remove メソッドは少し面倒です。
public bool Remove(T item)
{
int index = -1;
if (!m_Dictionary.TryGetValue(item, out index))
return false;
RemoveAt(index);
return true;
}
RemoveAt:
public void RemoveAt(int index)
{
T item = m_List[index];
m_Dictionary.Remove(item);
if (index == m_List.Count - 1)
m_List.RemoveAt(index);
else
{
int replaceItemIndex = m_List.Count - 1;
T replaceItem = m_List[replaceItemIndex];
m_List[index] = replaceItem;
m_Dictionary[replaceItem] = index;
m_List.RemoveAt(replaceItemIndex);
}
}
それが最後の項目でない場合は、最後の項目を削除された位置に移動します。ここで、IndexedSet が順序付けされている場合、オブジェクトを削除した後に再順序付けが必要になる場合があることに注意してください。
並べ替え:
//Sorts the internal list, this makes the exposed index accessor sorted as well.
//But note that any insertion or deletion, can unorder the collection again.
public void Sort(Comparison<T> sortLayoutFunction)
{
//There might be better ways to sort and keep the dictionary index up to date.
m_List.Sort(sortLayoutFunction);
//Rebuild the dictionary index.
for (int i = 0; i < m_List.Count; ++i)
{
T item = m_List[i];
m_Dictionary[item] = i;
}
}
m_List をソートすると、m_Dictionary に保存されている添字も変更されます。
ObjectPool は、解放されたオブジェクトをリサイクルしてメモリを節約するために使用されるオブジェクト プールです。
方法は非常に簡単です。
internal class ObjectPool<T> where T : new()
{
private readonly Stack<T> m_Stack = new Stack<T>();
private readonly UnityAction<T> m_ActionOnGet;
private readonly UnityAction<T> m_ActionOnRelease;
public int countAll { get; private set; }
public int countActive { get { return countAll - countInactive; } }
public int countInactive { get { return m_Stack.Count; } }
public ObjectPool(UnityAction<T> actionOnGet, UnityAction<T> actionOnRelease)
{
m_ActionOnGet = actionOnGet;
m_ActionOnRelease = actionOnRelease;
}
public T Get()
{
T element;
if (m_Stack.Count == 0)
{
element = new T();
countAll++;
}
else
{
element = m_Stack.Pop();
}
if (m_ActionOnGet != null)
m_ActionOnGet(element);
return element;
}
public void Release(T element)
{
if (m_Stack.Count > 0 && ReferenceEquals(m_Stack.Peek(), element))
Debug.LogError("Internal error. Trying to destroy object that is already released to pool.");
if (m_ActionOnRelease != null)
m_ActionOnRelease(element);
m_Stack.Push(element);
}
}
取得する際、スタックになければ作成し、あれば戻り値として取り出します。リリースするときは、オブジェクトをスタックに置きます。
ListPool は ObjectPool の特別なアプリケーションであり、リリースされた List を再利用するために UGUI でよく使用されます。
成し遂げる:
internal static class ListPool<T>
{
// Object pool to avoid allocations.
private static readonly ObjectPool<List<T>> s_ListPool = new ObjectPool<List<T>>(null, l => l.Clear());
public static List<T> Get()
{
return s_ListPool.Get();
}
public static void Release(List<T> toRelease)
{
s_ListPool.Release(toRelease);
}
}
これは静的ジェネリック クラスであるため、タイプごとに s_ListPool が 1 つだけ存在します。
s_ListPool は List<T> タイプの ObjectPool であり、Release 中に lamda コールバックが追加されます。つまり、List がクリアされます。
CoroutineTween は、ITweenValue インターフェイス、ITweenValue から継承した 2 つの構造 ColorTween と FloatTween、および ITweenValue をジェネリック型とした TweenRunner を含む名前空間 (名前空間 UnityEngine.UI.CoroutineTween) です。このクラスはそれぞれ Graphic で使用されます。色と透明度のグラデーション効果を実現します。
public void StartTween(T info)
{
if (m_CoroutineContainer == null)
{
Debug.LogWarning("Coroutine container not configured... did you forget to call Init?");
return;
}
if (m_Tween != null)
{
m_CoroutineContainer.StopCoroutine(m_Tween);
m_Tween = null;
}
if (!m_CoroutineContainer.gameObject.activeInHierarchy)
{
info.TweenValue(1.0f);
return;
}
m_Tween = Start(info);
m_CoroutineContainer.StartCoroutine(m_Tween);
}
使用m_CoroutineContainer(Init时传入的组件)的协程执行Start方法。
private static IEnumerator Start(T tweenInfo)
{
if (!tweenInfo.ValidTarget())
yield break;
var elapsedTime = 0.0f;
while (elapsedTime < tweenInfo.duration)
{
elapsedTime += tweenInfo.ignoreTimeScale ? Time.unscaledDeltaTime : Time.deltaTime;
var percentage = Mathf.Clamp01(elapsedTime / tweenInfo.duration);
tweenInfo.TweenValue(percentage);
yield return null;
}
tweenInfo.TweenValue(1.0f);
}
経過時間 (elapsedTime) を tweenInfo の期間 (duration) で割った値に基づいてパーセンテージを取得し、TweenValue メソッドを呼び出します。
ColorTween の TweenValue メソッド:
public void TweenValue(float floatPercentage)
{
if (!ValidTarget())
return;
var newColor = Color.Lerp(m_StartColor, m_TargetColor, floatPercentage);
if (m_TweenMode == ColorTweenMode.Alpha)
{
newColor.r = m_StartColor.r;
newColor.g = m_StartColor.g;
newColor.b = m_StartColor.b;
}
else if (m_TweenMode == ColorTweenMode.RGB)
{
newColor.a = m_StartColor.a;
}
m_Target.Invoke(newColor);
}
入力パーセンテージに基づいてカラー値またはアルファを計算し、新しいカラー値をリッスン メソッドに渡すイベントを送信します。
FloatTween の TweenValue メソッド:
public void TweenValue(float floatPercentage)
{
if (!ValidTarget())
return;
var newValue = Mathf.Lerp(m_StartValue, m_TargetValue, floatPercentage);
m_Target.Invoke(newValue);
}
これ以上言う必要はないようです。
上記は非常に単純なクラスであり、実際の開発では必ずしも使用できるとは限りません (たとえば、DOTween は CoroutineTween のより完全な実装です) が、参考として学習する価値はあります。