스크롤 목록은 창문 뒤에서 긴 종이를 움직이는 것이다
인벤토리 화면을 떠올려 봅니다. 아이템 칸이 격자로 놓여 있고, 위아래로 밀면 목록이 움직이고, 창 밖으로 나간 칸은 보이지 않습니다.
Unity 에서 이 화면은 부품 셋 으로 나뉩니다.
시안에서는 한 장의 그림이지만, 칸이 몇 줄로 꺾이는지는 1 번이, 끝에서 튕기는지는 3 번이 정합니다. 하나씩 봅니다.
칸: Grid Layout Group
앞에서 본 Horizontal Layout Group 은 자식이 알려 준 크기를 썼습니다. Grid Layout Group 은 그 값을 보지 않습니다.
모든 칸이 Cell Size 하나로 같아집니다. 인벤토리 칸, 스킬 슬롯, 스테이지 선택 버튼처럼 크기가 같은 것을 늘어놓을 때 씁니다.
Constraint 가 몇 칸에서 줄을 꺾을지 정합니다.
- Flexible: 부모 너비에 들어가는 만큼 한 줄에 놓고 꺾습니다. 화면이 넓어지면 한 줄의 칸 수가 늘어납니다
- Fixed Column Count: 너비와 상관없이 정한 칸 수에서 꺾습니다. 넓으면 옆이 남고, 좁으면 칸이 부모 밖으로 나갑니다
- Fixed Row Count: 줄 수를 고정합니다. 옆으로 스크롤되는 목록에 씁니다
부모 너비와 설정을 바꿔 가며 보면, 칸이 어느 순서로 채워지고 어디서 꺾이는지 보입니다.
- Child Alignment 는 칸 하나가 아니라 칸 묶음 전체 를 옮깁니다. Flexible 에서 남는 폭이 한쪽에 몰려 보이면 가운데로 두면 됩니다
- 한 줄의 칸 수를 시안과 똑같이 지켜야 하면 Fixed Column Count 입니다. Flexible 은 기기마다 칸 수가 달라집니다
- Start Corner 와 Start Axis 는 채우는 순서를 바꿉니다. 칸 안 숫자가 하이어라키 순서라, 아이템을 정렬해 넣을 때 무엇이 첫 칸에 오는지가 여기서 정해집니다
줄이 늘어나면 높이도 늘려야 한다
위에서 좁게를 누르면 넘친 칸이 생깁니다. Grid 도 부모 크기는 바꾸지 않습니다.
스크롤 목록의 종이는 아이템 수만큼 길어져야 하니, 그리드에 Content Size Fitter 를 붙이고 Vertical Fit 을 Preferred Size 로 둡니다. 그리드의 Preferred 높이는 “지금 너비에서 필요한 줄 수” 로 계산됩니다.
Horizontal Fit 은 Unconstrained 로 둡니다. Flexible 그리드의 Preferred 너비는 아이템 수의 제곱근만큼의 칸 수라서, 아이템이 10개면 4칸 폭으로 줄어듭니다.
종이와 창: Scroll View
GameObject > UI > Scroll View 로 만들면 이런 구조가 생깁니다.
- Scroll View 에 붙은 Scroll Rect 가 끌기와 튕기기를 맡습니다
- Viewport 가 창입니다. 이 크기가 곧 보이는 범위이고, 마스크가 붙어 창 밖을 가립니다
- Content 가 종이입니다. 앵커는 위쪽 가로 stretch, 피벗은 위입니다. 앞에서 본 대로 크기는 피벗을 중심으로 바뀌니, 아이템이 늘어도 윗부분은 제자리이고 아래로만 길어집니다
처음 만들어진 Content 는 높이가 정해진 값이라, 위 절처럼 Content Size Fitter 로 아이템 수에 맞춰야 합니다.
2 번 Horizontal 은 기본으로 켜져 있습니다. 세로 목록인데 켜 두면 옆으로도 조금씩 흔들리니 끕니다.
4 번부터 7 번은 말로 설명하기보다 직접 끌어 보는 편이 빠릅니다.
- Elastic: 끝에서 고무줄처럼 버팁니다. 당길수록 무거워져서 손가락만큼 따라오지 않고, 놓으면 돌아옵니다. Elasticity 가 작을수록 빨리 돌아옵니다
- Clamped: 끝에서 딱 멈춥니다
- Unrestricted: 끝이 없습니다. 목록보다는 큰 지도처럼 경계가 없는 화면에 씁니다
- Inertia 를 켜면 손을 뗀 뒤에도 미끄러집니다. Deceleration Rate 0.135 는 1초 뒤 속도가 13.5 퍼센트 남는다는 뜻입니다. 0 에 가까울수록 금방 멈추고, 1 에 가까울수록 오래 미끄러집니다
- 위에서 마스크를 끄면 창 밖의 종이가 그대로 보입니다. 다음 절의 가림막이 하는 일입니다
가림막: Mask 와 RectMask2D
창 밖을 가리는 컴포넌트는 둘입니다. 자르는 모양이 다릅니다.
Mask 는 붙은 오브젝트의 이미지 모양대로 자식을 자릅니다.
- 동그란 초상화, 둥근 모서리 카드처럼 사각형이 아닌 모양 을 만들 수 있습니다
- GPU 의 스텐실이라는 방식을 써서 가장자리가 딱딱합니다. 흐리게 할 수 없습니다
- 마스크를 그리고 되돌리는 그리기가 추가되고, 마스크 안의 요소는 밖의 요소와 배칭이 갈립니다
RectMask2D 는 RectTransform 사각형으로만 자릅니다.
- 이미지가 필요 없습니다
- Softness 로 가장자리를 흐리게, Padding 으로 자르는 사각형을 안쪽으로 줄일 수 있습니다
- 추가 그리기가 없고, 창 밖으로 완전히 나간 요소는 아예 그리지 않습니다. 아이템이 많은 긴 목록에 유리합니다
- 같은 평면에 있는 2D 요소만 제대로 자릅니다. 3D 로 기울인 요소에는 맞지 않습니다
고르는 법은 간단합니다. 스크롤 창처럼 사각형이면 RectMask2D, 사각형이 아닌 모양이면 Mask 입니다. Mask 로 자르면서 가장자리까지 흐리게 하고 싶다면 기본 컴포넌트로는 안 되고, 부드러운 마스크를 만들어 주는 플러그인이 필요합니다.
시안을 넘길 때 적어 주면 좋은 것
| 적어 줄 것 | 무엇이 정해지나 |
|---|---|
| 한 줄에 몇 칸인지, 화면이 넓어지면 칸 수가 늘어나는지 | Constraint (Flexible 또는 Fixed Column Count) |
| 칸 크기와 간격, 칸 묶음이 어느 쪽에 붙는지 | Cell Size, Spacing, Child Alignment |
| 아이템이 어느 모서리부터 어떤 순서로 채워지는지 | Start Corner, Start Axis |
| 어느 방향으로 스크롤되는지 | Scroll Rect 의 Horizontal, Vertical |
| 끝에서 튕기는지 멈추는지, 손을 떼면 얼마나 미끄러지는지 | Movement Type, Elasticity, Inertia, Deceleration Rate |
| 창의 모양 과 가장자리를 흐리게 할지 | RectMask2D 의 Softness, 또는 Mask |
미끄러지는 느낌은 글로 전하기 어렵습니다. 비슷한 느낌의 게임이나 앱을 예로 들어 주면 맞추기 쉽습니다.
정리
- 스크롤 목록은 칸(Grid Layout Group), 종이(Content), 창(Viewport) 에 가림막(마스크) 을 더한 것입니다
- Grid Layout Group 은 자식 크기를 무시하고 모두 Cell Size 로 만듭니다. Constraint 가 몇 칸에서 꺾을지 정합니다
- 그리드도 부모 크기는 안 바꿉니다. 종이 높이는 Content Size Fitter 의 Vertical Fit Preferred Size 로 늘립니다
- Content 는 피벗이 위 라서 아래로 자랍니다. 세로 목록이면 Scroll Rect 의 Horizontal 을 끕니다
- 끝에서의 동작은 Movement Type, 손을 뗀 뒤의 미끄러짐은 Inertia 와 Deceleration Rate 가 정합니다
- 사각형 창은 RectMask2D, 다른 모양은 Mask 입니다. RectMask2D 는 가볍고 가장자리를 흐리게 할 수 있습니다