IndexedSet是UGUI的内部类(internal class),是一个不会存在相同对象的表,如果有需要的话可以直接复制一份到工程里。
主要看这些代码:
readonly List m_List = new List();
Dictionary m_Dictionary = new Dictionary();
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做key,item在m_List的下标做value。
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);
}
}
如果不是最后一个,把最后一个item移动到被移除的位置。这里需要注意,如果你的IndexedSet是有序的,在移除对象的之后,就有可能要重新排序了。
Sort:
//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 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 where T : new()
{
private readonly Stack m_Stack = new Stack();
private readonly UnityAction m_ActionOnGet;
private readonly UnityAction 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 actionOnGet, UnityAction 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);
}
}
Get时,stack里面没有就创建,有就pop一个出来作为返回值。Release时,把对象放入stack。
ListPool是ObjectPool的一个特殊应用,在UGUI里经常被用到,用于回收被释放的List。
实现:
internal static class ListPool
{
// Object pool to avoid allocations.
private static readonly ObjectPool> s_ListPool = new ObjectPool>(null, l => l.Clear());
public static List Get()
{
return s_ListPool.Get();
}
public static void Release(List toRelease)
{
s_ListPool.Release(toRelease);
}
}
因为是一个静态泛型类,所以对于每个类型只有一个s_ListPool。
s_ListPool是一个类型为List
CoroutineTween是一个命名空间(namespace UnityEngine.UI.CoroutineTween),其中有ITweenValue接口,继承自ITweenValue的ColorTween和FloatTween两个结构,以及以ITweenValue为泛型的TweenRunner,这个类在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);
}
根据输入的百分比就算出色彩值或Alpha,并发送事件将新的色彩值传给监听方法。
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功能更完善的实现),但是作为参考也是值得学习一下的。