← 모든 글

픽셀 아트의 움직임: iPhone에서의 걷기, 카메라, 문

Pokémon Red, Crystal, Emerald는 Game Boy와 Game Boy Advance의 세 세대에서 한 편씩 고른 게임입니다. 세 게임 모두 16픽셀 셀 하나를 약 59.73헤르츠에서 16프레임에 걸어가며, 초당 3.73셀을 이동합니다. 한 걸음의 어느 부분도 시계에 맡겨져 있지 않습니다. Emerald는 플레이어를 프레임마다 정확히 1픽셀씩 움직이고, 한 걸음마다 내딛는 그림 한 장과 서 있는 그림 한 장을 보여 주며, 같은 프레임에 같은 픽셀만큼 카메라를 스크롤합니다.123 Red와 Crystal은 두 프레임마다 2픽셀씩 움직여 같은 16프레임에 도달합니다.4 Emerald의 문은 그림 네 장을 각각 5프레임씩 보여 주며 열리고, 플레이어는 강제로 한 걸음 안으로 걸어 들어가며, 문이 닫히고, 화면은 계단식 9단계로 페이드됩니다. 다음 맵을 불러오기 시작할 수 있을 때까지 최소 79프레임, 1.32초가 걸립니다.15 Kiradex의 세계는 경과 시간으로 걷고 카메라에 이징을 적용합니다. 그 코드의 모델(휴대폰에서 캡처한 것이 아니라 어디까지나 모델)에 따르면, 60헤르츠에서는 스프라이트가 타일마다 한 번 2픽셀씩 끊기듯 건너뜁니다. 카메라는 60헤르츠로 걸을 때 타일 하나를 지날 때마다 약 1픽셀씩 더 뒤처지고(다섯 번째 타일에서 9픽셀, 달리면 13픽셀), 멈추면 플레이어보다 4에서 9픽셀 모자란 곳에 멈춥니다. 걷기 그림은 자기만의 시계를 따르므로, Emerald의 한 사이클이 2타일을 덮는 데 비해 한 사이클에 3.00타일을 이동합니다.6 Apple은 게임에 30헤르츠와 60헤르츠에 대한 특별한 우선순위를 주고, RealityKit은 보통 60으로 렌더링합니다. 그래서 브리프는 다음과 같습니다. 정수 픽셀 단위로 움직이는 고정 60헤르츠 틱, 걸은 거리로 고르는 걷기 그림(휴대용 게임기의 방식이 아니라 제 제안입니다), 고정된 카메라, 저희 아트로 재현한 Emerald의 문 의식, 선택 사항인 햅틱 이벤트 세 가지, 그리고 120이 아닌 60헤르츠입니다.78 이 글은 원전을 디컴파일에서 측정하고, iPhone 쪽은 Apple의 문서에서 읽고, 브리프는 통과해야 할 검사와 함께 제시합니다.

TL;DR

  • 한 걸음은 속도가 아니라 계약입니다. Emerald의 스텝 테이블은 모두 합계가 16픽셀입니다. 걷기는 프레임당 1픽셀을 16프레임(셀당 268밀리초), 달리기와 파도타기는 2픽셀을 8프레임, 마하자전거의 최고 속도는 4픽셀을 4프레임이며, 고르지 않은 것은 묘기자전거의 2-3-3-2-3-3뿐입니다. Red와 Crystal은 같은 16프레임을 초당 30회 업데이트로 2픽셀씩 건너뛰며 걷습니다.124
  • 다리와 걸음은 서로 맞도록 타이밍이 정해져 있습니다. Emerald는 그림과 걸음을 따로따로 프레임 단위로 세고, 그 수가 서로 맞게 만듭니다. 걷기는 내딛기, 서기, 내딛기, 서기를 그림당 8프레임씩 16프레임 셀 두 개에 걸쳐 보여 줍니다. 더 빠른 이동에서는 그림을 늘리지 않고 표시 시간을 절반으로 줄이므로, 어느 속도에서든 16픽셀마다 발 디딤은 한 번입니다. 서 있는 상태에서 방향을 바꾸는 데는 8프레임(134밀리초), 걷는 중에 방향을 바꾸는 데는 0프레임이 걸리고, 벽을 향해 걸으면 32프레임짜리 부딪힘이 재생됩니다.19102
  • 카메라가 곧 플레이어입니다. Emerald의 카메라는 플레이어의 위치를 복사하고 같은 프레임에 같은 픽셀만큼 스크롤합니다. 뒤처짐도 앞서감도 없으며, 맵 가장자리에서도 멈추지 않습니다. 바깥쪽은 경계 타일로 그려지기 때문입니다. Game Freak은 자전거 앞쪽을 보여 주는 카메라를 작성해 놓고 꺼 둔 채로 출시했습니다.231112
  • 문은 의식입니다. 5프레임짜리 그림 네 장(335밀리초), 16프레임의 강제 한 걸음, 20프레임에 걸쳐 닫히는 문, 그리고 마지막 블렌드가 17번째 프레임에 오고 22번째 프레임에 끝나는 페이드. 맵을 불러올 수 있을 때까지 입력 없이 최소 79프레임, 1.32초입니다. 이는 이 시리즈의 건물 편 글에 나온 그림당 약 84밀리초라는 수치를 확인해 주고, 그 글의 바탕이 된 조사 노트의 “4 ticks each (16 frames, about 0.27 s at 59.7 Hz)”(각 4틱, 16프레임, 59.7Hz에서 약 0.27초)를 바로잡습니다. Crystal은 8프레임 만에 흰색으로 페이드하고, Red는 32프레임에 걸쳐 검은색으로 페이드합니다.1513144
  • iPhone에서는 60으로 고르게 진행하십시오. preferredFrameRateRange(iOS 15.0)는 힌트일 뿐입니다. ProMotion iPhone은 10에서 120헤르츠 사이를 12단계로 오갑니다. 60을 넘으려면 CADisableMinimumFrameDurationOnPhone이 필요합니다. 게임은 “special priority to 30Hz and 60Hz”(30Hz와 60Hz에 대한 특별한 우선순위)를 받고, RealityKit은 “typically limits the refresh rate”(보통 주사율을 제한한다)라고 하여 60으로 묶습니다. 정수 픽셀 걷기는 120에서 얻는 것이 없습니다. 각 픽셀이 두 번의 리프레시 동안 유지될 뿐입니다.1571686
  • 현재의 Kiradex는 측정이 아니라 모델로 봅니다. 코드는 경과 시간으로 초당 4타일을 걷기 때문에, 안정된 60헤르츠 모델에서는 타일당 15프레임이 걸리고 그중 한 프레임에서 2픽셀을 움직입니다. 카메라는 이징 후 반올림되며, 걸을수록 더 뒤처지고(첫 타일 뒤 5픽셀, 다섯 번째 타일 뒤 9픽셀, 달리면 13픽셀), 멈추면 60헤르츠에서는 플레이어로부터 4픽셀, 120헤르츠에서는 9픽셀 떨어진 곳에 멈춥니다. 그림 여섯 장짜리 걷기를 초당 8장으로 재생하므로 한 사이클에 걷기로는 3.00타일, 달리기로는 4.50타일을 이동합니다. 휴대폰에서 측정한 적은 아직 없습니다.176
  • Kiradex가 얻는 것은 출시된 빌드가 아니라 브리프입니다. 1픽셀씩 움직이며 밀린 시간을 처리하는 규칙을 명시한 고정 60헤르츠 틱, 거리로 고르는 걷기 그림, 정수 픽셀로 고정한 카메라, 계단식 페이드를 포함한 문 시퀀스 전체, 눈에 보이게 거부되는 걸음, 선택 사항인 햅틱 이벤트 세 가지, 120헤르츠를 요청하지 않음. 그리고 각 항목마다 모션 로그나 캡처, 스크립트로 검증할 수 있는 검사가 붙어 있습니다.

1. Emerald의 걷기: 16프레임에 16픽셀

이 시리즈의 첫 두 글은 픽셀 세계와 그 안의 사람들이 어떻게 보이는지를 측정했고, 세 번째 글은 그 건물들을 측정했습니다. 이 글이 측정하는 것은 시간입니다. 한 프레임에 몇 픽셀을 가는지, 한 걸음에 몇 프레임이 걸리는지, 어느 프레임에 어느 그림이 보이는지, 방향 전환과 문과 페이드에 각각 얼마나 걸리는지, 그리고 그동안 카메라가 무엇을 하는지입니다. 게임은 앞선 글들과 같은 방식으로 읽었습니다. 출시된 Game Boy와 Game Boy Advance 게임을 pret이 디컴파일한 것을, 앞선 글들이 사용한 커밋으로 읽었습니다. pokered d2704a6, pokecrystal 5beda23, pokeemerald 731ad5b입니다.18 개수를 센 것은 작은 스크립트 다섯 개이며, 각각의 이름과 저장된 출력을 주석에 적어 두었습니다.

다른 모든 것이 프레임 단위이므로, 숫자 하나를 먼저 짚겠습니다. Game Boy Advance는 2^24헤르츠 클럭의 280,896사이클 동안 한 프레임을 그립니다. 초당 59.7275프레임, 프레임당 16.743밀리초입니다. GBATEK은 이를 “ca. 59.737 Hz”(약 59.737Hz)로 반올림합니다.19 초대 Game Boy의 4,194,304헤르츠 클럭을 프레임당 70,224도트로 나누어도 같은 59.7275가 나오며, Pan Docs는 “@ 59.7 fps”라고 적습니다.19 이 글의 밀리초 값은 모두 59.7275로 환산했습니다.19

모든 속도는 합이 16인 테이블입니다

Emerald는 걷는 캐릭터를 속도 곱하기 시간으로 움직이지 않습니다. 필드의 각 속도는 프레임별 픽셀 이동량을 나열한 테이블이며, 소스는 모든 테이블의 공통점을 이렇게 밝힙니다. “Over the course of the step animation, these sum to 16 pixels (one full metatile).”(한 걸음 애니메이션 동안 이 값들의 합은 16픽셀, 즉 메타타일 하나가 된다)2 sStep1Funcs는 1픽셀 이동 16개, sStep2Funcs는 2픽셀 이동 8개, sStep4Funcs는 4픽셀 4개, sStep8Funcs는 8픽셀 2개이고, sStep3Funcs만 예외로 Step2, Step3, Step3, Step2, Step3, Step3입니다.2 이동 소스에 대해 스크립트로 세어 보면 다음과 같습니다.

속도 상수 프레임당 픽셀 셀당 프레임 초당 셀 셀당 밀리초 용도
MOVE_SPEED_NORMAL 매 프레임 1 16 3.73 267.9 걷기, NPC 걷기
MOVE_SPEED_FAST_1 매 프레임 2 8 7.47 133.9 달리기, 파도타기, 얼음 미끄러짐
MOVE_SPEED_FAST_2 2, 3, 3, 2, 3, 3 6 9.95 100.5 묘기자전거, 물살
MOVE_SPEED_FASTER 매 프레임 4 4 14.93 67.0 최고 속도의 마하자전거
MOVE_SPEED_FASTEST 8, 8 2 29.86 33.5 미끄러지는 이동 액션

모든 행의 출처: src/event_object_movement.c에 대한 measure_gen3_motion.py.1

기억해 둘 숫자는 걷기입니다. 매 프레임 1픽셀, 셀당 16프레임, 초당 3.73셀, 셀당 268밀리초이며, PlayerWalkNormal과 걷는 모든 NPC가 사용합니다.1 B 버튼으로 달리는 것은 PlayerRun이고, 파도타기는 PlayerWalkFast인데, 소스에는 “same speed as running”(달리기와 같은 속도)이라는 주석이 붙어 있습니다. 얼음 위 미끄러짐도 같은 함수를 호출합니다. 셋 모두 프레임당 2픽셀, 셀당 8프레임, 초당 7.47셀입니다.110 이벤트 장면용으로 느린 걷기 UpdateWalkSlowAnim도 있는데, 타이머 값이 짝수일 때 1픽셀씩 움직이므로 셀당 31에서 32프레임이 걸립니다.1

픽셀이 고르지 않은 속도는 묘기자전거뿐입니다. 6프레임에 걸쳐 2, 3, 3, 2, 3, 3, 평균 프레임당 2.67픽셀, 초당 9.95셀입니다.1 이 속도에는 AcroBikeTransition_Moving에서 PlayerRideWaterCurrent를 거쳐 도달하며, 물살도 같은 함수를 씁니다.120 제 해석으로는, 이 불균일함은 16의 약수가 아닌 속도를 택한 대가입니다. 프레임당 3픽셀이면 셀을 지나쳐 버리므로, 테이블은 2와 3을 번갈아 놓아 정확히 16에 착지합니다. 나머지 속도는 모두 셀의 약수입니다.

마하자전거는 프레임 단위가 아니라 걸음 단위로 가속합니다. sMachBikeSpeedCallbacks는 PlayerWalkNormal, PlayerWalkFast, PlayerWalkFaster이고, bikeFrameCounter는 걸음마다 1씩 올라 상한 2에 이릅니다. 그래서 정지 상태에서의 첫 걸음은 16프레임, 두 번째는 8프레임, 그 이후는 모두 4프레임이 걸립니다. 프레임당 4픽셀, 초당 14.93셀입니다.120 자전거가 두 속도 사이에 머무는 일은 없습니다. 걸음 하나하나가 테이블 중 하나를 끝까지 실행한 것이고, 속도는 셀 경계에서만 바뀝니다.

필드 이동 속도를 초당 16픽셀 셀 수로 나타낸 가로 막대 그래프. Red, Crystal, Emerald의 걷기는 3.73. Red의 자전거, Crystal의 자전거, Emerald의 달리기와 파도타기는 7.47. Emerald의 묘기자전거는 9.95, 마하자전거 최고 속도는 14.93. 모델로 구한 Kiradex의 현재 걷기는 4.00, 달리기는 6.00. 브리프의 걷기는 3.75, 달리기는 7.50.

Red, Crystal, Emerald에서 측정한 모든 이동 방식은 16픽셀 셀당 정수 개의 프레임입니다. Kiradex의 수치는 코드의 모델이며 캡처가 아닙니다.14621

16픽셀마다 발 디딤 한 번

걷기 애니메이션은 내딛기, 서기, 내딛기, 서기입니다. sAnim_GoSouth는 ANIMCMD_FRAME(3, 8), (0, 8), (4, 8), (0, 8)입니다. 그림 3을 8프레임, 서 있는 그림 0을 8프레임, 그림 4를 8프레임, 다시 서 있는 그림을 8프레임, 모두 32프레임이며 이는 걷기로 셀 두 개에 해당합니다.91 표시 시간은 정확합니다. sprite.c는 프레임 길이에서 1을 뺀 값을 지연 카운터에 넣고 0까지 세어 내려가므로, 길이 8인 프레임은 화면에 8프레임 동안 표시됩니다.91 따라서 한 걸음마다 내딛는 그림 한 장과 서 있는 그림 한 장이 보입니다. 또 SetStepAnimHandleAlternation은 새 걸음을 매번 사이클의 반대쪽 절반에서 시작하므로(animPos = {1, 3, 0, 2}), 플레이어가 멈췄다가 다시 걸어도 왼발과 오른발이 걸음마다 번갈아 나옵니다.2 이 시리즈의 첫 글은 같은 사이클을 아트 쪽에서 “step, stand, step, stand at eight ticks each, which is the bob everyone remembers.”(각 8틱씩 내딛기, 서기, 내딛기, 서기. 모두가 기억하는 그 들썩임이다)라고 설명했습니다.22

더 빠른 이동은 그림은 그대로 두고 표시 시간을 줄입니다. GoFast는 각 그림을 4프레임(16프레임 사이클), GoFaster는 2프레임(8프레임), GoFastest는 1프레임(4프레임) 보여 줍니다.1 달리기에는 전용 그림이 있고, 표시 시간이 고르지 않습니다. sAnim_RunSouth는 (12, 5), (9, 3), (13, 5), (9, 3)으로 16프레임 사이클입니다.19

두 테이블을 나란히 놓으면 설계가 보입니다. 걷기의 32프레임 사이클은 16프레임 셀 두 개를, 달리기의 16프레임 사이클은 8프레임 셀 두 개를, 마하자전거의 8프레임 GoFaster 사이클은 4프레임 셀 두 개를 덮습니다.19 어느 속도에서든 16픽셀마다 발 디딤이 한 번 있습니다.1 이것은 카운터 하나가 아니라 시계 두 개입니다. 그림은 sprite.c에서 각 프레임 길이로부터 채워지는 animDelayCounter가 다 떨어지면 넘어갑니다. 걸음은 NpcTakeStep이 스프라이트의 sTimer로 속도 테이블을 프레임마다 한 항목씩 짚으면서 진행됩니다. 그리고 SetStepAnimHandleAlternation이 걸음이 시작될 때마다 그 이동 방식의 애니메이션을 고릅니다.92 둘을 맞춰 주는 것은, 둘 다 같은 프레임 단위로 세어지고 이동 방식마다 길이가 맞도록 골라져 있다는 점입니다. 제 해석으로는 바로 이것이 따라 할 점입니다. 각 걸음의 그림이 그 걸음과 정확히 같은 프레임 수만큼 지속되므로, 그림의 리듬이 움직임에서 어긋날 수가 없습니다. 다만 이 테이블에도, 제 모델에도 디딘 발이 지면의 어디에 놓이는지를 측정하는 것은 없으므로, 그 이상은 주장하지 않습니다.

방향 전환, 부딪힘, 입력을 읽는 시점

걸음은 일단 시작하면 끝까지 갑니다. 패드는 걸음이 끝날 때 읽히므로, 방향을 계속 누르고 있으면 걸음이 틈 없이 이어지고, 걷는 중에 방향을 바꾸는 데는 비용이 들지 않습니다.10 서 있는 상태에서는 다릅니다. CheckMovementInputNotOnBike는 새 방향이 현재 바라보는 방향과 다르고 플레이어가 아직 움직이고 있지 않을 때만 TURN_DIRECTION을 반환하며, 이때 PlayerTurnInPlace가 빠른 제자리걸음을 재생하는데, InitMoveInPlace는 그 길이를 8프레임으로 정합니다. 제자리에서 도는 데 134밀리초입니다.101 이 덕분에 플레이어는 표지판이나 사람 쪽으로 한 걸음 내딛지 않고도 그쪽을 바라볼 수 있습니다. 벽을 향해 걸으면 대신 느린 제자리걸음이 32프레임, 536밀리초 동안 부딪히는 소리와 함께 재생됩니다. 거부된 걸음은 조용히 삼켜지는 것이 아니라 보여집니다.110 보통 속도와 더 빠른(faster) 제자리걸음은 각각 16프레임(268밀리초)과 4프레임(67밀리초)입니다.1

제 해석으로는, 이 네 숫자가 그리드 게임에서 말하는 “반응성”의 대부분입니다. 세계는 셀의 일부만큼 움직이는 일이 없으므로 플레이어의 의도는 걸음 단위로 표현되고, 지연은 진행 중인 걸음의 남은 부분뿐입니다. 걸을 때 최대 268밀리초, 달릴 때 134밀리초입니다.1 서 있는 상태에서의 방향 전환은 지연이 아니라, 눈에 보이는 결과를 가진 별개의 동작입니다.

2. Emerald의 카메라, 문, 페이드, 흔들림

카메라에는 뒤처짐도 앞서감도 없습니다

Emerald의 카메라는 플레이어를 따라가는 보이지 않는 스프라이트입니다. CameraObject_UpdateMove는 따라가는 스프라이트의 x와 y를 복사하고, 이전 프레임과의 차이를 sCamera_MoveX와 sCamera_MoveY에 저장합니다. CameraUpdateCallback이 그 차이를 CameraUpdate에 넘기고, CameraUpdate는 정확히 그 픽셀만큼 맵을 스크롤합니다.212 필드 루프는 매 프레임 RunTasks(); AnimateSprites(); CameraUpdate(); UpdateCameraPanning();을 이 순서로 실행합니다. AnimateSprites는 스프라이트 콜백을 슬롯 순서대로 실행하고, 카메라 오브젝트는 플레이어 다음에 만들어지므로(InitPlayerAvatar 다음에 InitCameraUpdateCallback(gPlayerAvatar.spriteId)), 카메라는 플레이어가 바로 그 프레임에 도달한 위치를 읽습니다.3 이 순서는 실행해 본 것이 아니라 코드를 따라가며 읽은 것이므로 측정이 아니라 해석입니다. 하지만 거기서 나오는 결과는 단순합니다. 플레이어의 이동과 스크롤은 같은 프레임에, 같은 픽셀만큼 일어납니다. 화면에서 플레이어는 전혀 움직이지 않고, 그 아래로 세계가 미끄러져 갑니다.

Itay Keren은 GDC 2015에서 횡스크롤 게임의 카메라에 대해 강연하면서 이것을 position-locking(위치 고정)이라고 불렀습니다. 카메라가 플레이어에게 붙어 있으면서 “keeping the car in focus at all times and the camera motion completely predictable.”(차를 항상 초점 안에 두고 카메라의 움직임을 완전히 예측 가능하게 한다)는 것입니다.23 Emerald는 그의 정의가 열어 둔 한 가지, 즉 맵 가장자리에서 무슨 일이 일어나는지에 답을 더합니다. 멈추지 않습니다. 맵 레이아웃 바깥의 셀은 GetBorderBlockAt을 통해 읽히는데, 이 함수는 레이아웃의 2×2 경계 메타타일을 반복하고 MAPGRID_IMPASSABLE 표시를 붙여 돌려줍니다. 그래서 화면은 플레이어를 같은 화면 위치에 둔 채 바깥을 경계의 나무나 물로 채웁니다.11 대안인 Keren의 edge-snapping(가장자리 스냅)은 “simply snaps the camera to the edge of the level, allowing the character to move away from its anchor point.”(카메라를 레벨 가장자리에 붙여 두고, 캐릭터가 기준점에서 벗어나 움직이게 한다)입니다.23 Emerald는 그림을 그려서 그것이 필요 없는 길을 택했습니다.

Game Freak이 작성하고 꺼 둔 카메라

field_camera.c에는 자전거용으로 완성된 앞보기 카메라가 들어 있습니다. CameraPanningCB_PanAhead는 세로 팬을 업데이트마다 2픽셀씩, 쉬고 있을 때의 값 32에서 진행 방향에 따라 72 또는 마이너스 8 쪽으로 옮깁니다. 어느 쪽이든 40픽셀이며, 플레이어가 가려는 곳으로 카메라가 앞서 나가는 방식입니다.12 이 코드는 gUnusedBikeCameraAheadPanback이 참일 때만 실행되는데, 이 변수에는 (bike.c에서) FALSE만 대입되고, 해당 분기에는 “this code is never reached.”(이 코드에는 절대 도달하지 않는다)라는 주석이 달려 있습니다.1220 Keren의 용어로는 dual-forward-focus(양방향 전방 초점) 또는 target-focus(목표 초점)이며, 그의 규칙 “When you walk left, you want to see more to the left.”(왼쪽으로 걸을 때는 왼쪽을 더 보고 싶다)를 따르는 카메라입니다.23 꺼 둔 이유가 무엇이든, 출시된 게임은 마하자전거의 프레임당 4픽셀에서도 카메라를 고정해 두었습니다.1

문은 16프레임이 아니라 20프레임에 열립니다

field_door.c의 문 테이블은 각 그림이 4프레임씩 걸리는 것처럼 읽힙니다. sDoorOpenAnimFrames는 {4, -1}, {4, 0}, {4, 0x100}, {4, 0x200}으로, 닫힌 그림 다음에 열린 그림 세 장이 이어집니다.24 그러나 AnimateDoorFrame은 카운터가 0일 때 그리고, 카운터가 그 항목의 시간과 같아질 때 다음으로 넘어가므로, 각 그림은 다섯 번의 업데이트 동안 유지됩니다. 그림당 84밀리초, 네 장에 335밀리초입니다.241 이 시리즈의 건물 편 글도 다섯 번의 업데이트, 약 84밀리초라는 같은 수치를 제시했습니다.13 그 글의 바탕이 된 조사 노트는 “4 ticks each (16 frames, about 0.27 s at 59.7 Hz)”(각 4틱, 16프레임, 59.7Hz에서 약 0.27초)라고 잘못 적었고, 앱 자체의 문 코드에 있는 주석도 마찬가지로, 문이 “at about Emerald’s four ticks a frame”(Emerald의 그림당 4틱 정도로) 열린다고 적은 뒤 첫 그림을 70밀리초 동안 보여 줍니다. 둘 다 이 글과 브리프에서 바로잡습니다.1417

출입 자체를 맡는 Task_DoDoorWarp는 다섯 가지 상태로 이루어지며, 어느 것도 건너뛸 수 없습니다. 다른 오브젝트를 멈추고, 문 소리를 내고, 플레이어 위쪽의 문을 엽니다. MOVEMENT_ACTION_WALK_NORMAL_UP 걸음을 강제해 출입구로 들어가게 합니다. 플레이어가 멈추면 문을 닫고 플레이어를 숨깁니다. 문 태스크가 끝나면 음악과 화면을 페이드합니다. 맵을 불러옵니다.25 나올 때는 의식을 거꾸로 진행합니다. Task_ExitDoor는 문을 이미 열린 상태로 보여 주고, 페이드인을 기다리고, WALK_NORMAL_DOWN 걸음을 한 번 강제하고, 문을 닫은 다음에야 조작을 돌려줍니다.25

위의 개수와 아래에 나오는 페이드 추적을 더하면, 문이 열리는 데 20프레임, 걸음에 16프레임, 문이 닫히는 데 20프레임이 걸립니다. 이어지는 페이드는 17번째 프레임에 마지막 블렌드를 하므로, 화면은 최소 73프레임, 1.22초 뒤에 완전히 어두워집니다. 맵은 페이드가 22번째 프레임에 비활성화되고 Task_WarpAndLoadMap이 그것을 확인하고 넘어간 뒤에야 불러오기 시작할 수 있습니다. 최소 79프레임, 1.32초입니다.525 문의 각 단계 사이의 상태 전환과 음악이 멈추기를 기다리는 시간은 프레임을 늘릴 수만 있으므로, 두 수치 모두 하한입니다.5

밀리초 단위 타임라인. Emerald의 행: 0에서 335까지 문이 열리고, 603까지 안으로 한 걸음, 938까지 문이 닫히고, 1,306에 비활성화될 때까지 페이드아웃, 맵 불러오기는 빨라도 1,323. Kiradex의 행: 반쯤 열린 그림이 70밀리초, 문이 제거되는 1,470까지 열린 그림, 220에 장소가 바뀌며, 걸음도 페이드도 없음.

Emerald의 출입은 프레임 수에서 구한 하한입니다. Kiradex의 행은 앱의 문 코드에서 읽은 것이며, 휴대폰에서 시간을 잰 것이 아닙니다.151721

페이드는 9단계입니다

Emerald의 워프 페이드는 매끄러운 경사가 아닙니다. BeginNormalPaletteFade는 0에서 16까지 움직이는 블렌드 계수에 2라는 스텝을 설정하고, UpdateNormalPaletteFade는 한 번의 호출에서 배경 팔레트를, 다음 호출에서 스프라이트 팔레트를 블렌드한 다음 계수를 한 스텝 올리며, IsSoftwarePaletteFadeFinishing이 끝에 다섯 번의 호출을 덧붙입니다.26 각 호출이 어느 프레임에 떨어지는지는 누가 호출하느냐에 달려 있습니다. 문 태스크는 RunTasks 안에서 페이드를 시작하고, BeginNormalPaletteFade는 스스로 업데이트를 한 번 실행한 뒤 결과를 곧바로 팔레트 메모리에 복사하고, 그렇지 않으면 다음 업데이트가 수직 블랭크를 기다리게 만들었을 플래그를 지웁니다. 같은 프레임의 뒷부분에서 OverworldBasic이 UpdatePaletteFade를 다시 호출합니다.25263 그래서 첫 프레임에는 업데이트가 두 번(둘 다 레벨 0), 이후 프레임에는 한 번씩 일어납니다. 이 일정으로 palette.c를 Python으로 옮기고 태스크의 프레임을 0으로 세면, 레벨은 0, 2, 4 하는 식으로 16까지 9단계가 됩니다. 배경 팔레트는 프레임 1에 레벨 2, 프레임 15에 레벨 16에 도달하고, 스프라이트 팔레트는 매번 한 프레임 뒤처지며, 마지막 블렌드는 프레임 16(프레임 0부터 세어 285밀리초), 페이드가 비활성화되는 것은 프레임 21(368밀리초)입니다.5 어떤 프레임에서 계산된 블렌드는 그 프레임을 끝내는 수직 블랭크에 화면에 반영됩니다. 이 포트는 이전에 진행 중인 페이드가 없었고 일반 필드 업데이트가 돌고 있다고 가정합니다. 비, 눈, 안개, 그늘, 가뭄이 활성화되어 있으면 페이드아웃은 날씨로 물든 색에서 같은 방식으로 진행되지만(FadeScreen은 물든 버퍼를 먼저 복사한 뒤 같은 BeginNormalPaletteFade를 호출합니다), 페이드인은 날씨 코드가 실행하므로 그 경로는 시뮬레이션하지 않았습니다.5

문 태스크의 프레임 0부터 시작하는 Emerald의 검은색 페이드 계단 차트. 배경 팔레트와 스프라이트 팔레트의 블렌드 계수가 한 프레임 어긋난 채, 16분의 2씩 여덟 번의 스텝으로 9단계를 올라간다. 프레임 1부터 프레임 16까지 오르고, 프레임 21에 페이드가 비활성화된다.

16분의 2 간격의 9단계, 배경과 스프라이트는 한 프레임 차이: 브리프가 베일 하나로 옮겨 쓰는 경사입니다.521

워프는 검은색으로 페이드하지만, 예외가 한 갈래 있고 그 예외에는 방향이 있습니다. WarpFadeOutScreen은 맵 유형 쌍에 대해 GetMapPairFadeToType에 묻고, WarpFadeInScreen은 GetMapPairFadeFromType에 묻고, 각각 답이 참이면 흰색으로, 거짓이면 검은색으로 FadeScreen을 호출합니다.25 둘 다 sTransitionTypes에서 그 쌍을 찾습니다. 이 테이블의 16행은 모든 맵 유형과 MAP_TYPE_UNDERGROUND 사이의 출입 전부이고, 앞의 함수는 행의 진입 플래그를, 뒤의 함수는 탈출 플래그를 돌려주는데, 이 플래그는 동굴로 들어가는 행과 동굴에서 나오는 행에서만 참입니다.27 따라서 동굴에 들어갈 때는 화면이 흰색으로 페이드아웃했다가 검은색에서 페이드인하고, 동굴에서 나올 때는 검은색으로 페이드아웃했다가 흰색에서 페이드인합니다. 각 행에는 전용 동굴 전환 루틴도 지정되어 있는데, 저는 그것을 추적하지 않았습니다.27 더 느린 흰색 페이드인, 지연 8의 FadeInFromWhite는 86 또는 87프레임, 1.44에서 1.46초 뒤에 비활성화됩니다. 이 페이드는 맵 불러오기 콜백에서 시작되는데 그 첫 프레임은 추적하지 않았으므로, 포트는 두 경우를 모두 제시합니다.525

흔들림은 스크립트로 짠 팬입니다

Emerald의 화면 흔들림은 물리 효과가 아니라 스크립트 명령입니다. ShakeCamera는 스크립트 변수 네 개에서 세로 팬, 가로 팬, 흔들림 횟수, 흔들림 사이의 프레임 수를 읽고, 흔들 때마다 팬의 부호를 뒤집습니다.28 게임의 스크립트 전체에 9개 파일, 24번의 호출이 있으며, 스크립트로 전부 세었습니다.29

세로 px 가로 px 흔들림 횟수 간격 프레임 호출 수 길이
1 1 8 5 10 40프레임, 670 ms
1 1 8 3 4 24프레임, 402 ms
1 2 8 5 3 40프레임, 670 ms
2 2 8 5 2 40프레임, 670 ms
0 3 4 2 2 8프레임, 134 ms
1 3 20 5 1 100프레임, 1,674 ms
1 1 16 3 1 48프레임, 804 ms
1 1 32 2 1 64프레임, 1,072 ms

출처: data/**/*.inc 전체에 대한 measure_gen3_shake.py.29

24번 중 10번은 같은 흔들림입니다. 상하좌우 1픽셀, 여덟 번 뒤집기, 5프레임 간격, 670밀리초.29 가장 큰 것은 가로 3픽셀, 가장 긴 것은 100프레임, 1.67초입니다.29 건물 편 글에서 다룬 엘리베이터의 흔들림(이동한 층수에 따라 늘어나는 횟수만큼 3프레임마다 흔들림)은 별도의 루틴이라 이 집계에 들어 있지 않습니다.13 제 해석으로는 교훈은 이 절제에 있습니다. Emerald의 흔들림은 1, 2픽셀이며, 스크립트가 중요하다고 판단한 사건에만 쓰이고, 발걸음이나 문에는 절대 쓰이지 않습니다.

3. Red와 Crystal: 초당 30회 업데이트로 같은 걸음

Red는 두 프레임마다 2픽셀씩 움직입니다

Pokémon Red에는 스텝 테이블이 없습니다. 필드 루프 OverworldLoop는 DelayFrame을 호출한 뒤 OverworldLoopLessDelay로 넘어가고, 거기서 다시 DelayFrame을 호출하므로 세계는 두 프레임에 한 번 업데이트됩니다.30 걸음이 시작되면 wWalkCounter가 8로 설정되고, 루프를 한 번 돌 때마다 AdvancePlayerSprite가 그 값을 하나 줄이면서 배경 레지스터 hSCX와 hSCY를 걸음 벡터를 왼쪽으로 한 번 시프트한 만큼, 즉 2픽셀 스크롤합니다.30 2픽셀씩 8번이면 16프레임에 16픽셀로, Emerald와 같은 268밀리초, 초당 3.73셀을 초당 30회 업데이트로 2픽셀씩 건너뛰며 이동합니다.430 자전거는 루프 한 번에 한 번 더 전진하는 방식입니다. DoBikeSpeedup이 AdvancePlayerSprite를 한 번 더 호출하므로(사이클링로드에서 위, 왼쪽, 오른쪽 중 하나를 누르고 있을 때는 제외) 걸음 하나가 8프레임이 됩니다.430

Red의 걷기 그림은 루프 네 번, 즉 8프레임마다 바뀌며 서기, 내딛기, 서기, 뒤집은 내딛기의 네 장을 순환합니다. 그래서 Red도 걸음마다 내딛는 그림을 한 장 보여 줍니다.3122 UpdatePlayerSprite는 루프마다 애니메이션 내부 카운터를 올리고, 그것이 4에 이르면 그림을 넘깁니다.3122

Red의 방향 전환은 루프 한 번, 2프레임입니다. 서 있는 상태에서 새 방향이 들어오면 바라보는 방향을 기록하고 걸음 없이 루프로 돌아갑니다.30 180도 회전에는 코드상 중간 방향이 있지만, 소스 자체의 주석에 따르면 아무도 그것을 보지 못합니다. “It is unlikely for it to ever be visible because DelayFrame is called at the start of OverworldLoop.”(OverworldLoop 시작 부분에서 DelayFrame이 호출되므로 이것이 보일 가능성은 낮다)30 이는 코드에서 읽은 것이며 에뮬레이터에서 실행해 보지는 않았습니다.

워프는 소리와 페이드뿐이고 문은 그려지지 않습니다. PlayMapChangeSound는 타일이 문(타일 $0b)이면 SFX_GO_INSIDE를, 아니면 SFX_GO_OUTSIDE를 재생하고, 이어서 GBFadeOutToBlack이 팔레트 네 개를 써서 각각 8프레임씩 유지합니다. 32프레임, 536밀리초입니다.432 Red의 페이드인은 추적하지 않았습니다.

Crystal도 두 프레임마다 업데이트합니다

Crystal은 다른 메커니즘으로 Red의 리듬을 유지합니다. MaxOverworldDelay는 db 2이고, VBlank 핸들러가 wOverworldDelay를 세어 내려가므로 맵 오브젝트는 두 프레임에 한 번 업데이트됩니다.33 StepVectors는 걷기에 2픽셀 업데이트 여덟 번을, 자전거에 4픽셀 업데이트 네 번을 주므로 셀당 16프레임과 8프레임으로 Red, Emerald와 같습니다.4 느린 걸음은 1픽셀 업데이트 열여섯 번으로 32프레임, 초당 1.87셀입니다.433

Crystal의 문은 흰색으로, 그것도 빠르게 페이드합니다. MapSetupScript_Door는 FadeOutToWhite로 시작하고 MapSetupScript_Warp는 FadeInFromWhite로 끝납니다. 둘 다 2프레임 간격의 팔레트 스텝 네 번으로, 한쪽 방향에 8프레임, 134밀리초입니다.434 방향 전환은 네 부분으로 된 스텝 함수 StepFunction_Turn이며, 각 부분은 다음 부분으로 이어져 내려갑니다. 첫 업데이트에서 .init1이 OBJECT_STEP_DURATION을 2로 설정하고 .step1로 넘어가며, .step1이 이를 1로 세어 내립니다. 두 번째 업데이트에서 .step1이 0까지 세고, 새 방향을 기록하고 다시 2를 설정하는 .init2를 지나 .step2로 넘어가며, .step2가 이를 1로 세어 내립니다. 세 번째 업데이트에서 .step2가 0에 이르고 오브젝트를 STEP_TYPE_FROM_MOVEMENT로 돌려줍니다.33 명령어 단위로 재현하면 이 루틴은 업데이트 세 번, 즉 업데이트당 2프레임으로 6프레임이 걸리고, 새 방향은 두 번째 업데이트에서 기록됩니다. 이는 루틴 자체에 대한 것이며 코드에서 읽었습니다. 버튼을 누른 시점부터 루틴이 시작되기까지의 시간은 추적하지 않았습니다.33

세 세대, 세 가지 페이드

Red Crystal Emerald
문 그려지지 않음. 소리는 타일로 결정 그려지지 않음 5프레임짜리 그림 4장(335 ms), 미닫이 또는 여닫이 소리
문 안으로 문에 올라서는 걸음 문에 올라서는 걸음 위로 강제 한 걸음(16프레임), 이후 문이 닫힘
페이드아웃 검은색으로, 각각 8프레임씩 유지하는 팔레트 4개: 32프레임(536 ms) 흰색으로, 2프레임 간격 스텝 4번: 8프레임(134 ms) 검은색으로(동굴에 들어갈 때는 흰색으로), 9단계, 문 태스크의 프레임을 0으로 세어 마지막 블렌드는 프레임 16(285 ms), 프레임 21에 비활성화
페이드인 추적하지 않음 흰색에서, 8프레임 검은색에서(동굴에서 나올 때는 흰색에서), 같은 9단계
문 밖으로 아래로 강제 한 걸음 강제 한 걸음 열린 문을 보여 주고, 아래로 강제 한 걸음, 문이 닫히고, 그다음 조작

출처: 1절부터 3절까지의 집계.14525 Red가 문에서 나올 때의 강제 걸음 PlayerStepOutFromDoor는 건물 편 글에서 가져왔습니다.13

세 게임이 모두 일치하는 행이야말로 남길 가치가 있는 행입니다. 맵 사이의 페이드, 플레이어를 문턱 너머로 옮기는 강제 걸음, 그리고 의식 동안 입력을 받지 않는 것입니다. 페이드 길이는 네 배나 차이가 나므로, 제 해석으로는 길이는 규범이 아니라 디자인상의 선택입니다. 그려진 문을 중심으로 짜인 것은 Emerald의 9단계 페이드이고, Kiradex도 문을 그리므로,13 브리프는 이것을 택합니다.

Red, Crystal, Emerald는 Game Boy와 Game Boy Advance라는 서로 다른 두 기기의 세 세대에서 한 편씩 고른 게임인데, 모두 같은 계약에 정착했습니다. 한 걸음은 16픽셀이고, 약 59.73헤르츠에서 16프레임이 걸리며, 일단 시작하면 중단할 수 없습니다.14 Red는 루프가 한 번에 두 프레임을 기다리기 때문에 2픽셀씩 건너뛰며 거기에 도달하고, Crystal은 같은 2프레임 지연과 2픽셀 벡터로, Emerald는 전체 프레임 레이트와 1픽셀 이동으로 도달합니다. 달리기, 파도타기, 자전거는 8프레임이나 4프레임으로 하는 같은 계약입니다.14 사람들이 이 게임들이 “레일 위에 있는” 느낌이라고 말할 때, 저는 그 레일이 바로 이것이라고 생각합니다. 걸음과 그림과 카메라가 모두 같은 프레임 단위로 세어지고, 길이가 서로 맞도록 골라져 있어서 어긋날 수가 없습니다.

4. 현대의 참고 사례: Celeste의 카메라, Keren의 어휘, 그리고 휴대폰 이식판

카메라를 부드럽게 해야 할 때와 그러지 말아야 할 때

Emerald의 고정 카메라는 Itay Keren이 정리한 어휘 속 하나의 답입니다. 그 어휘는 “Scroll Back: The Theory and Practice of Cameras in Side-Scrollers”(횡스크롤 게임 카메라의 이론과 실제)에 담겨 있습니다. 이 글은 GDC 2015의 Independent Games Summit에서 한 그의 강연을 고친 것으로, 2015년 5월 11일 Game Developer에 실렸습니다.23 이 글에서 쓰는 용어는 그의 것입니다. position-locking(위치 고정)은 카메라를 플레이어에 고정합니다. edge-snapping(가장자리 스냅)은 레벨 가장자리에서 카메라를 멈춥니다. camera-window(카메라 창)는 플레이어가 창의 가장자리를 밀 때만 카메라를 움직입니다. lerp-smoothing(lerp 스무딩)은 카메라를 목표 쪽으로 이징시키며, 그는 이를 “a standard tool in reducing jarring camera speeds, particularly jumps.”(갑작스러운 카메라 속도, 특히 점프를 완화하는 표준 도구)라고 부릅니다. target-focus(목표 초점)와 dual-forward-focus(양방향 전방 초점)는 플레이어보다 진행 방향으로 앞서 나갑니다. 그는 platform-snapping(발판 스냅), region-focus(영역 초점), cue attractors(단서 끌개)도 꼽는데, 그리드를 걷는 게임에는 쓸모가 없습니다.23

그는 애초에 카메라 움직임이 왜 중요한지도 설명합니다. “conflicting sensory signals (Visual vs. Vestibular) may lead to discomfort and nausea, and though it’s worse in 3D (especially VR), it is still very much in effect in 2D games.”(시각과 전정 감각의 신호가 충돌하면 불쾌감과 멀미가 생길 수 있으며, 3D, 특히 VR에서 더 심하지만 2D 게임에서도 여전히 나타난다) 그리고 가장 단순한 방식이 옳은 경우도 제시합니다. 위치 고정은 “a crafting adventure game like Terraria, with a small character relative to the screen with pretty small jumps, it works very well.”(Terraria 같은 크래프팅 어드벤처 게임처럼 화면에 비해 캐릭터가 작고 점프도 꽤 작으면 아주 잘 작동한다)23 30픽셀 수집가가 있는 탑다운 마을(이 시리즈의 캐릭터 편 글이 빌드 34에서 옮겨 간 크기)은 바로 그 경우에서 점프를 뺀 것입니다.35

다른 선택의 현대적 참고 사례는 Celeste입니다. 개발자들은 Player 클래스를 “as a learning resource and for general interest”(학습 자료이자 일반적인 관심을 위해) 공개했고, MIT 라이선스는 그 코드에만 적용됩니다. 카메라는 그 안의 몇 줄입니다. level.Camera.Position = from + (target - from) * (1f - (float)Math.Pow(0.01f / multiplier, Engine.DeltaTime))이며, “Camera (lerp by distance using delta-time)”(카메라: 델타 타임을 사용해 거리에 따라 lerp)라는 주석 아래에 있습니다.3637 multiplier가 1이고 목표가 가만히 있으면, 프레임 레이트와 관계없이 매초 간격의 99퍼센트를 좁힙니다. 하지만 Celeste의 목표는 가만히 있지 않습니다. 매 프레임 플레이어의 위치와 상태로부터 다시 계산되므로, 이 수치는 이징의 성질을 설명할 뿐 카메라가 결국 어디에 놓이는지를 말해 주지는 않습니다.36 목표는 게임 화면 중앙에 플레이어를 둔 위치(X - Celeste.GameWidth / 2)를 방의 경계로 제한한 것이며, 몇몇 상태에서는 오프셋이 더해집니다. StRedDash 대시에서는 진행 방향으로 48픽셀 앞, 정상 발사에서는 위로 64픽셀입니다.36 이 시리즈의 첫 글은 Celeste가 세계를 320×180으로 렌더링하고 6배로 확대한다고 언급했습니다.22

제 해석으로는 둘은 충돌하지 않습니다. Celeste가 부드럽게 하는 것은 플랫포머에서는 점프할 때마다 시야가 위아래로 끌려다니기 때문이며, Keren 자신의 lerp 스무딩 정의도 점프에 관한 것입니다. 그리드를 걷는 게임은 일정한 속도로 직선을 따라 움직이므로 고정 카메라에서도 다듬어야 할 충격이 생기지 않고, 부드러운 카메라는 이미 고르던 움직임 뒤에 꼬리를 남길 뿐입니다. 7절에서 그 꼬리가 Kiradex의 모델에서 얼마로 측정되는지 보여 드립니다.

다른 게임들이 속도에 대해 알려 주는 것

Stardew Valley(스타듀 밸리)는 플레이어 속도를 단위 없는 능력치로 밝힙니다. “2 when walking”(걸을 때 2), “5 when running”(달릴 때 5), “6.6 when riding a Horse (7 if the horse was fed a carrot that day)”(말을 탈 때 6.6, 그날 말에게 당근을 먹였다면 7)이고, 1 아래로는 내려가지 않습니다.38 위키는 1단위가 틱당 몇 픽셀인지 말하지 않으므로, 여기서는 Stardew의 초당 타일 수치를 제시하지 않습니다.

Sea of Stars, Eastward, CrossCode의 카메라에 관한 일차 자료도 찾아보았지만 찾지 못했습니다. 이동과 조명에 관한 인터뷰는 있어도 카메라 이야기는 없었고, Sea of Stars의 “Pixel Perfect” 옵션은 공략 사이트와 포럼에서만 설명되어 있었습니다. Maddy Thorson의 카메라 강연이나 글도 찾지 못했기 때문에, Celeste는 코드에서 인용했습니다.39

휴대폰에서는: 탭으로 걷되, 비상구를 둡니다

Stardew Valley의 모바일 버전은 아홉 가지 조작 방식을 제공하며, 기본값은 “Tap-to-move & Auto-Attack”(탭 이동 및 자동 공격)입니다. “Tap anywhere on screen and the farmer will walk to where you tapped.”(화면 아무 곳이나 탭하면 농부가 탭한 곳으로 걸어간다)40 손가락을 대고 있으면 “will cause the character to follow the touch”(캐릭터가 터치를 따라간다)이며, 위키는 이 따라가기 모드가 “is very literal, moving directly towards the finger without routing around blocking objects.”(매우 문자 그대로여서, 막는 물체를 돌아가지 않고 손가락을 향해 곧장 움직인다)라고 경고합니다. 보이지 않는 조이스틱 방식은 “the left half of the screen”(화면 왼쪽 절반)을 쓰며, 터치한 곳이 중심이 됩니다. 그리고 위키는 기본값의 한계에 대해서도 솔직합니다. 정밀한 위치 조정이 필요한 일부 작업은 “can not be completed using the default controls; temporarily switching to a control style with a movement joystick is necessary in such cases.”(기본 조작으로는 완료할 수 없으며, 그런 경우 이동 조이스틱이 있는 조작 방식으로 잠시 바꿔야 한다)라는 것입니다.40 이 방식들은 TouchArcade가 2018년 11월 1일에 보도한 업데이트에서 추가되었고, “the default tap-to-move and auto-attack controls.”(기본 탭 이동 및 자동 공격 조작)로 되돌리는 토글도 함께 들어왔습니다.41

Square Enix의 iOS용 첫 번째 파이널 판타지 픽셀 리마스터는 이 앱의 App Store 기록상 2025년 3월 11일자 버전 1.2.0에서 걷기 또는 달리기 기본값을 추가했습니다(시리즈는 작품마다 별도의 앱으로 나와 있으며, 확인한 것은 이 앱 하나뿐입니다). “In tap based movement mode the character controlled will always run as the default speed when moving.”(탭 기반 이동 모드에서 조작 캐릭터는 이동할 때 기본 속도로 항상 달린다)42 모바일 버전의 컨트롤러 지원은 그보다 앞서 TouchArcade가 2024년 1월 30일에 다룬 업데이트로 들어왔습니다.43 정확한 터치 이동 방식을 문서화한 자료는 이 노트들 외에는 찾지 못했고, 제가 가져온 위키 페이지에서 Terraria 모바일판의 이동 방식에 대한 설명도 찾지 못했습니다.39

Apple의 Human Interface Guidelines는 플랫폼 쪽에서 같은 이야기를 합니다. 터치 게임에 대해 “consider letting players tap objects to select them instead of adding a virtual selection button”(가상 선택 버튼을 추가하는 대신 오브젝트를 탭해 선택하게 하는 것을 고려한다), “For movement control, opt to show a virtual thumbstick wherever the player lands their thumb instead of a static thumbstick position”(이동 조작에는 고정된 위치의 썸스틱 대신 플레이어가 엄지를 댄 곳에 가상 썸스틱을 보여 준다), “Make sure frequently used controls are a minimum size of 44x44 pt”(자주 쓰는 조작은 최소 44x44pt로 한다), “Always include visible and tactile press states”(눈에 보이고 촉각으로 느껴지는 눌림 상태를 항상 포함한다)라고 하며, 걷기와 질주에 대해서는 “consider combining the actions into a single control.”(두 동작을 하나의 조작으로 합치는 것을 고려한다)라고 합니다. 페이지의 변경 기록에 따르면 이 터치 조작 지침은 2025년 6월 9일자입니다.44 WWDC25의 터치 조작 세션에서 Apple은 전제를 분명히 밝혔습니다. “the vast majority of players won’t have a controller available.”(대다수의 플레이어는 컨트롤러를 갖고 있지 않을 것이다)45

이 자료 중 어느 것도 휴대폰 게임에서는 탭으로 걷는 것이 규칙이라고 말하지 않으며, 저도 그렇게 주장하지 않습니다. 제가 여기서 얻는 것은 발견이 아니라 Kiradex에 대한 제 권고로서, Kiradex가 이미 절반쯤 따르고 있는 방식입니다. Stardew의 모바일 버전이 출시한 것처럼 기본값은 탭으로 걷기, HIG가 권하는 것처럼 정밀 조작을 위해 엄지가 닿는 곳에 나타나는 썸스틱, 그리고 화면에 고정된 십자 패드는 두지 않기입니다.4044

5. 기법을, 숫자가 붙은 규칙으로

7절의 브리프는 아래 규칙에 맞춰 작성되었고, 각 규칙은 위의 측정으로 거슬러 올라갑니다. “틱”은 1/60초짜리 시뮬레이션 한 단계를 뜻하며, 59.73헤르츠의 한 프레임을 iPhone이 지킬 수 있는 단위로 바꾼 것입니다. 셀당 16틱이면 걷기는 초당 3.73셀이 아니라 3.75셀이 됩니다.119

요소 규칙 출처
걷기 16픽셀 타일 위를 틱당 1픽셀: 타일당 16틱, 초당 3.75타일 Red, Crystal, Emerald 모두 16픽셀을 16프레임에 걸으며 초당 3.73셀
달리기 틱당 2픽셀, 타일당 8틱. 16을 나누어떨어지지 않는 속도는 쓰지 않음 Emerald의 달리기와 파도타기는 2, 자전거는 4. 고르지 않은 것은 묘기자전거의 2-3-3뿐
걷기 사이클 어느 속도에서든 16픽셀마다 발 디딤 한 번. Kiradex에 대한 제 제안은 걸은 거리로 그림을 고르는 것으로, 32픽셀에 걸친 그림 여섯 장이면 그림 floor(distance × 6 / 32) mod 6을 표시 Emerald는 그림과 걸음을 따로 프레임으로 재되 길이를 맞춤. 걷기는 16프레임 셀 두 개에 걸친 32프레임 사이클, 달리기는 8프레임 셀 두 개에 걸친 16프레임 사이클
걸음 일단 시작하면 끝까지 감. 입력은 타일 위에서 읽음 Emerald와 Red는 걸음이 끝날 때 패드를 읽음
방향 전환 서 있는 상태에서 8틱, 약 133 ms(Emerald의 8프레임은 134). 걷는 중에는 없음 Emerald WalkInPlaceFast는 8. Crystal의 방향 전환 루틴은 6프레임, Red는 2
거부된 걸음 무시하지 않고 보여 줌: 부딪힘을 동반한 32틱 제자리걸음 Emerald WalkInPlaceSlow는 32
카메라 플레이어에 고정: 뒤처짐도 앞서감도 없이, 같은 틱에 같은 픽셀만큼 이동 Emerald의 카메라 오브젝트. Game Freak이 작성한 유일한 앞보기는 꺼져 있음
맵 가장자리 바깥을 그리고 고정을 유지하거나, 제한함(가장자리 스냅). 이징은 절대 하지 않음 Emerald의 경계 타일, Keren의 가장자리 스냅, Celeste의 방 경계
문 그림 4장을 각 5틱: 그림당 83 ms, 열리는 데 333 ms(Emerald는 84와 335) Emerald field_door.c
페이드 계단식 9단계를 2틱마다, 마지막 단계는 16틱째, 전체 18틱(0.30초), 나갈 때와 들어올 때 모두. 기본은 검은색. 복제가 아니라 각색 Emerald의 일반 페이드는 스프라이트 팔레트에서 같은 짝수 프레임에 각 단계에 도달하고(배경 팔레트보다 한 프레임 뒤), 마지막 블렌드는 프레임 16, 프레임 21에 비활성화. 동굴에 들어갈 때는 흰색으로 페이드아웃하고, 나올 때는 흰색에서 페이드인. Crystal의 8프레임이 빠른 쪽 끝, Red의 32프레임이 느린 쪽 끝
출입 의식 입력 없이 74틱, 약 1.23초: 열기 20, 안으로 걸음 16, 닫기 20, 페이드 18 Emerald의 문 출입: 최소 73프레임 뒤 어두워지고, 맵 불러오기는 빨라도 프레임 79(1.32초)
흔들림 1픽셀, 8번 뒤집기, 5틱 간격(40틱, 0.67초), 스크립트로 정한 사건에만 Emerald의 흔들림 24번 중 가장 흔한 것
프레임 레이트 디스플레이가 무엇을 하든 60으로 시뮬레이션. 120이 아니라 60을 요청 게임에 대한 Apple의 30과 60 우선순위. RealityKit은 보통 60으로 렌더링
햅틱 걸음이 아니라 사건을 확인하는 데 쓰고, 선택 사항으로 둠 햅틱 재생에 관한 Apple의 HIG
입력 제 권고: 기본값은 탭으로 걷기, 정밀 조작 옵션으로 플로팅 썸스틱, 고정 십자 패드는 없음 Stardew 모바일의 기본값, HIG의 플로팅 썸스틱. 둘 다 규칙으로 명시하지는 않음

표의 출처: Emerald의 걷기, 애니메이션, 문 집계.1 그림과 걸음의 별도 타이머.92 카메라.123 페이드와 흔들림.529 Red와 Crystal.433 Keren과 Celeste.2336 Apple의 프레임 페이싱, RealityKit, 햅틱 지침.7846 입력 참고 자료.404244

이 중 두 규칙에는 한 문장씩 설명이 필요합니다. 걷기 사이클 규칙은 현대 엔진에서 가장 틀리기 쉬운 규칙입니다. 애니메이션 시스템은 자기만의 초를 세고, 걷기는 세계 속 거리를 세기 때문입니다. 휴대용 게임기들은 둘을 같은 프레임 단위로 세고 길이가 맞도록 골라 둘을 함께 묶었습니다. 프레임 시간이 변하는 휴대폰에서 제 제안은 걸은 거리로부터 그림을 읽는 것이며, 그러면 발을 맞춰야 할 두 번째 시계 없이 같은 결과를 얻습니다. 그리고 프레임 레이트 규칙은 야심이 부족해서가 아닙니다. 틱마다 정확히 1픽셀씩 움직이는 세계에는 틱과 틱 사이에 보여 줄 것이 없으므로, 더 빠른 디스플레이는 같은 그림을 반복할 수밖에 없으며, 6절에서 실제로 그렇게 된다는 것을 보여 드립니다.

6. Apple의 방식: 프레임 페이싱, RealityKit의 시계, 그리고 햅틱

이 시리즈의 첫 글은 Kiradex의 세계가 돌아가는 엔진을 설명했습니다. 2D 렌더러로 쓰는 RealityKit 장면, 직교 카메라, 텍스처를 입힌 사각형들을 하나의 메시로 묶은 지면, 사각형으로 그리는 걷는 캐릭터들, 그리고 매 프레임 정수 월드 단위로 반올림되는 모든 스프라이트의 위치입니다.22 이 절은 그 세계가 시간 속에서 어떻게 움직이는지를 결정하는 Apple 문서의 부분입니다. 아래의 모든 페이지는 2026년 10월 4일에 Apple이 게시한 그대로 읽었고, 지원 버전은 각 페이지에 적힌 것입니다.

ProMotion에서의 프레임 페이싱

CADisplayLink.preferredFrameRateRange(iOS 15.0, iPadOS 15.0, Mac Catalyst 15.0, visionOS 1.0)는 설정이 아니라 요청입니다.15 페이지의 조언은 “Choose a frame rate range that your app can consistently maintain”(앱이 일관되게 유지할 수 있는 프레임 레이트 범위를 고른다)이며, 시스템이 그 요청으로 무엇을 하는지도 설명합니다. “The system typically provides a consistent frame rate by choosing one that’s a factor of the display’s maximum refresh rate.”(시스템은 보통 디스플레이 최대 주사율의 약수인 레이트를 골라 일관된 프레임 레이트를 제공한다) 기본적으로 범위는 디스플레이의 최댓값과 같습니다.15 범위 자체는 최소, 최대, 선호 레이트로 이루어진 CAFrameRateRange(iOS 15.0)입니다.47

ProMotion에 관한 Apple의 문서가 숫자를 줍니다. ProMotion 디스플레이는 iPad Pro에서는 24에서 120헤르츠 사이, 지원되는 iPhone에서는 10에서 120헤르츠 사이를 오가며, iPhone의 레이트는 120, 80, 60, 48, 40, 30, 24, 20, 16, 15, 12, 10헤르츠의 12단계이고 iPad Pro는 그중 다섯 가지입니다.7 기기 목록에는 이제 “iPhone 13 Pro and later”(iPhone 13 Pro 이후)와 함께 iPhone Air와 “iPhone 17 and later”(iPhone 17 이후)가 올라 있습니다.7 iPhone에서는 앱의 Info.plist에서 CADisableMinimumFrameDurationOnPhone(iOS 15.0)을 true로 설정하지 않으면 60을 넘는 일이 없습니다. “If you don’t enable this support, Core Animation won’t access higher frame rates (above 60Hz).”(이 지원을 켜지 않으면 Core Animation은 더 높은 프레임 레이트, 즉 60Hz 초과를 사용하지 않는다)716 이 문서에서 게임에 다른 부분보다 더 중요한 문장이 두 개 있습니다. 하나는 “In iOS 15 and later, the system provides games with special priority to 30Hz and 60Hz refresh rates to ensure optimal performance”(iOS 15 이후 시스템은 최적의 성능을 보장하기 위해 게임에 30Hz와 60Hz 주사율에 대한 특별한 우선순위를 준다)로, CAFrameRateRange(minimum: 30, maximum: 60, preferred: 60)으로 얻을 수 있습니다.7 다른 하나는 “Prepare your app to operate at any refresh rate, not just those it requests.”(요청한 레이트뿐 아니라 어떤 주사율에서도 동작하도록 앱을 준비한다)입니다.7 그리고 움직이는 모든 것에 대해서는 “Always use targetTimestamp to drive any animation, physics, or other time-related content provided in your CADisplayLink callback”(CADisplayLink 콜백에서 제공하는 애니메이션, 물리, 기타 시간 관련 콘텐츠는 항상 targetTimestamp로 구동한다)입니다(targetTimestamp는 iOS 10.0).748

WWDC21 세션 “Optimize for variable refresh rate displays”(가변 주사율 디스플레이에 최적화하기)는 iPad Pro의 ProMotion과 Mac의 Adaptive-Sync 디스플레이를 모두 다룹니다.49 Mac의 Adaptive-Sync 디스플레이에 대해 이 세션은 Apple의 이전 지침을 바꿉니다. 고정 레이트 디스플레이에서는 “we’ve previously recommended that you slow down your rendering to hit the next factor of the display’s fastest refresh rate”(디스플레이 최고 주사율의 다음 약수에 맞도록 렌더링을 늦추라고 이전에 권장했다)였지만, Adaptive-Sync에서는 “You should instead attempt to present frames at the highest rate your app can do so evenly.”(대신 앱이 고르게 표시할 수 있는 가장 높은 레이트로 프레임을 표시하도록 해야 한다)라는 것입니다.49 제가 여기서 휴대폰을 위해 가져가는 단어는 evenly(고르게)입니다.

RealityKit의 시계

Kiradex의 세계는 디스플레이 링크를 갖고 있지 않습니다. RealityKit의 프레임별 이벤트인 SceneEvents.Update(iOS 13.0), 즉 “An event invoked once per frame interval”(프레임 간격마다 한 번 호출되는 이벤트)에 맞춰 틱을 진행하며, 그 deltaTime은 “The elapsed time since the last update.”(마지막 업데이트 이후 경과한 시간)입니다.5051 RealityView(iOS 18.0)는 프레임별 작업에 바로 그 경로를 문서화해 두었습니다. “you can use a System or directly subscribe to the engine’s SceneEvents.Update“(System을 쓰거나 엔진의 SceneEvents.Update를 직접 구독할 수 있다)이며, 자체 프레임 레이트 설정은 제공하지 않습니다.52 RealityKit 성능에 관한 Apple의 문서는 어떤 레이트를 예상해야 하는지 말해 줍니다. “RealityKit typically limits the refresh rate”(RealityKit은 보통 주사율을 제한한다)라고 하고, 그것을 프레임워크가 화면용 업데이트를 렌더링하는 레이트로 정의한 뒤 “to 60 frames per second (fps).”(초당 60프레임, fps로)라고 이어 갑니다.8 iPhone 18 Pro Max나 iPhone Duo의 RealityView가 실제로 60과 120 중 어느 쪽으로 렌더링하는지는 제가 측정하지 않았으며, 7절은 그 측정을 브리프의 첫 번째 검사로 삼습니다.

정수 픽셀 걷기에 120헤르츠가 아무것도 해 주지 못하는 이유

계산을 보여 드리겠습니다. 7절에서 앱의 현재 코드에 쓰는 것과 같은 모델 스크립트를, 이번에는 제안 쪽에 적용한 것입니다. 고정 60헤르츠 틱으로 진행하며 틱당 1픽셀씩 움직이고, 디스플레이는 가장 최근 틱이 만든 것을 그대로 보여 주는 세계입니다. 60헤르츠 디스플레이에서는 세계의 모든 픽셀 위치가 정확히 한 번의 리프레시 동안 표시됩니다.6 120헤르츠 디스플레이에서는 1초 동안 61개 위치 중 59개가 정확히 두 번의 리프레시 동안, 2개가 한 번 동안 표시됩니다.6 80헤르츠 디스플레이에서는 표시가 한 번과 두 번의 리프레시 사이를 오가며, 42개 위치가 한 번, 19개가 두 번 표시됩니다.6 제 해석으로는 120헤르츠의 경우는 60헤르츠의 경우를 두 번씩 그린 것이고, 80헤르츠의 경우는 고르지 않은 페이싱입니다. 어떤 픽셀은 12.5밀리초 동안, 어떤 픽셀은 25밀리초 동안 머무릅니다.

그래서 iPhone 위의 그리드 세계에 대한 질문은 120헤르츠가 아니라 60에서의 고른 페이싱입니다. 고정 60헤르츠 시뮬레이션 틱, 틱당 정수 이동, 틱으로 세는 애니메이션, 그리고 가장 최근 틱을 보여 주는 렌더러입니다. 이렇게 해야 RealityKit이 어떤 레이트를 고르든 움직임이 그에 좌우되지 않습니다. WWDC21 세션은 시간 차이에 관해 관련된 점을 짚습니다. 느린 프레임 때문에 디스플레이 링크가 콜백을 한 번 건너뛰면, 진행해야 할 차이는 “not 8ms, but rather 16ms”(8ms가 아니라 16ms)가 됩니다. 이어서 세션은 “uses time delta to advance the state of your custom drawing”(시간 차이로 사용자 정의 그리기의 상태를 진행하는) 앱은 콜백을 건너뛸 때마다 “slow down your custom drawing by one frame”(사용자 정의 그리기가 한 프레임만큼 느려진다)이라고 말하는데, 저는 이를 실제로 경과한 시간이 아니라 예상한 8밀리초만큼 진행하는 앱에 대한 이야기로 읽습니다. 그리고 세션은 앱이 “can instead keep track of a previous targetTimestamp so that you can advance the state correctly.”(대신 상태를 올바르게 진행할 수 있도록 이전 targetTimestamp를 기록해 둘 수 있다)라고 말합니다.49

햅틱

Core Haptics(iOS 13.0, iPadOS 13.0, Mac Catalyst 13.0, visionOS 1.0)는 딕셔너리, CHHapticEvent 오브젝트의 배열, 또는 AHAP 파일로부터 CHHapticPattern, 즉 “An object representing a haptic waveform”(햅틱 파형을 나타내는 오브젝트)을 만듭니다.53 이벤트의 햅틱 유형은 hapticTransient와 hapticContinuous 두 가지이며, transient는 “brief impulses that occur at a specific point in time.”(특정 시점에 일어나는 짧은 충격)입니다.54 각각 hapticIntensity, hapticSharpness, attackTime, decayTime, releaseTime, sustained 파라미터를 받습니다.55 이를 재생하는 것은 CHHapticEngine이며, 기기가 재생할 수 있는지는 capabilitiesForHardware()가 알려 줍니다.56

더 간단한 경로는 UIImpactFeedbackGenerator(iOS 10.0)입니다. “A concrete feedback generator subclass that creates haptics to simulate physical impacts”(물리적 충격을 흉내 내는 햅틱을 만드는 구체 피드백 생성기 하위 클래스)이며, 그 스타일(light, medium, heavy, soft, rigid)은 “The mass of the objects in the collision”(충돌하는 물체의 질량)을 나타냅니다. impactOccurred(intensity:)는 iOS 13.0이고, 페이지는 init(style:view:)를 “Initializing the feedback generator”(피드백 생성기 초기화) 항목에, init(style:)을 Deprecated(사용 중단) 항목에 올려 두었습니다.575859 prepare()는 작업할 시간이 있을 때만 지연 시간을 줄입니다. “Calling prepare() and then immediately triggering feedback (without any time in between) does not improve latency”(prepare()를 호출하고 사이에 아무 시간도 두지 않고 곧바로 피드백을 일으키면 지연 시간이 개선되지 않는다)이며, 엔진은 “A short period of time passes (typically seconds).”(짧은 시간, 보통 몇 초가 지나면) 유휴 상태로 돌아갑니다.60 SwiftUI에서는 sensoryFeedback(_:trigger:)(iOS 17.0)가 “Plays the specified feedback when the provided trigger value changes”(주어진 trigger 값이 바뀌면 지정한 feedback을 재생한다)이며, .impact(weight:intensity:)도 포함됩니다.61

햅틱 재생에 관한 HIG 페이지는 디자인 쪽 절반입니다. “Avoid overusing haptics”(햅틱을 남용하지 않는다)46, 그 이유로 “Often, the best haptic experience is one that people may not be conscious of, but miss when it’s turned off.”(최고의 햅틱 경험은 흔히 사람들이 의식하지 못하지만, 꺼지면 아쉬워하는 것이다) “Make haptics optional.”(햅틱을 선택 사항으로 만든다) 그리고 “the intensity and sharpness of a haptic with the intensity and sharpness of the animation it accompanies.”(햅틱의 강도와 선명도를, 함께하는 애니메이션의 강도와 선명도에) 맞춥니다.46 선명도는 “that’s soft, rounded, or organic, or one that’s crisp, precise, or mechanical.”(부드럽고 둥글고 유기적인 것, 또는 또렷하고 정밀하고 기계적인 것) 같은 경험을 전할 수 있습니다.46 그리고 사용자 정의 햅틱은 “a collision or a hit”(충돌이나 타격)을 “from subtle experiences like the approach of footsteps or a looming danger.”(다가오는 발소리나 임박한 위험 같은 미묘한 경험으로부터) 크게 다르게 느끼게 할 수 있습니다.46 초당 3.75걸음의 걷기가 몇 분씩 이어지는 것은 첫 번째 규칙이 말하는 바로 그 경우입니다.1

입력

터치 조작 API는 4절의 용어로 보면 다음과 같습니다. Touch Controller(iOS 26.0, iPadOS 26.0, Mac Catalyst 26.0, visionOS 26.0)는 그 페이지에서 “Integrate onscreen touch controls into your Metal-based games”(Metal 기반 게임에 화면 터치 조작을 통합한다)로 요약됩니다. 버튼, 방향 패드, 썸스틱, 스로틀, 터치패드를 GCController를 통해 제공합니다.62 그 TCDirectionPad(iOS 26.0, iPadOS 26.0, Mac Catalyst 26.0)는 “to behave as either a composite direction pad”(하나의 복합 방향 패드로 동작하거나) 또는 “as four separate buttons.”(네 개의 독립된 버튼으로) 동작하도록 설정할 수 있습니다.6263 그보다 오래된 GCVirtualController(iOS 15.0)는 “A software emulation of a real controller that you configure specifically for your game.”(자기 게임에 맞게 특별히 구성하는 실제 컨트롤러의 소프트웨어 에뮬레이션)입니다.64 제가 시도한 주소 중 하나인 developer.apple.com/documentation/touchcontrols는 404를 반환했고, 프레임워크의 페이지는 touchcontroller입니다.39

비용

이 절의 어떤 것도 휴대폰에서 시간을 재 보지 않았습니다. 기기에서의 세계 프레임 시간, 60헤르츠 누산기의 비용, 세계가 화면에 있는 동안 햅틱 엔진을 계속 돌리는 비용은 모두 측정되지 않았습니다. 7절의 브리프 검사는 이 중 앞의 두 가지를 측정하도록 작성되어 있습니다.

7. 브리프: Kiradex가 만들 것과 통과해야 할 검사

걷기의 현재 상태

2026년 10월 4일에 Kiradex 저장소에서 세계의 걷기, 카메라, 전환 코드를 변경 없이 읽고, 이를 프레임 단위로 재현하는 모델을 작성했습니다. 이 소절의 모든 내용은 그 코드에서 읽은 것이거나 모델로 계산한 것이며, 어느 쪽인지 밝혀 둡니다. 모델은 코드의 Float 연산을 하나하나 32비트로, 코드와 같은 순서로, Swift의 반올림 방식으로, 프레임당 정확히 1/60초 또는 1/120초로 수행합니다. 맵 가장자리, iPhone Duo의 접힘, 발 위치 오프셋은 뺐는데, 맵 한가운데를 곧게 걷는 동안에는 어느 것도 결과를 바꾸지 않습니다. 이것은 코드의 모델입니다. 캡처가 아니며, 휴대폰에서 측정한 적은 없습니다.176

코드에서 읽은, 코드가 하는 일:

  • 입력은 탭으로 걷기, 그것뿐입니다. 스테이지는 탭을 월드 좌표로 바꾼 뒤, 수집가나 키오스크를 탭한 것으로 처리하거나 walk(to:)를 호출합니다. 드래그도, 십자 패드도 없으며, 앱 타깃 어디에도 햅틱 호출이 없습니다. UIImpactFeedbackGenerator, sensoryFeedback, CHHaptic을 검색해도 아무것도 나오지 않습니다.17
  • 경로는 8방향 최단 경로 탐색입니다. 지금까지 걸은 비용 순으로 정렬하고 남은 거리의 추정치를 쓰지 않으므로, A*가 아니라 다익스트라 탐색입니다. 대각선 비용은 √2이고, 벽 모서리를 대각선으로 가로지르지 않습니다. 대각선 걸음은 옆을 바라보며, 달리기의 1.6을 적용한 뒤 진행량을 √2로 나누므로, 모델에서 60헤르츠의 대각선 걸음은 걷기로 22프레임, 달리기로 14프레임이 걸립니다. 직선 걸음은 15프레임과 10프레임입니다.176
  • 속도는 시간입니다. tilesPerSecond는 4, 즉 초당 64픽셀이며, 경로가 6걸음 이상이면 걷기가 그 1.6배인 달리기로 바뀝니다. 그래서 앱에서 걷기는 1에서 5타일이고 그보다 긴 것은 모두 달리기입니다. 각 프레임은 걸음의 진행량에 dt × speed / length를 더하고, 진행량이 1에 이르면 걷는 캐릭터가 다음 타일로 스냅되며 진행량은 0으로 돌아가고 넘친 부분은 버려집니다.17
  • 걷기 그림은 시간으로 고릅니다. 열은 cycle[Int(walker.clock * framesPerSecond) % count]이고, framesPerSecond는 8, 사이클은 포지가 만든 그림 여섯 장짜리 걷기와 달리기입니다. 이 시리즈의 캐릭터 편 글은 같은 리듬을 “the walk at eight frames a second”(초당 8프레임의 걷기)라고 적었고, 이는 코드를 정확히 설명합니다.1735
  • 카메라는 이징한 뒤 반올림합니다. 매 프레임 플레이어를 향해 (target - current) * min(1, dt * 6)만큼 움직이고, 그다음 두 축을 정수 단위로 반올림합니다. 맵이 화면보다 크면 맵 범위로 제한됩니다. 모든 스프라이트와 카메라가 “are rounded to whole world units each frame, after easing”(이징 후 매 프레임 정수 월드 단위로 반올림된다)이라는 첫 글의 문장도 정확합니다.1722
  • 문과 워프. 문은 시트의 반쯤 열린 그림을 즉시 보여 주고 70밀리초 뒤에 열린 그림을 보여 주며, 그 1.4초 뒤에 문을 제거합니다. 워프 위로 올라서는 걸음은 220밀리초를 기다린 뒤 장소를 바꿉니다. 건물 편 글은 이것을 알려진 공백으로 적었습니다.1713
  • 전환은 내비게이션 push입니다. 기본 애니메이션 그대로이며, 층 이동은 스테이지를 identity로 교체할 뿐 페이드가 없고, 나갈 때는 dismiss()를 호출합니다. 앱의 유일한 베일은 카드 뷰어의 검은 오버레이이며, .easeOut(duration: 0.25)로 애니메이션됩니다.17
  • 프레임 레이트 요청이 없습니다. 앱에도 프로젝트 파일에도 CADisplayLink, preferredFrameRateRange, CADisableMinimumFrameDurationOnPhone이 없습니다. 세계는 이벤트의 deltaTime으로 SceneEvents.Update에 맞춰 틱을 진행합니다.17

일정한 프레임 시간에서 그 코드가 화면에서 하는 일에 대해 모델이 말해 주는 것:

  • 60헤르츠에서 타일마다 한 번의 2픽셀 끊김. 60헤르츠에서 걷기는 타일당 15프레임, 초당 4.00타일이 걸리며, 각 타일의 16픽셀은 1픽셀짜리 14프레임과 2픽셀짜리 1프레임으로 이루어집니다. 타일마다 2픽셀 점프가 한 번, 5타일 걷기에서는 다섯 번입니다. Emerald는 모든 프레임에서 정확히 1픽셀 움직입니다.61
  • 120헤르츠에서는 불규칙한 0과 1. 120헤르츠에서 걷기는 타일당 30프레임이 걸리며, 각 타일의 16픽셀은 1픽셀짜리 16프레임과 0픽셀짜리 14프레임이 불규칙하게 섞여 이루어집니다.6
  • 달리기는 상수보다 느립니다. 60헤르츠에서 달리기는 타일당 10프레임, 초당 6.00타일이 걸립니다. 4 곱하기 1.6이면 6.4여야 하지만 타일마다 넘친 부분이 버려지기 때문입니다. 각 타일의 16픽셀은 1픽셀짜리 4프레임과 2픽셀짜리 6프레임입니다. 120헤르츠에서는 타일당 19프레임, 초당 6.32타일이므로 달리기 속도가 프레임 레이트에 따라 달라집니다.6
  • 끝내 따라잡지 못하고 뒤처지는 카메라. 60헤르츠로 걷는 동안 카메라는 타일 하나마다 약 1픽셀씩 더 뒤처집니다. 첫 타일 끝에서 5픽셀, 앱이 하는 가장 긴 걷기인 다섯 번째 타일 끝에서 9픽셀이며, 화면 위 플레이어의 위치는 첫 프레임 이후 걷기의 73프레임 중 8프레임에서 바뀝니다. 6타일 이상의 모든 경로인 달리기에서는 두 번째 타일부터 13픽셀 뒤처집니다. 120헤르츠에서는 걷든 달리든 첫 타일 안에 9에 이르고 그대로 유지됩니다. 플레이어가 멈추면 카메라는 60헤르츠에서는 플레이어로부터 4픽셀, 120헤르츠에서는 9픽셀 떨어진 곳에 멈추고 그대로 있습니다. 간격에 min(1, dt × 6)을 곱한 값이 0.5픽셀 아래로 내려가면 반올림이 매 프레임 같은 위치를 돌려주기 때문입니다. 어느 쪽에 멈추는지는 마지막 걷기의 방향에 달려 있습니다. Emerald의 뒤처짐과 정지 오프셋은 둘 다 0입니다.6
  • 그림은 자기만의 시계를 따릅니다. 초당 8장의 그림 여섯 장 사이클은 0.75초 동안 이어집니다. 60헤르츠에서 연속된 사이클 시작 사이의 모델 위치로 재면, 걷기로 3.00타일, 달리기로 4.50타일을 덮습니다(달리기의 명목상 초당 6.4타일이면 4.80이지만, 버려지는 넘침 때문에 거기에 이르지 못합니다). Emerald의 발 디딤 두 번짜리 사이클은 2.00타일을 덮습니다. 제 해석으로는 발 디딤 횟수보다 절반만큼 더 많은 땅을 덮는 사이클은 발이 미끄러지는 것처럼 보이지만, 모델은 발이 지면 어디에 놓이는지를 측정하지 않습니다. 또 60헤르츠와 120헤르츠 모두에서 15번째 프레임마다 그림이 아슬아슬한 경계에 놓입니다. 시계에 8을 곱한 값이 정확히 정수, 즉 두 그림의 경계에 떨어지는 것입니다. 앱처럼 시계를 64비트가 아닌 32비트로 유지하면, 60헤르츠로 12타일을 걷는 동안 그런 프레임 11개 중 9개에서, 120헤르츠에서는 23개 중 14개에서 표시되는 그림이 달라집니다.6
  • 걸음 도중의 두 번째 탭은 걷는 캐릭터를 뒤로 되돌립니다. 이것은 모델이나 캡처가 아니라 코드에서 읽은 것입니다. walk(to:)는 걷는 캐릭터의 타일이 아직 걸음의 출발점인 동안 진행량을 0으로 만들기 때문에, 다음 프레임에서 캐릭터가 원래 있던 곳보다 최대 15픽셀 뒤에 그려집니다. 서버에서 오는 다른 수집가들의 이동도 마찬가지입니다.17

걷기의 첫 1초 동안 표시된 프레임마다 막대 하나씩을 놓은 띠 네 개. 59.73헤르츠의 Emerald는 모든 프레임에서 1픽셀 움직인다. 모델로 구한 60헤르츠의 Kiradex 현재 코드는 1픽셀씩 움직이며, 그 1초 동안 2픽셀 프레임이 네 번 있다. 모델로 구한 120헤르츠에서는 0 또는 1픽셀을 불규칙하게 움직이며, 0인 프레임이 56개다. 120헤르츠 디스플레이 위의 브리프의 60헤르츠 틱은 1, 0으로 고르게 움직인다.

표시 프레임당 픽셀: 원전은 고르고, 지금의 코드(캡처가 아닌 모델)는 고르지 않으며, 고정 틱은 120에서도 고릅니다.621

모델로 구한, 플레이어와 카메라 사이의 간격을 월드 픽셀로 나타낸 선 그래프. 5타일 걷기와 12타일 달리기 각각 뒤에 3초 동안 서 있는다. Emerald의 선은 0에서 평평하다. Kiradex의 60헤르츠 걷기는 타일마다 1픽셀씩 올라 9에 이르고, 걷기가 끝나면 4에 자리 잡는다. 60헤르츠 달리기는 두 번째 타일까지 13으로 뛰어오르고 역시 4에 자리 잡는다. 120헤르츠 걷기는 곧바로 9로 뛰어오르고 걷기가 끝난 뒤에도 9에 머문다.

모델로 본, 이징 후 반올림하는 카메라: 걷거나 달리는 동안 뒤처지고, 걷기가 끝나면 모자란 곳에서 멈춥니다.621

휴대폰에서 측정하지 않았으므로, 이 중 어느 것도 손에 쥐었을 때의 느낌에 대한 판단이 아닙니다. 실제 프레임 시간은 흔들리므로 1과 2의 정확한 패턴은 바뀔 것입니다. 뒤처짐과 정지 오프셋도 프레임 시간에 달려 있습니다. 이징 스텝 min(1, dt × 6)이 프레임 시간에 달려 있기 때문이며, 그래서 모델은 정지 오프셋을 60헤르츠에서 4픽셀, 120헤르츠에서 9픽셀로 제시합니다. 제 해석으로는 휴대폰이 보통 보이는 범위의 흔들림은 크기를 바꿀 뿐 없애지는 못합니다. 조건은 프레임이 1/6초, 약 167밀리초보다 짧게 유지되는 것입니다. 그 길이에 이르면 이징 계수가 1이 되어 카메라가 한 프레임 만에 플레이어 위치에 도달하므로, 충분히 긴 끊김은 그 프레임에서 간격을 없앱니다.176 그림과 걷기의 어긋남은 프레임 레이트에 달려 있지 않습니다. 초당 8장의 그림 시계와 초당 4타일의 걷기는 60헤르츠에서 사이클당 3.00타일, 120헤르츠에서도 거의 같습니다. 달리기의 어긋남은 프레임 레이트에 달려 있어서, 60헤르츠에서 사이클당 4.50타일, 120헤르츠에서 4.75타일입니다. 타일마다 버리는 넘침이 프레임 레이트에 달려 있기 때문입니다.6

브리프

각 항목은 Kiradex/World/에 대한 변경, 그 이유, 그리고 로그 한 줄이나 스크립트, 캡처로 검증할 수 있는 검사로 이루어집니다. 어느 게임의 에셋, 이름, 소리도 제안하지 않습니다. 숫자는 메커니즘이고, 아트와 소리와 말은 Kiradex의 것입니다.

1. 시계가 아니라 틱으로 걷습니다.

변경. 리그의 업데이트에 60헤르츠 누산기를 추가합니다. 각 콜백은 자기 dt를 더합니다. 그 시점에 누산기가 8틱 분량(133밀리초)을 넘는 시간을 담고 있으면, 초과분을 버리고 그 밀리초를 dropped 줄로 모션 로그에 기록합니다. 그다음 누산기 안의 온전한 틱을 모두 실행하며, 하나 실행할 때마다 한 틱 분량을 뺍니다. 누산기는 시간을 부동소수점 초가 아니라 정수 단위(나노초, 또는 모델의 1/60,000,000초)로 셉니다. Double이나 Float 누산기를 쓰면, 10초 테스트의 600번째 틱이 어떤 레이트에서는 경계에 반올림 한 번만큼 못 미쳐서 개수가 599로 나오기 때문입니다. 이것이 밀린 시간에 대한 규칙입니다. 8틱까지의 지연은 다음 콜백에서 만회하고, 그 이상은 일시 중단으로 취급하므로, 세계는 따라잡으려고 서두르지 않고 멈춘 곳에서 다시 시작합니다. 8은 ProMotion iPhone에 대한 Apple 목록의 모든 레이트를, 콜백당 6틱이 필요한 10헤르츠까지 포함해 감당하도록 고른 값입니다.7 모델에서 12개 레이트 각각에 대해 10초 분량의 콜백을 실행하면 정확히 600틱이 실행되고 아무것도 버려지지 않습니다. 콜백당 4틱 상한이라면 12헤르츠에서 480, 10헤르츠에서 400만 실행됩니다.6 60헤르츠에서 1초짜리 끊김이 생긴 뒤, 이 규칙은 다음 콜백에서 8틱, 그 뒤로는 콜백마다 1틱을 실행하고 866.7밀리초를 버렸다고 기록합니다. 같은 끊김에서 밀린 시간을 유지한 채 4의 상한을 두면 19번 연속 콜백에서 4틱씩 실행하게 되는데, 이것이 바로 이 규칙이 막으려는 빨리 감기입니다.6 각 걷는 캐릭터는 소수 진행량 대신 걸음의 틱 수를 갖고, 그려지는 오프셋은 그 수에 틱당 픽셀을 곱한 값입니다. 정확한 정수이므로 걷는 캐릭터는 더 이상 반올림할 필요가 없습니다. 걷기는 틱당 1픽셀, 타일당 16틱이고, 달리기는 틱당 2픽셀, 8틱이며, 경로가 6걸음 이상이면 달리기라는 지금의 규칙은 유지합니다. 경로 탐색이 허용하는 대각선 걸음은 코드가 이미 의도하고 있는 √2와 그 순서, 즉 달리기 페이스를 적용한 뒤 √2로 나누는 순서를 유지합니다. 걷는 대각선은 23틱(16√2는 약 22.6, 올림), 달리는 대각선은 12틱(8√2는 약 11.3, 올림)이며, 각각 floor(16 × t / 23) 또는 floor(16 × t / 12)가 바뀌는 틱에 움직여 축마다 16픽셀을 갑니다.17 걸을 때는 23틱 중 16틱에 1픽셀, 나머지 7틱에 0픽셀이고, 달릴 때는 12틱 중 8틱에 1픽셀, 4틱에 2픽셀입니다. 브리프에서 유일하게 고르지 않은 이동 방식이며, Emerald에서 묘기자전거가 유일하게 고르지 않은 이동 방식인 것과 같습니다.1 버려야 할 넘침은 없습니다.

이유. 5절의 걷기와 달리기 규칙. 그리고 모델이 보여 준 60헤르츠에서 타일당 2픽셀 끊김과 120헤르츠에서의 불규칙한 0과 1.61

검사. -motionLog 실행 인자로 매 틱의 틱 번호와 플레이어의 x, y, 그리고 모든 dropped 줄을 기록합니다. 각 방향으로 한 타일씩 걷는 기존 방향 데모에서, 스크립트가 다음을 확인합니다. 걷는 모든 틱이 정확히 1픽셀 움직이고, 각 타일이 16틱 걸리며, 2픽셀 움직이는 틱이 없을 것. 각 방향으로 걷는 대각선 걸음과 달리는 대각선 걸음을 한 번씩 하는 대각선 데모에서는, 23틱과 12틱, 걸음당 축마다 16픽셀, 축별 이동이 걸을 때 0 또는 1픽셀이고 달릴 때 1 또는 2픽셀일 것, 그리고 달리는 대각선이 걷는 대각선보다 빠를 것을 확인합니다. 단위 테스트는 누산기에 정확한 정수 단위로 만든 dt 수열을 넣습니다. 120에서 10헤르츠까지의 12개 iPhone 레이트 각각에서 10초 동안이면 아무것도 버리지 않고 600틱을 실행할 것. 60헤르츠에서 1초의 공백이 있으면 다음 콜백에서 8틱, 그 뒤로는 콜백마다 1틱을 실행하고, 866.7밀리초를 버렸다고 기록할 것. iPhone 18 Pro Max와 iPhone Duo의 안쪽 디스플레이에서 얻은 같은 모션 로그가 타일당 같은 틱 수를 보일 것. 디스플레이의 레이트가 걷기를 바꿔서는 안 됩니다.

2. 걷기 그림은 거리로 고릅니다.

변경. 이 항목은 복제가 아니라 제 제안입니다. Emerald는 그림을 걸음에 맞춰 프레임으로 재며, 거리에서 읽지 않습니다.92 걷는 동안에는 걸음 수로 센 걷기의 진행, 즉 걷기를 시작한 이후 완료한 걸음 수에 현재 걸음의 틱 수를 그 길이로 나눈 값을 더한 것으로 열을 고릅니다. walkCycle[floor(steps × walkCycle.count / 2) % walkCycle.count]이며, 두 걸음에 발 디딤 두 번입니다. 달리기도 마찬가지입니다. 직선 걸음에서는 이것이 걸은 거리를 32픽셀로 나눈 것, 곧 floor(distancePx × 6 / 32) mod 6입니다. 대각선에서는 √2배 길이가 아니라 걸음 자체를 세므로, 보폭은 길어지지만 발 디딤은 여전히 모든 걸음의 시작에 떨어집니다. 직선 보폭에 맞춰 그린 그림을 위한 제 선택입니다. 걷기가 끝나면 Emerald처럼 서 있는 그림을 보여 줍니다. 대기, 눈 깜박임, 이모트의 시계는 그대로 둡니다. Emerald처럼 틱으로 재는 사이클도 걸음에 맞출 수는 있지만, 32틱에 그림 여섯 장이면 5틱과 6틱의 고르지 않은 표시 시간이 필요합니다. 진행으로 세면 두 번째 테이블이 필요 없고, 앱이 어떤 이동 방식을 추가하더라도 성립합니다.

이유. 어느 속도에서든 16픽셀마다 발 디딤 한 번, 그리고 움직임에서 어긋날 수 없는 리듬. 지금의 사이클은 모델에서 걷기로 3.00타일, 달리기로 4.50타일을 덮습니다.16 이것으로 캐릭터 편 글의 “eight frames a second”(초당 8프레임)도 정리됩니다. 틱당 1픽셀이면 32픽셀에 걸친 그림 여섯 장은 5.33픽셀마다 바뀌며, 이는 초당 8장이 아니라 11.25장입니다.35

검사. 모션 로그와 표시된 열로 확인합니다. 두 접지 그림(포지 사이클의 walk_a와 walk_d)이 60헤르츠와 120헤르츠 모두에서 32픽셀마다 0픽셀과 16픽셀 위치(±1)에 나타날 것. 대각선 데모에서는 각 걸음의 첫 틱에 나타날 것.

3. 걸음은 끝까지, 탭은 유지, 플로팅 스틱 추가.

변경. 걸음 도중의 탭은 출발점이 아니라 지금 향하고 있는 타일에서 경로를 계획하며, 현재 걸음을 먼저 끝냅니다. 걸음 수는 절대 초기화되지 않습니다. 서버에서 오는 다른 수집가들의 이동에도 같은 규칙을 적용합니다. 서 있는 상태에서 첫 걸음이 방향을 바꾸는 탭을 하면, 움직이기 전에 8틱 동안 방향을 돌립니다. 플레이어 옆의 막힌 칸이나 플레이어 앞의 타일을 탭하면, 그쪽을 바라보고 항목 6의 거부 햅틱과 함께 32틱 부딪힘을 재생합니다. 손가락을 대고 있으면 Stardew의 기본값처럼 그 손가락을 따라가되, 손가락이 타일을 건널 때마다 기존 경로 탐색으로 경로를 다시 짭니다. Stardew의 따라가기는 장애물을 돌아가지 않지만, 저희 것은 돌아갈 수 있기 때문입니다. 플로팅 썸스틱은 4방향이고 기본값은 꺼짐이며, 엄지가 닿는 곳에 나타나고, 세계 위에 얹은 SwiftUI 드래그 제스처로 만듭니다. 그려지는 것은 모두 최소 44×44포인트로 합니다. Touch Controller의 썸스틱(iOS 26)은 테스트 빌드에서 그것이 RealityView 위에 그려지는 것이 확인될 때만 드래그를 대체합니다. Apple은 이 프레임워크를 “Metal-based games”(Metal 기반 게임)용이라고 설명하고, WWDC25 세션은 “integrates directly with Metal”(Metal과 직접 통합된다)이라고 말하지만, Kiradex의 세계는 RealityView가 그리며 자체 렌더 패스가 없기 때문입니다.4562

이유. 5절의 걸음, 방향 전환, 거부된 걸음, 입력 규칙.14044624517

검사. UI 테스트로 동쪽 6타일 앞을 탭하고, 120밀리초 뒤에 북쪽 6타일 앞을 탭합니다. 모션 로그에 플레이어의 x가 줄어드는 틱이 하나도 없고, 방향 전환 전에 첫 걸음의 타일이 완료될 것. UI 테스트로 집 옆의 벽을 탭합니다. 로그에 방향 변화와 32틱 부딪힘이 나타나고 위치 변화는 없을 것. 스틱을 켜고 움직임의 첫 틱부터 60틱 동안 오른쪽으로 계속 밀면, 48틱까지 세 타일을 마치고, 스틱을 아직 밀고 있는 동안 네 번째 타일을 시작하며, 손을 뗀 뒤 64틱에 그것을 마칠 것. 로그에는 플레이어가 동쪽으로 4타일, 온전한 타일 위에 있고, 손을 뗀 시점에 진행 중이던 걸음이 완료되었으며, 타일 사이에서 멈추지 않았다는 것이 나타납니다.

4. 카메라를 고정합니다.

변경. 이징을 없애고, 플레이어가 움직이는 같은 틱에 정수 월드 픽셀로만 카메라를 정합니다. camera = clamp(player + foldOffset, low, high)입니다. 각 항을 정수로 유지하는 이유는, 지금은 마지막 반올림만이 카메라를 정수로 만들기 때문입니다. 경계와 접힘 오프셋에는 소수가 있습니다. 플레이어의 위치는 항목 1에 의해 정수입니다. follow가 포인트에 displayScale / pixelScale을 곱해 계산하는 접힘 오프셋은, 접힘이 바뀔 때 한 번만 정수 월드 픽셀로 반올림합니다. 제한 경계는 안쪽으로, 즉 아래쪽은 올림, 위쪽은 내림으로 반올림합니다. 월드 픽셀로 본 화면의 절반 크기가 정수가 아닐 수 있기 때문입니다. fit은 화면의 스크린 픽셀을 텍셀당 정수 개의 스크린 픽셀로 나누므로, 예시로 3배 393×852포인트 화면이면 7이 되고, 화면 폭은 168.43월드 픽셀, 절반 폭은 84.21입니다. 카메라 경계는 85와 맵 폭에서 85를 뺀 값이 되어, 어떤 프레임도 맵 가장자리 너머를 보여 주지 않습니다.176 맵이 화면보다 작으면 지금의 제한이 허용하듯 경계가 뒤바뀌고, 같은 안쪽 반올림이 맵 전체를 화면 안에 둡니다. 맵에 대한 제한은 유지합니다. 5절의 규칙은 제한과 바깥 그리기를 모두 허용하며, Kiradex의 카메라는 Duo가 반쯤 열렸을 때만 정수 타일 여백만큼 맵 가장자리를 넘어 숲으로 나갑니다.17 접힘 오프셋은 접힘이 바뀔 때까지 고정으로 취급합니다. a에서 b로 바뀔 때는 i를 0부터 8까지 움직이며 a + round((b − a) × i / 8)의 단계를 2틱에 한 단계씩, 페이드와 같은 리듬으로 밟으므로, 도중의 모든 위치가 정수 픽셀입니다. 예를 들어 0에서 마이너스 37로 바뀌면 0, −5, −9, −14, −19, −23, −28, −32, −37을 지납니다.6 숲의 시차는 고정된 카메라로부터 계산하고 지금처럼 반올림합니다. 탭한 목적지를 향한 앞보기는 하지 않습니다. Game Freak은 하나를 만들고 꺼 두었으며, 탭으로 걷는 경우 목적지는 이미 화면에 있습니다.12

이유. 5절의 카메라 규칙. 그리고 모델의 뒤처짐(걷는 동안 늘어나 다섯 번째 타일에서 9픽셀, 달리기에서는 13픽셀), 그로 인한 화면 위 플레이어 위치의 변화, 4픽셀과 9픽셀의 정지 오프셋.6

검사. 모션 로그에서, 트인 광장을 걷는 동안 모든 틱에서 카메라에서 플레이어를 뺀 값이 일정하고(0, 또는 접힘 오프셋) 플레이어가 멈춘 뒤에도 같으며, 모든 카메라 값이 정수일 것. 단위 테스트로 3배 393×852포인트 화면을 fit에 넘기고, 플레이어를 맵의 양쪽 가장자리까지 걷게 하여, 카메라 위치가 정수이고 85와 맵 폭에서 85를 뺀 값에서 멈추는지 확인합니다. 이어서 같은 테스트에서 접힘을 바꾸고, 카메라가 2틱 간격의 정수 픽셀 위치 9개를 거쳐 새 오프셋에 도달하는지, 그 사이 어느 틱에서도 경계를 벗어나지 않는지 확인합니다. 캡처로 확인할 때는 걷기 그림을 기준점으로 쓸 수 없습니다. 발 모양이 그림마다 바뀌기 때문입니다. 디버그 빌드가 다른 어디에도 쓰지 않는 색의 1텍셀 표지를 플레이어 엔티티 위치에 그리고, 스크립트가 트인 광장에서 6타일을 걷는 동안의 모든 프레임에서 그것을 찾습니다. 모든 프레임에서 같은 스크린 픽셀에 있을 것. 맵 가장자리 근처에서는 표지가 움직이고 카메라는 움직이지 않으며, 그 움직임은 정수 픽셀 단위로만 일어날 것.

5. 문, 걸음, 페이드를 Kiradex 자체 아트로.

변경. 문으로 들어가는 경우, 즉 문 시트가 있는 워프의 경우입니다. 지금은 걷는 캐릭터가 문 칸에 착지한 뒤에 문이 열립니다. 걸음의 onStep이 그 타일에서 워프를 찾아 그 자리에서 openDoor를 호출하는 것입니다.17 Emerald는 플레이어를 닫힌 문 위에 세우는 일이 없습니다. TryDoorWarp는 아래 칸에 있는 플레이어가 북쪽의 문을 향해 밀 때만 발동하고, Task_DoDoorWarp는 한 칸 위의 문을 연 다음 그 위로 걸음을 강제합니다.6525 브리프는 이에 맞춰 트리거를 한 칸 앞으로 옮깁니다.

  1. 경로의 다음 걸음이 문 워프로 올라서는 걸음이면, 걷기는 그 앞 칸에서 멈추고, 출입은 거기서 0틱에 시작합니다. 입력을 멈추고 문 소리를 재생합니다. 소리는 Kiradex의 것입니다.
  2. 지금의 “반쯤 열린 그림을 즉시, 70밀리초에 열린 그림” 대신, 5틱짜리 슬롯 네 개로 문을 엽니다. Emerald의 그림 네 장을 각 5프레임으로 보여 주는 방식입니다(초당 60틱에서 슬롯당 83밀리초, Emerald의 5프레임은 84, 모두 20틱). 슬롯은 닫힘, 반쯤 열림, 열림, 열림입니다. Kiradex의 문 시트는 닫힘, 반쯤 열림, 열림 세 장(포지의 door_sheet()가 세 장을 그리고, 앱은 SpriteSheet.bundled(name, columns: 3, rows: 1)로 불러옵니다)이고 Emerald의 문은 닫힘에 열린 그림 세 장이므로, 네 번째 슬롯에서도 열린 그림을 계속 보여 줍니다. 포지가 반쯤 열림과 열림 사이에 네 번째 그림을 그린다면 대신 그 슬롯을 채우겠지만, 이는 선택 사항인 아트이지 요구 사항이 아닙니다. “four ticks”(4틱)라고 적힌 주석을 고칩니다.2417
  3. 플레이어를 그 칸에서 문 칸으로 16틱 동안 강제로 한 걸음 걷게 합니다. 마을의 문은 건물 맨 아래 줄에 있고 양옆과 위가 건물 칸이며, 경로는 벽 모서리를 가로지르지 않으므로, 이 걸음은 Emerald처럼 항상 아래 칸에서 위로 향하는 걸음입니다.17
  4. 걷는 캐릭터를 숨기고, 슬롯을 거꾸로(열림, 열림, 반쯤 열림, 닫힘) 밟아 문을 닫습니다. 20틱입니다.
  5. 세계 위에 검은 베일을 계단식 9단계 불투명도(0, 2/16 하는 식으로 16/16까지)로 페이드합니다. 단계 i는 페이드의 2i틱에 두므로, 마지막 단계는 16틱에 오고 17틱까지 유지됩니다. 18틱입니다. 이는 일부러 고른 각색이며 Emerald의 타이밍 그대로가 아닙니다. Emerald의 스프라이트 팔레트는 이 베일과 같은 짝수 프레임에 각 단계에 도달하고, 배경 팔레트는 한 프레임 먼저 도달하며, 프레임 16의 마지막 블렌드 뒤에 마무리 업데이트 다섯 번을 실행하고 프레임 21에 비활성화됩니다. 베일 하나에는 뒤처질 두 번째 층도, 마무리할 것도 없습니다.5 RealityView 자체를 담은 뷰의 불투명도는 절대 애니메이션하지 마십시오. 첫 글이 카드 뷰어에서 그 교훈을 얻었습니다.22
  6. 애니메이션을 끈 채 목적지를 push하고, 같은 9단계로 베일을 다시 걷어 냅니다.
  7. 실내 매트 위에 위를 바라본 채 도착합니다. 나갈 때는 거꾸로입니다. 문이 열린 채 문 칸에 도착하고, 16틱 동안 아래로 강제 한 걸음, 문이 닫히고, 그다음 입력을 받습니다.

지금은 페이드 없이 스테이지를 교체하는 층 이동은, 문도 강제 걸음도 없이 같은 베일을 씁니다. 지금은 dismiss()를 호출하는 방 나가기는 베일과 밖으로 향하는 강제 걸음을 씁니다.

이유. 5절의 문, 페이드, 의식 규칙. 지금은 걸음 220밀리초 뒤에 장소가 바뀌고, 문은 70밀리초 간격으로 그림 두 장을 보여 주며, 화면은 옆으로 미끄러집니다.1517

검사. 모션 로그는 문 아래에서 걷기가 멈춘 틱부터 틱을 셉니다. 로그에는 플레이어가 아직 그 칸에 있고, 문의 슬롯이 0, 5, 10, 15틱(닫힘, 반쯤 열림, 열림, 열림)에 있으며, 강제 걸음이 20틱부터 35틱까지 틱당 1픽셀씩 진행되어 35틱에 문 칸에 도달하고 그보다 일찍 도달하지 않으며, 닫는 슬롯이 36, 41, 46, 51틱에 있고, 베일의 첫 단계가 56틱에 있으며, push는 빨라도 74틱이라는 것이 나타납니다. 이 시퀀스 없이 문 칸에서 끝나는 걷기는 검사에 실패합니다. 모션 로그는 매 틱 베일의 불투명도도 기록합니다. 나갈 때는 56틱부터 73틱까지 서로 다른 값 9개가 각각 2틱씩, 들어올 때도 같은 9개입니다. 화면 녹화로는 그림에서 가만히 있는 부분으로 확인합니다. 프레임 전체로는 안 됩니다. 물이 있는 맵에서는 지면의 물이 초당 4번 그림을 바꾸고, 다른 수집가들이 걸어 다닐 수 있기 때문입니다. 그래서 스크립트는 미리 골라 둔 건물 벽의 한 부분, 즉 애니메이션되는 타일이 없고 녹화 중 위로 걷는 캐릭터가 지나가지 않는 부분을 살펴, 나갈 때 9개, 들어올 때 9개의 서로 다른 단계를 세고, 각각이 2프레임씩(60헤르츠에서 ±1) 유지되는지 확인합니다.17 워프의 화면 녹화에서 세계는 절대 수평으로 이동하지 않습니다. 내비게이션 슬라이드는 없습니다.

6. 햅틱: 이벤트 세 가지와 스위치 하나.

변경. 세계가 소유하는 CHHapticEngine 하나를 두고, 세계가 나타날 때 시작해 사라질 때 멈춥니다. 설정에는 기본값이 켜짐인 햅틱 스위치를 두고 시스템의 햅틱 설정을 따릅니다. 패턴은 AHAP 파일로 번들에 넣습니다.

이벤트 패턴 이유
거부된 걸음(부딪힘) hapticTransient 하나, 강도 0.4, 선명도 0.2 HIG의 impact는 “a thud when two heavy objects collide”(무거운 두 물체가 부딪칠 때의 쿵 소리).46 아트가 부드러우니 부드럽게
문이 열림 열리면서 그림이 바뀌는 각 슬롯에 강도 0.3, 선명도 0.6의 hapticTransient. 5틱의 반쯤 열린 그림과 10틱의 열린 그림(83 ms와 167 ms) 함께하는 애니메이션에 맞춤
카드 들어 올리기(3D 카드) 꼭대기에 이르면 0.7과 0.8의 hapticTransient 세계에 있는 유일한 진짜 물체
발걸음 기본값은 없음 초당 3.75걸음이 몇 분씩 이어지는 것은 HIG가 경고하는 남용 그 자체

강도와 선명도 값은 측정값이 아니라 제 출발점이며, 기기에서 조정하기 위한 것입니다. capabilitiesForHardware()가 Core Haptics를 쓸 수 없다고 할 때의 대안은 UIImpactFeedbackGenerator(style: .soft, view:)이며, 걷기가 시작될 때 prepare()를 호출합니다.

이유. 햅틱 규칙. 6절에서 인용한 Apple의 지침.46565760

검사. -hapticLog 인자로 각 이벤트를 기록합니다. 스크립트로 진행하는 문 출입에서, 항목 5의 셈으로 5틱과 10틱에 문 이벤트가 정확히 2개 기록되고 걸음마다의 이벤트는 하나도 없을 것. 스위치를 끄면 아무것도 기록되지 않을 것.

7. 프레임 레이트: 60으로, 고르게.

변경. Info.plist는 바꾸지 않습니다. CADisableMinimumFrameDurationOnPhone을 추가하지 마십시오. 세계는 120에서 얻는 것이 없기 때문입니다. RealityView는 유지합니다. 항목 1의 틱 덕분에 RealityView가 어떤 레이트로 렌더링하든 움직임은 그에 좌우되지 않습니다. 언젠가 디스플레이 링크를 추가한다면, Apple의 게임 우선순위를 위해 CAFrameRateRange(minimum: 30, maximum: 60, preferred: 60)을 설정합니다.76

검사. iPhone 18 Pro Max와 iPhone Duo의 바깥쪽 및 안쪽 디스플레이에서, 60초 동안 걷는 사이의 SceneEvents.Update.deltaTime 히스토그램을 기록합니다. 브리프는 최빈값이 16.7밀리초라고 가정합니다. 만약 8.3이라면, 항목 1의 누산기가 걷기를 타일당 16틱으로 유지하고 로그가 그것을 증명합니다. 모션 로그의 dropped 줄은 어느 레이트에서든 일반적인 걷기에서는 없을 것.

8. 흔들림은 단 한 순간에만.

변경. 정수 텍셀 단위의 카메라 흔들림을, 1텍셀, 8번 뒤집기, 5틱 간격으로, 그만한 가치가 있는 이벤트 하나에만 씁니다. 예를 들어 광장에서의 희귀 카드 공개에 카드 들어 올리기 햅틱과 함께 씁니다. 문, 걸음, 도착에는 쓰지 않습니다.

이유. Emerald의 흔들림 24번 중 가장 흔한 것과, 그 절제.29

검사. 모션 로그에서 카메라 오프셋이 0, 5틱 하는 식으로 35틱까지 플러스 1과 마이너스 1텍셀을 번갈아 오가고, 40틱에 0으로 돌아올 것.

이 브리프에 넣지 않은 것

  • 그리드 위에서의 카메라 스무딩이나 앞보기. 다듬을 점프가 없고, Game Freak은 앞보기를 꺼 둔 채 출시했습니다.1223
  • 세계를 위한 120헤르츠. 목표는 60에서의 고른 페이싱입니다. 120에서는 각 픽셀이 두 번 보일 뿐입니다.6
  • 기본값으로 걸음마다 햅틱.46
  • 화면에 고정된 십자 패드. 제 권고는 탭으로 걷기와 선택 사항인 플로팅 스틱입니다.44
  • 게임의 문이나 워프 소리, 징글 그 무엇도. 소리는 표현이며, Kiradex에는 자기 소리가 필요합니다.
  • 자전거, 파도타기, 얼음, 물살. 앱의 걷는 캐릭터들은 걷고 달리는 것 말고는 하지 않으며, 위의 속도들은 그것이 바뀔 때를 위해 기록해 둔 것입니다.17

남은 질문: 기기에서의 타이밍

이 절의 모든 모델 수치는 프레임 사이가 일정한 1/60초 또는 1/120초라고 가정합니다. iPhone 18 Pro Max나 iPhone Duo의 RealityView가 60과 120 중 어느 쪽으로 렌더링하는지, 그 deltaTime이 얼마나 흔들리는지는 측정되지 않았습니다. 가장 먼저 실행할 것은 항목 7의 히스토그램이며, 항목 1은 그 답이 Apple의 iPhone 목록에 있는 어떤 레이트이든 걷기에 아무 변화도 주지 않도록 작성되어 있습니다.687

핵심 요약

아트를 그리는 분께

  • 걷기는 거리에 맞춰 그리십시오. Emerald의 걷기는 32픽셀에 걸친 내딛기 두 번과 서기 두 번이며, 걸음에 맞는 프레임 수만큼 표시됩니다. 더 빠른 이동은 프레임을 늘리지 않고 같은 그림을 더 짧게 보여 주므로, 어느 속도에서든 16픽셀마다 발 디딤이 한 번입니다.192
  • 그림 여섯 장짜리 사이클도 괜찮습니다. 단, 엔진이 그것을 일정한 거리에 걸쳐 재생해야 합니다. 따로 시간을 재는 걷기에 대해 일정한 레이트로 재생하면 리듬이 움직임에서 어긋나며, 저희 코드의 모델에서는 사이클당 걷기 3.00타일, 달리기 4.50타일입니다.6
  • Emerald의 문은 닫힘에 그림 세 장이며, 각각 84밀리초씩 화면에 머뭅니다. Kiradex처럼 닫힘, 반쯤 열림, 열림 세 장짜리 시트라면 열린 그림을 두 슬롯 동안 유지해 네 슬롯을 채우거나, 포지가 네 번째 그림을 그립니다. 어느 쪽이든 열린 상태를 볼 만한 것으로 그리십시오.12417

엔진을 만드는 분께

  • 세계는 고정 틱으로, 타일을 나누어떨어지게 하는 정수 픽셀 이동으로 진행하고, 입력은 타일 위에서 읽으며, 렌더러는 가장 최근 틱을 보여 주게 하십시오. 밀린 시간을 어떻게 처리할지 밝혀 두십시오. 저희는 8틱까지 만회하고 나머지는 버린 뒤 기록합니다. 본보기는 Emerald의 스텝 테이블이며, 모두 합이 16입니다.216
  • 카메라는 같은 틱에 플레이어에 고정하고, 경계와 오프셋을 정수 픽셀로 두어 나중에 반올림할 것이 없게 하십시오. 이징 후 반올림하면 걷는 플레이어 뒤로 처지고, 저희 코드의 모델에서는 다음 걷기 전까지 4에서 9픽셀 모자란 곳에 멈춥니다.36
  • iPhone에서는 픽셀 움직임을 위해 120헤르츠를 요청하지 마십시오. Apple은 게임에 30과 60을 우선하고, RealityKit은 보통 60으로 렌더링하며, 120에서의 정수 픽셀 걷기는 각 픽셀을 두 번의 리프레시 동안 유지할 뿐입니다.786
  • 페이드는 매끄러운 경사가 아니라 단계로 하십시오. Emerald의 문 페이드는 16분의 2 간격의 9단계이며, 마지막 블렌드는 17번째 프레임, 페이드의 끝은 22번째 프레임입니다. 저희의 18틱 베일은 그 경사의 각색이지 복제가 아닙니다.5
  • RealityView를 담은 SwiftUI 뷰의 불투명도는 절대 애니메이션하지 마십시오. 그 위에 베일을 덮으십시오.22

게임 루프를 설계하는 분께

  • Emerald에서 문 출입은 입력 없는 최소 1.32초의 의식이며, 문턱을 넘는 강제 걸음이 있기에 걸어 들어가는 것으로 읽힙니다.525
  • 8프레임 동안의 제자리 방향 전환은 플레이어가 움직이지 않고 무언가를 바라보게 해 주고, 32프레임 부딪힘은 걸음이 거부되었음을 알려 줍니다.1
  • 휴대폰에서의 제 권고: Stardew의 모바일 버전이 출시한 것처럼 기본값은 탭으로 걷기, HIG가 권하는 것처럼 정밀 조작의 대안으로 플로팅 스틱, 고정 십자 패드는 없음.4044
  • 햅틱은 발걸음이 아니라 사건을 확인하는 데 쓰고, 스위치를 함께 두십시오.46

자주 묻는 질문

Pokémon에서 플레이어는 얼마나 빨리 걷습니까?

Red, Crystal, Emerald에서 걷는 한 걸음은 16픽셀 셀 하나를 초당 59.7275프레임에서 16프레임에 걸쳐 이동합니다. 셀당 268밀리초, 초당 3.73셀입니다. Emerald는 매 프레임 1픽셀, Red와 Crystal은 두 프레임마다 2픽셀을 움직입니다. Emerald의 달리기와 파도타기, Red와 Crystal의 자전거는 셀당 8프레임, 초당 7.47셀입니다.1419

Pokémon Emerald의 걷기 사이클은 몇 프레임입니까?

32프레임에 걸친 네 항목입니다. 내딛는 그림 8프레임, 서 있는 그림 8프레임, 다른 쪽 내딛기 8프레임, 서기 8프레임입니다. 이는 걷기로 셀 두 개에 해당하므로, 걸음마다 내딛기와 서기를 한 번씩 보여 주고 다리는 걸음마다 번갈아 나옵니다. 달리기는 8프레임 셀 두 개에 걸친 16프레임 사이클입니다.192

Pokémon Emerald의 카메라는 플레이어보다 뒤처집니까?

아닙니다. Emerald의 카메라는 플레이어의 위치를 복사하고 같은 프레임에 같은 픽셀만큼 맵을 스크롤하므로, 세계가 움직이는 동안 플레이어는 화면에 고정되어 있습니다. 맵 가장자리에서도 멈추지 않습니다. 바깥은 레이아웃의 경계 타일로 그려집니다. 자전거용 앞보기 카메라는 코드에 있지만 켜지는 일이 없습니다.231112

Pokémon Emerald의 문과 페이드 전환은 얼마나 걸립니까?

문은 5프레임짜리 그림 네 장으로 열리고(335밀리초), 플레이어는 16프레임의 강제 걸음으로 안에 들어가며, 문은 20프레임에 닫히고, 화면은 9단계로 페이드합니다. 마지막 블렌드는 페이드의 17번째 프레임, 페이드의 끝은 22번째 프레임이며, 다음 맵을 불러오기 시작할 수 있을 때까지 최소 79프레임, 1.32초입니다. 동굴에 들어갈 때는 화면이 검은색 대신 흰색으로 페이드아웃하고, 동굴에서 나올 때는 흰색에서 다시 페이드인합니다.152527

픽셀 아트 게임은 ProMotion iPhone에서 120Hz로 돌려야 합니까?

정수 픽셀 움직임이라면 그럴 필요가 없습니다. 60헤르츠 틱마다 1픽셀 움직이는 세계는 120헤르츠에서 각 픽셀을 두 번의 리프레시 동안 보여 줄 뿐이고, 80에서는 표시 시간이 고르지 않습니다. ProMotion에 관한 Apple의 문서에 따르면 게임은 “special priority to 30Hz and 60Hz”(30Hz와 60Hz에 대한 특별한 우선순위)를 받고, iPhone 앱이 60을 넘으려면 CADisableMinimumFrameDurationOnPhone을 설정해야 하며, RealityKit은 보통 60으로 렌더링합니다. 고정 60헤르츠 틱으로 시뮬레이션하고, 디스플레이는 어떻든 그대로 두십시오.67168

걸을 때 스프라이트의 발이 미끄러지는 이유는 무엇입니까?

대개 걷기 그림은 한 시계로, 움직임은 다른 시계로 돌아가고 길이가 서로 맞지 않기 때문입니다. Kiradex의 현재 코드는 초당 4타일을 걸으면서 그림 여섯 장짜리 사이클을 초당 8장으로 재생하므로, 코드의 모델이 보여 주듯 한 사이클이 2타일이 아니라 3타일을 덮습니다. 휴대용 게임기는 둘 다 프레임으로 세고 각 걸음의 그림이 그 걸음만큼 지속되게 합니다. 걸은 거리로 그림을 고르는 방법, 즉 제가 Kiradex에 제안하는 방법은 두 번째 시계 없이 같은 일치를 얻습니다. 어느 쪽이든 리듬이 움직임에서 어긋날 수는 없으며, 발이 땅에 붙어 있어 보이는지는 그림 자체에도 달려 있습니다.619

모바일 픽셀 게임에 가상 십자 패드를 써야 합니까?

제가 자료를 읽은 바로는 기본값으로는 아닙니다. Stardew Valley 모바일의 기본값은 탭 이동이며, 정밀한 작업을 위한 다른 방식 가운데 보이지 않는 조이스틱이 있습니다. Apple의 HIG는 오브젝트를 직접 탭하는 것과, “wherever the player lands their thumb instead of a static thumbstick position.”(고정된 썸스틱 위치 대신 플레이어가 엄지를 댄 곳에) 나타나는 썸스틱을 권합니다.4044

이 사이트의 관련 글: iPhone에서 만드는 픽셀 아트 세계는 이 시리즈의 첫 가이드로, Emerald의 내딛기와 서기 걷기, 이 세계가 돌아가는 RealityKit 레시피, 카드 뷰어의 불투명도 교훈을 다룹니다. 픽셀 아트 인물: iPhone의 캐릭터와 크리에이터는 두 번째 글로, 이 글이 타이밍을 시계에서 거리로 옮기는 그림 여섯 장짜리 걷기를 다룹니다. 픽셀 아트 건물: iPhone의 집, 홀, 실내는 세 번째 글로, 2절이 타이밍을 측정한 문, 워프, 층을 다룹니다. 개발자를 위한 iPhone Duo와 iPhone Duo에 맞춰 앱 준비하기는 브리프의 카메라가 비켜 가는 두 디스플레이와 접힘을 다룹니다. RealityKit의 공간 멘탈 모델은 SceneEvents.Update 뒤에 있는 엔티티와 시스템 모델을 설명합니다.

출처


  1. 저자의 측정, 2026년 10월 4일: measure_gen3_motion.py(이 글을 위한 저자의 증거 폴더에 있음)를 pret의 pokeemerald 커밋 731ad5b에 대해 실행. src/event_object_movement.c의 스텝 함수 테이블과 InitMoveInPlace 길이, src/data/object_events/object_event_anims.h의 애니메이션 테이블, src/sprite.c의 지연 카운터 규칙, src/field_door.c의 문 프레임을 파싱하고, 프레임을 59.7275헤르츠로 환산함. 출력은 옆에 measure_gen3_motion.out.txt로 저장(속도는 초당 3.73, 7.47, 9.95, 14.93, 29.86셀. 제자리걸음은 32, 16, 8, 4프레임. sAnim_GoSouth는 32프레임. 문 그림은 업데이트 5번, 83.7 ms 유지, 네 장에 335 ms). ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  2. pret, pokeemerald/src/event_object_movement.c(sStep1Funcs부터 sStep8Funcs까지, 그리고 주석 “Over the course of the step animation, these sum to 16 pixels (one full metatile)”. sStepTimes와, sTimer로 스텝 테이블을 프레임마다 한 항목씩 짚는 NpcTakeStep. 걸음이 시작될 때 이동 방식의 애니메이션과 교대를 설정하는 SetStepAnimHandleAlternation. CameraObject_UpdateMove), 커밋 731ad5b, 2026년 10월 4일 열람, https://github.com/pret/pokeemerald/blob/master/src/event_object_movement.c ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  3. pret, pokeemerald/src/overworld.c(OverworldBasic의 순서 RunTasks(); AnimateSprites(); CameraUpdate(); UpdateCameraPanning();와 같은 함수 안에서 그 뒤에 이어지는 UpdatePaletteFade(). TransferPlttBuffer를 호출하는 VBlankCB_Field. InitCameraUpdateCallback(gPlayerAvatar.spriteId)보다 앞선 InitPlayerAvatar) 및 src/sprite.c(AnimateSprites는 콜백을 슬롯 순서로 실행), 2026년 10월 4일 열람, https://github.com/pret/pokeemerald/blob/master/src/overworld.c 및 https://github.com/pret/pokeemerald/blob/master/src/sprite.c . 같은 프레임이라는 결론은 저자의 코드 해석이며 실행한 것이 아님. ↩↩↩↩↩↩↩

  4. 저자의 측정, 2026년 10월 4일: measure_gen12_motion.py(이 글을 위한 저자의 증거 폴더에 있음)를 pret의 pokered d2704a6(home/overworld.asm, home/fade.asm)과 pokecrystal 5beda23(engine/overworld/events.asm, engine/overworld/map_objects.asm, data/maps/setup_scripts.asm, engine/tilesets/timeofday_pals.asm)에 대해 실행. 출력은 measure_gen12_motion.out.txt로 저장(Red: 16프레임에 16 px, 자전거 8프레임, 워프 페이드 32프레임. Crystal: 걷기는 2 px 업데이트 8번, 자전거는 4 px 4번, 느린 걸음은 1 px 16번으로 초당 1.87셀, 문 페이드는 한 방향 8프레임). ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  5. 저자의 측정, 2026년 10월 5일: measure_gen3_fade.py(이 글을 위한 저자의 증거 폴더에 있음). pret의 pokeemerald/src/palette.c(731ad5b)에서 BeginNormalPaletteFade, 보류 중인 전송 플래그를 포함한 UpdatePaletteFade, UpdateNormalPaletteFade, IsSoftwarePaletteFadeFinishing을 Python으로 옮긴 것으로, 호출자의 일정대로 실행함. 문 태스크가 RunTasks 안에서 페이드를 시작함(Task_DoDoorWarp, src/field_screen_effect.c). BeginNormalPaletteFade는 한 번 업데이트하고, 버퍼를 팔레트 메모리에 복사하고, 플래그를 지움. OverworldBasic(src/overworld.c)이 같은 프레임에 다시 업데이트함. 이후 각 프레임은 한 번씩 업데이트하고, VBlank 전송이 플래그를 지움. 프레임은 태스크의 프레임을 0으로 셈. 이전에 진행 중인 페이드가 없었다고 가정함. 비, 눈, 안개, 그늘, 가뭄이 활성화되어 있으면 src/field_weather.c의 FadeScreen이 날씨로 물든 버퍼를 복사한 뒤 같은 BeginNormalPaletteFade를 호출하므로(페이드아웃에 대한 날씨 자체의 핸들러는 DoNothing) 페이드아웃 일정은 유지되지만, 페이드인은 날씨 코드를 거치므로 시뮬레이션하지 않음. 워프 뒤의 페이드인은 맵 불러오기 콜백에서 시작되며 그 첫 프레임은 추적하지 않았으므로 두 경우를 모두 제시함. 출력은 measure_gen3_fade.out.txt로 저장(레벨은 0부터 16까지 2씩. 처음 보이는 블렌드는 프레임 1. 마지막 블렌드, 즉 16의 스프라이트 팔레트는 프레임 16, 프레임 0부터 세어 285 ms. 프레임 21에 비활성화, 368 ms. 지연 8의 FadeInFromWhite는 프레임 0부터 세어 프레임 85 또는 86에 비활성화, 즉 86 또는 87프레임 뒤, 1,440 또는 1,457 ms. 문 출입: 열기 20, 걸음 16, 닫기 20프레임, 마지막 페이드 블렌드는 최소 73프레임 뒤, 1.22 s. Task_WarpAndLoadMap이 페이드의 비활성화를 기다리므로 WarpIntoMap은 빨라도 프레임 79, 1.32 s). ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  6. 저자의 모델, 2026년 10월 5일: measure_kiradex_motion.py(이 글을 위한 저자의 증거 폴더에 있음). Kiradex/World/PlazaRig.swift에서 tilesPerSecond(4), framesPerSecond(8), runPace(1.6), 달리기 기준(6걸음), 카메라의 min(1, dt * 6)을, Kiradex/World/TileMap.swift에서 centre를, scripts/forge/rig.py에서 그림 여섯 장짜리 walk와 run 사이클을 읽고, advance, place, animate, follow를 프레임 단위로 모델링함. 모든 Float 연산을 32비트(numpy float32)로 코드의 순서대로, Swift의 0에서 먼 쪽으로 반올림하는 방식으로, 정확히 1/60 s와 1/120 s에서 수행하며, 5타일 걷기, 12타일 동안 유지한 걷기 페이스, 12타일 달리기를 실행하고 각각 뒤에 3 s 동안 서 있게 함. 맵 제한, 접힘, 발 위치 오프셋은 뺌. 코드의 모델이며 기기에서의 캡처가 아님. 출력은 measure_kiradex_motion.out.txt로 저장. 60 Hz 걷기: 타일당 15프레임, 각 타일은 1 px 14프레임과 2 px 1프레임. 카메라 간격은 첫 다섯 타일 끝에서 5, 6, 7, 8, 9 px, 걷기 페이스를 유지하면 아홉 번째 타일부터 13. 5타일 걷기의 첫 프레임 이후 이동 중인 73프레임 중 8프레임에서 화면 위치가 바뀜(모델의 걷기는 이동 중인 프레임이 74개이며, 마지막 타일의 마지막 프레임은 서 있는 것으로 셈). 정지 오프셋 4 px. 120 Hz: 타일당 30프레임, 1 px 16개와 0 14개. 간격 9, 정지 9. 60 Hz 달리기: 타일당 10프레임, 초당 6.00타일, 타일당 1 px 4프레임과 2 px 6프레임. 두 번째 타일부터 간격 13, 정지 4. 120 Hz 달리기: 타일당 19프레임, 초당 6.32타일. 간격 9, 정지 9. 60 Hz에서 시뮬레이션한 위치로 잰 연속된 사이클 시작 사이의 이동: 걷기 3.00타일, 달리기 4.50타일(명목상 초당 6.4타일이면 4.80). 120 Hz에서는 걷기 3.00과 2.94, 달리기 4.75. 현재 코드의 대각선 걸음: 60 Hz에서 걷기 22프레임, 달리기 14프레임, 120 Hz에서 43과 27. 시계, 위치, 카메라를 배정밀도로 바꾼 같은 모델과 비교하면 모든 위치와 카메라 값은 그대로이고, 60 Hz에서 9개, 120 Hz에서 14개의 걷기 그림이 다르며, 모두 시계에 8을 곱한 값이 정수인 프레임에서 일어남(12타일 걷기 중 그런 프레임은 60 Hz에서 11개, 120 Hz에서 23개). 제안: 고정 60 Hz 틱은 60 Hz에서 각 픽셀을 리프레시 1번, 120 Hz에서 61개 위치 중 59개를 리프레시 2번, 80 Hz에서 1번 또는 2번(42개와 19개 위치) 유지함. 누산기: 120에서 10 Hz까지의 12개 iPhone 레이트 각각에서 10초 분량의 콜백을 실행하면, 밀린 시간 상한 8틱이면 600틱을 실행하고 아무것도 버리지 않으며, 콜백당 4틱 상한이면 12 Hz에서 480, 10 Hz에서 400. 60 Hz에서 1 s 끊김 뒤 8틱 규칙은 8틱, 그 뒤로 콜백당 1틱을 실행하고 866.7 ms를 버리며, 밀린 시간을 유지하는 4틱 상한은 19번 연속 콜백에서 4틱씩 실행함. 카메라 계산: 3배 393×852 pt 화면에 대한 fit은 텍셀당 스크린 픽셀 7, 168.43×365.14월드 픽셀 화면, 절반 폭 84.21, 안쪽으로 반올림한 경계 85와 맵 폭에서 85를 뺀 값을 줌. 0에서 −37로의 접힘 변화를 9단계로 하면 0, −5, −9, −14, −19, −23, −28, −32, −37을 지남. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  7. Apple Developer Documentation, “Optimizing iPhone and iPad apps to support ProMotion displays”(iPhone의 10에서 120 Hz 범위와 그 12개 레이트, 기기 목록, CADisableMinimumFrameDurationOnPhone, 30 Hz와 60 Hz 게임 우선순위, “Prepare your app to operate at any refresh rate”, targetTimestamp), 2026년 10월 4일 열람, https://developer.apple.com/documentation/quartzcore/optimizing-iphone-and-ipad-apps-to-support-promotion-displays ↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  8. Apple Developer Documentation, “Improving the Performance of a RealityKit App”(“RealityKit typically limits the refresh rate”를 초당 60프레임으로), 2026년 10월 4일 열람, https://developer.apple.com/documentation/realitykit/improving-the-performance-of-a-realitykit-app ↩↩↩↩↩↩↩

  9. pret, pokeemerald/src/data/object_events/object_event_anims.h(sAnim_GoSouth, sAnim_GoFastSouth, sAnim_GoFasterSouth, sAnim_GoFastestSouth, sAnim_RunSouth) 및 src/sprite.c(프레임 길이에서 1을 뺀 값이 들어가는 animDelayCounter를 ContinueAnim이 세어 내리고, 0에 이르면 다음 프레임으로 넘어감), 2026년 10월 4일 열람, https://github.com/pret/pokeemerald/blob/master/src/data/object_events/object_event_anims.h 및 https://github.com/pret/pokeemerald/blob/master/src/sprite.c ↩↩↩↩↩↩↩↩↩↩↩

  10. pret, pokeemerald/src/field_player_avatar.c(PlayerWalkNormal, PlayerRun, 주석 “same speed as running”이 붙은 PlayerWalkFast, CheckMovementInputNotOnBike와 TURN_DIRECTION, PlayerTurnInPlace, 벽 부딪힘), 2026년 10월 4일 열람, https://github.com/pret/pokeemerald/blob/master/src/field_player_avatar.c ↩↩↩↩↩

  11. pret, pokeemerald/src/fieldmap.c(GetBorderBlockAt: 레이아웃의 2×2 경계 메타타일, MAPGRID_IMPASSABLE 표시), 2026년 10월 4일 열람, https://github.com/pret/pokeemerald/blob/master/src/fieldmap.c ↩↩↩

  12. pret, pokeemerald/src/field_camera.c(CameraUpdate. sVerticalCameraPan을 쉬는 값 32에서 72 또는 마이너스 8 쪽으로 2씩 옮기는 CameraPanningCB_PanAhead로, gUnusedBikeCameraAheadPanback으로 막혀 있고 “this code is never reached”라는 주석이 붙어 있음), 2026년 10월 4일 열람, https://github.com/pret/pokeemerald/blob/master/src/field_camera.c ↩↩↩↩↩↩↩↩

  13. Blake Crosley, “Pixel-Art Structures: Houses, Halls and Interiors on iPhone”, blakecrosley.com, 2026년 10월 3일(Emerald의 문 그림이 업데이트 5번, 약 84밀리초 유지됨. Red의 PlayerStepOutFromDoor. Emerald의 엘리베이터 흔들림. 공백으로 적은 Kiradex의 70밀리초 문과 220밀리초 워프), https://blakecrosley.com/blog/pixel-art-structures-on-iphone ↩↩↩↩↩↩

  14. 건물 편 글을 위한 저자의 조사 노트, Kiradex 저장소(비공개), docs/research/structures/01-structures-in-the-canon.md, 2026년 10월 3일. Emerald의 문 프레임을 “4 ticks each (16 frames, about 0.27 s at 59.7 Hz)”로 적었음. 이 글에서 제시한 field_door.c의 AnimateDoorFrame 해석으로 바로잡음. ↩↩

  15. Apple Developer Documentation, “preferredFrameRateRange”(CADisplayLink. iOS 15.0, iPadOS 15.0, Mac Catalyst 15.0, visionOS 1.0), 2026년 10월 4일 열람, https://developer.apple.com/documentation/quartzcore/cadisplaylink/preferredframeraterange ↩↩↩

  16. Apple Developer Documentation, “CADisableMinimumFrameDurationOnPhone”(Information Property List 키. iOS 15.0, iPadOS 15.0), 2026년 10월 4일 열람, https://developer.apple.com/documentation/bundleresources/information-property-list/cadisableminimumframedurationonphone ↩↩↩

  17. 저자의 Kiradex 저장소(비공개) 읽기, 커밋 b1b78b1, 2026년 10월 4일, 읽기 전용: Kiradex/World/PlazaRig.swift(tilesPerSecond, framesPerSecond, runPace, walk(to:)와 그 steps.count >= 6 달리기 규칙, 진행 스텝이 dt * Self.tilesPerSecond * (walker.running ? Self.runPace : 1) / length인 advance, animate, place, 화면의 스크린 픽셀을 정수 pixelScale로 나누는 fit, 접힘의 포인트를 displayScale / pixelScale로 환산하고 min(1, dt * 6)으로 이징한 뒤 반올림하며, Duo가 반쯤 열렸을 때만 beyondMargin, 즉 12타일만큼 맵 가장자리를 넘어 숲으로 카메라를 내보내는 제한을 가진 follow, 접힘과 관계없이 숲을 까는 build, 물이 있는 맵에서 지면의 물 그림을 초당 4번 바꾸는 ripple, openDoor와 그 주석 “at about Emerald’s four ticks a frame”), Kiradex/World/PlazaStage.swift(탭 처리, 220밀리초 워프 지연, SceneEvents.Update 구독, .id에 의한 층 교체), Kiradex/World/TileMap.swift(8방향 path. 지금까지의 비용이 가장 낮은 열린 타일을 꺼내고, 걸음마다 1 또는 √2를 더하며, 목표까지의 거리 추정치를 쓰지 않음. 대각선 걸음은 양옆 칸이 모두 걸을 수 있을 때만 허용), Kiradex/World/WorldMap.swift(워프의 문 시트, “closed, half open, open”), 그 시트를 SpriteSheet.bundled(name, columns: 3, rows: 1)로 불러와 열 1, 이어서 열 2를 보여 주는 openDoor, scripts/forge/kit.py(door_sheet(), “The three frames side by side, 48 × 32”), scripts/forge/town.py와 scripts/forge/buildings.py(마을의 문 워프는 모두 건물 맨 아래 줄의 문 칸에 있고, 모든 문의 왼쪽, 오른쪽, 위쪽 건물 칸은 막혀 있음. 출시된 town.json의 마을 문 일곱 개 전부에서 확인), Kiradex/Views/Card/CardViewer.swift(베일의 .easeOut(duration: 0.25)), scripts/forge/rig.py(사이클). UIImpactFeedbackGenerator, sensoryFeedback, CHHaptic, CADisplayLink, preferredFrameRateRange, CADisableMinimumFrameDurationOnPhone이 없다는 것은 2026년 10월 4일 Kiradex/와 project.yml을 검색한 결과이며, 하나도 나오지 않았음. 같은 검색으로 Kiradex/에서 MTKView, MTLRenderCommandEncoder, TouchController도 찾지 못했으므로, 세계에는 자체 렌더 패스가 없음. 걸음 도중 탭에 의한 되돌림은 walk(to:)에서 읽은 것이며 캡처한 것이 아님. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  18. pret, pokered(커밋 d2704a6, 2026년 9월 22일), pokecrystal(5beda23, 2026년 9월 29일), pokeemerald(731ad5b, 2026년 10월 1일)의 얕은 클론. 출시된 게임에 대한 커뮤니티 디컴파일, https://github.com/pret ↩

  19. Martin Korth, GBATEK, “LCD Dimensions and Timings”(280,896사이클, 프레임당 16.743 ms, “ca. 59.737 Hz”), 2026년 10월 4일 열람, https://problemkaputt.de/gbatek-lcd-dimensions-and-timings.htm . Pan Docs, “Rendering”(“One frame: 70224 dots @ 59.7 fps”), 2026년 10월 4일 열람, https://gbdev.io/pandocs/Rendering.html . 59.7275라는 수치는 저자의 계산: 2^24 / 280,896, 그리고 4,194,304 / 70,224. ↩↩↩↩↩

  20. pret, pokeemerald/src/bike.c(sMachBikeSpeedCallbacks, 상한 2의 bikeFrameCounter, PlayerRideWaterCurrent를 호출하는 AcroBikeTransition_Moving, 유일한 대입 gUnusedBikeCameraAheadPanback = FALSE), 2026년 10월 4일 열람, https://github.com/pret/pokeemerald/blob/master/src/bike.c ↩↩↩

  21. 그림은 저자의 스크립트 make_figures.py(svgkit.py 사용)로 2026년 10월 4일에 그렸으며, 디스크에 있는 데이터만 사용함: 원전의 속도는 measure_gen3_motion.out.txt와 measure_gen12_motion.out.txt에서. Kiradex의 걷기와 카메라는 measure_kiradex_motion.py 자체의 simulate()에서(5타일 걷기의 첫 1초. 60 Hz와 120 Hz의 5타일 걷기와 60 Hz의 12타일 달리기의 카메라), 제안의 틱 루프도 같은 스크립트에서. Emerald의 페이드는 measure_gen3_fade.py의 run(), 즉 호출자의 일정으로 실행하는 포트에서, 문 차트의 페이드와 가장 이른 맵 불러오기도 같은 것에서. Kiradex의 문 타이밍(70 ms, 1,400 ms, 220 ms)은 b1b78b1의 PlazaRig.swift와 PlazaStage.swift에서 읽음. 게임 아트는 쓰지 않음. ↩↩↩↩↩

  22. Blake Crosley, “Pixel-Art Worlds on iPhone: What the 16-Bit Masters Knew”, blakecrosley.com, 2026년 10월 3일(Emerald의 걷기 “step, stand, step, stand at eight ticks each”, Red의 “stand, step, stand, step-flipped”, 320×180으로 렌더링하고 6배로 확대하는 Celeste, RealityKit 엔진, “rounded to whole world units each frame, after easing”되는 위치, 카드 뷰어의 불투명도 교훈), https://blakecrosley.com/blog/pixel-art-world-on-iphone ↩↩↩↩↩↩↩↩

  23. Itay Keren, “Scroll Back: The Theory and Practice of Cameras in Side-Scrollers”, Game Developer, 2015년 5월 11일. GDC 2015 Independent Games Summit 강연을 고친 것(position-locking, edge-snapping, camera-window, lerp-smoothing, target-focus, dual-forward-focus, 각 인용), 2026년 10월 4일 열람, https://www.gamedeveloper.com/design/scroll-back-the-theory-and-practice-of-cameras-in-side-scrollers ↩↩↩↩↩↩↩↩

  24. pret, pokeemerald/src/field_door.c(sDoorOpenAnimFrames {4, -1}, {4, 0}, {4, 0x100}, {4, 0x200}. 카운터 0에서 그리고 카운터가 프레임 시간과 같아지면 넘어가는 AnimateDoorFrame), 2026년 10월 4일 열람, https://github.com/pret/pokeemerald/blob/master/src/field_door.c ↩↩↩↩

  25. pret, pokeemerald/src/field_screen_effect.c(Task_DoDoorWarp의 상태들. 다섯 번째는 WarpFadeOutScreen을 호출하고 태스크를 Task_WarpAndLoadMap에 넘기며, Task_WarpAndLoadMap은 WarpIntoMap 전에 PaletteFadeActive(), 즉 gPaletteFade.active와 BGMusicStopped()를 기다림. Task_ExitDoor, GetMapPairFadeToType을 호출하는 WarpFadeOutScreen, GetMapPairFadeFromType을 호출하는 WarpFadeInScreen, FadeScreen(FADE_FROM_WHITE, 8)을 쓰는 FadeInFromWhite), 2026년 10월 4일 열람, https://github.com/pret/pokeemerald/blob/master/src/field_screen_effect.c ↩↩↩↩↩↩↩↩↩↩

  26. pret, pokeemerald/src/palette.c(deltaY = 2를 설정하고, 스스로 UpdatePaletteFade를 호출하며, 팔레트 메모리로의 CpuCopy32와 sPlttBufferTransferPending = FALSE를 수행하는 BeginNormalPaletteFade. 그 플래그가 서 있는 동안 곧바로 반환하고, 업데이트마다 gPaletteFade_selectedPalettes로부터 플래그를 세우는 UpdatePaletteFade. 플래그를 지우는 TransferPlttBuffer. UpdateNormalPaletteFade. IsSoftwarePaletteFadeFinishing), 2026년 10월 4일 열람, https://github.com/pret/pokeemerald/blob/master/src/palette.c ↩↩

  27. pret, pokeemerald/src/fldeff_flash.c(MAP_TYPE_UNDERGROUND로의 출입 16행으로 이루어지고, 행마다 진입 플래그, 탈출 플래그, 전환 루틴을 가진 sTransitionTypes. 행의 진입 플래그를 반환하는 GetMapPairFadeToType과 탈출 플래그를 반환하는 GetMapPairFadeFromType), 2026년 10월 4일 열람, https://github.com/pret/pokeemerald/blob/master/src/fldeff_flash.c ↩↩↩

  28. pret, pokeemerald/src/field_specials.c(VAR_0x8004부터 VAR_0x8007에서 세로 팬, 가로 팬, 흔들림 횟수, 지연을 읽고, 흔들 때마다 팬의 부호를 뒤집는 ShakeCamera), 2026년 10월 4일 열람, https://github.com/pret/pokeemerald/blob/master/src/field_specials.c ↩

  29. 저자의 집계, 2026년 10월 4일: measure_gen3_shake.py(이 글을 위한 저자의 증거 폴더에 있음)는 pret의 pokeemerald(731ad5b)의 data/**/*.inc 전체에서 모든 special ShakeCamera 호출과 그 앞의 setvar 값 네 개를 읽음. 출력은 measure_gen3_shake.out.txt로 저장(9개 파일에 24번 호출, 표에 나온 8가지 파라미터 조합과 그 횟수). ↩↩↩↩↩↩

  30. pret, pokered/home/overworld.asm(OverworldLoop, OverworldLoopLessDelay, wWalkCounter, AdvancePlayerSprite, DoBikeSpeedup, 180도 회전에 관한 주석), 커밋 d2704a6, 2026년 10월 4일 열람, https://github.com/pret/pokered/blob/master/home/overworld.asm . 방향 전환 동작은 코드에서 읽은 것이며 에뮬레이터에서 실행한 것이 아님. ↩↩↩↩↩↩

  31. pret, pokered/engine/overworld/movement.asm(UpdatePlayerSprite, 4에서 그림을 넘기는 애니메이션 내부 카운터), 2026년 10월 4일 열람, https://github.com/pret/pokered/blob/master/engine/overworld/movement.asm ↩↩

  32. pret, pokered/home/fade.asm(GBFadeOutToBlack) 및 home/overworld.asm(PlayMapChangeSound, 문 타일 $0b이면 SFX_GO_INSIDE, 그렇지 않으면 SFX_GO_OUTSIDE), 2026년 10월 4일 열람, https://github.com/pret/pokered/blob/master/home/fade.asm ↩

  33. pret, pokecrystal/engine/overworld/events.asm(MaxOverworldDelay: db 2) 및 engine/overworld/map_objects.asm(StepVectors. .init1, .step1, .init2, .step2가 서로 이어져 내려가는 StepFunction_Turn과 ObjectStep_AnonJumptable), 커밋 5beda23, 2026년 10월 4일 열람, https://github.com/pret/pokecrystal/blob/master/engine/overworld/events.asm 및 https://github.com/pret/pokecrystal/blob/master/engine/overworld/map_objects.asm . 방향 전환의 업데이트 세 번은 저자가 루틴을 명령어 단위로 재현한 것이며 에뮬레이터 실행이 아님. ↩↩↩↩↩

  34. pret, pokecrystal/data/maps/setup_scripts.asm(FadeOutToWhite로 시작하는 MapSetupScript_Door, FadeInFromWhite로 끝나는 MapSetupScript_Warp) 및 engine/tilesets/timeofday_pals.asm, 2026년 10월 4일 열람, https://github.com/pret/pokecrystal/blob/master/data/maps/setup_scripts.asm 및 https://github.com/pret/pokecrystal/blob/master/engine/tilesets/timeofday_pals.asm ↩

  35. Blake Crosley, “Pixel-Art People: Characters and a Creator on iPhone”, blakecrosley.com, 2026년 10월 3일(그림 여섯 장짜리 걷기. 광장의 “the walk at eight frames a second”. “Since publishing” 항목에 적힌, TestFlight 빌드 34에서 26픽셀 인물을 32×40 셀의 30픽셀 인물로 바꾼 것. b1b78b1의 scripts/forge/rig.py는 이를 CELL_W, CELL_H = 32, 40으로 설정함), https://blakecrosley.com/blog/pixel-art-characters-on-iphone ↩↩↩

  36. Celeste 개발자들, GitHub의 NoelFB/Celeste 저장소, Source/Player/Player.cs(“Camera (lerp by distance using delta-time)” 아래의 카메라 업데이트와 CameraTarget 게터), 2026년 10월 4일 열람, https://raw.githubusercontent.com/NoelFB/Celeste/master/Source/Player/Player.cs ↩↩↩↩

  37. Celeste 개발자들, NoelFB/Celeste 저장소, README.md(클래스 파일을 “as a learning resource and for general interest” 공개. MIT 라이선스는 그 코드에만 적용), 2026년 10월 4일 열람, https://raw.githubusercontent.com/NoelFB/Celeste/master/README.md ↩

  38. Stardew Valley Wiki, “Speed”(플레이어 기본 속도: 걷기 2, 달리기 5, 말 6.6, 당근 뒤 7), 2026년 10월 4일 열람, https://stardewvalleywiki.com/Speed ↩

  39. 이 글을 위한 저자의 조사 노트, 2026년 10월 4일 작성. 찾아보았지만 찾지 못한 것을 기록함: Sea of Stars, Eastward, CrossCode의 카메라에 관한 일차 자료, Maddy Thorson의 카메라 강연이나 글, 픽셀 리마스터의 정확한 터치 방식, https://terraria.wiki.gg/wiki/Mobile_version 의 Terraria 모바일 이동, 404를 반환한 developer.apple.com/documentation/touchcontrols. ↩↩↩

  40. Stardew Valley Wiki, “Mobile Controls”(각 방식. 기본값인 “Tap-to-move & Auto-Attack”. 탭 이동, 터치 따라가기, 보이지 않는 조이스틱, 기본 조작의 한계에 관한 인용), 2026년 10월 4일 열람, https://stardewvalleywiki.com/Mobile_Controls ↩↩↩↩↩↩↩

  41. Jared Nelson, “‘Stardew Valley’ is Getting A TON of New Control Options in the Next Update”, TouchArcade, 2018년 11월 1일, 2026년 10월 4일 열람, https://toucharcade.com/2018/11/01/stardew-valley-mobile-controls-update/ ↩

  42. App Store, SQUARE ENIX의 “FINAL FANTASY”, 버전 기록(버전 1.2.0, 03/11/2025, 그리고 탭 기반 이동에 관한 메모), 2026년 10월 4일 열람, https://apps.apple.com/us/app/final-fantasy/id1492041278 ↩↩

  43. Mikhail Madnani, TouchArcade, 2024년 1월 30일, 모바일에 컨트롤러 지원을 가져온 파이널 판타지 픽셀 리마스터 업데이트에 관해, 2026년 10월 4일 열람, https://toucharcade.com/2024/01/30/final-fantasy-pixel-remaster-mobile-controller-support-update-boosts-cheats-font-not-fixed-steam-deck-patch-notes/ ↩

  44. Apple Human Interface Guidelines, “Game controls”(터치 조작 모범 사례. 2025년 6월 9일 변경 기록 항목), 2026년 10월 4일 열람, https://developer.apple.com/design/human-interface-guidelines/game-controls ↩↩↩↩↩↩↩

  45. Apple, WWDC25 세션 209(Touch Controls 프레임워크, “the vast majority of players won’t have a controller available”, “integrates directly with Metal”), 대본 2026년 10월 4일 열람, https://developer.apple.com/videos/play/wwdc2025/209/ ↩↩↩

  46. Apple Human Interface Guidelines, “Playing haptics”(모범 사례, 사용자 정의 햅틱, iOS의 impact 범주, 각 인용), 2026년 10월 4일 열람, https://developer.apple.com/design/human-interface-guidelines/playing-haptics ↩↩↩↩↩↩↩↩↩

  47. Apple Developer Documentation, “CAFrameRateRange”(iOS 15.0, iPadOS 15.0, Mac Catalyst 15.0, visionOS 1.0), 2026년 10월 4일 열람, https://developer.apple.com/documentation/quartzcore/caframeraterange ↩

  48. Apple Developer Documentation, “targetTimestamp”(CADisplayLink. iOS 10.0, iPadOS 10.0, Mac Catalyst 13.1, visionOS 1.0), 2026년 10월 4일 열람, https://developer.apple.com/documentation/quartzcore/cadisplaylink/targettimestamp ↩

  49. Apple, WWDC21 세션 10147, “Optimize for variable refresh rate displays”(Mac의 Adaptive-Sync 디스플레이와 iPad Pro의 ProMotion, 각 인용), 대본 2026년 10월 4일 열람, https://developer.apple.com/videos/play/wwdc2021/10147/ ↩↩↩

  50. Apple Developer Documentation, “SceneEvents.Update”(iOS 13.0, iPadOS 13.0, Mac Catalyst 13.0), 2026년 10월 4일 열람, https://developer.apple.com/documentation/realitykit/sceneevents/update ↩

  51. Apple Developer Documentation, “deltaTime”(SceneEvents.Update. iOS 13.0), 2026년 10월 4일 열람, https://developer.apple.com/documentation/realitykit/sceneevents/update/deltatime ↩

  52. Apple Developer Documentation, “RealityView”(iOS 18.0, iPadOS 18.0, Mac Catalyst 18.0, visionOS 1.0. System 또는 SceneEvents.Update를 통한 프레임별 코드), 2026년 10월 4일 열람, https://developer.apple.com/documentation/realitykit/realityview ↩

  53. Apple Developer Documentation, “CHHapticPattern”(Core Haptics. iOS 13.0, iPadOS 13.0, Mac Catalyst 13.0, visionOS 1.0), 2026년 10월 4일 열람, https://developer.apple.com/documentation/corehaptics/chhapticpattern . Core Haptics 프레임워크 페이지, https://developer.apple.com/documentation/corehaptics ↩

  54. Apple Developer Documentation, “CHHapticEvent” 및 “CHHapticEvent.EventType”(hapticTransient, hapticContinuous. iOS 13.0), 2026년 10월 4일 열람, https://developer.apple.com/documentation/corehaptics/chhapticevent 및 https://developer.apple.com/documentation/corehaptics/chhapticevent/eventtype ↩

  55. Apple Developer Documentation, “CHHapticEvent.ParameterID”(iOS 13.0), 2026년 10월 4일 열람, https://developer.apple.com/documentation/corehaptics/chhapticevent/parameterid ↩

  56. Apple Developer Documentation, “CHHapticEngine”(iOS 13.0. capabilitiesForHardware()), 2026년 10월 4일 열람, https://developer.apple.com/documentation/corehaptics/chhapticengine ↩↩

  57. Apple Developer Documentation, “UIImpactFeedbackGenerator”(iOS 10.0, iPadOS 10.0, Mac Catalyst 13.1. “Initializing the feedback generator” 항목의 init(style:view:)와 Deprecated 항목의 init(style:)), 2026년 10월 4일 열람, https://developer.apple.com/documentation/uikit/uiimpactfeedbackgenerator ↩↩

  58. Apple Developer Documentation, “UIImpactFeedbackGenerator.FeedbackStyle”(iOS 10.0), 2026년 10월 4일 열람, https://developer.apple.com/documentation/uikit/uiimpactfeedbackgenerator/feedbackstyle ↩

  59. Apple Developer Documentation, “impactOccurred(intensity:)”(iOS 13.0, iPadOS 13.0, Mac Catalyst 13.1), 2026년 10월 4일 열람, https://developer.apple.com/documentation/uikit/uiimpactfeedbackgenerator/impactoccurred(intensity:) ↩

  60. Apple Developer Documentation, “prepare()”(UIFeedbackGenerator. iOS 10.0), 2026년 10월 4일 열람, https://developer.apple.com/documentation/uikit/uifeedbackgenerator/prepare() ↩↩

  61. Apple Developer Documentation, “sensoryFeedback(:trigger:)” 및 “SensoryFeedback”(SwiftUI. iOS 17.0, iPadOS 17.0, Mac Catalyst 17.0, visionOS 26.0. impact(weight:intensity:)), 2026년 10월 4일 열람, https://developer.apple.com/documentation/swiftui/view/sensoryfeedback(:trigger:) 및 https://developer.apple.com/documentation/swiftui/sensoryfeedback ↩

  62. Apple Developer Documentation, “Touch Controller”(iOS 26.0, iPadOS 26.0, Mac Catalyst 26.0, visionOS 26.0), 2026년 10월 4일 열람, https://developer.apple.com/documentation/touchcontroller ↩↩↩↩

  63. Apple Developer Documentation, “TCDirectionPad”(iOS 26.0, iPadOS 26.0, Mac Catalyst 26.0), 2026년 10월 4일 열람, https://developer.apple.com/documentation/touchcontroller/tcdirectionpad ↩

  64. Apple Developer Documentation, “GCVirtualController”(Game Controller. iOS 15.0, iPadOS 15.0, Mac Catalyst 15.0), 2026년 10월 4일 열람, https://developer.apple.com/documentation/gamecontroller/gcvirtualcontroller ↩

  65. pret, pokeemerald/src/field_control_avatar.c(TryDoorWarp. 누르고 있는 방향이 바라보는 방향과 같을 때 플레이어 앞의 칸을 넘겨 호출되며, 그 방향이 북쪽이고 칸이 문 워프일 때만 워프함), 2026년 10월 4일 열람, https://github.com/pret/pokeemerald/blob/master/src/field_control_avatar.c ↩

관련 게시물

Pixel-Art Structures: Houses, Halls and Interiors on iPhone

How Pokémon, the SNES RPGs and Stardew build houses, interiors and floors, measured from source, and how Kiradex's town …

80 분 소요

Pixel-Art People: Characters and a Creator on iPhone

How the best pixel-art games build and dress their people, measured, and how we rebuilt Kiradex's collector, its wardrob…

79 분 소요

iPhone Duo에 맞춰 앱 준비하기: 실제 앱으로 따라가는 예제

iPhone Duo용 앱을 준비하고 제출하는 방법. 레이아웃을 켜는 SDK 스탬프, 모든 포즈로 돌려 본 실제 앱, 스크린샷, App Store 규칙까지 정리했습니다.

53 분 소요