2D Godot 2D 게임 기초
P4 화면 연출

15강해상도와 스케일링

기준 해상도, stretch 모드, 픽셀아트가 뭉개지는 이유.

이 강의 목차 (7)
  1. 문제 — 세상에는 화면 비율이 너무 많다
  2. 1) 기준 해상도 정하기
  3. 2) Stretch Mode — 어떻게 늘릴 것인가
  4. 3) Aspect — 비율이 다르면
  5. 안전 영역(Safe Area)
  6. 픽셀아트가 뭉개지는 이유
  7. 실습 — 여러 비율에서 확인하기
이번 강의의 목표
  • 기준 해상도를 정하고 stretch 모드를 이해한다.
  • 화면 비율이 다른 기기에서 깨지지 않게 만든다.
  • 픽셀아트가 뭉개지는 이유와 해결법을 안다.

문제 — 세상에는 화면 비율이 너무 많다#

여러분이 지금 이 글을 읽는 기기만 봐도 그렇습니다.

기기 대략적인 비율
폴드 커버 화면(접음) 약 21:9 (아주 길쭉)
폴드 메인 화면(펼침) 1:1 (거의 정사각)
일반 스마트폰 20:9
태블릿 4:3
PC 모니터 16:9
울트라와이드 21:9

같은 게임이 이 전부에서 그럴듯해 보여야 합니다. 그리고 폴드는 한 기기 안에서 비율이 바뀝니다. 게임이 돌아가는 도중에요.

해상도 대응의 세 가지 질문
  1. 기준을 무엇으로 잡을 것인가 (기준 해상도)
  2. 화면이 크거나 작으면 어떻게 할 것인가 (stretch mode)
  3. 비율이 다르면 어떻게 할 것인가 (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 같은 작은 값을 쓰나

픽셀아트는 논리 해상도를 작게 잡고 통째로 확대하는 게 정석입니다. 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 남는 쪽을 더 보여준다 가장 자연스러움. 대신 "얼마나 보일지" 예측 불가
폴드 대응에는 expand

폴드는 접었을 때 21:9, 폈을 때 1:1입니다. 가로세로 비율이 극단적으로 바뀝니다.

  • keep — 어느 쪽이든 검은 띠가 크게 생김. 화면 낭비가 심함
  • expand — 접으면 위아래가 더 보이고, 펴면 좌우가 더 보임. 낭비가 없음

대신 "화면 끝에 무엇이 있을지 모른다" 는 전제로 만들어야 합니다.

expand를 쓸 때의 규칙
  1. UI는 가장자리에 앵커로 고정 (18강). 절대 좌표로 두면 어긋납니다.
  2. 게임플레이에 필수인 것은 화면 중앙 안전 영역 안에 둡니다.
  3. 화면 밖에서 적이 갑자기 나타나면 안 되는 게임(경쟁 게임)에는 expand가 부적합합니다. 보이는 범위가 플레이어마다 다르면 불공평하니까요.

안전 영역(Safe Area)#

폴드나 노치가 있는 폰에서는 화면의 일부가 물리적으로 가려집니다. 카메라 홀, 곡면 가장자리, 접히는 부분, 제스처 바 등.

gdscript
func _ready() -> void:
	var safe := DisplayServer.get_display_safe_area()
	var full := DisplayServer.screen_get_size()
	print("전체 ", full, " / 안전 ", safe)
안전 영역 설계 규칙
  • 중요한 UI는 안전 영역 안에
  • 배경·장식은 화면 끝까지 채워도 됨
  • 세로 방향 여백은 최소 24~48px 잡아두기

간단히는 UI 루트 ControlMarginContainer를 씌우고 안전 영역 값만큼 마진을 코드로 넣어주면 됩니다.

픽셀아트가 뭉개지는 이유#

픽셀아트를 넣었는데 흐릿하게 나온다면 원인은 셋 중 하나입니다.

원인 1 — 텍스처 필터가 부드럽게 되어 있다#

프로젝트 설정 → Rendering → Textures → Canvas Textures → Default Texture FilterNearest 로 바꾸세요.

기본값 Linear는 확대할 때 픽셀 사이를 섞어서 흐리게 만듭니다. 사진에는 좋지만 도트에는 최악입니다.

개별 노드마다 바꾸려면 인스펙터의 CanvasItem → Texture → FilterNearest로.

원인 2 — 정수배가 아닌 확대#

320×180을 1366×768 화면에 맞추면 배율이 4.27배입니다. 픽셀 하나가 4.27픽셀이 되어 어떤 픽셀은 4칸, 어떤 픽셀은 5칸이 됩니다. 울퉁불퉁해집니다.

해결: Stretch → Scale Modeinteger 로 설정. 4배만 적용하고 남는 부분은 검은 띠로 둡니다. 선명함을 택하고 화면을 조금 포기하는 것입니다.

원인 3 — 소수점 위치#

캐릭터가 x = 100.3 에 있으면 픽셀이 반 칸 걸쳐서 흐려지거나 떨립니다.

gdscript
# 프로젝트 설정에서 한 번에
# Rendering → 2D → Snap 2D Transforms to Pixel   ✓
# Rendering → 2D → Snap 2D Vertices to Pixel     ✓
픽셀아트 프로젝트 표준 설정 모음
text
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) 코드로 비율 실험#

gdscript
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) 화면 경계 표시하기#

기준 해상도의 경계를 그려두면 무엇이 잘리는지 한눈에 보입니다.

gdscript
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)
직접 바꿔보기
  1. Aspectignore로 바꾸고 창을 가로로 길게 늘여보세요. 캐릭터가 찌그러집니까?
  2. keep으로 바꾸면 검은 띠가 어디에 생깁니까?
  3. expand로 바꾸고 폴드 커버 비율(412×960)로 만들어보세요. 위아래로 무엇이 더 보입니까? 그게 보여도 되는 것입니까?
  4. 픽셀아트 텍스처를 넣고 Default Texture FilterLinearNearest로 바꿔가며 비교하세요.
  • 개발자 — 화면 크기는 코드에서 상수로 박지 말고 get_viewport_rect().size 로 매번 읽으세요. 폴드처럼 실행 중에 바뀌는 기기가 있습니다. get_tree().root.size_changed 시그널로 변화를 감지할 수 있습니다.
  • 기획자 — 대응할 최소/최대 비율을 초기에 문서로 정하세요. "16:9 기준, 21:9까지 대응, 1:1은 UI만 재배치" 같은 식입니다. 나중에 정하면 모든 화면을 다시 검수해야 합니다.
  • 디자이너 — 배경은 기준 해상도보다 20% 크게 그려두면 넓은 화면에서 빈 곳이 안 생깁니다. UI 요소는 가장자리 기준(앵커)으로 배치할 것을 전제로 디자인하세요. "정중앙에서 좌표 몇" 방식은 비율이 바뀌면 무너집니다.
자주 나는 오류

"UI가 화면 밖으로 나간다"Control 노드의 앵커가 좌상단 고정입니다. 18강에서 다룹니다.

"폰에서 화면이 잘린다"Aspectkeep_height인데 세로가 긴 기기라 좌우가 잘립니다. expand를 시도하세요.

"고해상도 폰에서 흐릿하다"Display → Window → Stretch → Scale 값이나 DPI 설정 문제입니다. window/size/viewport_width를 실제 기기 해상도에 가깝게 올려보세요.

"에디터에서는 멀쩡한데 실행하면 다르다" → 에디터 뷰포트는 stretch를 적용하지 않습니다. 반드시 실행해서 확인하세요.

여기까지 확인
  • 기준 해상도를 정했다
  • stretch mode 세 가지 차이를 안다
  • expand가 폴드 대응에 적합한 이유를 안다
  • 픽셀아트가 뭉개지는 세 가지 원인을 안다
  • 여러 창 크기에서 직접 확인했다

다음 강의 — 화면이 정리됐으니 캐릭터가 살아 움직일 차례입니다. 애니메이션과 상태 머신을 봅니다.