
이 블로그의 예전 글 체력바 · 가짜 로딩바 · 쿨타임 세 편은 모두 Image 의 fillAmount 로 만들었지만, 글마다 채우는 방식 한 가지만 코드에 넣고 Filled 의 나머지 설정은 설명하지 않았습니다. 그 뒤 제가 잰드케이로 낸 앱에서는 fillAmount 를 막대가 아닌 곳에도 썼습니다. 랜덤 결정 앱 RandomGO 의 룰렛 칸은 원 그림 하나를 잘라서 만들고, 기억력 게임 뇌깜빡할사이의 별 점수는 별 세 개가 그려진 그림 한 장을 점수만큼 채웁니다. 스킬을 쓰면 아이콘 위로 시계처럼 돌아가는 쿨타임, 다음 화면으로 넘어갈 때 차오르는 로딩 바도 모두 그림의 일부만 보여 주는 UI 입니다. Unity 에서는 Image 컴포넌트의 Image Type 을 Filled 로 두고 fillAmount 에 0~1 사이 값을 넣으면 그 비율만큼만 그림이 보입니다. 이번 예시에서는 쿨타임 원과 로딩 바를 스크립트 하나로 만들고, 채우는 방향을 정하는 fillMethod · fillOrigin 과 헷갈리기 쉬운 점을 함께 적어 보겠습니다.
C# (UNITY 3D)

fillMethod 는 다섯 가지입니다. Horizontal 은 가로로, Vertical 은 세로로 차고, Radial90 · Radial180 · Radial360 은 부채꼴로 돌면서 찹니다. 세 Radial 의 차이는 회전 중심으로, Radial90 은 모서리 하나, Radial180 은 변 하나의 가운데, Radial360 은 그림 한가운데가 중심입니다. 어디서부터 채울지는 fillOrigin 으로 정합니다. fillOrigin 은 int 라서 fillMethod 에 맞는 값을 골라 형 변환해 넣습니다. Horizontal 은 Image.OriginHorizontal(Left · Right), Vertical 은 Image.OriginVertical(Bottom · Top), Radial90 은 Image.Origin90(네 모서리), Radial180 은 Image.Origin180, Radial360 은 Image.Origin360(Bottom · Left · Top · Right) 입니다. 시계 방향(true)으로 채울지 반시계 방향(false)으로 채울지는 fillClockwise 로 정하는데, 이 값은 Radial 세 가지에서만 쓰입니다.
쿨타임은 스킬 아이콘 Image 위에 같은 크기의 Image 를 하나 더 겹쳐 반투명한 검은색으로 칠하고, 그 위에 남은 초를 보여 줄 TextMeshPro 글자를 둡니다. 로딩 바는 바탕 Image 와 그 위에 겹친 채움 Image 두 장입니다. 채울 Image 의 Source Image 에는 스프라이트를 넣어 둡니다.
using TMPro;
using UnityEngine.UI;
Image 와 Button 을 쓰기 위해 UnityEngine.UI 를, TMP_Text 를 쓰기 위해 TMPro 를 사용합니다. 빈 오브젝트에 스크립트를 붙이고 각 오브젝트를 스크립트에 할당합니다.
using TMPro;
using UnityEngine;
using UnityEngine.UI;
//CodeFinder 코드파인더
//From https://codefinder.janndk.com/
public class FillAmountExample : MonoBehaviour
{
public Image image_cooldown; // 스킬 아이콘 위에 겹친 덮개
public TMP_Text text_cooldown;
public Button button_skill;
public float time_cooldown = 3f;
public Image image_loading; // 로딩 바의 채움
public TMP_Text text_loading;
public float time_loading = 5f;
private float time_left;
private float time_loaded;
void Start()
{
Init_UI();
button_skill.onClick.RemoveAllListeners();
button_skill.onClick.AddListener(Use_Skill);
}
void Update()
{
Update_Cooldown();
Update_Loading();
}
void Init_UI()
{
// 쿨타임 : 위 가운데에서 시작해 반시계 방향으로 채우는 원
image_cooldown.type = Image.Type.Filled;
image_cooldown.fillMethod = Image.FillMethod.Radial360;
image_cooldown.fillOrigin = (int)Image.Origin360.Top;
image_cooldown.fillClockwise = false;
image_cooldown.fillAmount = 0f;
text_cooldown.text = "";
// 로딩 바 : 왼쪽에서 오른쪽으로 차는 막대
image_loading.type = Image.Type.Filled;
image_loading.fillMethod = Image.FillMethod.Horizontal;
image_loading.fillOrigin = (int)Image.OriginHorizontal.Left;
Set_Loading(0f);
}
public void Use_Skill()
{
if (time_left > 0f)
{
return; // 쿨타임 중에는 다시 쓸 수 없습니다
}
time_left = time_cooldown;
button_skill.interactable = false;
}
void Update_Cooldown()
{
if (time_left <= 0f)
{
return;
}
time_left -= Time.deltaTime;
if (time_left <= 0f)
{
time_left = 0f;
text_cooldown.text = "";
button_skill.interactable = true;
}
else
{
text_cooldown.text = time_left.ToString("0.0");
}
image_cooldown.fillAmount = time_left / time_cooldown; // 남은 비율 1 → 0
}
void Update_Loading()
{
if (time_loaded >= time_loading)
{
return;
}
time_loaded += Time.deltaTime;
Set_Loading(time_loaded / time_loading);
}
public void Set_Loading(float progress)
{
progress = Mathf.Clamp01(progress); // fillAmount 는 0~1 비율
image_loading.fillAmount = progress;
text_loading.text = Mathf.RoundToInt(progress * 100f) + "%";
}
}
Use_Skill 이 time_left 를 쿨타임 길이로 채우면, Update_Cooldown 이 매 프레임 그 값을 줄이면서 남은 비율을 덮개의 fillAmount 에 넣습니다. 로딩 바는 Update_Loading 이 지난 시간을 time_loading 으로 나눠 Set_Loading 에 넘깁니다. 실제 데이터를 받는 로딩이라면 Update_Loading 대신 받은 양을 전체 양으로 나눈 값을 Set_Loading 에 넘기면 됩니다.
함수(function)
Init_UI : 두 Image 의 Image Type · fillMethod · fillOrigin 을 코드로 정합니다. 인스펙터에서 손으로 맞춰도 됩니다.
Use_Skill : 버튼을 누르면 쿨타임을 시작하고 버튼을 잠급니다.
Update_Cooldown : 남은 시간을 줄이고, 남은 비율을 fillAmount 에 넣습니다. 0 이 되면 버튼을 다시 엽니다.
Update_Loading : 지난 시간으로 가짜 진행률을 만듭니다.
Set_Loading : 0~1 진행률을 로딩 바와 퍼센트 글자로 보여 줍니다.
변수(variable)
image_cooldown(Image) : 쿨타임 덮개입니다.
text_cooldown(TMP_Text) : 남은 초를 소수 첫째 자리까지 보여 줍니다.
button_skill(Button) : 스킬 버튼입니다.
time_cooldown(float) : 쿨타임 길이(초)입니다.
image_loading(Image) : 로딩 바의 채움입니다.
text_loading(TMP_Text) : 진행률(%)입니다.
time_loading(float) : 가짜 로딩에 걸리는 시간(초)입니다.
time_left(float) : 쿨타임이 끝날 때까지 남은 시간입니다.
time_loaded(float) : 로딩을 시작하고 지난 시간입니다.
fillAmount 는 Filled 일 때만 보이는 양입니다
문서에서 fillAmount 는 Image Type 이 Filled 일 때 보이는 양입니다. Simple · Sliced · Tiled 로 둔 채 값만 바꾸면 코드는 도는데 화면은 그대로라서 헷갈리기 쉽습니다. 인스펙터에서 Image Type 을 Filled 로 바꾸거나 예시의 Init_UI 처럼 코드에서 type 을 정합니다.
초가 아니라 0~1 비율을 넣습니다
fillAmount 는 0 이면 아무것도 안 보이고 1 이면 그림 전체가 보이는 비율입니다. image_cooldown.fillAmount = time_left; 처럼 초를 그대로 넣으면 3초 쿨타임에서 3, 2.5, 2 같은 값은 1 로 잘려서, 남은 시간이 1초 아래로 내려갈 때까지 덮개가 꽉 찬 채로 있습니다. 늘 전체 값으로 나눈 비율을 넣고, 계산 결과가 범위를 넘을 수 있으면 Mathf.Clamp01 로 0~1 안에 묶습니다.
fillMethod 를 바꾸면 fillOrigin 도 같이 바꿉니다
fillOrigin 은 그냥 int 라서 (int)Image.Origin360.Top 을 Horizontal 이미지에 넣어도 컴파일 오류가 나지 않습니다. 문서대로 같은 숫자가 fillMethod 마다 다른 자리를 뜻하기 때문에, 방식만 바꾸면 시작점이 엉뚱한 곳이 됩니다. fillMethod 를 정하는 줄 바로 아래에서 그 방식의 Origin 으로 fillOrigin 을 같이 정합니다.
막대의 방향은 fillClockwise 가 아니라 fillOrigin 입니다
fillClockwise 는 Radial90 · Radial180 · Radial360 에서만 쓰입니다. 로딩 바를 오른쪽에서 왼쪽으로 채우고 싶으면 fillOrigin 을 (int)Image.OriginHorizontal.Right 로 바꿉니다.
RandomGO 룰렛 칸은 원 그림 한 장을 잘라 만듭니다

RandomGO 의 룰렛은 칸마다 원 그림 한 장을 통째로 씁니다. 그림을 Radial360 · Top · 시계 방향으로 두고 fillAmount 를 1 / 칸 수로 넣으면 원에서 한 칸만큼의 부채꼴만 남습니다. 그 조각을 칸 번호만큼 360 / 칸 수 도씩 돌려 놓아 원을 채우고, 칸 이름 글자는 반 칸만큼 더 돌려 조각의 가운데에 오게 했습니다.
// 원 그림 하나를 count 칸 중 index 번째 조각으로
public void Make_Slice(Image slice, int index, int count)
{
slice.type = Image.Type.Filled;
slice.fillMethod = Image.FillMethod.Radial360;
slice.fillOrigin = (int)Image.Origin360.Top;
slice.fillClockwise = true;
slice.fillAmount = 1f / count;
// 칸 번호만큼 돌려 다음 자리로 (RandomGO 와 같은 식)
slice.rectTransform.localEulerAngles = Vector3.back * (360f / count * index);
}
뇌깜빡할사이 별 점수에는 1 을 넘는 값이 들어갑니다

뇌깜빡할사이는 한 판이 여섯 문제이고, 레벨 선택 화면과 결과 화면의 별 세 개가 Image 한 장입니다. Horizontal · Left 로 두고, 아래 식의 값을 fillAmount 에 넣습니다. 코드 주석 그대로 2문제마다 0.35, 홀수면 0.15 를 더합니다.
// 결과 화면의 별 채움(0~1) — 2문제마다 0.35, 홀수면 0.15 더
public static float StarFill(int score) => score / 2 * 0.35f + (score % 2 == 0 ? 0 : 0.15f);
테스트에도 1문제 0.15, 2문제 0.35 가 들어 있고, 3문제를 맞힌 위 화면의 1레벨은 0.35 + 0.15 = 0.5 로 별 한 개 반입니다. 그런데 여섯 문제를 다 맞히면 3 × 0.35 = 1.05 로 1 을 넘습니다. 테스트도 StarFill(6) 을 1.05 로 적어 두었습니다. fillAmount 는 0~1 범위라 1 을 넘는 값은 1 로 잘려서 별 세 개가 꽉 차 보이고, 그래서 화면에서는 문제가 드러나지 않습니다. 범위를 넘는 값을 넣어도 오류가 나지 않으니, 식에서 1 이 넘을 수 있으면 Mathf.Clamp01 로 직접 묶어 두는 편이 읽는 사람에게도 분명합니다. 이 식은 2022년 레벨을 넣을 때 처음 들어갔고, 2026년 10월 규칙 파일로 옮길 때도 동작은 그대로 두었습니다.
예전 글은 체력바(Horizontal), 가짜 로딩바(Time.time), 스킬 쿨타임(Radial360) 에 있습니다. 레거시 Text 를 쓰던 때의 코드라 이번 예시는 TMP_Text 로 바꿨습니다.
위 예시 코드는 Unity 6000.3.14f1 빈 씬에서 컴파일 · 실행해 확인했습니다. 오류 · 경고 없이 쿨타임 덮개는 3초 동안 1에서 0으로 줄고 그동안 버튼이 꺼져 있다가 다시 켜졌고, 6칸 룰렛 조각은 fillAmount 0.167 에 60도씩 시계 방향으로 놓였습니다.
fillAmount · fillMethod · fillOrigin · fillClockwise 의 동작은 Unity 공식 Unity UI 문서(Image) 와 Image.FillMethod · Image.Origin360 문서를 기준으로 적었습니다.
룰렛 칸을 자르는 방법은 제가 잰드케이로 낸 랜덤 결정 앱 RandomGO 에서 실제로 쓰고 있습니다(스토어 개발사 표시는 Jakadela Games).
'Unity3D > C#' 카테고리의 다른 글
| 유니티 랜덤 Random.Range 정수 · 실수 차이와 자주 하는 실수 (int, float, InitState) (0) | 2026.10.16 |
|---|---|
| 유니티 안드로이드 화면 최대 밝기, 화면 꺼짐 방지 (screenBrightness / Screen.sleepTimeout) (0) | 2026.10.15 |
| 유니티 떨어지는 이미지, 화면 벗어나면 자동 파괴 (0) | 2020.11.22 |
| Unity Cursor Tail (Instantiate, Destroy Delay) (0) | 2020.11.08 |
| Unity 클릭으로 오브젝트 생성, 파괴 (Instantiate/Destroy) (0) | 2020.11.07 |