15강해상도와 스케일링
기준 해상도, stretch 모드, 픽셀아트가 뭉개지는 이유.
이 강의 목차 (7)
- 기준 해상도를 정하고 stretch 모드를 이해한다.
- 화면 비율이 다른 기기에서 깨지지 않게 만든다.
- 픽셀아트가 뭉개지는 이유와 해결법을 안다.
문제 — 세상에는 화면 비율이 너무 많다#
여러분이 지금 이 글을 읽는 기기만 봐도 그렇습니다.
| 기기 | 대략적인 비율 |
|---|---|
| 폴드 커버 화면(접음) | 약 21:9 (아주 길쭉) |
| 폴드 메인 화면(펼침) | 약 1:1 (거의 정사각) |
| 일반 스마트폰 | 20:9 |
| 태블릿 | 4:3 |
| PC 모니터 | 16:9 |
| 울트라와이드 | 21:9 |
같은 게임이 이 전부에서 그럴듯해 보여야 합니다. 그리고 폴드는 한 기기 안에서 비율이 바뀝니다. 게임이 돌아가는 도중에요.
- 기준을 무엇으로 잡을 것인가 (기준 해상도)
- 화면이 크거나 작으면 어떻게 할 것인가 (stretch mode)
- 비율이 다르면 어떻게 할 것인가 (aspect)
1) 기준 해상도 정하기#
프로젝트 설정 → Display → Window → Size
| 항목 | 의미 |
|---|---|
Viewport Width / Height |
게임이 상상하는 화면 크기. 코드의 좌표가 이 기준 |
Window Width / Height Override |
실제로 띄울 창 크기 (개발 편의용) |
어떤 값을 넣어야 할까요?
| 게임 종류 | 추천 기준 해상도 |
|---|---|
| 픽셀아트 (도트) | 320×180, 384×216, 640×360 |
| 일반 2D (고해상도 아트) | 1280×720, 1920×1080 |
| 모바일 세로 | 720×1280 |
| 이 강좌 | 1280×720 |
픽셀아트는 논리 해상도를 작게 잡고 통째로 확대하는 게 정석입니다. 320×180을 4배 하면 1280×720, 6배 하면 1920×1080으로 정수배가 딱 떨어집니다. 정수배로 확대하면 픽셀이 뭉개지지 않습니다.
2) Stretch Mode — 어떻게 늘릴 것인가#
프로젝트 설정 → Display → Window → Stretch → Mode
disabled — 늘리지 않는다. 화면이 크면 더 넓게 보인다.
→ 큰 모니터의 플레이어가 더 많은 정보를 봄. 경쟁 게임에서는 불공평.
canvas_items — 그리는 것을 확대한다. 논리 해상도는 유지.
→ 가장 무난한 기본값. 고해상도 아트가 선명하게 나옴.
viewport — 작은 화면에 렌더한 뒤 통째로 확대한다.
→ 픽셀아트 전용. 의도적으로 계단 픽셀을 유지.
같은 그림책을 큰 책상에 놓았을 때,
disabled— 책은 그대로, 주변 책상이 더 보임canvas_items— 책을 확대 복사해서 크게 만듦. 글씨도 선명하게 다시 인쇄viewport— 책을 사진 찍어 확대. 픽셀이 커져서 도트가 보임
3) Aspect — 비율이 다르면#
프로젝트 설정 → Display → Window → Stretch → Aspect
| 값 | 동작 | 문제 |
|---|---|---|
ignore |
비율 무시하고 늘림 | 찌그러진다. 쓰지 마세요 |
keep |
비율 유지, 남는 곳은 검은 띠 | 안전하지만 화면이 낭비됨 |
keep_width |
가로 기준. 세로가 길면 위아래를 더 보여줌 | 위아래에 뭐가 더 보임 |
keep_height |
세로 기준. 가로가 길면 좌우를 더 보여줌 | 좌우에 뭐가 더 보임 |
expand |
남는 쪽을 더 보여준다 | 가장 자연스러움. 대신 "얼마나 보일지" 예측 불가 |
폴드는 접었을 때 21:9, 폈을 때 1:1입니다. 가로세로 비율이 극단적으로 바뀝니다.
keep— 어느 쪽이든 검은 띠가 크게 생김. 화면 낭비가 심함expand— 접으면 위아래가 더 보이고, 펴면 좌우가 더 보임. 낭비가 없음
대신 "화면 끝에 무엇이 있을지 모른다" 는 전제로 만들어야 합니다.
- UI는 가장자리에 앵커로 고정 (18강). 절대 좌표로 두면 어긋납니다.
- 게임플레이에 필수인 것은 화면 중앙 안전 영역 안에 둡니다.
- 화면 밖에서 적이 갑자기 나타나면 안 되는 게임(경쟁 게임)에는
expand가 부적합합니다. 보이는 범위가 플레이어마다 다르면 불공평하니까요.
안전 영역(Safe Area)#
폴드나 노치가 있는 폰에서는 화면의 일부가 물리적으로 가려집니다. 카메라 홀, 곡면 가장자리, 접히는 부분, 제스처 바 등.
func _ready() -> void:
var safe := DisplayServer.get_display_safe_area()
var full := DisplayServer.screen_get_size()
print("전체 ", full, " / 안전 ", safe)- 중요한 UI는 안전 영역 안에
- 배경·장식은 화면 끝까지 채워도 됨
- 세로 방향 여백은 최소 24~48px 잡아두기
간단히는 UI 루트 Control에 MarginContainer를 씌우고
안전 영역 값만큼 마진을 코드로 넣어주면 됩니다.
픽셀아트가 뭉개지는 이유#
픽셀아트를 넣었는데 흐릿하게 나온다면 원인은 셋 중 하나입니다.
원인 1 — 텍스처 필터가 부드럽게 되어 있다#
프로젝트 설정 → Rendering → Textures → Canvas Textures → Default Texture Filter
→ Nearest 로 바꾸세요.
기본값 Linear는 확대할 때 픽셀 사이를 섞어서 흐리게 만듭니다.
사진에는 좋지만 도트에는 최악입니다.
개별 노드마다 바꾸려면 인스펙터의 CanvasItem → Texture → Filter 를 Nearest로.
원인 2 — 정수배가 아닌 확대#
320×180을 1366×768 화면에 맞추면 배율이 4.27배입니다. 픽셀 하나가 4.27픽셀이 되어 어떤 픽셀은 4칸, 어떤 픽셀은 5칸이 됩니다. 울퉁불퉁해집니다.
해결: Stretch → Scale Mode 를 integer 로 설정.
4배만 적용하고 남는 부분은 검은 띠로 둡니다. 선명함을 택하고 화면을 조금 포기하는 것입니다.
원인 3 — 소수점 위치#
캐릭터가 x = 100.3 에 있으면 픽셀이 반 칸 걸쳐서 흐려지거나 떨립니다.
# 프로젝트 설정에서 한 번에
# Rendering → 2D → Snap 2D Transforms to Pixel ✓
# Rendering → 2D → Snap 2D Vertices to Pixel ✓픽셀아트 프로젝트 표준 설정 모음
Display → Window → Size
Viewport Width = 384
Viewport Height = 216
Display → Window → Stretch
Mode = viewport (또는 canvas_items)
Aspect = keep (또는 expand)
Scale Mode = integer
Rendering → Textures → Canvas Textures
Default Texture Filter = Nearest
Rendering → 2D
Snap 2D Transforms to Pixel = on
Snap 2D Vertices to Pixel = on중요 — 이 설정들은 프로젝트 초기에 정해야 합니다. 중간에 바꾸면 이미 배치한 모든 좌표와 UI가 어긋납니다.
실습 — 여러 비율에서 확인하기#
1) 창 크기를 바꿔가며 보기#
실행 중인 창의 크기를 마우스로 늘였다 줄였다 해보세요.
Aspect 설정을 바꿔가며 각각 어떻게 반응하는지 봅니다.
2) 코드로 비율 실험#
extends Node
# 폴드 두 화면을 흉내 내는 크기
const PRESETS := {
"폴드 커버(접음)": Vector2i(412, 960),
"폴드 메인(펼침)": Vector2i(880, 900),
"일반 폰": Vector2i(390, 844),
"PC 16:9": Vector2i(1280, 720),
"울트라와이드": Vector2i(1720, 720),
}
var _keys: Array = PRESETS.keys()
var _i := 0
func _unhandled_input(e: InputEvent) -> void:
if not e.is_action_pressed("ui_accept"):
return
_i = (_i + 1) % _keys.size()
var name: String = _keys[_i]
DisplayServer.window_set_size(PRESETS[name])
print("→ ", name, " ", PRESETS[name])스페이스를 누를 때마다 창 크기가 바뀝니다. 각 크기에서 UI가 잘리거나 겹치지 않는지 확인하세요.
3) 화면 경계 표시하기#
기준 해상도의 경계를 그려두면 무엇이 잘리는지 한눈에 보입니다.
extends Node2D
const BASE := Vector2(1280, 720)
func _draw() -> void:
# 기준 해상도 영역
draw_rect(Rect2(Vector2.ZERO, BASE),
Color(0, 1, 0, 0.6), false, 2.0)
# 안전 영역(가장자리 10%)
var m := BASE * 0.1
draw_rect(Rect2(m, BASE - m * 2.0),
Color(1, 1, 0, 0.4), false, 2.0)Aspect를ignore로 바꾸고 창을 가로로 길게 늘여보세요. 캐릭터가 찌그러집니까?keep으로 바꾸면 검은 띠가 어디에 생깁니까?expand로 바꾸고 폴드 커버 비율(412×960)로 만들어보세요. 위아래로 무엇이 더 보입니까? 그게 보여도 되는 것입니까?- 픽셀아트 텍스처를 넣고
Default Texture Filter를Linear↔Nearest로 바꿔가며 비교하세요.
- 개발자 — 화면 크기는 코드에서 상수로 박지 말고
get_viewport_rect().size로 매번 읽으세요. 폴드처럼 실행 중에 바뀌는 기기가 있습니다.get_tree().root.size_changed시그널로 변화를 감지할 수 있습니다. - 기획자 — 대응할 최소/최대 비율을 초기에 문서로 정하세요. "16:9 기준, 21:9까지 대응, 1:1은 UI만 재배치" 같은 식입니다. 나중에 정하면 모든 화면을 다시 검수해야 합니다.
- 디자이너 — 배경은 기준 해상도보다 20% 크게 그려두면 넓은 화면에서 빈 곳이 안 생깁니다. UI 요소는 가장자리 기준(앵커)으로 배치할 것을 전제로 디자인하세요. "정중앙에서 좌표 몇" 방식은 비율이 바뀌면 무너집니다.
"UI가 화면 밖으로 나간다"
→ Control 노드의 앵커가 좌상단 고정입니다. 18강에서 다룹니다.
"폰에서 화면이 잘린다"
→ Aspect가 keep_height인데 세로가 긴 기기라 좌우가 잘립니다. expand를 시도하세요.
"고해상도 폰에서 흐릿하다"
→ Display → Window → Stretch → Scale 값이나 DPI 설정 문제입니다.
window/size/viewport_width를 실제 기기 해상도에 가깝게 올려보세요.
"에디터에서는 멀쩡한데 실행하면 다르다" → 에디터 뷰포트는 stretch를 적용하지 않습니다. 반드시 실행해서 확인하세요.
- 기준 해상도를 정했다
- stretch mode 세 가지 차이를 안다
-
expand가 폴드 대응에 적합한 이유를 안다 - 픽셀아트가 뭉개지는 세 가지 원인을 안다
- 여러 창 크기에서 직접 확인했다
다음 강의 — 화면이 정리됐으니 캐릭터가 살아 움직일 차례입니다. 애니메이션과 상태 머신을 봅니다.