이름을 붙여야 스타일을 걸 수 있다
Esc

    이름을 붙여야 스타일을 걸 수 있다

    –UnityUIUI Toolkit

    화면 하나가 파일 두 개와 오브젝트 하나가 된다에서 .uxml 은 구조, .uss 는 모양이라고 나눴습니다. 파일이 나뉘면 바로 따라오는 질문이 하나 있습니다. 이 모양을 누구에게 걸 것인가 를 어딘가에 적어야 합니다.

    uGUI 에는 이 질문이 없었습니다. 버튼을 클릭하고 인스펙터에서 색을 바꾸면 그 버튼이 바뀌었습니다. 고르는 일과 값을 넣는 일이 한 동작이었습니다. UI Toolkit 에서는 그 둘이 떨어져 있습니다.

    모양은 요소가 아니라 문장에 붙습니다

    uss 에 적는 규칙 하나는 이렇게 생겼습니다.

    .primary {
        background-color: #2f6fed;
        color: white;
    }

    앞의 .primary 가 누구에게 이고, 중괄호 안이 무엇을 입니다. 앞부분을 선택자라고 부릅니다.

    그래서 순서가 뒤집힙니다. uGUI 는 요소를 고른 다음 값을 넣었고, 여기서는 값을 먼저 적어 두고 그 값을 받을 요소에 표식을 답니다. 표식이 없는 요소는 어느 규칙에도 안 걸리고, 그래서 아무 모양도 받지 못합니다.

    부르는 방법은 네 가지입니다

    선택자 적는 법 걸리는 것
    타입 Button { ... } 그 타입인 요소 전부
    이름 #ok { ... } 그 이름을 가진 요소 하나
    클래스 .primary { ... } 그 클래스를 단 요소 전부
    전체 * { ... } 전부

    타입 선택자에는 네임스페이스를 안 적습니다. 문서가 못 박아 둔 부분입니다. “When you write type selectors, specify only the concrete object type. Don’t include the namespace in the type name.” UnityEngine.UIElements.Button 이 아니라 Button 입니다.

    이어 붙이는 방법도 있습니다. .row Button 은 .row 안 어딘가에 있는 버튼 전부이고, .row > Button 은 바로 아래 칸의 버튼만입니다. .primary.danger 처럼 띄우지 않고 붙이면 두 클래스를 다 단 요소만 걸립니다. 쉼표로 늘어놓으면 같은 값을 여럿에게 한 번에 겁니다.

    # 와 . 은 uss 에만 씁니다

    uxml 쪽에는 기호를 쓰지 않습니다.

    <Button name="ok" class="primary" text="확인" />
    <Button name="delete" class="primary danger" text="지우기" />

    클래스를 여럿 달 때는 띄어쓰기로 늘어놓습니다.

    문서가 이 부분을 두 번 적어 뒀습니다. 클래스 쪽은 “Only use a period when you write the selector in a USS file. Don’t include it when you assign the class to an element in a UXML or C# file.” 이고, 이름 쪽은 더 분명합니다. “An element name that includes the number sign is invalid. For example <Button name="#OK" /> is invalid.”

    name="#ok" 이라고 적어 놓고 왜 안 걸리지 하는 일이 처음에 한 번쯤 있습니다.

    코드에서 다는 것도 같습니다.

    var ok = root.Q<Button>("ok");
    ok.AddToClassList("primary");
    ok.RemoveFromClassList("ghost");
    ok.EnableInClassList("danger", isDangerous);

    마지막 줄이 실제로 제일 많이 쓰입니다. 값이 참이면 붙이고 거짓이면 뗍니다. 상태에 따라 모양이 바뀌는 자리는 대부분 이 한 줄로 끝납니다.

    이름은 하나, 클래스는 여럿입니다

    문서 표현으로 “To avoid unexpected matches, make element names unique within a panel.” 입니다. 이름은 한 요소를 가리키려고 있는 것입니다.

    그래서 이름 선택자로 모양을 다 걸면 화면이 하나 늘 때마다 uss 도 같이 늘어납니다. #ok, #shopOk, #resultOk 같은 것이 쌓이고, 나중에 버튼 모서리를 둥글게 바꾸려면 그 줄을 전부 찾아야 합니다.

    클래스는 반대입니다. 한 번 적고 여러 군데서 답니다. uGUI 에서 같은 버튼을 여러 화면에 쓰려고 프리팹을 만들었다면, UI Toolkit 에서 그 자리에 있는 것이 클래스입니다. 다른 점은 프리팹은 구조까지 같이 묶여 오고, 클래스는 모양만 온다는 것입니다.

    한 요소가 클래스를 여럿 달 수 있다는 것이 여기서 힘이 됩니다. 문서 표현으로 “When an element has more than one class assigned, a selector only has to match one of them to match the element.” 입니다. class="primary danger" 는 primary 규칙과 danger 규칙을 둘 다 받습니다. 공통 모양은 primary 에 적어 두고, 다른 부분만 danger 에 적으면 됩니다.

    정할 것은 하나입니다. 이 값이 이 요소에만 해당하는가, 같은 종류 전부에 해당하는가. 앞이면 이름, 뒤면 클래스입니다.

    상태 이름은 이미 정해져 있습니다

    uGUI 에서 버튼 색을 상태별로 넣던 자리가 여기서는 uss 에 있습니다.

    .primary:hover {
        background-color: #4a83f0;
    }
    .primary:disabled {
        opacity: 0.4;
    }

    쓸 수 있는 이름은 이렇습니다.

    이름 언제
    :hover 커서가 그 요소 위에 있을 때
    :active 버튼이나 토글을 누르고 있는 동안
    :inactive 누르기를 그만뒀을 때
    :focus 그 요소가 포커스를 가졌을 때
    :enabled / :disabled 켜져 있을 때 / 꺼져 있을 때
    :checked 토글이나 라디오가 선택됐을 때
    :root 그 스타일시트가 걸린 트리의 맨 위

    같이 알아 둘 것이 둘 있습니다. 하나는 새로 만들 수 없다 는 것입니다. 문서가 “You can’t extend pseudo-classes or create custom ones” 라고 적어 뒀습니다. “선택된 아이템” 이나 “새로 들어온 항목” 같은 우리 쪽 상태는 클래스를 직접 붙였다 뗐다 해서 만듭니다. 위의 EnableInClassList 가 그 자리입니다.

    다른 하나는 CSS 쪽에 있는 :selected 가 여기에는 없다는 것입니다. 토글은 :checked 를 씁니다.

    유니티가 만든 컨트롤에도 클래스가 이미 붙어 있습니다

    <Button /> 하나를 놓으면 클래스를 아무것도 안 단 것 같지만 아닙니다. 유니티 소스에 이렇게 적혀 있습니다.

    // Button.cs
    public new static readonly string ussClassName = "unity-button";
    public static readonly string iconUssClassName = ussClassName + "--with-icon";
    public static readonly string imageUSSClassName = ussClassName + "__image";
    // Toggle.cs
    public new static readonly string ussClassName = "unity-toggle";
    public new static readonly string labelUssClassName = ussClassName + "__label";
    public new static readonly string inputUssClassName = ussClassName + "__input";
    public static readonly string checkmarkUssClassName = ussClassName + "__checkmark";

    이름 짓는 규칙이 보입니다.

    유니티가 붙여 둔 클래스 이름은 조각으로 되어 있습니다 unity-toggle __label 컨트롤 하나 그 안의 부품 Toggle 안의 글자 부분에만 걸립니다 unity-button --with-icon 컨트롤 하나 그 컨트롤의 변형 아이콘이 붙은 버튼일 때만 붙습니다
    밑줄 두 개는 부품, 하이픈 두 개는 변형입니다.

    밑줄 두 개는 그 컨트롤 안의 부품이고, 하이픈 두 개는 그 컨트롤의 변형입니다. 웹에서 쓰는 BEM 방식 그대로입니다.

    이게 왜 중요하냐면, 컨트롤 속을 건드릴 손잡이가 그 이름들뿐 이기 때문입니다. 토글의 체크 표시만 키우고 싶으면 .unity-toggle__checkmark 에 겁니다. 하이어라키를 열어서 자식 오브젝트를 찾는 대신 이름을 아는 것이 방법입니다.

    대신 조심할 것이 있습니다. uss 에 .unity-button { ... } 이라고 적으면 그 파일이 걸린 화면의 버튼이 전부 바뀝니다. 기본을 갈아 끼우는 셈이라 의도했으면 편하고 아니면 사고입니다. 특정 버튼만 바꿀 것이면 내 클래스를 하나 만들어서 거기에 적습니다.

    둘 다 맞으면 무엇이 이깁니까

    한 요소에 규칙이 여러 개 맞는 일은 드물지 않습니다. .unity-button 도 맞고 .primary 도 맞고 #ok 도 맞습니다.

    한 요소에 여러 규칙이 맞을 때 위가 이깁니다 C# 에서 직접 넣은 값 el.style.color = ... uxml 에 직접 적은 인라인 style="color: red" 이름 선택자 #ok 클래스 선택자 .primary 타입 선택자 Button 전체 선택자 * 급이 같으면 파일에서 아래쪽에 적힌 것이 이깁니다
    위쪽이 셉니다. 물려받은 값은 이 사다리와 상관없이 직접 걸린 값에 집니다.

    문서가 정한 순서가 있습니다.

    • 코드에서 직접 넣은 값이 제일 셉니다. “Styles that you set in a C# script override USS styles and inline styles set in a UXML document.”
    • 그다음이 uxml 태그에 직접 적은 style="..." 입니다.
    • uss 안에서는 이름, 클래스, 타입, 전체 순입니다.
    • 급이 같으면 파일에서 아래쪽에 적힌 것 이 이깁니다.

    여기에 한 줄이 더 붙는데, 실무에서 이게 자주 걸립니다. 직접 걸린 값이 물려받은 값보다 셉니다. 문서 표현으로 “Styles that target an element directly take precedence over styles that the element inherits, even if the inherited styles are defined in a selector with higher specificity” 입니다. 글자색처럼 부모에서 물려받는 속성은, 부모 쪽 선택자가 아무리 구체적이어도 자식에 직접 걸린 한 줄에 집니다.

    색이 안 바뀔 때 볼 순서는 셋입니다. uxml 에 style= 이 박혀 있는지, 더 센 선택자가 어디에 있는지, 같은 급 규칙이 파일 아래쪽에 또 있는지.

    정리

    • uss 규칙은 선택자 + 값 입니다. 표식이 없는 요소는 어느 규칙에도 안 걸립니다
    • 부르는 방법은 타입 Button, 이름 #ok, 클래스 .primary, 전체 * 네 가지이고, 띄어쓰기와 > 로 이어 붙일 수 있습니다
    • # 와 . 은 uss 에만 씁니다. uxml 의 name 과 class 에는 기호를 안 붙입니다
    • 이름은 한 판에서 유일해야 하고, 클래스는 한 요소가 여러 개 달 수 있습니다. 재사용되는 쪽은 클래스입니다
    • 상태는 :hover 처럼 정해진 이름으로 옵니다. 새로 만들 수 없어서 우리 쪽 상태는 클래스를 붙였다 뗍니다
    • 유니티 컨트롤에는 unity-button, unity-toggle__label 같은 클래스가 이미 붙어 있습니다. 속을 건드리는 손잡이가 그 이름입니다
    • 둘 다 맞으면 코드, 인라인, 이름, 클래스, 타입, 전체 순으로 이기고, 급이 같으면 파일 아래쪽이 이깁니다