
유니티 Scroll View 를 코드로 맨 아래까지 내리려면 ScrollRect 의 verticalNormalizedPosition 에 0 을 넣습니다. 0 이 맨 아래, 1 이 맨 위입니다. 방금 줄을 추가했다면 그 전에 Canvas.ForceUpdateCanvases() 를 불러 Content 높이를 먼저 다시 계산해야 새 줄까지 내려갑니다.
Canvas.ForceUpdateCanvases(); // 방금 넣은 줄까지 Content 높이를 다시 계산
scroll_rect.verticalNormalizedPosition = 0f; // 0 이 맨 아래
채팅창이나 게임 기록창처럼 아래로 줄이 계속 쌓이는 목록은 새 줄이 들어올 때마다 맨 아래가 보여야 합니다. 그런데 줄을 넣자마자 0 을 넣었는데도 방금 넣은 줄만 가려진 채 한 줄 위에 멈추거나, 위쪽 지난 줄을 읽는 중인데 새 줄이 들어올 때마다 맨 아래로 끌려 내려가는 일이 생깁니다. 이번 글에서는 줄을 추가하는 ScrollList 를 만들면서, 맨 아래를 보고 있을 때만 따라 내려가고 위를 읽는 중이면 그대로 두는 처리와 맨 위 · 맨 아래로 가는 버튼을 함께 넣었습니다.
C# (UNITY 3D)
button_add 를 누르면 목록 아래에 「메시지 1」, 「메시지 2」… 줄이 하나씩 붙고, 맨 아래를 보고 있었다면 새 줄이 보이게 따라 내려갑니다. 위로 올려 지난 줄을 보는 중이면 줄만 붙고 화면은 그대로입니다. button_top · button_bottom 은 맨 위 · 맨 아래로 바로 갑니다.
1. 준비 — Scroll View 와 Content
using TMPro;
using UnityEngine.UI;
ScrollRect 와 Button 은 UnityEngine.UI, 줄 글자 TMP_Text 는 TMPro 에 있습니다.
Hierarchy 에서 UI > Scroll View 를 만들면 Scroll View > Viewport > Content 가 함께 생깁니다. 세로 목록만 쓸 거라면 Scroll View 의 ScrollRect 에서 Horizontal 을 끕니다. Content 에는 Vertical Layout Group 과 Content Size Fitter(Vertical Fit 은 Preferred Size)를 붙여, 줄이 늘어나는 만큼 Content 도 길어지게 합니다. Vertical Layout Group 은 Control Child Size 의 Height 를 켜고 Child Force Expand 의 Height 는 끕니다. Content 의 기준점(Pivot)은 Y 1, 위쪽에 붙게 둡니다.
한 줄로 쓸 오브젝트(높이가 정해진 Layout Element 와 그 안의 TextMeshPro 글자)를 하나 만들어 꺼 두거나 Prefab 으로 만들고, prefab_item 에 넣습니다.
2. 줄을 넣고 맨 아래로 내리는 코드
using TMPro;
using UnityEngine;
using UnityEngine.UI;
//CodeFinder 코드파인더
//From https://codefinder.janndk.com/
public class ScrollList : MonoBehaviour
{
public ScrollRect scroll_rect; // Scroll View 에 붙은 ScrollRect
public RectTransform content; // Scroll View > Viewport > Content
public GameObject prefab_item; // 한 줄(안에 TMP_Text 가 있는 것)
public Button button_add;
public Button button_top;
public Button button_bottom;
int count_added;
void Start()
{
if (button_add != null)
{
button_add.onClick.RemoveAllListeners();
button_add.onClick.AddListener(() => Add_Item("메시지 " + (count_added + 1)));
}
if (button_top != null)
{
button_top.onClick.RemoveAllListeners();
button_top.onClick.AddListener(Scroll_ToTop);
}
if (button_bottom != null)
{
button_bottom.onClick.RemoveAllListeners();
button_bottom.onClick.AddListener(Scroll_ToBottom);
}
}
public void Add_Item(string message)
{
if (content == null || prefab_item == null)
return;
bool was_bottom = Is_AtBottom(); // 넣기 전에 맨 아래를 보고 있었는지
GameObject item = Instantiate(prefab_item, content);
item.SetActive(true);
TMP_Text text = item.GetComponentInChildren<TMP_Text>();
if (text != null)
text.text = message;
count_added++;
if (was_bottom)
Scroll_ToBottom(); // 위를 읽고 있던 사람은 끌어내리지 않음
}
public bool Is_AtBottom()
{
if (scroll_rect == null)
return true;
return scroll_rect.verticalNormalizedPosition <= 0.01f;
}
public void Scroll_ToBottom()
{
if (scroll_rect == null)
return;
Canvas.ForceUpdateCanvases(); // 방금 넣은 줄까지 Content 높이를 다시 계산
scroll_rect.verticalNormalizedPosition = 0f; // 0 이 맨 아래
}
public void Scroll_ToTop()
{
if (scroll_rect == null)
return;
Canvas.ForceUpdateCanvases();
scroll_rect.verticalNormalizedPosition = 1f; // 1 이 맨 위
}
}
verticalNormalizedPosition 은 스크롤 위치를 0 에서 1 사이로 나타낸 값이고, Unity 문서대로 0 이 맨 아래입니다. Add_Item 은 줄을 넣기 전에 이 값을 먼저 보고, 맨 아래(0.01 이하)였을 때만 Scroll_ToBottom 을 부릅니다. 줄을 넣고 높이가 다시 계산된 뒤에 보면, 맨 아래였어도 값이 0.2 처럼 바뀌어 있어(3절) 맨 아래였는지 알 수 없습니다. 그래서 넣기 전에 봅니다.
Instantiate 로 줄을 붙여도 Layout Group 과 Content Size Fitter 는 그 자리에서 Content 높이를 바꾸지 않고, 화면을 그리기 전에 한꺼번에 다시 계산합니다. 그 사이에 0 을 넣으면 새 줄이 들어오기 전 높이를 기준으로 내려가 버립니다. Canvas.ForceUpdateCanvases() 는 그 계산을 지금 바로 하게 해서, 새 줄까지 포함한 높이로 맨 아래를 잡습니다.
이 스크립트는 Unity 6000.6.3f1 에서 컴파일 오류 없이 들어갔고, 배치 모드에서 Start 를 불러도 예외가 없었습니다. 배치 모드라 손으로 끌어 보는 것은 하지 못했고, 높이 800 짜리 Scroll View 에 높이 100 줄을 넣어 가며 Content 높이와 위치 값을 확인했습니다(3절).
변수(variable)
scroll_rect(ScrollRect) : Scroll View 에 붙은 ScrollRect
content(RectTransform) : 줄이 붙는 Content
prefab_item(GameObject) : 한 줄로 복제할 오브젝트
button_add(Button) : 줄 추가 버튼
button_top(Button) : 맨 위로 버튼
button_bottom(Button) : 맨 아래로 버튼
count_added(int) : 지금까지 넣은 줄 수
함수(function)
Start : 세 버튼에 Add_Item · Scroll_ToTop · Scroll_ToBottom 을 연결합니다.
Add_Item : 맨 아래였는지 먼저 보고, 줄을 붙여 글자를 넣은 뒤 맨 아래였으면 따라 내려갑니다.
Is_AtBottom : 지금 맨 아래를 보고 있는지 돌려줍니다.
Scroll_ToBottom : 높이를 다시 계산하고 맨 아래로 갑니다.
Scroll_ToTop : 높이를 다시 계산하고 맨 위로 갑니다.
3. 줄을 넣어 가며 본 값

줄이 3개(Content 높이 300)일 때는 Content 가 Viewport(800)보다 짧아 스크롤할 것이 없고, 값은 0, Is_AtBottom 은 True 였습니다. 12개(1200)가 되자 Content 가 400 만큼 위로 올라가 마지막 「메시지 12」가 보이는 자리에 있었습니다. ForceUpdateCanvases 없이 13번째 줄을 넣고 바로 0 을 넣었을 때는 Content 높이가 1200 그대로라 위치도 400 에서 움직이지 않았고, 그 뒤 ForceUpdateCanvases 를 부르자 높이가 1300 이 되면서 값이 0.2 로 바뀌었습니다. 맨 아래가 아니라 한 줄(100) 위에 걸려 있다는 뜻입니다. 이때 0 을 다시 넣어야 위치가 500 으로 내려갔습니다.
Scroll_ToTop 으로 맨 위(1)에 둔 채 줄을 넣으면 Content 는 1400 으로 길어졌지만 값은 1 그대로라 화면이 끌려 내려가지 않았고, Scroll_ToBottom 을 부르자 위치 600, 값 0 이 되었습니다.
4. 가로 · 한 번에 · 관성 멈추기
Canvas.ForceUpdateCanvases();
scroll_rect.horizontalNormalizedPosition = 1f; // 가로는 0 이 왼쪽 · 1 이 오른쪽
Canvas.ForceUpdateCanvases();
scroll_rect.normalizedPosition = new Vector2(0f, 1f); // (가로, 세로) 한 번에
scroll_rect.StopMovement(); // 손으로 튕긴 관성을 멈춤
Canvas.ForceUpdateCanvases();
scroll_rect.verticalNormalizedPosition = 0f;
가로 목록은 horizontalNormalizedPosition 을 씁니다. 세로와 달리 0 이 왼쪽 끝, 1 이 오른쪽 끝입니다. normalizedPosition 은 가로 · 세로를 Vector2 로 한 번에 넣고, (0, 0) 이 왼쪽 아래입니다. 손가락으로 세게 튕긴 직후에 버튼으로 맨 아래로 보내면 남은 관성(Inertia) 때문에 다시 밀려날 수 있어서, StopMovement 로 속도를 먼저 0 으로 만든 뒤 위치를 넣습니다. 세 조각 모두 Unity 6000.6.3f1 에서 컴파일만 확인했습니다.
5. 안 될 때 — 자주 하는 실수
새 줄 하나만 가려진 채 멈춘다
줄을 넣고 바로 verticalNormalizedPosition 에 0 을 넣으면, 높이가 아직 새 줄을 넣기 전 그대로라 한 줄 위에서 멈춥니다(3절에서 0.2). 0 을 넣기 전에 Canvas.ForceUpdateCanvases() 를 부릅니다.
줄을 넣어도 스크롤이 안 생긴다
Content 에 Content Size Fitter 가 없으면 줄이 늘어나도 Content 높이는 처음 크기 그대로라, 줄은 Viewport 밖으로 넘쳐 가려지고 스크롤할 길이도 생기지 않습니다. Vertical Layout Group 과 Content Size Fitter(Vertical Fit 은 Preferred Size)를 함께 붙입니다.
0 을 넣었는데 맨 위로 간다
세로는 0 이 맨 아래, 1 이 맨 위입니다. 화면 좌표처럼 위가 0 이라고 생각하면 반대로 넣게 됩니다.
범위 밖 값을 넣었다
1.5 를 넣어 보니 값이 1 로 묶이지 않고 그대로 1.5 가 되어, Content 가 맨 위보다 300 더 내려가 빈 곳이 보이는 자리에 놓였습니다. 그 뒤 프레임에서 Movement Type(Clamped · Elastic)이 되돌리는지는 배치 모드라 보지 못했으니, 처음부터 0 과 1 사이만 넣습니다.
함께 보기: 버튼에 함수를 코드로 다는 법은 유니티 Button onClick 코드로 연결 · interactable 로 잠금 · 연타 막기 (AddListener, 반복문 람다) 에 있습니다.
ScrollRect 동작은 Unity 공식 문서 ScrollRect (uGUI 2.0) · Scroll Rect 와 Unity 6000.6.3f1 에디터(Windows)에서 값을 넣어 본 결과를 기준으로 적었습니다.
'Unity3D > C#' 카테고리의 다른 글
| 유니티 코루틴으로 몇 초 뒤 실행 (WaitForSeconds, StopCoroutine) (0) | 2026.11.03 |
|---|---|
| 유니티 씬 전환 · 로딩 바 (SceneManager.LoadScene, LoadSceneAsync) (0) | 2026.11.02 |
| 유니티 노치 화면 대응 · 안전 영역 (Screen.safeArea) (0) | 2026.10.31 |
| 유니티 안드로이드 뒤로가기 · 앱 종료 (KeyCode.Escape) (0) | 2026.10.30 |
| 유니티 앱 내릴 때 자동 저장 (OnApplicationPause) (0) | 2026.10.29 |