public Color normalColor { get { return m_NormalColor; } set { m_NormalColor = value; } } public Color highlightedColor { get { return m_HighlightedColor; } set { m_HighlightedColor = value; } } public Color pressedColor { get { return m_PressedColor; } set { m_PressedColor = value; } } public Color disabledColor { get { return m_DisabledColor; } set { m_DisabledColor = value; } } public float colorMultiplier { get { return m_ColorMultiplier; } set { m_ColorMultiplier = value; } } public float fadeDuration { get { return m_FadeDuration; } set { m_FadeDuration = value; } }
最后关于相等的判断,定义如下,并且重载了==和!=运算符,均使用Equals来判断。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
public override bool Equals(object obj) { if (!(obj is ColorBlock)) return false;
s_List.Add(this); var state = SelectionState.Normal;
// The button will be highlighted even in some cases where it shouldn't. // For example: We only want to set the State as Highlighted if the StandaloneInputModule.m_CurrentInputMode == InputMode.Buttons // But we dont have access to this, and it might not apply to other InputModules. // TODO: figure out how to solve this. Case 617348. if (hasSelection) state = SelectionState.Highlighted;
private readonly List<CanvasGroup> m_CanvasGroupCache = new List<CanvasGroup>(); protected override void OnCanvasGroupChanged() { // Figure out if parent groups allow interaction // If no interaction is alowed... then we need // to not do that :) 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++) { // if the parent group does not allow interaction // we need to break if (!m_CanvasGroupCache[i].interactable) { groupAllowInteraction = false; shouldBreak = true; } // if this is a 'fresh' group, then break // as we should not consider parents if (m_CanvasGroupCache[i].ignoreParentGroups) shouldBreak = true; } if (shouldBreak) break;
// Change the button to the correct state private void EvaluateAndTransitionToSelectionState(BaseEventData eventData) { if (!IsActive() || !IsInteractable()) return;
// The current visual state of the control. protected void UpdateSelectionState(BaseEventData eventData) { if (IsPressed()) { m_CurrentSelectionState = SelectionState.Pressed; return; }
if (IsHighlighted(eventData)) { m_CurrentSelectionState = SelectionState.Highlighted; return; }
// Whether the control should be 'selected'. protected bool IsHighlighted(BaseEventData eventData) { if (!IsActive()) return false;
if (IsPressed()) return false;
bool selected = hasSelection; if (eventData is PointerEventData) { var pointerData = eventData as PointerEventData; selected |= (isPointerDown && !isPointerInside && pointerData.pointerPress == gameObject) // This object pressed, but pointer moved off || (!isPointerDown && isPointerInside && pointerData.pointerPress == gameObject) // This object pressed, but pointer released over (PointerUp event) || (!isPointerDown && isPointerInside && pointerData.pointerPress == null); // Nothing pressed, but pointer is over } else { selected |= isPointerInside; } return selected; }
更新SelectionState之后就是根据更新后的选择状态来计算和调整转换动画:
InternalEvaluateAndTransitionToSelectionState
1 2 3 4 5 6 7
private void InternalEvaluateAndTransitionToSelectionState(bool instant) { var transitionState = m_CurrentSelectionState; if (IsActive() && !IsInteractable()) transitionState = SelectionState.Disabled; DoStateTransition(transitionState, instant); }
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; } }
根据事件中给出的移动方向(上下左右),向对应方向的Selectable对象导航。
1 2 3 4 5 6
// Convenience function -- change the selection to the specified object if it's not null and happens to be active. void Navigate(AxisEventData eventData, Selectable sel) { if (sel != null && sel.IsActive()) eventData.selectedObject = sel.gameObject; }
// Find the selectable object to the right of this one. public virtual Selectable FindSelectableOnRight() { if (m_Navigation.mode == Navigation.Mode.Explicit) { return m_Navigation.selectOnRight; } if ((m_Navigation.mode & Navigation.Mode.Horizontal) != 0) { return FindSelectable(transform.rotation * Vector3.right); } return null; }
首先m_Navigation有各种模式:
1 2 3 4 5 6 7 8 9
[Flags] public enum Mode { None = 0, // No navigation Horizontal = 1, // Automatic horizontal navigation Vertical = 2, // Automatic vertical navigation Automatic = 3, // Automatic navigation in both dimensions Explicit = 4, // Explicitly specified only }
// Find the next selectable object in the specified world-space direction. 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; for (int i = 0; i < s_List.Count; ++i) { Selectable sel = s_List[i];
if (sel == this || sel == null) continue;
if (!sel.IsInteractable() || sel.navigation.mode == Navigation.Mode.None) continue;
// Value that is the distance out along the direction. float dot = Vector3.Dot(dir, myVector);
// Skip elements that are in the wrong direction or which have zero distance. // This also ensures that the scoring formula below will not have a division by zero error. 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;