private readonly List<CanvasGroup> m_CanvasGroupCache = new List<CanvasGroup>();
protected override void OnCanvasGroupChanged()
{
//判断父节点中是否允许交互
var groupAllowInteraction = true;
Transform t = transform;
while (t != null)
{
t.GetComponents(m_CanvasGroupCache);
bool shouldBreak = false;
for (var i = 0; i < m_CanvasGroupCache.Count; i++)
{
//如果父节点中的canvasgroup不允许交互,那么断开循环,设置false
if (!m_CanvasGroupCache[i].interactable)
{
groupAllowInteraction = false;
shouldBreak = true;
}
// 如果此节点设置了“忽略父设置”,那么也直接断开
if (m_CanvasGroupCache[i].ignoreParentGroups)
shouldBreak = true;
}
if (shouldBreak)
break;
t = t.parent;
}
if (groupAllowInteraction != m_GroupsAllowInteraction)
{
m_GroupsAllowInteraction = groupAllowInteraction;
OnSetProperty();
}
}
在canvasGroup属性改变时调用OnCanvasGroupChanged
public virtual bool IsInteractable()
{
return m_GroupsAllowInteraction && m_Interactable;
}
物体的可交互属性需要一起判断m_GroupsAllowInteraction 和 m_Interactable,即自身或父物体上的canvasGroup是否允许交互,以及自身是否允许交互
protected override void OnEnable()
{
base.OnEnable();
if (s_IsDirty)
RemoveInvalidSelectables(); //如果标记为脏,就从可选择物体数组中移除
m_WillRemove = false;
if (s_SelectableCount == s_Selectables.Length) //如果可选择物体数组满了,则扩容两倍
{
Selectable[] temp = new Selectable[s_Selectables.Length * 2];
Array.Copy(s_Selectables, temp, s_Selectables.Length);
s_Selectables = temp;
}
s_Selectables[s_SelectableCount++] = this;
isPointerDown = false;
DoStateTransition(currentSelectionState, true); //设置初始状态
}
protected override void OnTransformParentChanged()
{
base.OnTransformParentChanged();
// 如果父物体有改变,需要判断自身和父物体的canvasGroup属性是否有改变
OnCanvasGroupChanged();
}
private void OnSetProperty()
{
#if UNITY_EDITOR
if (!Application.isPlaying)
DoStateTransition(currentSelectionState, true);
else
#endif
DoStateTransition(currentSelectionState, false);
}
设置状态属性,如果是Editor模式,如果正在运行,那么立即改变,没有过渡,如果非Editor模式,那么有过渡
protected override void OnDisable()
{
m_WillRemove = true;
s_IsDirty = true;
InstantClearState(); //重置各种状态
base.OnDisable();
}
取消激活时,将自身标记为脏
private static void RemoveInvalidSelectables()
{
for (int i = s_SelectableCount - 1; i >= 0; --i)
{
// Swap last element in array with element to be removed
if (s_Selectables[i] == null || s_Selectables[i].m_WillRemove)
s_Selectables[i] = s_Selectables[--s_SelectableCount];
}
s_IsDirty = false;
}
将元素从可选择物体数组列表中移除,同时标记为脏
protected SelectionState currentSelectionState
{
get
{
if (!IsInteractable())
return SelectionState.Disabled;
if (isPointerDown)
return SelectionState.Pressed;
if (hasSelection)
return SelectionState.Selected;
if (isPointerInside)
return SelectionState.Highlighted;
return SelectionState.Normal;
}
}
获取当前的选择状态
protected virtual void InstantClearState()
{
string triggerName = m_AnimationTriggers.normalTrigger;
isPointerInside = false;
isPointerDown = false;
hasSelection = false;
switch (m_Transition)
{
case Transition.ColorTint:
StartColorTween(Color.white, true);
break;
case Transition.SpriteSwap:
DoSpriteSwap(null);
break;
case Transition.Animation:
TriggerAnimation(triggerName);
break;
}
}
即时清除状态,将Selectable的状态还原
protected virtual void DoStateTransition(SelectionState state, bool instant)
{
if (!gameObject.activeInHierarchy)
return;
Color tintColor;
Sprite transitionSprite;
string triggerName;
switch (state)
{
case SelectionState.Normal:
tintColor = m_Colors.normalColor;
transitionSprite = null;
triggerName = m_AnimationTriggers.normalTrigger;
break;
case SelectionState.Highlighted:
tintColor = m_Colors.highlightedColor;
transitionSprite = m_SpriteState.highlightedSprite;
triggerName = m_AnimationTriggers.highlightedTrigger;
break;
case SelectionState.Pressed:
tintColor = m_Colors.pressedColor;
transitionSprite = m_SpriteState.pressedSprite;
triggerName = m_AnimationTriggers.pressedTrigger;
break;
case SelectionState.Selected:
tintColor = m_Colors.selectedColor;
transitionSprite = m_SpriteState.selectedSprite;
triggerName = m_AnimationTriggers.selectedTrigger;
break;
case SelectionState.Disabled:
tintColor = m_Colors.disabledColor;
transitionSprite = m_SpriteState.disabledSprite;
triggerName = m_AnimationTriggers.disabledTrigger;
break;
default:
tintColor = Color.black;
transitionSprite = null;
triggerName = string.Empty;
break;
}
switch (m_Transition)
{
case Transition.ColorTint:
StartColorTween(tintColor * m_Colors.colorMultiplier, instant);
break;
case Transition.SpriteSwap:
DoSpriteSwap(transitionSprite);
break;
case Transition.Animation:
TriggerAnimation(triggerName);
break;
}
}
状态切换方法
public Selectable FindSelectable(Vector3 dir)
{
dir = dir.normalized;
Vector3 localDir = Quaternion.Inverse(transform.rotation) * dir;
Vector3 pos = transform.TransformPoint(GetPointOnRectEdge(transform as RectTransform, localDir));
float maxScore = Mathf.NegativeInfinity;
Selectable bestPick = null;
if (s_IsDirty)
RemoveInvalidSelectables();
for (int i = 0; i < s_SelectableCount; ++i)
{
Selectable sel = s_Selectables[i];
if (sel == this)
continue;
if (!sel.IsInteractable() || sel.navigation.mode == Navigation.Mode.None)
continue;
#if UNITY_EDITOR
除了运行时使用,FindSelectable被自定义编辑器用来在不同的可选项之间绘制箭头。对于场景视图摄像机,只考虑同一阶段的可选对象。
if (Camera.current != null && !UnityEditor.SceneManagement.StageUtility.IsGameObjectRenderedByCamera(sel.gameObject, Camera.current))
continue;
#endif
var selRect = sel.transform as RectTransform;
Vector3 selCenter = selRect != null ? (Vector3)selRect.rect.center : Vector3.zero;
Vector3 myVector = sel.transform.TransformPoint(selCenter) - pos;
//值就是沿着这个方向的距离
float dot = Vector3.Dot(dir, myVector);
//跳过方向错误或距离为0的元素这也确保了下面的评分公式不会有除数为零的误差
if (dot <= 0)
continue;
// This scoring function has two priorities:
// - Score higher for positions that are closer.
// - Score higher for positions that are located in the right direction.
// This scoring function combines both of these criteria.
// It can be seen as this:
// Dot (dir, myVector.normalized) / myVector.magnitude
// The first part equals 1 if the direction of myVector is the same as dir, and 0 if it's orthogonal.
// The second part scores lower the greater the distance is by dividing by the distance.
// The formula below is equivalent but more optimized.
//
// If a given score is chosen, the positions that evaluate to that score will form a circle
// that touches pos and whose center is located along dir. A way to visualize the resulting functionality is this:
// From the position pos, blow up a circular balloon so it grows in the direction of dir.
// The first Selectable whose center the circular balloon touches is the one that's chosen.
float score = dot / myVector.sqrMagnitude;
if (score > maxScore)
{
maxScore = score;
bestPick = sel;
}
}
return bestPick;
}
此方法用来寻找临近的可选择对象
public virtual void OnMove(AxisEventData eventData)
{
switch (eventData.moveDir)
{
case MoveDirection.Right:
Navigate(eventData, FindSelectableOnRight());
break;
case MoveDirection.Up:
Navigate(eventData, FindSelectableOnUp());
break;
case MoveDirection.Left:
Navigate(eventData, FindSelectableOnLeft());
break;
case MoveDirection.Down:
Navigate(eventData, FindSelectableOnDown());
break;
}
}
当焦点移动到另一个可选择对象时,调用此方法,确定选择的对象
protected bool IsHighlighted()
{
if (!IsActive() || !IsInteractable())
return false;
return isPointerInside && !isPointerDown && !hasSelection;
}
判断是否高亮,即鼠标移上去的时候,需判断三个状态,鼠标在UI范围内,没有点下,没有选择
protected bool IsPressed()
{
if (!IsActive() || !IsInteractable())
return false;
return isPointerDown;
}
是否按下
private void EvaluateAndTransitionToSelectionState()
{
if (!IsActive() || !IsInteractable())
return;
DoStateTransition(currentSelectionState, false);
}
执行状态改变
public virtual void OnPointerDown(PointerEventData eventData)
{
if (eventData.button != PointerEventData.InputButton.Left) //如果不是鼠标左键,return
return;
//如果可以交互,导航模式不为空,且EventSystem存在,那么设置EventSystem的当前选择物体
if (IsInteractable() && navigation.mode != Navigation.Mode.None && EventSystem.current != null)
EventSystem.current.SetSelectedGameObject(gameObject, eventData);
isPointerDown = true;
EvaluateAndTransitionToSelectionState();
}
鼠标按下回调
public virtual void OnPointerUp(PointerEventData eventData)
{
if (eventData.button != PointerEventData.InputButton.Left)
return;
isPointerDown = false;
EvaluateAndTransitionToSelectionState();
}
鼠标抬起回调
public virtual void OnPointerEnter(PointerEventData eventData)
{
isPointerInside = true;
EvaluateAndTransitionToSelectionState();
}
鼠标进入回调
public virtual void OnPointerExit(PointerEventData eventData)
{
isPointerInside = false;
EvaluateAndTransitionToSelectionState();
}
鼠标离开回调
public virtual void OnSelect(BaseEventData eventData)
{
hasSelection = true;
EvaluateAndTransitionToSelectionState();
}
选择回调
public virtual void OnDeselect(BaseEventData eventData)
{
hasSelection = false;
EvaluateAndTransitionToSelectionState();
}
取消选择回调
public virtual void Select()
{
if (EventSystem.current == null || EventSystem.current.alreadySelecting)
return;
EventSystem.current.SetSelectedGameObject(gameObject);
}
直接确定EventSystem的选择物体