-
[Unity][UI Toolkit] uss에서 first-child, last-child 구현하기 [USS]앱 개발/Unity, C# 2024. 11. 29. 20:07
UI Toolkit -> UGUI (Nova) -> 다시 UI Toolkit으로 migration 하는 중...
유니티 버전을 2021에서 2022로 업그레이드 하면서 UI Toolkit도 많이 안정되었구나 싶긴 했는데,
그래도 여전히 기존 css에서 구현되는 기능이 다 사용 가능한 건 아니라 한계를 다시 체험하고 있다.
그 중에 제일 답답했던 것이 child 사이의 거리를 설정할 수 없는 것...
웹을 할 때는 css에서 flex 속성과 함께 gap 속성을 주면 간단하고 깔끔하게 정렬할 수 있었는데,
유니티 uss에는 해당 속성이 없어서 자식의 margin을 하나씩 다 설정해주는 방식으로 가야한다.
이 기능을 자동으로 해주는 스크립트를 작성하고 싶었다.
그러기 위해서는 last-child를 알아야 했는데, 역시 속성이 존재하지 않아 괴로워하던 중
인터넷에서 미리 괴로워한 사람의 코드를 발견했다.
이번 포스팅은 그 코드를 샤라웃만 하고 넘어가겠다.
참고 코드
아래의 코드를 아주 살짝 변경하였다 (OnChildChange 메서드 private -> protected)
더보기/* Original code[1] Copyright (c) 2022 Shane Celis[1] Licensed under the MIT License[1] [1]: https://gist.github.com/shanecelis/1ab175c46313da401138ccacceeb0c90 [1]: https://twitter.com/shanecelis [1]: https://opensource.org/licenses/MIT */ using UnityEngine.Scripting; using UnityEngine.UIElements; namespace UI.Common { /** This event represents a change in the children of a VisualElement. */ public class ChildChangeEvent : EventBase<ChildChangeEvent>, IChangeEvent { public int previousValue { get; protected set; } public int newValue { get; protected set; } protected override void Init() { base.Init(); this.LocalInit(); } private void LocalInit() { this.bubbles = false; this.tricklesDown = false; } public static ChildChangeEvent GetPooled(int previousValue, int newValue) { ChildChangeEvent pooled = EventBase<ChildChangeEvent>.GetPooled(); pooled.previousValue = previousValue; pooled.newValue = newValue; return pooled; } public ChildChangeEvent() => this.LocalInit(); } /** Make `ChildChangeEvent` part of the events a VisualElement receives. There are two principle ways to trigger the ChildChangeEvent: a) Call CheckChildChange() which emits an event if the child count has changed. A manual poll. b) Set `checkInterval` to some milliseconds and `CheckChildChange()` will be called on that interval. Note: It would be nice to not have to do this with a poll. Internally `VisualElement` has a version and hierarchy change event, but we have no access to it that I know of. In the future, hopefully polling will not be required. If that happens, I'll try to add a note about it here and mark this class obsolete. */ public class ChildChangeManipulator : IManipulator { private int lastChildCount; private IVisualElementScheduledItem task; private int _checkInterval; /** Set up a poll to check the child counts every so many milliseconds. */ public int checkInterval { get => _checkInterval; set { task?.Pause(); if (_checkInterval == value) return; _checkInterval = value; if (_checkInterval > 0) task = target?.schedule.Execute(CheckChildChange) .Every(_checkInterval); } } private VisualElement _target; public VisualElement target { get => _target; set => _target = value; } /** Check whether the child count differs from the last time an event was sent. If the counts differ, send a `ChildChangeEvent`. This may have false negatives meaning that if one adds and removes from the elements in equal amounts, they won't be caught. In that case you might want to compute a hash of the children to check for differences. */ public void CheckChildChange() { if (target?.childCount != lastChildCount) SendChildChange(); } public void SendChildChange() { if (target == null) return; var e = ChildChangeEvent.GetPooled(lastChildCount, target.childCount); e.target = target; lastChildCount = target.childCount; target.SendEvent(e); } } /** Fake CSS pseudo classes :first-child and :last-child as USS regular classes .first-child and .last-child, i.e., the first child will have a .first-child USS class, and the last child will have a .last-child USS class. Knowing the first and last child helps with styling elements. If the children are fixed, no further setup ought to be required. If the children are changing, one must setup the `childChanger`; either calling `childChanger.CheckChildChange()` or `childChanger.checkInterval = 1000` to check children every second for instance. Finally one can set that interval as a UXML attribute `check-interval`. Note: Hopefully Unity will add pseudo class support so one doesn't need to resort to this. Or hopefully Unity exposes some event for detecting the addition or removal of elements. They have one internally but nothing is exposed. */ public class ChildAnnotator : VisualElement { public readonly ChildChangeManipulator childChanger; private VisualElement _firstChild; protected VisualElement firstChild { get => _firstChild; set { if (_firstChild != value) { _firstChild?.RemoveFromClassList("first-child"); _firstChild = value; _firstChild?.AddToClassList("first-child"); } } } private VisualElement _lastChild; protected VisualElement lastChild { get => _lastChild; set { if (_lastChild != value) { _lastChild?.RemoveFromClassList("last-child"); _lastChild = value; _lastChild?.AddToClassList("last-child"); } } } public ChildAnnotator() { this.AddManipulator(childChanger = new ChildChangeManipulator()); RegisterCallback<ChildChangeEvent>(OnChildChange); schedule.Execute(() => childChanger.CheckChildChange()); } protected virtual void OnChildChange(ChildChangeEvent evt) { if (childCount == 0) { firstChild = null; lastChild = null; return; } if (childCount > 0) { firstChild = this[0]; lastChild = this[childCount - 1]; } } [Preserve] public new class UxmlFactory : UxmlFactory<ChildAnnotator, UxmlTraits> { } [Preserve] public new class UxmlTraits : VisualElement.UxmlTraits { private readonly UxmlIntAttributeDescription checkInterval = new UxmlIntAttributeDescription { name = "check-interval", defaultValue = 0 }; public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc) { base.Init(ve, bag, cc); var item = (ChildAnnotator) ve; item.childChanger.checkInterval = checkInterval.GetValueFromBag(bag, cc); } } } }
사용 방법
아래와 같이 클래스를 연결하여 스타일 적용 가능하다.
끗~
'앱 개발 > Unity, C#' 카테고리의 다른 글
[Unity][UI Toolkit] uss에서 gap 옵션(child 사이 spacing) 구현하기 [USS] (0) 2024.11.29 [Unity] 커서 모양 바꾸기 + 커서 회전하기 [How to rotate custom cursor texture] (2) 2024.02.18 [Unity][UI Toolkit] 버튼 누르는 동안 동작 실행 (0) 2024.02.18 [Unity][ClickEvent] 버튼 클릭 이벤트를 처리하는 n가지 방법 (0) 2024.02.18 [Unity][UI Toolkit] UI Document를 생성하고 Visual Tree Asset(uxml 파일) 적용시키기 (0) 2022.09.06