[공부] 261103 - UI2
UI 2일차
Unity UI Layout 기능 정리
1. Layout Element
개별 UI의 레이아웃 설정
- 개별 UI 오브젝트의 레이아웃 크기와 배치 조건을 지정하는 컴포넌트.
- Layout Group이 자식 UI를 배치할 때 참고하는 정보를 제공한다.
주요 속성
| 속성 | 설명 |
|---|---|
| Ignore Layout | 자동 레이아웃 배치에서 제외 |
| Min Width | 최소 너비 |
| Min Height | 최소 높이 |
| Preferred Width | 선호 너비 |
| Preferred Height | 선호 높이 |
| Flexible Width | 남는 가로 공간을 차지할 비율 |
| Flexible Height | 남는 세로 공간을 차지할 비율 |
사용 예시
- 특정 버튼만 크게 만들기
- 특정 UI의 최소 크기 지정
- 여러 UI 중 특정 UI가 남는 공간을 더 많이 사용하도록 설정
2. Vertical Layout Group
세로 자동 정렬
- 자식 UI들을 위에서 아래 방향으로 자동 정렬하는 컴포넌트.
- 자식 UI가 추가되거나 제거되면 레이아웃을 갱신한다.
주요 속성
| 속성 | 설명 |
|---|---|
| Padding | 부모와 자식 사이의 여백 |
| Spacing | 자식 UI 사이의 간격 |
| Child Alignment | 자식 UI의 정렬 위치 |
| Control Child Size | 자식 UI의 크기를 제어 |
| Child Force Expand | 남는 공간을 자식 UI가 확장해서 사용하도록 설정 |
사용 예시
- 설정 메뉴 버튼 정렬
- 퀘스트 목록
- 채팅 메시지 목록
- 세로형 아이템 목록
Grid Layout Group
격자형 자동 정
- 자식 UI들을 격자 형태로 자동 배치하는 컴포넌트.
- 행과 열을 기준으로 UI를 정렬한다.
- 기본적으로 자식 UI에 동일한 셀 크기를 적용한다.
주요 속성
| 속성 | 설명 |
|---|---|
| Padding | 부모와 자식 사이의 여백 |
| Cell Size | 각 셀의 크기 |
| Spacing | 셀 사이의 간격 |
| Start Corner | 배치 시작 모서리 |
| Start Axis | 행 또는 열 기준 배치 |
| Child Alignment | 자식 UI 정렬 |
| Constraint | 행 또는 열 개수 제한 |
사용 예시
- 인벤토리 슬롯
- 캐릭터 선택 화면
- 스킬 목록
- 아이템 도감
4. Content Size Fitter
콘텐츠에 맞춰 크기 조절
- 자신의 RectTransform 크기를 콘텐츠의 크기에 맞춰 자동으로 조절하는 컴포넌트.
- 자식 UI의 크기 정보를 기반으로 부모 UI의 크기를 조절할 때 사용한다.
주요 속성
| 속성 | 설명 |
|---|---|
| Horizontal Fit | 가로 크기 조절 방식 |
| Vertical Fit | 세로 크기 조절 방식 |
설정 종류
| 설정 | 설명 |
|---|---|
| Unconstrained | 자동 크기 조절하지 않음 |
| Min Size | 최소 크기에 맞춤 |
| Preferred Size | 선호 크기에 맞춤 |
사용 예시
- 채팅 메시지가 늘어날 때 Content 높이 확장
- 아이템 목록이 늘어날 때 부모 영역 크기 조절
- 텍스트 길이에 맞춰 UI 크기 변경
5. 기능 비교 및 관계
| 기능 | 핵심 역할 |
|---|---|
| Layout Element | 개별 UI의 레이아웃 조건 지정 |
| Vertical Layout Group | 자식 UI 세로 정렬 |
| Grid Layout Group | 자식 UI 격자 정렬 |
| Content Size Fitter | 자신의 크기를 콘텐츠에 맞게 조절 |
채팅창을 만든다고 가정하면
- Vertical Layout Group으로 채팅 메시지를 세로 정렬.
- Content Size Fitter로 메시지가 늘어날 때 Content 높이 조절.
- Layout Element로 특정 메시지 UI의 크기 조건 지정.
핵심 개념
- Layout Group = 자식 UI의 배치 관리
- Layout Element = 개별 UI의 레이아웃 조건 제공
- Content Size Fitter = 자신의 크기 자동 조절 주의: Layout Group과 Content Size Fitter를 함께 사용할 때는 부모와 자식의 크기 제어가 서로 충돌하지 않도록 구성해야 한다. Grid Layout Group은 기본적으로 동일한 셀 크기를 사용하므로 개별 Layout Element의 크기 설정이 그대로 적용되지 않을 수 있다.
이 네 가지는 전부 외울 필요는 없어. 우선 배치(Layout Group) / 크기 조건(Layout Element) / 크기 조절(Content Size Fitter) 이 세 가지 역할을 구분
6. 보간
중요 개념
Mathf.Lerp
- 보간이란 시작 값과 목표 값 사이의 중간 값을 계산하는 방법
예를 들어 점수가 0점에서 1000점으로 증가한다고 가정해 보자.
단순히 값을 변경하면:
score = 1000;
즉시 1000점으로 변경돼.
하지만 보간을 사용하면:
0 → 200 → 400 → 600 → 800 → 1000
이런 식으로 시작 값에서 목표 값까지 점진적으로 변화하는 것처럼 표현할 수 있음
- 기본 문법
Mathf.Lerp(startValue, endValue, t);
| 매개변수 | 의미 |
|---|---|
startValue | 시작 값 |
endValue | 목표 값 |
t | 진행률 (0~1) |
여기서 가장 중요한 건 t야.
t는 시작 값과 목표 값 사이에서 얼마나 진행했는지를 나타내.
private IEnumerator AnimateTotalScore()
{
float duration = 2f;
float elapsedTime = 0f;
while (elapsedTime < duration)
{
elapsedTime += Time.deltaTime;
// 진행률 0~1
float t = Mathf.Clamp01(elapsedTime / duration);
// 시작 점수부터 최종 점수까지 보간
float currentValue = Mathf.Lerp(0f, totalScore, t);
int displayScore = Mathf.RoundToInt(currentValue);
textTotalScore.text = $"{displayScore:D9}";
yield return null;
}
// 마지막에는 정확히 최종 점수 표시
textTotalScore.text = $"{totalScore:D9}";
}
토글그룹
실습1. 스탯 띄우기
스탯 띄우기 딜레이와
총 점수 띄우기
- 왼쪽 라벨 + 스탯 띄우기
- 오른쪽 라벨 + 스탯 띄우기
- 그다음 아래총점수 뚜루루 올라가는 연출
설계
왼쪽 라벨 코루틴으로 반복해서 띄움 -> 오른쪽도 라벨 코루틴으로 반복해서 띄움 -> 총점수 올리기
using System.Collections;
using TMPro;
using Unity.VisualScripting;
using UnityEngine;
public class GameOverWindow : GenericWindow
{
public TMP_Text leftStatsLabel;
public TMP_Text rightStatsLabel;
public TMP_Text leftStatsValues;
public TMP_Text rightStatsValues;
public TMP_Text textTotalScore;
//리셋될떄 초기화되는스탯들
private int[] scores = new int[6]; //초기화해서 쓰기
private int totalScore;
//
public float startValue = 0f;
float currentTime = 0;
public override void Open()
{
base.Open();
ResetScores();
}
private void ResetScores()
{
currentTime = 0f;
startValue = 0f;
StartCoroutine(ResetLeft());
}
private IEnumerator ResetLeft()
{
for (int i = 0; i < scores.Length; i++)
{
scores[i] = Random.Range(0, 1000);
}
leftStatsLabel.text = string.Empty;
rightStatsLabel.text = string.Empty;
leftStatsValues.text = string.Empty;
rightStatsValues.text = string.Empty;
textTotalScore.text = string.Empty;
for (int i = 0; i < scores.Length; i++)
{
yield return new WaitForSeconds(1f);
TMP_Text label = i < 3 ? leftStatsLabel : rightStatsLabel;
TMP_Text value = i < 3 ? leftStatsValues : rightStatsValues;
label.text += $"STAT {i + 1}\n"; //:D 쓰면 숫자만큼 앞에 0으로 채움
value.text += $"{scores[i % 3]:D4}\n";
}
yield return new WaitForSeconds(1f);
totalScore = Random.Range(0, 100000000);
yield return StartCoroutine(AnimateTotalScore());
}
private IEnumerator AnimateTotalScore()
{
float duration = 2f;
float elapsedTime = 0f;
while (elapsedTime < duration)
{
elapsedTime += Time.deltaTime;
// 진행률 0~1
float t = Mathf.Clamp01(elapsedTime / duration);
// 시작 점수부터 최종 점수까지 보간
float currentValue = Mathf.Lerp(0f, totalScore, t);
int displayScore = Mathf.RoundToInt(currentValue);
textTotalScore.text = $"{displayScore:D9}";
yield return null;
}
// 마지막에는 정확히 최종 점수 표시
textTotalScore.text = $"{totalScore:D9}";
}
public void OnNext()
{
manager.Open(Windows.Start);
}
public void OnClickContinue()
{
manager.Open(Windows.GameOver);
}
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
실습2. 키보드 시스템 만들기
- 커서 깜빡이는 기능
- 키보드 눌러 글자입력
- Cancel 누를시 전체 제거
- Delete 누를씨 지금 글자 제거
- Accept 누르면
- 7글자 이상일경우 입력 불가
실습3. 토글 저장 만들기
- 옵션누르면 창열리기
- Esc 누르면 원래 창으로
- 스페이스바 누르면 현재 토글 저장
using UnityEngine;
using UnityEngine.UI;
public class DifficultyWindow : GenericWindow
{
public enum Difficulty
{
None = -1,
Easy,
Normal,
Hard,
}
int intDifficulty = 1;
public Toggle[] toggles;
public Difficulty CurrentDifficulty
{
get
{
for (int i = 0; i < toggles.Length; i++)
{
if(toggles[i].isOn)
{
return (Difficulty)i;
}
}
return Difficulty.None;
}
set
{
toggles[(int)value].isOn = true;
}
}
private void OnEnable()
{
intDifficulty = PlayerPrefs.GetInt("CurrentDifficulty", intDifficulty);
}
//private void Start()
//{
// intDifficulty = 1;
//}
private void Update()
{
if(Input.GetKeyDown(KeyCode.LeftArrow))
{
if(intDifficulty > 0)
{
intDifficulty--;
}
}
else if(Input.GetKeyDown(KeyCode.RightArrow))
{
if (intDifficulty < 2)
{
intDifficulty++;
}
}
if(intDifficulty == 0)
{
CurrentDifficulty = Difficulty.Easy;
}
else if(intDifficulty == 1)
{
CurrentDifficulty = Difficulty.Normal;
}
else if (intDifficulty == 2)
{
CurrentDifficulty = Difficulty.Hard;
}
if (Input.GetKeyDown(KeyCode.Alpha1))
{
CurrentDifficulty = Difficulty.Easy;
intDifficulty = 0;
}
if(Input.GetKeyDown(KeyCode.Alpha2))
{
CurrentDifficulty = Difficulty.Normal;
intDifficulty = 1;
}
if(Input.GetKeyDown(KeyCode.Alpha3))
{
CurrentDifficulty = Difficulty.Hard;
intDifficulty = 2;
}
if(Input.GetKeyDown(KeyCode.Escape))
{
manager.Open(Windows.Start);
}
if (Input.GetKeyDown(KeyCode.Space))
{
Debug.Log(CurrentDifficulty);
Debug.Log(intDifficulty);
//저장
PlayerPrefs.SetInt("CurrentDifficulty", intDifficulty);
}
}
public void OnClickEasy(bool value)
{
intDifficulty = 0;
Debug.Log($"OnClickEasy: {value}");
}
public void OnClickNormal(bool value)
{
intDifficulty = 1;
Debug.Log($"OnClickNormal: {value}");
}
public void OnClickHard(bool value)
{
intDifficulty = 2;
Debug.Log($"OnClickHard: {value}");
}
}
내용



