← Wszystkie wpisy

Ruch w pixel art: chód, kamera i drzwi na iPhonie

Pokémon Red, Crystal i Emerald, po jednej grze z każdej z trzech generacji na Game Boyu i Game Boyu Advance, przechodzą komórkę 16 pikseli w 16 klatkach przy około 59,73 Hz, czyli 3,73 komórki na sekundę, i nic w tym kroku nie zależy od zegara: Emerald przesuwa gracza dokładnie o jeden piksel na klatkę, pokazuje na każdy krok jeden rysunek wykroku i jeden rysunek postawy, a kamerę przewija w tej samej klatce o te same piksele.123 Red i Crystal dochodzą do tych samych szesnastu klatek, przesuwając postać o dwa piksele co drugą klatkę.4 Drzwi w Emerald otwierają się w czterech rysunkach trzymanych po pięć klatek, gracz zostaje wprowadzony jednym wymuszonym krokiem do środka, drzwi się zamykają, a ekran wygasa w dziewięciu skokowych poziomach: co najmniej 79 klatek, 1,32 sekundy, zanim może zacząć się wczytywanie następnej mapy.15 Świat Kiradex chodzi według upływającego czasu i wygładza ruch kamery, a model tego kodu (model, nie nagranie z telefonu) mówi, że przy 60 Hz sprite raz na kafelek przeskakuje o dwa piksele, że kamera przy 60 Hz z każdym kafelkiem chodu zostaje o mniej więcej piksel bardziej w tyle (9 pikseli przy piątym, 13 w biegu) i zatrzymuje się 4 do 9 pikseli przed graczem, oraz że rysunki chodu mają własny zegar: jeden cykl na 3,00 kafelka tam, gdzie cykl Emerald obejmuje dwa.6 Apple daje grom specjalny priorytet przy 30 i 60 Hz, a RealityKit zwykle renderuje w 60, więc brief to stały tik 60 Hz z krokami o pełne piksele, rysunki chodu wybierane według przebytej odległości (moja propozycja, nie rozwiązanie z konsol przenośnych), zablokowana kamera, ceremonia drzwi z Emerald w naszej własnej grafice, trzy opcjonalne zdarzenia haptyczne i 60 Hz zamiast 120.78 Oto kanon zmierzony w dekompilacjach, strona iPhone’a według stron Apple oraz brief wraz z testami, które musi przejść.

W skrócie

  • Krok to kontrakt, a nie prędkość. Każda tabela kroków w Emerald sumuje się do 16 pikseli: chód to 1 piksel na klatkę przez 16 klatek (268 milisekund na komórkę), bieg i surfowanie to 2 przez 8, maksymalna prędkość roweru Mach Bike to 4 przez 4, a nierówny jest tylko układ 2-3-3-2-3-3 roweru Acro Bike. Red i Crystal pokonują te same 16 klatek skokami po 2 piksele, przy 30 aktualizacjach na sekundę.124
  • Nogi i krok są do siebie dopasowane czasowo. Emerald liczy rysunki i krok w klatkach, osobno, i dba o to, by te liczby się zgadzały: chód to wykrok, postawa, wykrok, postawa po 8 klatek na rysunek, rozłożone na dwie komórki po 16 klatek; szybsze tryby ruchu skracają czas rysunku o połowę zamiast dodawać rysunki, więc przy każdej prędkości wypada jedno stąpnięcie na 16 pikseli. Obrót z miejsca trwa 8 klatek (134 milisekundy), obrót w trakcie chodu nie zajmuje ani jednej, a wejście w ścianę odtwarza 32-klatkowe odbicie.19102
  • Kamera to gracz. Kamera w Emerald kopiuje pozycję gracza i przewija się o te same piksele w tej samej klatce, bez opóźnienia i bez wyprzedzania, i nigdy nie zatrzymuje się na krawędzi mapy, bo to, co na zewnątrz, rysowane jest z kafelków brzegowych. Game Freak napisał kamerę wyprzedzającą rower i wydał grę z tą kamerą wyłączoną.231112
  • Drzwi to ceremonia. Cztery rysunki po pięć klatek (335 milisekund), wymuszony krok trwający 16 klatek, zamykanie drzwi w 20 i wygaszanie, którego ostatnie mieszanie wypada w 17. klatce, a które kończy się w 22.: co najmniej 79 klatek, 1,32 sekundy, bez reakcji na sterowanie, zanim mapa może się wczytać. To potwierdza artykuł z tej serii o budowlach, około 84 milisekund na rysunek, i koryguje notatki badawcze, na których się opierał, gdzie stało „4 ticks each (16 frames, about 0.27 s at 59.7 Hz)” (po 4 tiki, 16 klatek, około 0,27 s przy 59,7 Hz). Crystal wygasza do bieli w 8 klatek; Red wygasza do czerni w 32.1513144
  • Na iPhonie równe tempo przy 60. preferredFrameRateRange (iOS 15.0) to wskazówka; iPhone’y z ProMotion pracują od 10 do 120 Hz w dwunastu stopniach; powyżej 60 potrzebny jest CADisableMinimumFrameDurationOnPhone; gry dostają „special priority to 30Hz and 60Hz” (specjalny priorytet dla 30 Hz i 60 Hz); RealityKit „typically limits the refresh rate” (zwykle ogranicza częstotliwość odświeżania) do 60. Chód o pełne piksele nic nie zyskuje przy 120: każdy piksel jest po prostu trzymany przez dwa odświeżenia.1571686
  • Kiradex dziś, w modelu, a nie w pomiarze. Kod chodzi z prędkością 4 kafelków na sekundę według upływu czasu, więc model przy stałych 60 Hz potrzebuje 15 klatek na kafelek i w jednej z nich przesuwa postać o 2 piksele; kod wygładza i zaokrągla kamerę, która w miarę trwania chodu zostaje coraz bardziej w tyle (5 pikseli po pierwszym kafelku, 9 po piątym, 13 w biegu) i zatrzymuje się 4 piksele od gracza przy 60 Hz i 9 przy 120; jej sześciorysunkowy chód odtwarzany w tempie 8 na sekundę obejmuje 3,00 kafelka na cykl w chodzie i 4,50 w biegu. Żaden telefon nie został zmierzony.176
  • Co to daje Kiradex: brief, a nie wydana kompilacja. Stały tik 60 Hz z krokami o 1 piksel i jasno określoną regułą dla zaległości, rysunki chodu wybierane według odległości, kamera zablokowana na pełnych pikselach, pełna sekwencja drzwi ze skokowym wygaszaniem, pokazywany odrzucony krok, trzy opcjonalne zdarzenia haptyczne, żadnego żądania 120 Hz, a dla każdego punktu test, który da się zweryfikować dziennikiem ruchu, nagraniem lub skryptem.

1. Chód w Emerald: szesnaście pikseli w szesnastu klatkach

Pierwsze dwa artykuły tej serii zmierzyły, jak wygląda pikselowy świat i jego mieszkańcy; trzeci zmierzył jego budynki. Ten mierzy czas: ile pikseli na klatkę, ile klatek na krok, który rysunek pojawia się w której klatce, ile trwa obrót, drzwi i wygaszanie oraz co w tym czasie robi kamera. Gry są czytane tak samo jak we wcześniejszych artykułach, z dekompilacji pret wydanych gier na Game Boya i Game Boya Advance, w tych samych commitach: pokered d2704a6, pokecrystal 5beda23, pokeemerald 731ad5b.18 Liczeniem zajęło się pięć małych skryptów; każdy jest wymieniony w przypisach razem z zapisanym wynikiem.

Najpierw jedna liczba, bo wszystko inne jest w klatkach. Game Boy Advance rysuje klatkę w 280 896 cyklach zegara o częstotliwości 2^24 Hz, co daje 59,7275 klatki na sekundę, 16,743 milisekundy na klatkę; GBATEK zaokrągla to do „ca. 59.737 Hz”.19 Zegar oryginalnego Game Boya, 4 194 304 Hz, przy 70 224 punktach na klatkę daje te same 59,7275; Pan Docs podaje „@ 59.7 fps”.19 Milisekundy w tym artykule liczone są dla 59,7275.19

Każda prędkość to tabela, która sumuje się do szesnastu

Emerald nie przesuwa postaci według wzoru prędkość razy czas. Każda prędkość na mapie świata to tabela przesunięć w pikselach na klatkę, a kod źródłowy mówi, co łączy wszystkie tabele: „Over the course of the step animation, these sum to 16 pixels (one full metatile).” (w trakcie animacji kroku sumują się do 16 pikseli, jednego pełnego metatile’a).2 sStep1Funcs to szesnaście przesunięć o jeden piksel, sStep2Funcs osiem przesunięć o dwa piksele, sStep4Funcs cztery po cztery, sStep8Funcs dwa po osiem, a wyjątkiem jest sStep3Funcs: Step2, Step3, Step3, Step2, Step3, Step3.2 Po przeliczeniu skryptem w kodzie ruchu wychodzi:

Stała prędkości Piksele na klatkę Klatki na komórkę Komórki na sekundę Milisekundy na komórkę Zastosowanie
MOVE_SPEED_NORMAL 1 w każdej klatce 16 3,73 267,9 chód; chód postaci niezależnych (NPC)
MOVE_SPEED_FAST_1 2 w każdej klatce 8 7,47 133,9 bieg, surfowanie, ślizg po lodzie
MOVE_SPEED_FAST_2 2, 3, 3, 2, 3, 3 6 9,95 100,5 Acro Bike, prądy wodne
MOVE_SPEED_FASTER 4 w każdej klatce 4 14,93 67,0 Mach Bike przy maksymalnej prędkości
MOVE_SPEED_FASTEST 8, 8 2 29,86 33,5 akcje ruchu ślizgowego

Źródło każdego wiersza: measure_gen3_motion.py uruchomiony na src/event_object_movement.c.1

Chód to liczba, którą warto zapamiętać: jeden piksel w każdej klatce, szesnaście klatek na komórkę, 3,73 komórki na sekundę, 268 milisekund na komórkę; korzysta z niego PlayerWalkNormal i każda chodząca postać niezależna.1 Bieg z przyciskiem B to PlayerRun, a surfowanie to PlayerWalkFast, opatrzone w kodzie komentarzem „same speed as running” (ta sama prędkość co bieg); ślizgi po lodzie wywołują tę samą funkcję. Wszystkie trzy to dwa piksele na klatkę, osiem klatek na komórkę, 7,47 komórki na sekundę.110 Jest też powolny chód, UpdateWalkSlowAnim, który przesuwa postać o piksel przy parzystych wartościach licznika, 31 do 32 klatek na komórkę, na potrzeby oskryptowanych scen.1

Acro Bike to jedyna prędkość z nierównymi pikselami: 2, 3, 3, 2, 3, 3 w sześciu klatkach, średnio 2,67 piksela na klatkę, 9,95 komórki na sekundę.1 Dochodzi się do niej przez PlayerRideWaterCurrent z AcroBikeTransition_Moving, tę samą funkcję, której używają prądy wodne.120 W mojej interpretacji ta nierówność jest ceną prędkości, która nie dzieli szesnastu: trzy piksele na klatkę przekroczyłyby komórkę, więc tabela przeplata dwójki z trójkami, żeby trafić dokładnie w szesnaście. Każda inna prędkość jest całkowitym dzielnikiem komórki.

Mach Bike przyspiesza krokami, a nie klatkami. sMachBikeSpeedCallbacks to PlayerWalkNormal, PlayerWalkFast, PlayerWalkFaster, a bikeFrameCounter rośnie o jeden na krok do limitu 2, więc pierwszy krok z miejsca trwa 16 klatek, drugi 8, a każdy kolejny 4: cztery piksele na klatkę, 14,93 komórki na sekundę.120 Rower nigdy nie jest pomiędzy dwiema prędkościami. Każdy krok to jedna z tabel, wykonana do końca, a prędkość zmienia się tylko na granicy komórki.

Poziomy wykres słupkowy prędkości na mapie świata w komórkach 16-pikselowych na sekundę. Red, Crystal i Emerald chodzą z prędkością 3,73; rower w Red, rower w Crystal oraz bieg i surfowanie w Emerald mają 7,47; Acro Bike w Emerald 9,95; maksymalna prędkość jego Mach Bike 14,93. Obecny chód Kiradex w modelu to 4,00, a bieg 6,00; chód z briefu to 3,75, a bieg 7,50.

Każdy tryb ruchu zmierzony w Red, Crystal i Emerald to całkowita liczba klatek na 16-pikselową komórkę; liczby Kiradex to model jego kodu, a nie nagranie.14621

Jedno stąpnięcie na szesnaście pikseli

Animacja chodu to wykrok, postawa, wykrok, postawa. sAnim_GoSouth to ANIMCMD_FRAME(3, 8), (0, 8), (4, 8), (0, 8): rysunek 3 przez osiem klatek, rysunek postawy 0 przez osiem, rysunek 4 przez osiem i znowu postawa przez osiem, łącznie 32 klatki, czyli dwie komórki chodu.91 Czas trwania jest dokładny: sprite.c wpisuje do licznika opóźnienia czas trwania klatki pomniejszony o jeden i odlicza do zera, więc klatka o czasie trwania 8 jest na ekranie przez osiem klatek.91 Każdy krok pokazuje zatem jeden rysunek wykroku i jeden rysunek postawy, a SetStepAnimHandleAlternation zaczyna każdy nowy krok w drugiej połowie cyklu (animPos = {1, 3, 0, 2}), więc lewa i prawa noga zmieniają się krok po kroku nawet wtedy, gdy gracz się zatrzymuje i rusza ponownie.2 Pierwszy artykuł tej serii opisał ten sam cykl od strony grafiki: „step, stand, step, stand at eight ticks each, which is the bob everyone remembers.” (krok, postawa, krok, postawa po osiem tików, czyli to kołysanie, które wszyscy pamiętają).22

Szybsze tryby ruchu zachowują rysunki i skracają czas ich trwania. GoFast trzyma każdy rysunek przez cztery klatki (cykl 16-klatkowy), GoFaster przez dwie (8 klatek), GoFastest przez jedną (4 klatki).1 Bieg ma własne rysunki, trzymane nierówno: sAnim_RunSouth to (12, 5), (9, 3), (13, 5), (9, 3), cykl 16-klatkowy.19

Gdy zestawić obie tabele obok siebie, projekt staje się widoczny. 32-klatkowy cykl chodu obejmuje dwie komórki po 16 klatek; 16-klatkowy cykl biegu obejmuje dwie komórki po 8; 8-klatkowy cykl GoFaster roweru Mach Bike obejmuje dwie komórki po 4.19 Przy każdej prędkości wypada jedno stąpnięcie na 16 pikseli.1 To dwa zegary, a nie jeden licznik. Rysunek zmienia się, gdy wyczerpie się animDelayCounter, ładowany w sprite.c czasem trwania każdej klatki; krok przesuwa się, gdy NpcTakeStep indeksuje tabelę danej prędkości wartością sTimer sprite’a, jedna pozycja na klatkę; a SetStepAnimHandleAlternation wybiera animację trybu ruchu na początku każdego kroku.92 Razem trzyma je to, że oba liczone są w tych samych klatkach, a czasy trwania dobrano tak, by się zgadzały, tryb po trybie. W mojej interpretacji właśnie to warto skopiować: kadencja rysunków nie może odjechać od ruchu, bo rysunki każdego kroku trwają dokładnie tyle klatek, ile sam krok. Nic w tych tabelach ani w moim modelu nie mierzy, gdzie postawiona stopa styka się z podłożem, więc nie twierdzę niczego ponad to.

Obrót, odbicie i moment odczytu sterowania

Rozpoczęty krok zawsze się kończy; krzyżak jest odczytywany po jego zakończeniu, więc przytrzymany kierunek łączy kroki bez przerwy, a zmiana kierunku w trakcie chodu nic nie kosztuje.10 Z miejsca jest inaczej. CheckMovementInputNotOnBike zwraca TURN_DIRECTION tylko wtedy, gdy nowy kierunek różni się od tego, w który gracz jest zwrócony, a gracz jeszcze się nie porusza, a PlayerTurnInPlace odtwarza szybki chód w miejscu, któremu InitMoveInPlace nadaje czas trwania 8 klatek: 134 milisekundy obrotu w miejscu.101 To mechanika, która pozwala graczowi zwrócić się do tabliczki albo do osoby bez robienia kroku w jej stronę. Wejście w ścianę odtwarza natomiast powolny chód w miejscu, 32 klatki, 536 milisekund, z dźwiękiem odbicia: odrzucony krok zostaje pokazany, a nie połknięty.110 Normalny i szybszy chód w miejscu trwają 16 klatek (268 milisekund) i 4 (67).1

W mojej interpretacji te cztery liczby to większość tego, co w grze na siatce znaczy „responsywność”. Świat nigdy nie przesuwa się o ułamek komórki, więc zamiar gracza wyraża się w pełnych krokach, a jedynym opóźnieniem jest reszta kroku w toku: najwyżej 268 milisekund w chodzie, 134 w biegu.1 Obrót z miejsca nie jest opóźnieniem, lecz osobną akcją z własnym widocznym efektem.

2. Kamera, drzwi, wygaszanie i wstrząsy w Emerald

Kamera nie ma opóźnienia ani wyprzedzenia

Kamera w Emerald to niewidzialny sprite, który podąża za graczem. CameraObject_UpdateMove kopiuje x i y śledzonego sprite’a i zapisuje różnicę względem poprzedniej klatki w sCamera_MoveX i sCamera_MoveY; CameraUpdateCallback przekazuje tę różnicę do CameraUpdate, które przewija mapę dokładnie o tyle pikseli.212 Pętla mapy świata wykonuje w każdej klatce RunTasks(); AnimateSprites(); CameraUpdate(); UpdateCameraPanning(); w tej kolejności, a ponieważ AnimateSprites uruchamia callbacki sprite’ów w kolejności slotów, a obiekt kamery tworzony jest po graczu (InitPlayerAvatar, potem InitCameraUpdateCallback(gPlayerAvatar.spriteId)), kamera odczytuje pozycję, którą gracz osiągnął w tej samej klatce.3 Tę kolejność prześledziłem w kodzie, a nie uruchomiłem, więc jest to interpretacja, a nie pomiar; wynik, który z niej wynika, jest jednak prosty. Ruch gracza i przewinięcie ekranu wypadają w tej samej klatce, o te same piksele. Na ekranie gracz w ogóle się nie porusza, a świat przesuwa się pod nim.

Itay Keren w swoim wystąpieniu na GDC 2015 o kamerach w grach z przewijaniem bocznym nazywa to position-locking (zablokowaniem pozycji): kamera pozostaje na graczu, „keeping the car in focus at all times and the camera motion completely predictable” (utrzymując samochód cały czas w kadrze, a ruch kamery całkowicie przewidywalny).23 Emerald dodaje jedną rzecz, którą jego definicja pozostawia otwartą: co się dzieje na krawędzi mapy. Kamera nigdy się nie zatrzymuje. Komórki spoza układu mapy odczytywane są przez GetBorderBlockAt, które zwraca powtarzane brzegowe metatile’e układu o wymiarach 2 na 2, oznaczone jako MAPGRID_IMPASSABLE, więc widok utrzymuje gracza w tym samym miejscu ekranu i wypełnia zewnętrze brzegowymi drzewami lub wodą.11 Alternatywa Kerena, edge-snapping (przyciąganie do krawędzi), „simply snaps the camera to the edge of the level, allowing the character to move away from its anchor point.” (po prostu przykleja kamerę do krawędzi poziomu, pozwalając postaci oddalić się od punktu zakotwiczenia).23 Emerald wyrysował sobie drogę wyjścia z tej potrzeby.

Kamera, którą Game Freak napisał i wyłączył

field_camera.c zawiera gotową kamerę wyprzedzającą dla roweru. CameraPanningCB_PanAhead przesuwa pionowe przesunięcie kamery o 2 piksele na aktualizację, od wartości spoczynkowej 32 w stronę 72 albo minus 8, zależnie od kierunku jazdy, czyli o 40 pikseli w każdą stronę: kamera, która wyprzedza gracza tam, dokąd zmierza.12 Działa tylko wtedy, gdy gUnusedBikeCameraAheadPanback ma wartość prawda, tej zmiennej przypisuje się wyłącznie FALSE (w bike.c), a gałąź opatrzona jest komentarzem „this code is never reached.” (ten kod nigdy nie zostaje osiągnięty).1220 W słowniku Kerena to dual-forward-focus (podwójne ognisko do przodu) albo target-focus (ognisko na celu), kamera zgodna z jego regułą „When you walk left, you want to see more to the left.” (gdy idziesz w lewo, chcesz widzieć więcej po lewej).23 Bez względu na powód jej wyłączenia wydana gra trzymała kamerę zablokowaną nawet przy 4 pikselach na klatkę roweru Mach Bike.1

Drzwi otwierają się w dwudziestu klatkach, nie w szesnastu

Tabela drzwi w field_door.c wygląda tak, jakby każdy rysunek trwał cztery klatki: sDoorOpenAnimFrames to {4, -1}, {4, 0}, {4, 0x100}, {4, 0x200}, zamknięte plus trzy rysunki otwarte.24 Jednak AnimateDoorFrame rysuje, gdy licznik wynosi 0, i przechodzi dalej, gdy licznik zrówna się z czasem pozycji, więc każdy rysunek trzymany jest przez pięć aktualizacji: 84 milisekundy na rysunek, 335 na cztery.241 Artykuł z tej serii o budowlach podał tę samą wartość, pięć aktualizacji i około 84 milisekund.13 Notatki badawcze, na podstawie których powstał tamten artykuł, miały to źle, „4 ticks each (16 frames, about 0.27 s at 59.7 Hz),” (po 4 tiki, 16 klatek, około 0,27 s przy 59,7 Hz), podobnie jak komentarz we własnym kodzie drzwi aplikacji, według którego drzwi otwierają się „at about Emerald’s four ticks a frame” (w przybliżeniu w tempie czterech tików na klatkę z Emerald), a który potem trzyma pierwszy rysunek przez 70 milisekund; oba błędy są poprawione tutaj i w briefie.1417

Samo wejście, Task_DoDoorWarp, to pięć stanów, z których żadnego nie da się pominąć: zamrozić pozostałe obiekty, odtworzyć dźwięk drzwi i otworzyć drzwi nad graczem; wymusić krok MOVEMENT_ACTION_WALK_NORMAL_UP w próg; gdy gracz stanie, zamknąć drzwi i ukryć gracza; gdy zakończy się zadanie drzwi, wyciszyć muzykę i wygasić ekran; wczytać mapę.25 Wyjście odtwarza ceremonię wstecz: Task_ExitDoor pokazuje drzwi już otwarte, czeka na rozjaśnienie, wymusza jeden krok WALK_NORMAL_DOWN, zamyka drzwi i dopiero wtedy oddaje sterowanie.25

Po zsumowaniu powyższych liczb i śladu wygaszania opisanego niżej drzwi otwierają się w 20 klatek, krok trwa 16, a drzwi zamykają się w 20; następujące potem wygaszanie wykonuje ostatnie mieszanie w swojej 17. klatce, więc ekran jest całkowicie ciemny po co najmniej 73 klatkach, 1,22 sekundy. Mapa nie może zacząć się wczytywać, dopóki wygaszanie nie stanie się nieaktywne, w jego 22. klatce, a Task_WarpAndLoadMap tego nie zauważy i nie przejdzie dalej: co najmniej 79 klatek, 1,32 sekundy.525 Zmiany stanu między fazami drzwi i oczekiwanie na zatrzymanie muzyki mogą tylko dodać klatek, więc obie wartości są dolnymi ograniczeniami.5

Oś czasu w milisekundach. Wiersz Emerald: drzwi otwierają się od 0 do 335, krok do środka do 603, drzwi zamykają się do 938, wygaszanie trwa, aż stanie się nieaktywne przy 1306, a wczytanie mapy nie wcześniej niż przy 1323. Wiersz Kiradex: rysunek uchylonych drzwi przez 70 milisekund, rysunek otwartych drzwi aż do usunięcia drzwi przy 1470 i zmiana miejsca przy 220, bez kroku i bez wygaszania.

Wejście w Emerald to dolne ograniczenie wynikające z liczby klatek; wiersz Kiradex odczytano z kodu drzwi aplikacji, a nie zmierzono na telefonie.151721

Wygaszanie to dziewięć poziomów

Wygaszanie przy warpie w Emerald nie jest płynną rampą. BeginNormalPaletteFade ustawia krok 2 dla współczynnika mieszania biegnącego od 0 do 16, UpdateNormalPaletteFade w jednym wywołaniu miesza palety tła, w następnym palety sprite’ów, a potem przesuwa współczynnik, a IsSoftwarePaletteFadeFinishing dodaje na końcu pięć wywołań.26 To, w której klatce wypada dane wywołanie, zależy od tego, kto je wykonuje. Zadanie drzwi uruchamia wygaszanie z wnętrza RunTasks, a BeginNormalPaletteFade samo wykonuje jedną aktualizację, od razu kopiuje wynik do pamięci palet i czyści flagę, która w przeciwnym razie kazałaby następnej aktualizacji czekać na wygaszanie pionowe; później w tej samej klatce OverworldBasic ponownie wywołuje UpdatePaletteFade.25263 Pierwsza klatka dostaje więc dwie aktualizacje, obie na poziomie 0, a każda kolejna jedną. Port palette.c do Pythona z takim harmonogramem, liczący klatkę zadania jako 0, daje dziewięć poziomów, 0, 2, 4 i tak dalej do 16: palety tła osiągają poziom 2 w klatce 1 i poziom 16 w klatce 15, palety sprite’ów za każdym razem klatkę później, ostatnie mieszanie w klatce 16 (285 milisekund, licząc klatkę 0), a wygaszanie staje się nieaktywne w klatce 21 (368 milisekund).5 Mieszanie obliczone w danej klatce trafia na ekran przy wygaszaniu pionowym, które ją kończy. Port zakłada, że wcześniej nie trwało żadne wygaszanie, oraz zwykłą aktualizację mapy świata; przy aktywnym deszczu, śniegu, mgle, cieniu lub suszy wygaszanie przebiega tak samo, wychodząc od kolorów zabarwionych przez pogodę (FadeScreen najpierw kopiuje zabarwiony bufor, a potem wywołuje to samo BeginNormalPaletteFade), ale rozjaśnianie obsługuje kod pogody, a tej ścieżki nie symulowano.5

Wykres schodkowy wygaszania do czerni w Emerald od klatki 0 zadania drzwi: współczynnik mieszania dla palet tła i dla palet sprite'ów, przesunięte o jedną klatkę, wspinające się przez dziewięć poziomów w ośmiu krokach po dwie szesnaste, od klatki 1 do klatki 16, z wygaszaniem nieaktywnym w klatce 21.

Dziewięć poziomów co dwie szesnaste, tło i sprite’y z różnicą jednej klatki: rampa, którą brief przenosi na jedną zasłonę.521

Warpy wygaszają do czerni, z jedną rodziną wyjątków, a wyjątek ma kierunek. WarpFadeOutScreen pyta GetMapPairFadeToType o parę typów map, WarpFadeInScreen pyta GetMapPairFadeFromType, a każda z nich wywołuje FadeScreen z bielą, gdy odpowiedź brzmi prawda, a w przeciwnym razie z czernią.25 Obie szukają pary w sTransitionTypes, którego 16 wierszy to wszystkie typy map prowadzące do MAP_TYPE_UNDERGROUND i z niego; pierwsza zwraca flagę wejścia wiersza, druga jego flagę wyjścia, a są one prawdziwe tylko w wierszach prowadzących do jaskini i tylko w wierszach wychodzących z niej.27 Przy wejściu do jaskini ekran wygasa więc do bieli i wraca z czerni, a przy wyjściu z niej wygasa do czerni i wraca z bieli. Każdy wiersz wskazuje też własną procedurę przejścia jaskiniowego, której nie prześledziłem.27 Wolniejsze białe przejście, FadeInFromWhite z opóźnieniem 8, staje się nieaktywne po 86 lub 87 klatkach, od 1,44 do 1,46 sekundy; uruchamia je callback wczytywania mapy, którego pierwszej klatki nie prześledziłem, więc port podaje oba przypadki.525

Wstrząs to oskryptowane przesunięcie kamery

Wstrząs ekranu w Emerald to polecenie skryptu, a nie efekt fizyczny. ShakeCamera odczytuje z czterech zmiennych skryptu przesunięcie pionowe, przesunięcie poziome, liczbę wstrząsów i liczbę klatek między nimi, a przy każdym wstrząsie odwraca znak przesunięcia.28 We wszystkich skryptach gry są 24 wywołania w 9 plikach, a skrypt policzył je wszystkie:29

Px w pionie Px w poziomie Wstrząsy Klatki odstępu Wywołania Czas trwania
1 1 8 5 10 40 klatek, 670 ms
1 1 8 3 4 24 klatki, 402 ms
1 2 8 5 3 40 klatek, 670 ms
2 2 8 5 2 40 klatek, 670 ms
0 3 4 2 2 8 klatek, 134 ms
1 3 20 5 1 100 klatek, 1674 ms
1 1 16 3 1 48 klatek, 804 ms
1 1 32 2 1 64 klatki, 1072 ms

Źródło: measure_gen3_shake.py uruchomiony na data/**/*.inc.29

Dziesięć z 24 to ten sam wstrząs: jeden piksel w każdą stronę, osiem odwróceń, co pięć klatek, 670 milisekund.29 Największy ma 3 piksele w poziomie; najdłuższy trwa 100 klatek, 1,67 sekundy.29 Wstrząs windy, omówiony w artykule o budowlach (wstrząs co trzy klatki przez liczbę powtórzeń rosnącą z liczbą pokonanych pięter), to osobna procedura i nie wchodzi do tego zestawienia.13 W mojej interpretacji lekcją jest powściągliwość: wstrząs w Emerald to piksel lub dwa, użyty przy zdarzeniu, które skrypt uznał za ważne, nigdy przy kroku ani przy drzwiach.

3. Red i Crystal: ten sam krok przy 30 aktualizacjach na sekundę

Red przesuwa postać o dwa piksele co drugą klatkę

Pokémon Red nie ma tabel kroków. Jego pętla mapy świata, OverworldLoop, wywołuje DelayFrame i przechodzi do OverworldLoopLessDelay, która wywołuje ją ponownie, więc świat aktualizuje się raz na dwie klatki.30 Krok ustawia wWalkCounter na 8, a przy każdym przebiegu AdvancePlayerSprite zmniejsza go i przewija rejestry tła hSCX i hSCY o wektor kroku przesunięty raz w lewo: 2 piksele.30 Osiem przebiegów po 2 piksele to 16 pikseli w 16 klatkach, te same 268 milisekund i 3,73 komórki na sekundę co w Emerald, skokami po 2 piksele przy 30 aktualizacjach na sekundę.430 Rower to drugie przesunięcie na przebieg: DoBikeSpeedup ponownie wywołuje AdvancePlayerSprite (z wyjątkiem Cycling Road, gdy przytrzymany jest kierunek w górę, w lewo lub w prawo), więc krok trwa 8 klatek.430

Rysunki chodu w Red zmieniają się co cztery przebiegi, czyli co osiem klatek, w cyklu czterech obrazów: postawa, krok, postawa i odbity lustrzanie krok, więc i tu na każdy krok przypada jeden rysunek wykroku.3122 UpdatePlayerSprite przy każdym przebiegu zwiększa wewnętrzny licznik animacji i przełącza rysunek, gdy licznik osiągnie 4.3122

Red obraca postać w jednym przebiegu pętli, czyli w dwóch klatkach: nowy kierunek z miejsca zapisuje zwrot postaci i wraca do pętli bez robienia kroku.30 Jego obrót o 180 stopni ma w kodzie kierunek pośredni, którego według komentarza w samym kodzie nikt nie widzi: „It is unlikely for it to ever be visible because DelayFrame is called at the start of OverworldLoop.” (raczej nigdy nie będzie widoczny, bo DelayFrame jest wywoływane na początku OverworldLoop).30 Odczytałem to z kodu i nie uruchamiałem w emulatorze.

Warp to dźwięk i wygaszanie bez narysowanych drzwi. PlayMapChangeSound odtwarza SFX_GO_INSIDE, gdy kafelek jest drzwiami (kafelek $0b), a w przeciwnym razie SFX_GO_OUTSIDE, po czym GBFadeOutToBlack zapisuje cztery palety, utrzymując każdą przez osiem klatek: 32 klatki, 536 milisekund.432 Nie prześledziłem powrotnego rozjaśniania w Red.

Crystal też aktualizuje co drugą klatkę

Crystal zachowuje kadencję Red innym mechanizmem. MaxOverworldDelay to db 2, a procedura obsługi VBlank odlicza wOverworldDelay w dół, więc obiekty mapy aktualizują się co drugą klatkę.33 StepVectors daje chodowi osiem aktualizacji po 2 piksele, a rowerowi cztery po 4: 16 i 8 klatek na komórkę, tak samo jak w Red i Emerald.4 Powolny krok to szesnaście aktualizacji po 1 pikselu, 32 klatki, 1,87 komórki na sekundę.433

Drzwi w Crystal wygaszają do bieli, i to szybko. MapSetupScript_Door zaczyna się od FadeOutToWhite, a MapSetupScript_Warp kończy na FadeInFromWhite; każde z nich to cztery kroki palety w odstępach dwóch klatek, 8 klatek, 134 milisekundy w każdą stronę.434 Obrót to czteroczęściowa funkcja kroku, StepFunction_Turn, której części przechodzą jedna w drugą. Przy pierwszej aktualizacji .init1 ustawia OBJECT_STEP_DURATION na 2 i przechodzi do .step1, które zmniejsza tę wartość do 1; przy drugiej .step1 zmniejsza ją do 0 i przechodzi przez .init2, które zapisuje nowy kierunek i znów ustawia 2, do .step2, które zmniejsza ją do 1; przy trzeciej .step2 dochodzi do 0 i oddaje obiekt z powrotem do STEP_TYPE_FROM_MOVEMENT.33 Odtworzona instrukcja po instrukcji procedura zajmuje trzy aktualizacje, sześć klatek przy dwóch klatkach na aktualizację, a nowy kierunek zapisywany jest przy drugiej. To sama procedura, odczytana z kodu; nie prześledziłem czasu od naciśnięcia przycisku do jej rozpoczęcia.33

Trzy generacje, trzy wygaszania

Red Crystal Emerald
Drzwi nienarysowane; dźwięk wybierany według kafelka nienarysowane 4 rysunki po 5 klatek (335 ms), z dźwiękiem drzwi przesuwnych lub na zawiasach
Wejście w drzwi krok, który na nie trafia krok, który na nie trafia wymuszony krok w górę (16 klatek), potem drzwi się zamykają
Wygaszanie do czerni, 4 palety utrzymywane po 8 klatek: 32 klatki (536 ms) do bieli, 4 kroki w odstępach 2 klatek: 8 klatek (134 ms) do czerni (do bieli przy wejściu do jaskini), 9 poziomów, ostatnie mieszanie w klatce 16, licząc klatkę zadania drzwi jako 0 (285 ms), nieaktywne w klatce 21
Rozjaśnianie nieprześledzone z bieli, 8 klatek z czerni (z bieli przy wyjściu z jaskini), te same 9 poziomów
Wyjście przez drzwi wymuszony krok w dół wymuszony krok drzwi pokazane jako otwarte, wymuszony krok w dół, drzwi się zamykają, potem sterowanie

Źródła: liczby z sekcji od 1 do 3;14525 wymuszony krok Red przy wyjściu z drzwi, PlayerStepOutFromDoor, pochodzi z artykułu o budowlach.13

Wiersze, które zgadzają się we wszystkich trzech grach, są tymi, które warto zachować: wygaszanie między mapami, wymuszony krok, który przenosi gracza przez próg, i brak reakcji na sterowanie w trakcie ceremonii. Długości wygaszania różnią się czterokrotnie, więc w mojej interpretacji długość jest decyzją projektową, a nie kanonem. Dziewięciopoziomowe wygaszanie z Emerald to to, które zbudowano wokół narysowanych drzwi, a Kiradex rysuje swoje drzwi,13 więc to właśnie je przyjmuje brief.

Red, Crystal i Emerald, po jednej grze z każdej z trzech generacji na Game Boyu i Game Boyu Advance, dwóch różnych maszynach, doszły do tego samego kontraktu: krok to 16 pikseli, trwa 16 klatek przy około 59,73 Hz i po rozpoczęciu nie da się go przerwać.14 Red osiąga to skokami po 2 piksele, bo jego pętla czeka dwie klatki na przebieg; Crystal tym samym dwuklatkowym opóźnieniem z wektorami 2-pikselowymi; Emerald przy pełnej liczbie klatek z przesunięciami o 1 piksel. Bieg, surfowanie i rower to ten sam kontrakt przy 8 lub 4 klatkach.14 Gdy ludzie mówią, że te gry chodzą „jak po szynach”, myślę, że to jest ta szyna: krok, rysunek i kamera liczone są w tych samych klatkach, z czasami trwania dobranymi tak, by się zgadzały, więc nie mogą się rozjechać.

4. Współczesne punkty odniesienia: kamera Celeste, słownik Kerena i wersje na telefony

Kiedy kamera powinna wygładzać ruch, a kiedy nie

Zablokowana kamera Emerald to jedna z odpowiedzi w słowniku, który Itay Keren przedstawił w „Scroll Back: The Theory and Practice of Cameras in Side-Scrollers”, zmodyfikowanej wersji swojego wystąpienia na Independent Games Summit podczas GDC 2015, opublikowanej w Game Developer 11 maja 2015 roku.23 Terminy, których używam w tym artykule, są jego. Position-locking (zablokowanie pozycji) przytwierdza kamerę do gracza. Edge-snapping (przyciąganie do krawędzi) zatrzymuje ją na krawędzi poziomu. Camera-window (okno kamery) przesuwa kamerę tylko wtedy, gdy gracz napiera na krawędź okna. Lerp-smoothing (wygładzanie interpolacją) stopniowo przybliża kamerę do celu, a Keren nazywa je „a standard tool in reducing jarring camera speeds, particularly jumps.” (standardowym narzędziem do łagodzenia gwałtownych ruchów kamery, zwłaszcza przy skokach). Target-focus i dual-forward-focus wysuwają kamerę przed gracza w kierunku ruchu. Wymienia też platform-snapping, region-focus i cue attractors, z których postać chodząca po siatce nie ma żadnego pożytku.23

Podaje powód, dla którego ruch kamery w ogóle ma znaczenie: „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.” (sprzeczne sygnały zmysłowe, wzrokowe kontra przedsionkowe, mogą prowadzić do dyskomfortu i mdłości, i choć w 3D, a zwłaszcza w VR, jest gorzej, w grach 2D zjawisko to nadal w pełni występuje). Podaje też przypadek, w którym najprostszy schemat jest właściwy: zablokowanie pozycji w przypadku „a crafting adventure game like Terraria, with a small character relative to the screen with pretty small jumps, it works very well.” (gry przygodowej z craftingiem, takiej jak Terraria, z postacią małą względem ekranu i dość niskimi skokami, sprawdza się bardzo dobrze).23 Miasteczko w widoku z góry z 30-pikselowym kolekcjonerem, figurą, do której artykuł z tej serii o postaciach przeszedł w kompilacji 34, to właśnie ten przypadek, tylko bez skoków.35

Celeste to współczesny punkt odniesienia dla drugiego wyboru. Twórcy gry opublikowali klasę Player „as a learning resource and for general interest” (jako materiał edukacyjny i z ogólnego zainteresowania), przy czym licencja MIT obejmuje wyłącznie ten kod, a kamera to w niej kilka linijek: level.Camera.Position = from + (target - from) * (1f - (float)Math.Pow(0.01f / multiplier, Engine.DeltaTime)), pod komentarzem „Camera (lerp by distance using delta-time)” (kamera, interpolacja według odległości z użyciem różnicy czasu).3637 Przy mnożniku 1 i nieruchomym celu domyka to 99 procent odstępu co sekundę, niezależnie od liczby klatek; cel w Celeste nie stoi jednak w miejscu, bo jest przeliczany w każdej klatce na podstawie pozycji i stanu gracza, więc ta wartość opisuje wygładzanie, a nie to, gdzie kamera ostatecznie się znajdzie.36 Celem jest gracz wyśrodkowany w widoku gry (X - Celeste.GameWidth / 2), ograniczony do granic pomieszczenia, z przesunięciami dla kilku stanów: 48 pikseli do przodu w kierunku zrywu StRedDash, 64 w górę przy wystrzeleniu na szczycie.36 Pierwszy artykuł tej serii zauważył, że Celeste renderuje swój świat w rozdzielczości 320 na 180 i mnoży ją przez sześć.22

W mojej interpretacji te dwa podejścia nie są ze sobą sprzeczne. Celeste wygładza, bo skoki w platformówce szarpałyby widokiem w górę i w dół przy każdym susie; sama definicja wygładzania interpolacją u Kerena dotyczy skoków. Postać na siatce porusza się ze stałą prędkością po liniach prostych, więc blokada nie powoduje żadnego szarpnięcia, które trzeba by wygładzać, a wygładzona kamera dodaje jedynie smugę za ruchem, który i tak był równy. Sekcja 7 pokazuje, ile ta smuga wynosi w modelu Kiradex.

Co inne gry mówią o prędkości

Stardew Valley podaje prędkość gracza jako bezwymiarową statystykę: „2 when walking,” „5 when running,” „6.6 when riding a Horse (7 if the horse was fed a carrot that day)” (2 w chodzie, 5 w biegu, 6,6 na koniu, 7, jeśli koń dostał tego dnia marchewkę), nigdy poniżej 1.38 Wiki nie mówi, ilu pikselom na tik odpowiada jedna jednostka, więc nie podaję tu dla Stardew żadnej wartości w kafelkach na sekundę.

Szukałem też źródła pierwotnego na temat kamer w Sea of Stars, Eastward i CrossCode i żadnego nie znalazłem: wywiady o poruszaniu się po świecie i oświetleniu, nic o kamerze, a opcję „Pixel Perfect” w Sea of Stars opisują tylko poradniki i fora. Nie znalazłem też żadnego wystąpienia ani artykułu Maddy Thorson o kamerze, dlatego Celeste cytuję na podstawie jej kodu.39

Na telefonie: dotknij, aby iść, z wyjściem awaryjnym

Mobilna wersja Stardew Valley oferuje dziewięć schematów sterowania, a domyślnym jest „Tap-to-move & Auto-Attack” (dotknij, aby się poruszyć, plus automatyczny atak): „Tap anywhere on screen and the farmer will walk to where you tapped.” (po dotknięciu dowolnego miejsca na ekranie farmer pójdzie w to miejsce).40 Przytrzymanie palca „will cause the character to follow the touch” (sprawi, że postać będzie podążać za dotykiem), a wiki ostrzega, że tryb podążania „is very literal, moving directly towards the finger without routing around blocking objects.” (jest bardzo dosłowny: postać idzie prosto do palca, nie omijając przeszkód). Schemat z niewidzialnym joystickiem zajmuje „the left half of the screen” (lewą połowę ekranu), a jego środek znajduje się tam, gdzie się dotknie. Wiki uczciwie opisuje też ograniczenie schematu domyślnego: niektórych zadań wymagających precyzyjnego ustawienia się „can not be completed using the default controls; temporarily switching to a control style with a movement joystick is necessary in such cases.” (nie da się wykonać przy domyślnym sterowaniu; w takich sytuacjach trzeba tymczasowo przełączyć się na styl sterowania z joystickiem ruchu).40 Schematy pojawiły się w aktualizacji, o której TouchArcade informował 1 listopada 2018 roku, z przełącznikiem przywracającym „the default tap-to-move and auto-attack controls.” (domyślne sterowanie dotknięciem i automatyczny atak).41

Pixel Remaster pierwszego Final Fantasy od Square Enix na iOS dodał w swojej wersji 1.2.0, datowanej w historii tej aplikacji w App Store na 11 marca 2025 roku, domyślne ustawienie chodu lub biegu (seria ukazuje się jako osobne aplikacje, a sprawdziłem tylko tę jedną): „In tap based movement mode the character controlled will always run as the default speed when moving.” (w trybie ruchu opartym na dotknięciach sterowana postać zawsze będzie domyślnie biegać).42 Obsługa kontrolerów trafiła do wersji mobilnych w aktualizacji opisanej przez TouchArcade 30 stycznia 2024 roku.43 Nie znalazłem nigdzie poza tymi notatkami dokumentacji dokładnego schematu ruchu dotykowego, ani opisu mobilnego ruchu w Terrarii na sprawdzonej przeze mnie stronie wiki.39

Human Interface Guidelines Apple mówią to samo od strony platformy. Dla gier dotykowych: „consider letting players tap objects to select them instead of adding a virtual selection button” (warto rozważyć wybieranie obiektów przez ich dotknięcie zamiast dodawania wirtualnego przycisku wyboru); „For movement control, opt to show a virtual thumbstick wherever the player lands their thumb instead of a static thumbstick position” (do sterowania ruchem lepiej wyświetlać wirtualną gałkę tam, gdzie gracz położy kciuk, zamiast w stałym miejscu); „Make sure frequently used controls are a minimum size of 44x44 pt” (często używane elementy sterujące powinny mieć co najmniej 44x44 pt); „Always include visible and tactile press states” (zawsze należy zapewnić widoczne i wyczuwalne stany naciśnięcia); a w przypadku chodu i sprintu „consider combining the actions into a single control.” (warto rozważyć połączenie tych akcji w jeden element sterujący). Dziennik zmian strony datuje te praktyki sterowania dotykowego na 9 czerwca 2025 roku.44 Na WWDC25 sesja Apple poświęcona sterowaniu dotykowemu ujęła założenie wprost: „the vast majority of players won’t have a controller available.” (zdecydowana większość graczy nie będzie miała pod ręką kontrolera).45

Żadne z tych źródeł nie mówi, że „dotknij, aby iść” jest regułą w grach na telefony, i ja też tego nie twierdzę. Wyciągam z nich, jako moją rekomendację dla Kiradex, a nie ustalenie, schemat, który Kiradex już częściowo stosuje: dotknięcie jako domyślny sposób chodzenia, tak jak w mobilnej wersji Stardew; gałka pojawiająca się tam, gdzie wyląduje kciuk, zgodnie z zaleceniem HIG, do precyzyjnego sterowania; i żadnego stałego krzyżaka na ekranie.4044

5. Rzemiosło jako reguły z liczbami

Oto reguły, według których napisany jest brief w sekcji 7, każda powiązana z jednym z powyższych pomiarów. „Tik” oznacza jeden krok symulacji trwający 1/60 sekundy; w ten sposób klatka 59,73 Hz staje się czymś, czego iPhone może się trzymać; przy 16 tikach na komórkę chód ma 3,75 komórki na sekundę zamiast 3,73.119

Element Reguła Źródło
Chód 1 piksel na tik na kafelkach 16-pikselowych: 16 tików na kafelek, 3,75 kafelka na sekundę Red, Crystal i Emerald przechodzą 16 pikseli w 16 klatkach, 3,73 komórki na sekundę
Bieg 2 piksele na tik, 8 tików na kafelek; żadnej prędkości, która nie dzieli 16 bieg i surfowanie w Emerald to 2, rower 4; nierówne jest tylko 2-3-3 roweru Acro Bike
Cykl chodu jedno stąpnięcie na 16 pikseli przy każdej prędkości; moja propozycja dla Kiradex to wybór rysunku według przebytej odległości, a dla sześciu rysunków na 32 piksele rysowanie floor(distance × 6 / 32) mod 6 Emerald mierzy czas rysunków i kroków w klatkach, osobno, z pasującymi długościami: jego chód to cykl 32 klatek na dwie komórki po 16 klatek, a bieg to cykl 16 klatek na dwie komórki po 8 klatek
Krok rozpoczęty zawsze się kończy; sterowanie odczytywane jest na kafelku Emerald i Red odczytują krzyżak po zakończeniu kroku
Obrót 8 tików, około 133 ms (8 klatek Emerald to 134), z miejsca; żadnego w trakcie chodu WalkInPlaceFast w Emerald to 8; procedura obrotu w Crystal 6 klatek; w Red 2
Odrzucony krok pokazany, a nie zignorowany: 32-tikowy chód w miejscu z odbiciem WalkInPlaceSlow w Emerald to 32
Kamera zablokowana na graczu: bez opóźnienia, bez wyprzedzenia, przesuwana w tym samym tiku o te same piksele obiekt kamery w Emerald; jedyne wyprzedzenie, jakie napisał Game Freak, jest wyłączone
Krawędź mapy narysować zewnętrze i zachować blokadę albo ograniczyć kamerę (przyciąganie do krawędzi); nigdy nie wygładzać kafelki brzegowe Emerald; przyciąganie do krawędzi u Kerena; granice pomieszczeń w Celeste
Drzwi 4 rysunki trzymane po 5 tików: 83 ms na rysunek, 333 ms na otwarcie (84 i 335 w Emerald) field_door.c w Emerald
Wygaszanie 9 skokowych poziomów, jeden co 2 tiki, ostatni w tiku 16, łącznie 18 tików (0,30 s), w obie strony; domyślnie czerń. Adaptacja, nie kopia zwykłe wygaszanie w Emerald osiąga każdy poziom w tych samych parzystych klatkach w paletach sprite’ów, klatkę po paletach tła, wykonuje ostatnie mieszanie w klatce 16 i staje się nieaktywne w klatce 21; przy wejściu do jaskini wygasza do bieli, a przy wyjściu rozjaśnia się z bieli; 8 klatek Crystal to szybki koniec skali, 32 klatki Red to wolny
Ceremonia wejścia 74 tiki, około 1,23 s, bez reakcji na sterowanie: otwarcie 20, krok do środka 16, zamknięcie 20, wygaszanie 18 wejście przez drzwi w Emerald: ciemno po co najmniej 73 klatkach, wczytanie mapy nie wcześniej niż w klatce 79 (1,32 s)
Wstrząs 1 piksel, 8 odwróceń, co 5 tików (40 tików, 0,67 s), tylko przy oskryptowanych zdarzeniach najczęstszy z 24 wstrząsów w Emerald
Liczba klatek symulować w 60 bez względu na to, co robi ekran; żądać 60, nie 120 priorytet Apple dla gier przy 30 i 60; RealityKit zwykle renderuje w 60
Haptyka potwierdzać zdarzenia, nie kroki; uczynić ją opcjonalną HIG Apple o odtwarzaniu haptyki
Sterowanie moja rekomendacja: domyślnie dotknięcie, aby iść; pływająca gałka jako opcja precyzyjna; żadnego stałego krzyżaka domyślne ustawienie mobilnego Stardew, pływająca gałka z HIG; żadne z nich nie formułuje tego jako reguły

Źródła tabeli: liczby dotyczące chodu, animacji i drzwi w Emerald;1 jego oddzielne liczniki rysunków i kroków;92 jego kamera;123 jego wygaszanie i wstrząsy;529 Red i Crystal;433 Keren i Celeste;2336 wskazówki Apple dotyczące tempa klatek, RealityKit i haptyki;7846 źródła dotyczące sterowania.404244

Dwie z tych reguł wymagają po jednym zdaniu. Reguła cyklu chodu jest tą, którą najłatwiej zepsuć we współczesnym silniku, bo system animacji liczy własne sekundy, a chód liczy odległość w świecie. Konsole przenośne utrzymywały jedno z drugim, licząc oba w tych samych klatkach z długościami dobranymi tak, by się zgadzały; na telefonie, gdzie czas klatki się zmienia, proponuję odczytywać rysunek z przebytej odległości, co daje ten sam wynik bez drugiego zegara, który trzeba by synchronizować. Reguła liczby klatek nie jest zaś brakiem ambicji. Świat, który przesuwa się o jeden pełny piksel na tik, nie ma nic do pokazania między tikami, więc szybszy ekran może tylko powtarzać ten sam obraz, a sekcja 6 pokazuje, że dokładnie to robi.

6. Droga Apple: tempo klatek, zegar RealityKit i haptyka

Pierwszy artykuł tej serii opisał silnik, na którym działa świat Kiradex: scena RealityKit używana jako renderer 2D, kamera ortograficzna, podłoże jako jedna siatka teksturowanych czworokątów, postacie jako czworokąty, a pozycja każdego sprite’a zaokrąglana w każdej klatce do pełnych jednostek świata.22 Ta sekcja to ta część dokumentacji Apple, która decyduje o tym, jak ten świat porusza się w czasie. Każdą z poniższych stron przeczytałem w wersji publikowanej przez Apple 4 października 2026 roku, a podana dostępność to ta, którą deklaruje każda strona.

Tempo klatek na ProMotion

CADisplayLink.preferredFrameRateRange (iOS 15.0, iPadOS 15.0, Mac Catalyst 15.0, visionOS 1.0) to prośba, a nie ustawienie.15 Strona radzi: „Choose a frame rate range that your app can consistently maintain,” (należy wybrać zakres liczby klatek, który aplikacja jest w stanie stale utrzymać) i opisuje, co system robi z tą prośbą: „The system typically provides a consistent frame rate by choosing one that’s a factor of the display’s maximum refresh rate.” (system zwykle zapewnia stałą liczbę klatek, wybierając wartość będącą dzielnikiem maksymalnej częstotliwości odświeżania ekranu). Domyślnie zakres równa się maksimum ekranu.15 Sam zakres to CAFrameRateRange (iOS 15.0) z wartością minimalną, maksymalną i preferowaną.47

Artykuł Apple o ProMotion podaje liczby. Ekrany ProMotion przełączają się między 24 a 120 Hz w iPadzie Pro oraz między 10 a 120 Hz w obsługiwanych iPhone’ach, a częstotliwości iPhone’a to dwanaście stopni: 120, 80, 60, 48, 40, 30, 24, 20, 16, 15, 12 i 10 Hz; iPad Pro ma pięć z nich.7 Lista urządzeń wymienia teraz iPhone’a Air i „iPhone 17 and later” (iPhone 17 i nowsze) obok „iPhone 13 Pro and later” (iPhone 13 Pro i nowsze).7 Na iPhonie nic powyżej 60 się nie dzieje, jeśli plik Info.plist aplikacji nie ustawia CADisableMinimumFrameDurationOnPhone (iOS 15.0) na true: „If you don’t enable this support, Core Animation won’t access higher frame rates (above 60Hz).” (bez włączenia tej obsługi Core Animation nie sięgnie po wyższe częstotliwości, powyżej 60 Hz).716 Dwa zdania z tego artykułu mają dla gry większe znaczenie niż cała reszta. Pierwsze: „In iOS 15 and later, the system provides games with special priority to 30Hz and 60Hz refresh rates to ensure optimal performance,” (w iOS 15 i nowszych system daje grom specjalny priorytet dla częstotliwości 30 Hz i 60 Hz, aby zapewnić optymalną wydajność) osiągane przez CAFrameRateRange(minimum: 30, maximum: 60, preferred: 60).7 Drugie: „Prepare your app to operate at any refresh rate, not just those it requests.” (aplikację należy przygotować do pracy przy dowolnej częstotliwości odświeżania, a nie tylko przy tych, o które prosi).7 I dla wszystkiego, co jest animowane: „Always use targetTimestamp to drive any animation, physics, or other time-related content provided in your CADisplayLink callback” (do sterowania animacją, fizyką i inną treścią zależną od czasu w callbacku CADisplayLink należy zawsze używać targetTimestamp) (targetTimestamp pochodzi z iOS 10.0).748

Sesja WWDC21 „Optimize for variable refresh rate displays” omawia zarówno ProMotion w iPadzie Pro, jak i ekrany Adaptive-Sync na Macu.49 W przypadku ekranów Adaptive-Sync na Macu zmienia wcześniejsze zalecenie Apple: na ekranie o stałej częstotliwości „we’ve previously recommended that you slow down your rendering to hit the next factor of the display’s fastest refresh rate” (wcześniej zalecaliśmy spowolnienie renderowania do najbliższego dzielnika najwyższej częstotliwości odświeżania ekranu); przy Adaptive-Sync „You should instead attempt to present frames at the highest rate your app can do so evenly.” (należy zamiast tego próbować wyświetlać klatki z najwyższą częstotliwością, przy której aplikacja robi to równomiernie).49 Słowo, które z tego biorę dla telefonu, to evenly, równomiernie.

Zegar RealityKit

Świat Kiradex nie ma własnego display linka. Tyka na zdarzeniu RealityKit wywoływanym co klatkę, SceneEvents.Update (iOS 13.0), „An event invoked once per frame interval,” (zdarzenie wywoływane raz na interwał klatki), którego deltaTime to „The elapsed time since the last update.” (czas, który upłynął od ostatniej aktualizacji).5051 RealityView (iOS 18.0) dokumentuje dokładnie tę drogę dla pracy wykonywanej co klatkę, „you can use a System or directly subscribe to the engine’s SceneEvents.Update,” (można użyć System albo bezpośrednio zasubskrybować SceneEvents.Update silnika) i nie oferuje własnego ustawienia liczby klatek.52 Artykuł Apple o wydajności RealityKit mówi, jakiej częstotliwości się spodziewać: „RealityKit typically limits the refresh rate,” (RealityKit zwykle ogranicza częstotliwość odświeżania), którą definiuje jako częstotliwość, z jaką framework renderuje aktualizacje na ekran, „to 60 frames per second (fps).” (do 60 klatek na sekundę).8 Czy RealityView na iPhonie 18 Pro Max albo iPhonie Duo faktycznie renderuje w 60, czy w 120, nie jest czymś, co zmierzyłem, a sekcja 7 czyni ten pomiar pierwszym testem briefu.

Dlaczego 120 Hz nic nie daje chodowi o pełne piksele

Oto arytmetyka z tego samego skryptu modelu, którego sekcja 7 używa dla obecnego kodu aplikacji, zastosowana tym razem do propozycji: świat, który przesuwa się na stałym tiku 60 Hz, jeden piksel na tik, a ekran pokazuje to, co wytworzył ostatni tik. Na ekranie 60 Hz każdy piksel świata jest pokazywany przez dokładnie jedno odświeżenie.6 Na ekranie 120 Hz w ciągu jednej sekundy 59 z 61 pozycji jest trzymanych przez dokładnie dwa odświeżenia, a dwie przez jedno.6 Na ekranie 80 Hz czasy trzymania przeplatają się między jednym a dwoma odświeżeniami: 42 pozycje trzymane raz i 19 dwa razy.6 W mojej interpretacji przypadek 120 Hz to przypadek 60 Hz narysowany dwukrotnie, a przypadek 80 Hz to nierówne tempo: piksel, który raz zostaje na 12,5 milisekundy, a raz na 25.

Dla świata na siatce na iPhonie pytanie nie brzmi więc „120 Hz”, lecz „równe tempo przy 60”: stały tik symulacji 60 Hz, całkowite przesunięcia na tik, animacje liczone w tikach i renderer pokazujący ostatni tik. To również sprawia, że ruch nie zależy od częstotliwości, którą wybierze RealityKit. Sesja WWDC21 porusza pokrewną kwestię różnic czasu. Gdy wolna klatka sprawia, że display link pomija callback, różnica, o którą trzeba przesunąć stan, wynosi „not 8ms, but rather 16ms” (nie 8 ms, lecz 16 ms); sesja mówi dalej, że aplikacja, która „uses time delta to advance the state of your custom drawing” (używa różnicy czasu do przesuwania stanu własnego rysowania), będzie „slow down your custom drawing by one frame” (spowalniać własne rysowanie o jedną klatkę) przy każdym pominiętym callbacku, co rozumiem jako aplikację przesuwającą stan o oczekiwane 8 milisekund zamiast o faktycznie upływający czas, i mówi, że aplikacja „can instead keep track of a previous targetTimestamp so that you can advance the state correctly” (może zamiast tego śledzić poprzedni targetTimestamp, by poprawnie przesuwać stan).49

Haptyka

Core Haptics (iOS 13.0, iPadOS 13.0, Mac Catalyst 13.0, visionOS 1.0) buduje CHHapticPattern, „An object representing a haptic waveform,” (obiekt reprezentujący haptyczny przebieg fali) ze słowników, z tablic obiektów CHHapticEvent albo z pliku AHAP.53 Zdarzenia mają dwa typy haptyczne, hapticTransient i hapticContinuous; zdarzenia przejściowe to „brief impulses that occur at a specific point in time.” (krótkie impulsy występujące w określonym momencie).54 Każde przyjmuje parametry hapticIntensity, hapticSharpness, attackTime, decayTime, releaseTime i sustained.55 Odtwarza je CHHapticEngine, a capabilitiesForHardware() mówi, czy urządzenie to potrafi.56

Prostszą drogą jest UIImpactFeedbackGenerator (iOS 10.0), „A concrete feedback generator subclass that creates haptics to simulate physical impacts,” (konkretna podklasa generatora sprzężenia zwrotnego, która tworzy haptykę symulującą fizyczne uderzenia), którego style opisują „The mass of the objects in the collision” (masę zderzających się obiektów) (light, medium, heavy, soft, rigid); impactOccurred(intensity:) pochodzi z iOS 13.0, a strona wymienia init(style:view:) w sekcji „Initializing the feedback generator”, a init(style:) wśród elementów przestarzałych.575859 prepare() zmniejsza opóźnienie tylko wtedy, gdy ma czas zadziałać: „Calling prepare() and then immediately triggering feedback (without any time in between) does not improve latency,” (wywołanie prepare() i natychmiastowe wyzwolenie sprzężenia zwrotnego, bez żadnego odstępu, nie zmniejsza opóźnienia), a silnik wraca do stanu bezczynności, gdy „A short period of time passes (typically seconds).” (upłynie krótki czas, zwykle kilka sekund).60 W SwiftUI sensoryFeedback(_:trigger:) (iOS 17.0) „Plays the specified feedback when the provided trigger value changes,” (odtwarza wskazany feedback, gdy zmieni się wartość przekazanego trigger), w tym .impact(weight:intensity:).61

Strona HIG o odtwarzaniu haptyki to połowa projektowa. „Avoid overusing haptics,”46 (nie należy nadużywać haptyki) z uzasadnieniem: „Often, the best haptic experience is one that people may not be conscious of, but miss when it’s turned off.” (często najlepsza haptyka to taka, której ludzie nie są świadomi, ale której brakuje im po wyłączeniu). „Make haptics optional.” (haptyka powinna być opcjonalna). Dopasować „the intensity and sharpness of a haptic with the intensity and sharpness of the animation it accompanies.” (intensywność i ostrość haptyki do intensywności i ostrości animacji, której towarzyszy).46 Ostrość może oddać doznanie „that’s soft, rounded, or organic, or one that’s crisp, precise, or mechanical.” (miękkie, zaokrąglone lub organiczne albo wyraziste, precyzyjne lub mechaniczne).46 Niestandardowa haptyka może sprawić, że „a collision or a hit” (zderzenie lub uderzenie) odczuwa się zupełnie inaczej „from subtle experiences like the approach of footsteps or a looming danger.” (niż subtelne doznania, takie jak zbliżające się kroki czy nadciągające niebezpieczeństwo).46 Chód w tempie 3,75 kroku na sekundę przez całe minuty to przypadek, którego dotyczy pierwsza reguła.1

Sterowanie

Interfejsy API sterowania dotykowego w kategoriach sekcji 4. Touch Controller (iOS 26.0, iPadOS 26.0, Mac Catalyst 26.0, visionOS 26.0) jest podsumowany na swojej stronie jako „Integrate onscreen touch controls into your Metal-based games” (integracja dotykowych elementów sterujących na ekranie z grami opartymi na Metal): przyciski, krzyżaki, gałki, przepustnice i panele dotykowe, udostępniane przez GCController.62 Jego TCDirectionPad (iOS 26.0, iPadOS 26.0, Mac Catalyst 26.0) można skonfigurować „to behave as either a composite direction pad” (tak, by działał jak złożony krzyżak) albo „as four separate buttons.” (jak cztery oddzielne przyciski).6263 Starszy GCVirtualController (iOS 15.0) to „A software emulation of a real controller that you configure specifically for your game.” (programowa emulacja prawdziwego kontrolera konfigurowana specjalnie pod daną grę).64 Jeden ze sprawdzonych adresów, developer.apple.com/documentation/touchcontrols, zwrócił błąd 404; strona frameworka to touchcontroller.39

Ile to kosztuje

Nic w tej sekcji nie zostało zmierzone na telefonie. Czas klatki świata na urządzeniu, koszt akumulatora 60 Hz i koszt silnika haptycznego działającego przez cały czas, gdy świat jest na ekranie, pozostają niezmierzone; testy briefu w sekcji 7 są napisane tak, by zmierzyć dwa pierwsze.

7. Brief: co buduje Kiradex i jakie testy musi przejść

Gdzie dziś jest chód

4 października 2026 roku przeczytałem kod chodu, kamery i przejść świata w repozytorium Kiradex, niczego w nim nie zmieniając, i napisałem jego model klatka po klatce. Wszystko w tej podsekcji jest albo odczytane z tego kodu, albo obliczone przez model, i zawsze zaznaczam, które z nich. Model wykonuje każdą operację Float z kodu w 32 bitach, w kolejności z kodu, z zaokrąglaniem jak w Swifcie, przy dokładnie 1/60 lub 1/120 sekundy na klatkę, i pomija krawędzie mapy, zgięcie iPhone’a Duo oraz przesunięcie stóp, z których żadne nie zmienia prostego chodu na środku mapy. To model kodu. Nie jest to nagranie i żaden telefon nie został zmierzony.176

Co robi kod, według jego odczytu:

  • Sterowanie to dotknięcie, aby iść, i nic więcej. Scena zamienia dotknięcie na punkt w świecie, po czym albo dotyka kolekcjonera lub kiosku, albo wywołuje walk(to:). Nie ma przeciągania, krzyżaka ani żadnego wywołania haptyki w całym targecie aplikacji: wyszukiwanie UIImpactFeedbackGenerator, sensoryFeedback i CHHaptic nic nie znajduje.17
  • Ścieżka to ośmiokierunkowe wyszukiwanie najkrótszej drogi, uporządkowane według dotychczas przebytego kosztu, bez szacowania pozostałej odległości, co czyni je algorytmem Dijkstry, a nie A*; przekątne kosztują √2 i nigdy nie ścinają narożnika ściany. Krok po przekątnej jest zwrócony bokiem i dzieli swój postęp przez √2 po zastosowaniu mnożnika biegu 1,6, więc w modelu krok po przekątnej trwa przy 60 Hz 22 klatki w chodzie i 14 w biegu, wobec 15 i 10 dla kroku prostego.176
  • Prędkość to czas. tilesPerSecond wynosi 4, czyli 64 piksele na sekundę, a chód zmienia się w bieg z prędkością 1,6 razy większą, gdy ścieżka ma 6 kroków lub więcej, więc chód w aplikacji to od 1 do 5 kafelków, a wszystko dłuższe pokonywane jest biegiem. Każda klatka dodaje dt × speed / length do postępu kroku, a gdy postęp osiąga 1, postać przeskakuje na następny kafelek, a postęp wraca do 0, z odrzuceniem nadwyżki.17
  • Rysunek chodu wybierany jest według czasu. Kolumna to cycle[Int(walker.clock * framesPerSecond) % count], przy framesPerSecond równym 8 i sześciorysunkowych cyklach chodu i biegu z kuźni. Artykuł z tej serii o postaciach podał tę samą kadencję, „the walk at eight frames a second” (chód w tempie ośmiu klatek na sekundę), i trafnie opisuje to kod.1735
  • Kamera wygładza, a potem zaokrągla. W każdej klatce przesuwa się o (target - current) * min(1, dt * 6) w stronę gracza, a następnie zaokrągla obie osie do pełnych jednostek; gdy mapa jest większa niż widok, jest ograniczana do mapy. Zdanie z pierwszego artykułu, że każdy sprite i kamera „are rounded to whole world units each frame, after easing” (są w każdej klatce zaokrąglane do pełnych jednostek świata, po wygładzeniu), również jest trafne.1722
  • Drzwi i warpy. Drzwi od razu pokazują rysunek uchylonych drzwi z arkusza, a po 70 milisekundach rysunek otwartych, i usuwają drzwi 1,4 sekundy później; krok na warp czeka 220 milisekund, po czym zmienia miejsce. Artykuł o budowlach opisał to jako znaną lukę.1713
  • Przejścia to wypchnięcia nawigacji (push) z domyślną animacją; zmiana piętra podmienia scenę według tożsamości, bez wygaszania; wyjście wywołuje dismiss(). Jedyną zasłoną w aplikacji jest czarna nakładka przeglądarki kart, animowana przez .easeOut(duration: 0.25).17
  • Żadnego żądania liczby klatek. W aplikacji ani w jej pliku projektu nie ma CADisplayLink, preferredFrameRateRange ani CADisableMinimumFrameDurationOnPhone; świat tyka na SceneEvents.Update z deltaTime zdarzenia.17

Co według modelu ten kod robi na ekranie przy stałym czasie klatki:

  • Dwupikselowy przeskok raz na kafelek przy 60 Hz. Przy 60 Hz chód zajmuje 15 klatek na kafelek, 4,00 kafelka na sekundę, a 16 pikseli każdego kafelka przychodzi jako 14 klatek po 1 pikselu i jedna klatka z 2: jeden przeskok o 2 piksele na kafelek, pięć w chodzie na pięć kafelków. Emerald przesuwa postać dokładnie o 1 piksel w każdej klatce.61
  • Nieregularne zera i jedynki przy 120 Hz. Przy 120 Hz chód zajmuje 30 klatek na kafelek, a 16 pikseli każdego kafelka przychodzi jako 16 klatek po 1 pikselu i 14 bez ruchu, przeplatanych nieregularnie.6
  • Bieg jest wolniejszy niż jego stała. Przy 60 Hz bieg zajmuje 10 klatek na kafelek, 6,00 kafelka na sekundę, choć 4 razy 1,6 to 6,4, bo nadwyżka każdego kafelka jest odrzucana; 16 pikseli każdego kafelka przychodzi jako 4 klatki po 1 pikselu i 6 po 2. Przy 120 Hz zajmuje 19 klatek na kafelek, 6,32 kafelka na sekundę, więc prędkość biegu zależy od liczby klatek.6
  • Wlokąca się kamera, która nigdy nie dociera. W chodzie przy 60 Hz kamera z każdym kafelkiem zostaje o mniej więcej piksel bardziej w tyle, od 5 pikseli na końcu pierwszego kafelka do 9 na końcu piątego, najdłuższego chodu w aplikacji, a pozycja gracza na ekranie zmienia się w 8 z 73 klatek chodu następujących po pierwszej. W biegu, czyli na każdej ścieżce o długości 6 kafelków lub więcej, od drugiego kafelka zostaje 13 pikseli w tyle. Przy 120 Hz osiąga 9 już w pierwszym kafelku, w chodzie lub w biegu, i tak już zostaje. Gdy gracz się zatrzyma, kamera staje 4 piksele od gracza przy 60 Hz i 9 przy 120 i tam pozostaje: gdy odstęp pomnożony przez min(1, dt × 6) spadnie poniżej pół piksela, zaokrąglenie w każdej klatce zwraca tę samą pozycję. Po której stronie się zatrzyma, zależy od kierunku ostatniego chodu. W Emerald zarówno opóźnienie, jak i przesunięcie w spoczynku wynoszą zero.6
  • Rysunki mają własny zegar. Cykl sześciu rysunków w tempie 8 na sekundę trwa 0,75 sekundy. Zmierzony na podstawie pozycji z modelu między początkami kolejnych cykli przy 60 Hz obejmuje 3,00 kafelka w chodzie i 4,50 w biegu (4,80 przy nominalnych 6,4 kafelka na sekundę w biegu, których odrzucana nadwyżka nigdy nie pozwala osiągnąć); cykl dwóch stąpnięć w Emerald obejmuje 2,00 kafelka. W mojej interpretacji cykl, który pokrywa o połowę więcej terenu niż jego stąpnięcia, wygląda jak ślizgające się stopy, ale model nie mierzy, gdzie stopa styka się z podłożem. Rysunek co 15. klatkę balansuje też na ostrzu noża, przy 60 i przy 120 Hz, gdy zegar pomnożony przez 8 wypada dokładnie na liczbie całkowitej, czyli na granicy między dwoma rysunkami: trzymanie zegara w 32 bitach, tak jak robi to aplikacja, zamiast w 64 zmienia wyświetlany rysunek w 9 z 11 takich klatek na 12 kafelkach chodu przy 60 Hz i w 14 z 23 przy 120.6
  • Drugie dotknięcie w połowie kroku cofa postać. To zostało odczytane z kodu, a nie wymodelowane ani nagrane: walk(to:) ustawia postęp na 0, dopóki kafelek postaci jest wciąż punktem wyjścia kroku, więc następna klatka rysuje postać nawet 15 pikseli za miejscem, w którym była; ruchy innych kolekcjonerów przychodzące z serwera działają tak samo.17

Cztery paski, jeden słupek na każdą wyświetloną klatkę w pierwszej sekundzie chodu. Emerald przy 59,73 Hz przesuwa postać o 1 piksel w każdej klatce. Obecny kod Kiradex przy 60 Hz, według modelu, przesuwa o 1 piksel, z klatką 2-pikselową cztery razy w ciągu tej sekundy. Przy 120 Hz, według modelu, przesuwa o 0 lub 1 piksel nieregularnie, z 56 klatkami bez ruchu. Tik 60 Hz z briefu na ekranie 120 Hz przesuwa o 1, potem o 0, równomiernie.

Piksele na wyświetloną klatkę: kanon jest równy, dzisiejszy kod (model, nie nagranie) nie jest, a stały tik jest równy także przy 120.621

Wykres liniowy odstępu między graczem a kamerą w pikselach świata, według modelu, dla chodu na 5 kafelków i biegu na 12 kafelków, po każdym z nich trzy sekundy stania. Linia Emerald jest płaska na zerze. Chód Kiradex przy 60 Hz rośnie o piksel na kafelek do 9 i po zakończeniu chodu ustala się na 4; bieg przy 60 Hz skacze do 13 przy drugim kafelku i również ustala się na 4; chód przy 120 Hz od razu skacze do 9 i po zakończeniu chodu zostaje na 9.

Wygładzana i zaokrąglana kamera według modelu: wlecze się w chodzie i w biegu, a po zakończeniu chodu zatrzymuje się, nie dochodząc do gracza.621

Nic z tego nie jest oceną tego, jak to się czuje w dłoni, bo żaden telefon nie został zmierzony. Rzeczywiste czasy klatek się wahają, co zmieni dokładny wzór jedynek i dwójek. Opóźnienie i przesunięcie w spoczynku też zależą od czasu klatki, bo zależy od niego krok wygładzania min(1, dt × 6), i dlatego model daje 4 piksele w spoczynku przy 60 Hz i 9 przy 120. W mojej interpretacji wahania w zakresie, jaki zwykle pokazuje telefon, zmienią ich wielkość, ale ich nie usuną; warunkiem jest, by klatki były krótsze niż 1/6 sekundy, około 167 milisekund, bo przy takiej długości współczynnik wygładzania osiąga 1 i kamera ląduje na graczu w jednej klatce, więc dostatecznie długie przycięcie domyka odstęp w tej klatce.176 Niedopasowanie rysunków do chodu nie zależy od liczby klatek: zegar rysunków 8 na sekundę wobec chodu 4 kafelków na sekundę daje 3,00 kafelka na cykl przy 60 Hz i mniej więcej tyle samo przy 120. Niedopasowanie biegu zależy, 4,50 kafelka na cykl przy 60 Hz i 4,75 przy 120, bo zależy od niej nadwyżka odrzucana na każdym kafelku.6

Brief

Każdy punkt to zmiana w Kiradex/World/, jej uzasadnienie i test, który może zweryfikować linia dziennika, skrypt lub nagranie. Nie proponuje się żadnych zasobów, nazw ani dźwięków z żadnej z gier: liczby to mechanika, a grafika, dźwięki i słowa należą do Kiradex.

1. Chodzić na tiku, nie na zegarze.

Zmiana. Dodać do aktualizacji riga akumulator 60 Hz. Każdy callback dodaje swoje dt; jeśli akumulator zawiera wtedy więcej niż 8 tików czasu (133 milisekundy), nadwyżka jest odrzucana i zapisywana w dzienniku ruchu jako linia dropped z liczbą milisekund; następnie wykonywany jest każdy pełny tik z akumulatora, a każdy odejmuje jeden tik. Akumulator liczy czas w całkowitych jednostkach (nanosekundach albo stosowanej w modelu 1/60 000 000 sekundy), nigdy w zmiennoprzecinkowych sekundach: przy akumulatorze Double lub Float 600. tik dziesięciosekundowego testu wypada przy niektórych częstotliwościach o jedno zaokrąglenie przed swoją granicą i licznik pokazuje 599. To jest reguła zaległości: do 8 tików opóźnienia nadrabia się w następnym callbacku, a wszystko ponad to traktuje się jak wstrzymanie, więc świat wznawia się tam, gdzie się zatrzymał, zamiast pędzić, by nadrobić. Ósemkę wybrano tak, by obejmowała każdą częstotliwość z listy Apple dla iPhone’ów z ProMotion, aż do 10 Hz, które wymaga 6 tików na callback.7 W modelu dziesięć sekund callbacków przy każdej z dwunastu częstotliwości wykonuje dokładnie 600 tików i niczego nie odrzuca, podczas gdy limit 4 tików na callback dałby 480 przy 12 Hz i 400 przy 10.6 Po jednosekundowym przycięciu przy 60 Hz reguła wykonuje 8 tików w następnym callbacku i po 1 w każdym kolejnym oraz zapisuje 866,7 milisekundy jako odrzucone; to samo przycięcie, gdy zaległość jest zachowywana przy limicie 4, wykonuje po 4 tiki w 19 callbackach z rzędu, czyli właśnie to przewijanie do przodu, któremu reguła ma zapobiegać.6 Każda postać przechowuje licznik tików kroku zamiast ułamkowego postępu, a jej rysowane przesunięcie to ten licznik pomnożony przez liczbę pikseli na tik: dokładne liczby całkowite, więc postacie nie wymagają już zaokrąglania. Chód to 1 piksel na tik, 16 tików na kafelek; bieg to 2 piksele na tik, 8 tików, z zachowaniem obecnej reguły, że ścieżka o 6 krokach lub więcej to bieg. Kroki po przekątnej, na które pozwala wyszukiwanie ścieżki, zachowują √2 zamierzone już w kodzie i jego kolejność, czyli tempo biegu stosowane przed dzieleniem przez √2: przekątna w chodzie trwa 23 tiki (16√2 to około 22,6, zaokrąglone w górę), a w biegu 12 (8√2 to około 11,3, zaokrąglone w górę), i każda przesuwa postać o 16 pikseli w każdej osi w tych tikach, w których zmienia się floor(16 × t / 23) lub floor(16 × t / 12).17 W chodzie oznacza to 1 piksel w 16 z 23 tików i zero w pozostałych 7; w biegu 1 piksel w 8 z 12 tików i 2 w 4, jedyny nierówny tryb ruchu w briefie, tak jak Acro Bike to jedyny nierówny tryb w Emerald.1 Nie ma żadnej nadwyżki do odrzucenia.

Dlaczego. Reguły chodu i biegu z sekcji 5; oraz przeskok o 2 piksele na kafelek przy 60 Hz i nieregularne zera i jedynki przy 120 w modelu.61

Testy. Argument uruchomieniowy -motionLog zapisuje w każdym tiku numer tiku i x, y gracza oraz każdą linię dropped. W istniejącym demo obrotów, które przechodzi po jednym kafelku w każdą stronę, skrypt sprawdza, że każdy tik chodu przesuwa postać dokładnie o 1 piksel, każdy kafelek trwa 16 tików i żaden tik nie przesuwa o 2. Demo przekątnych, z jednym krokiem po przekątnej w chodzie i jednym w biegu w każdą stronę, sprawdza 23 tiki i 12, 16 pikseli w każdej osi na krok, przesunięcia w osi o 0 lub 1 piksel w chodzie i o 1 lub 2 w biegu oraz to, że przekątna w biegu jest szybsza niż w chodzie. Testy jednostkowe podają akumulatorowi spreparowane sekwencje dt w dokładnych jednostkach całkowitych: dziesięć sekund przy każdej z dwunastu częstotliwości iPhone’a, od 120 do 10 Hz, wykonuje 600 tików bez odrzuceń, a jednosekundowa przerwa przy 60 Hz wykonuje 8 tików w następnym callbacku, potem po 1 na callback, z 866,7 milisekundy zapisanymi jako odrzucone. Ten sam dziennik ruchu na iPhonie 18 Pro Max i na wewnętrznym ekranie iPhone’a Duo daje tę samą liczbę tików na kafelek: częstotliwość ekranu nie może zmieniać chodu.

2. Wybierać rysunek chodu według odległości.

Zmiana. Ten punkt to moja propozycja, a nie kopia: Emerald mierzy czas swoich rysunków w klatkach dopasowanych do kroków i nie odczytuje ich z odległości.92 W trakcie chodu wybierać kolumnę na podstawie postępu chodu liczonego w krokach, czyli kroków ukończonych od początku chodu plus tików bieżącego kroku podzielonych przez jego długość: walkCycle[floor(steps × walkCycle.count / 2) % walkCycle.count], dwa stąpnięcia na dwa kroki, i tak samo dla biegu. Na prostym kroku to przebyta odległość podzielona przez 32 piksele, floor(distancePx × 6 / 32) mod 6. Na przekątnej liczy się krok, a nie jego długość √2, więc stąpnięcie wciąż wypada na początku każdego kroku przy dłuższym wykroku; to mój wybór dla rysunków stworzonych z myślą o prostych krokach. Po zakończeniu chodu pokazywać rysunek postawy, tak jak Emerald. Zegary bezczynności, mrugania i emotek pozostawić bez zmian. Cykl odmierzany tikami również mógłby pasować do kroków, tak jak w Emerald, ale sześć rysunków na 32 tiki wymagałoby nierównych czasów 5 i 6 tików; liczenie postępu nie wymaga drugiej tabeli i działa dla każdego trybu ruchu, który aplikacja doda.

Dlaczego. Jedno stąpnięcie na 16 pikseli przy każdej prędkości i kadencja, która nie może odjechać od ruchu; dzisiejszy cykl obejmuje w modelu 3,00 kafelka w chodzie i 4,50 w biegu.16 To rozstrzyga też kwestię „eight frames a second” (ośmiu klatek na sekundę) z artykułu o postaciach: przy 1 pikselu na tik sześć rysunków na 32 piksele zmienia się co 5,33 piksela, czyli 11,25 rysunku na sekundę, a nie 8.35

Test. Na podstawie dziennika ruchu i wyświetlanej kolumny: dwa rysunki kontaktu (walk_a i walk_d w cyklu z kuźni) pojawiają się na pikselu 0 i 16, z tolerancją jednego, w każdych 32, przy 60 i przy 120 Hz; w demo przekątnych pojawiają się w pierwszym tiku każdego kroku.

3. Kończyć krok; zachować dotknięcie; dodać pływającą gałkę.

Zmiana. Dotknięcie w połowie kroku planuje trasę od kafelka, do którego postać idzie, a nie od punktu wyjścia, a bieżący krok kończy się najpierw; licznik kroku nigdy nie jest zerowany. Ruchy innych kolekcjonerów z serwera podlegają tej samej regule. Z miejsca dotknięcie, którego pierwszy krok zmienia kierunek, obraca postać przez 8 tików przed ruszeniem. Dotknięcie zablokowanej komórki obok gracza albo kafelka przed jedną z nich obraca gracza w jej stronę i odtwarza 32-tikowe odbicie z haptyką odmowy z punktu 6. Przytrzymanie palca sprawia, że postać za nim podąża, tak jak w domyślnym trybie Stardew, ale trasa jest wyznaczana przez istniejące wyszukiwanie ścieżki za każdym razem, gdy palec przekroczy kafelek, bo podążanie w Stardew nie omija przeszkód, a nasze może. Pływająca gałka, czterokierunkowa, domyślnie wyłączona, pojawia się tam, gdzie wyląduje kciuk, zbudowana na geście przeciągania SwiftUI nad światem; wszystko, co jest rysowane, ma co najmniej 44 na 44 punkty. Gałka z Touch Controller (iOS 26) zastępuje przeciąganie tylko wtedy, gdy kompilacja testowa pokaże, że rysuje się nad RealityView: Apple opisuje ten framework jako przeznaczony dla „Metal-based games” (gier opartych na Metal), jego sesja na WWDC25 mówi, że „integrates directly with Metal” (integruje się bezpośrednio z Metal), a świat Kiradex rysuje RealityView bez własnego przebiegu renderowania.4562

Dlaczego. Reguły kroku, obrotu, odrzuconego kroku i sterowania z sekcji 5.14044624517

Testy. Test interfejsu dotyka kafelka 6 na wschód, a 120 milisekund później kafelka 6 na północ; dziennik ruchu nie pokazuje żadnego tiku, w którym x gracza maleje, a kafelek pierwszego kroku zostaje ukończony przed obrotem. Test interfejsu dotyka ściany obok domu; dziennik pokazuje zmianę kierunku i 32-tikowe odbicie, bez zmiany pozycji. Przy włączonej gałce przeciągnięcie w prawo przytrzymane przez 60 tików od pierwszego tiku ruchu kończy trzy kafelki do tiku 48, zaczyna czwarty, gdy gałka jest nadal przytrzymana, i kończy go w tiku 64 po puszczeniu: dziennik pokazuje gracza 4 kafelki na wschód, na pełnym kafelku, z ukończonym krokiem, który trwał w chwili puszczenia, i bez zatrzymania między kafelkami.

4. Zablokować kamerę.

Zmiana. Zastąpić wygładzanie kamerą ustawianą w tym samym tiku, w którym porusza się gracz, wyłącznie z pełnych pikseli świata: camera = clamp(player + foldOffset, low, high). Każdy składnik pozostaje całkowity, bo dziś dopiero końcowe zaokrąglenie czyni kamerę całkowitą: granice i przesunięcie zgięcia są ułamkowe. Pozycja gracza jest całkowita dzięki punktowi 1. Przesunięcie zgięcia, które follow liczy w punktach pomnożonych przez displayScale / pixelScale, jest zaokrąglane do pełnych pikseli świata raz, w chwili zmiany zgięcia. Granice clampa są zaokrąglane do środka, dolna w górę, a górna w dół, bo połowa rozmiaru widoku w pikselach świata nie musi być całkowita: fit dzieli piksele ekranu widoku przez całkowitą liczbę pikseli ekranu na teksel, więc przykładowy widok 393 na 852 punkty przy 3x dostaje 7, widok o szerokości 168,43 piksela świata i połowie szerokości 84,21, a granice kamery to 85 i szerokość mapy minus 85, dzięki czemu żadna klatka nie pokazuje niczego poza krawędzią mapy.176 Gdy mapa jest mniejsza niż widok, granice zamieniają się miejscami, na co pozwala dzisiejszy clamp, a to samo zaokrąglanie do środka utrzymuje całą mapę w widoku. Zachować ograniczenie do mapy; reguła z sekcji 5 pozwala ograniczać albo rysować zewnętrze, a kamera Kiradex wychodzi poza krawędź mapy w las tylko wtedy, gdy Duo jest do połowy otwarty, o margines pełnych kafelków.17 Traktować przesunięcie zgięcia jako stałe do chwili zmiany zgięcia; gdy zmieni się z a na b, przeprowadzić je przez a + round((b − a) × i / 8) dla i od 0 do 8, jeden poziom co 2 tiki, w kadencji wygaszania, tak by każda pozycja po drodze była pełnym pikselem; zmiana z 0 na minus 37 przechodzi na przykład przez 0, −5, −9, −14, −19, −23, −28, −32 i −37.6 Zachować paralaksę lasu, liczoną z zablokowanej kamery i zaokrąglaną jak dziś. Żadnego wyprzedzania w stronę dotkniętego celu: Game Freak takie zbudował i zostawił wyłączone, a cel dotknięcia i tak jest już na ekranie.12

Dlaczego. Reguła kamery z sekcji 5 oraz opóźnienie z modelu, które w trakcie chodu rośnie do 9 pikseli przy piątym kafelku i wynosi 13 w biegu, zmiany miejsca gracza na ekranie i przesunięcia w spoczynku o 4 i 9 pikseli.6

Testy. Według dziennika ruchu kamera minus gracz jest stała w każdym tiku chodu po otwartym placu (zero albo przesunięcie zgięcia) i taka sama po zatrzymaniu się gracza, a każda wartość kamery jest liczbą całkowitą. Test jednostkowy wywołuje fit z widokiem 393 na 852 punkty przy 3x, prowadzi gracza do obu krawędzi mapy i sprawdza całkowite pozycje kamery, które zatrzymują się na 85 i na szerokości mapy minus 85; ten sam test zmienia następnie zgięcie i sprawdza, że kamera osiąga nowe przesunięcie przez 9 pozycji o pełnych pikselach, co 2 tiki, i że żaden tik pośredni nie wychodzi poza granice. Na potrzeby nagrania rysunki chodu nie nadają się na znacznik, bo stopy zmieniają kształt z rysunku na rysunek; kompilacja debugowa rysuje jednotekselowy znacznik, w kolorze nieużywanym nigdzie indziej, w pozycji encji gracza, a skrypt odnajduje go w każdej klatce 6-kafelkowego chodu po otwartym placu: ten sam piksel ekranu w każdej klatce. Przy krawędzi mapy porusza się znacznik, a nie kamera, i to wyłącznie o pełne piksele.

5. Drzwi, krok i wygaszanie we własnej grafice Kiradex.

Zmiana. Wejście przez drzwi, czyli warp z arkuszem drzwi. Dziś drzwi otwierają się dopiero wtedy, gdy postać stanie już na komórce drzwi: onStep kroku znajduje warp na tym kafelku i tam wywołuje openDoor.17 Emerald nigdy nie pozwala graczowi stanąć w zamkniętych drzwiach: TryDoorWarp uruchamia się tylko wtedy, gdy gracz, stojąc na komórce poniżej, naciska na północ w stronę drzwi, a Task_DoDoorWarp otwiera drzwi jedną komórkę wyżej i dopiero potem wymusza na nie krok.6525 Brief przesuwa wyzwalacz o jedną komórkę wstecz, by to odwzorować:

  1. Gdy następny krok ścieżki prowadzi na warp drzwi, chód zatrzymuje się na komórce przed nim, a wejście zaczyna się tam, w tiku 0. Zablokować sterowanie i odtworzyć dźwięk drzwi, dźwięk Kiradex.
  2. Otworzyć drzwi w czterech slotach po 5 tików, czyli w czterech rysunkach Emerald po pięć klatek każdy (83 milisekundy na slot przy 60 tikach na sekundę, gdzie pięć klatek Emerald to 84; łącznie 20 tików), zamiast dzisiejszego natychmiastowego rysunku uchylonych drzwi i otwartych po 70 milisekundach: zamknięte, uchylone, otwarte, otwarte. Arkusz drzwi Kiradex ma trzy rysunki, zamknięte, uchylone i otwarte (door_sheet() z kuźni rysuje trzy, a aplikacja wczytuje arkusz przez SpriteSheet.bundled(name, columns: 3, rows: 1)), podczas gdy drzwi w Emerald to zamknięte plus trzy, więc rysunek otwartych drzwi trzyma się także przez czwarty slot. Czwarty rysunek z kuźni, pomiędzy uchylonymi a otwartymi, mógłby wypełnić ten slot; to opcjonalna grafika, a nie wymóg. Poprawić komentarz mówiący o „four ticks” (czterech tikach).2417
  3. Przeprowadzić gracza jednym wymuszonym krokiem z tej komórki na komórkę drzwi, 16 tików. Drzwi w miasteczku znajdują się w dolnym rzędzie budynku, z komórkami budynku po obu stronach i powyżej, a ścieżka nigdy nie ścina narożnika ściany, więc ten krok zawsze prowadzi w górę z komórki poniżej, tak jak w Emerald.17
  4. Ukryć postać i zamknąć drzwi, ze slotami w odwrotnej kolejności (otwarte, otwarte, uchylone, zamknięte), 20 tików.
  5. Nałożyć na świat czarną zasłonę w 9 skokowych poziomach krycia (0, 2/16 i tak dalej do 16/16), poziom i w tiku 2i wygaszania, tak by ostatni poziom wypadł w tiku 16 i trwał przez tik 17: 18 tików. To adaptacja, świadomie wybrana, a nie czasy z Emerald. Palety sprite’ów w Emerald osiągają każdy poziom w tych samych parzystych klatkach co ta zasłona, palety tła o klatkę wcześniej, a po ostatnim mieszaniu w klatce 16 gra wykonuje jeszcze pięć końcowych aktualizacji, zanim wygaszanie stanie się nieaktywne w klatce 21; jedna zasłona nie ma drugiej warstwy, która by się spóźniała, ani niczego do dokończenia.5 Nigdy nie animować krycia widoku, który zawiera samo RealityView; przeglądarka kart nauczyła tego pierwszy artykuł.22
  6. Wypchnąć (push) cel z wyłączonymi animacjami i zdjąć zasłonę w tych samych 9 poziomach.
  7. Gracz pojawia się na wycieraczce w środku, zwrócony w górę. Wyjście odwraca sekwencję: pojawienie się na komórce drzwi z otwartymi drzwiami, wymuszony krok w dół trwający 16 tików, zamknięcie drzwi, a potem sterowanie.

Piętra, które dziś podmieniają scenę bez wygaszania, dostają tę samą zasłonę bez drzwi i bez wymuszonego kroku. Wyjście z pomieszczenia, które dziś wywołuje dismiss(), dostaje zasłonę i wymuszony krok na zewnątrz.

Dlaczego. Reguły drzwi, wygaszania i ceremonii z sekcji 5; dziś miejsce zmienia się 220 milisekund po kroku, drzwi pokazują dwa rysunki w odstępie 70 milisekund, a ekran przesuwa się w bok.1517

Testy. Dziennik ruchu liczy tiki od tego, w którym chód zatrzymuje się pod drzwiami. Pokazuje gracza wciąż na tej komórce, sloty drzwi w tikach 0, 5, 10 i 15 (zamknięte, uchylone, otwarte, otwarte); wymuszony krok w tikach od 20 do 35, 1 piksel na tik, osiągający komórkę drzwi w tiku 35 i nigdy wcześniej; sloty zamykania w tikach 36, 41, 46 i 51; pierwszy poziom zasłony w tiku 56; i push nie wcześniej niż w tiku 74. Chód, który kończy się na komórce drzwi bez tej sekwencji, nie przechodzi testu. Dziennik ruchu zapisuje też krycie zasłony w każdym tiku: 9 różnych wartości, każda trzymana przez 2 tiki, w tikach od 56 do 73 przy wygaszaniu i te same 9 przy rozjaśnianiu. Nagranie ekranu potwierdza to na nieruchomym fragmencie obrazu: cała klatka się nie nada, bo woda na podłożu, na mapie, która ją ma, zmienia rysunek 4 razy na sekundę, a inni kolekcjonerzy mogą przechodzić, więc skrypt próbkuje wybrany z góry fragment ściany budynku, na którym w nagraniu nie ma żadnego animowanego kafelka ani postaci, i liczy 9 różnych poziomów przy wygaszaniu i 9 przy rozjaśnianiu, każdy trzymany przez 2 klatki, z tolerancją jednej przy 60 Hz.17 Na nagraniu ekranu warpa świat nigdy nie przesuwa się w poziomie: żadnego przesunięcia nawigacji.

6. Haptyka: trzy zdarzenia i przełącznik.

Zmiana. Jeden CHHapticEngine należący do świata, uruchamiany, gdy świat się pojawia, i zatrzymywany, gdy znika, z przełącznikiem Haptyka w ustawieniach, domyślnie włączonym i respektującym systemowe ustawienie haptyki. Wzorce, przechowywane jako pliki AHAP w pakiecie:

Zdarzenie Wzorzec Dlaczego
Odrzucony krok (odbicie) jeden hapticTransient, intensywność 0,4, ostrość 0,2 uderzenie w HIG to „a thud when two heavy objects collide” (głuchy łoskot przy zderzeniu dwóch ciężkich obiektów);46 miękko, bo grafika jest miękka
Drzwi się otwierają hapticTransient z intensywnością 0,3 i ostrością 0,6 na każdym slocie, który zmienia obraz przy otwieraniu: rysunek uchylonych drzwi w tiku 5 i otwartych w tiku 10 (83 i 167 ms) dopasowanie do animacji, której towarzyszy
Uniesienie karty (karta 3D) hapticTransient z wartościami 0,7 i 0,8, gdy karta dotrze na górę jedyny prawdziwy obiekt w świecie
Kroki domyślnie brak 3,75 kroku na sekundę przez całe minuty to nadużycie, przed którym ostrzegają HIG

Wartości intensywności i ostrości to moje punkty wyjścia, a nie pomiary, i są przeznaczone do strojenia na urządzeniu. UIImpactFeedbackGenerator(style: .soft, view:), z prepare() wywoływanym na początku chodu, to rozwiązanie zastępcze tam, gdzie capabilitiesForHardware() wskazuje, że Core Haptics jest niedostępne.

Dlaczego. Reguła haptyki; wskazówki Apple cytowane w sekcji 6.46565760

Test. Argument -hapticLog zapisuje każde zdarzenie. Oskryptowane wejście przez drzwi zapisuje dokładnie 2 zdarzenia drzwi, w tikach 5 i 10 według licznika z punktu 5, i żadnego na krok; przy wyłączonym przełączniku żadnego.

7. Liczba klatek: 60, równomiernie.

Zmiana. Żadnej w Info.plist: nie dodawać CADisableMinimumFrameDurationOnPhone, bo świat nic nie zyskuje na 120. Zachować RealityView; tik z punktu 1 uniezależnia ruch od częstotliwości, z jaką renderuje. Jeśli kiedykolwiek zostanie dodany display link, ustawić CAFrameRateRange(minimum: 30, maximum: 60, preferred: 60), by skorzystać z priorytetu Apple dla gier.76

Testy. Zapisać histogram SceneEvents.Update.deltaTime podczas 60-sekundowego chodu na iPhonie 18 Pro Max i na iPhonie Duo, na ekranie zewnętrznym i wewnętrznym. Brief zakłada, że dominanta wynosi 16,7 milisekundy; jeśli wynosi 8,3, akumulator z punktu 1 nadal utrzymuje chód na 16 tikach na kafelek, a dziennik to potwierdza. Linie dropped w dzienniku ruchu: żadnych w zwykłym chodzie, przy żadnej z tych częstotliwości.

8. Wstrząs tylko na jedną chwilę.

Zmiana. Wstrząs kamery o pełne teksele, 1 teksel, 8 odwróceń, co 5 tików, używany przy jednym zdarzeniu, które na to zasługuje, na przykład przy odsłonięciu rzadkiej karty na placu, razem z haptyką uniesienia karty. Nie przy drzwiach, krokach ani przybyciach.

Dlaczego. Najczęstszy z 24 wstrząsów w Emerald i ich powściągliwość.29

Test. Dziennik ruchu pokazuje przesunięcie kamery przeskakujące między plus i minus 1 tekselem w tikach 0, 5 i tak dalej do 35 i wracające do 0 w tiku 40.

Poza tym briefem

  • Wygładzanie lub wyprzedzanie kamery na siatce. Nie ma skoków do wygładzania, a Game Freak wydał grę z wyłączonym wyprzedzaniem.1223
  • 120 Hz dla świata. Celem jest równe tempo przy 60; 120 pokazuje każdy piksel dwukrotnie.6
  • Haptyka przy każdym kroku domyślnie.46
  • Stały krzyżak na ekranie. Moja rekomendacja to dotknięcie, aby iść, plus opcjonalna pływająca gałka.44
  • Jakiekolwiek dźwięki lub melodyjki drzwi i warpów z gier. Dźwięki są formą ekspresji, a Kiradex potrzebuje własnych.
  • Rowery, surfowanie, lód i prądy. Postacie w aplikacji chodzą i biegają, nic więcej, a powyższe prędkości są zapisane na wypadek, gdyby to się zmieniło.17

Pytanie otwarte: czasy na urządzeniu

Każda liczba z modelu w tej sekcji zakłada stałe 1/60 lub 1/120 sekundy między klatkami. Czy RealityView na iPhonie 18 Pro Max lub iPhonie Duo renderuje w 60, czy w 120, i jak bardzo waha się jego deltaTime, pozostaje niezmierzone; histogram z punktu 7 to pierwsza rzecz do uruchomienia, a punkt 1 jest napisany tak, by odpowiedź nie zmieniała niczego w chodzie przy żadnej częstotliwości z listy iPhone’ów Apple.687

Najważniejsze wnioski

Dla tych, którzy rysują grafikę

  • Rysować chód dla odległości. Chód w Emerald to dwa wykroki i dwie postawy na 32 pikselach, trzymane przez liczby klatek dopasowane do kroków, a szybsze tryby ruchu wykorzystują te same rysunki z krótszym czasem trwania zamiast dodawać klatki, więc przy każdej prędkości na 16 pikseli wypada jedno stąpnięcie.192
  • Cykl sześciu rysunków jest w porządku, pod warunkiem że silnik odtwarza go na stałej odległości; odtwarzany ze stałą częstotliwością wobec chodu odmierzanego osobno rozjeżdża się z ruchem: w modelu naszego kodu 3,00 kafelka na cykl w chodzie i 4,50 w biegu.6
  • Drzwi w Emerald to zamknięte plus trzy rysunki, każdy na ekranie przez 84 milisekundy. Arkusz z trzema rysunkami, zamkniętymi, uchylonymi i otwartymi, taki jak w Kiradex, wypełnia cztery sloty, trzymając rysunek otwartych drzwi przez dwa z nich, albo kuźnia rysuje czwarty; tak czy inaczej stan otwarty warto narysować jako coś, na co warto popatrzeć.12417

Dla tych, którzy budują silnik

  • Przesuwać świat na stałym tiku, o pełne piksele dzielące kafelek, odczytywać sterowanie na kafelku i pozwolić rendererowi pokazywać ostatni tik. Określić, co dzieje się z zaległością: nasz silnik nadrabia do 8 tików, a resztę odrzuca i zapisuje w dzienniku. Wzorem są tabele kroków Emerald: każda sumuje się do 16.216
  • Blokować kamerę na graczu w tym samym tiku, z granicami i przesunięciami w pełnych pikselach, tak by nic nie wymagało późniejszego zaokrąglania. Wygładzanie, a potem zaokrąglanie, sprawia, że kamera wlecze się za idącym graczem i, w modelu naszego kodu, zatrzymuje się 4 do 9 pikseli przed nim aż do następnego chodu.36
  • Na iPhonie nie prosić o 120 Hz dla ruchu pikselowego. Apple priorytetowo traktuje 30 i 60 dla gier, RealityKit zwykle renderuje w 60, a chód o pełne piksele przy 120 tylko trzyma każdy piksel przez dwa odświeżenia.786
  • Wygaszać skokowo, a nie płynną rampą. Wygaszanie przy drzwiach w Emerald to dziewięć poziomów co dwie szesnaste, z ostatnim mieszaniem w 17. klatce i zakończeniem w 22.; nasza 18-tikowa zasłona to adaptacja tej rampy, a nie kopia.5
  • Nigdy nie animować krycia widoku SwiftUI, który zawiera RealityView; nałożyć na niego zasłonę.22

Dla tych, którzy projektują pętlę rozgrywki

  • Wejście przez drzwi to w Emerald ceremonia trwająca co najmniej 1,32 sekundy bez reakcji na sterowanie, a wymuszony krok przez próg sprawia, że odbiera się to jako wejście do środka.525
  • Obrót w miejscu przez 8 klatek pozwala graczowi zwrócić się ku czemuś bez ruszania się; 32-klatkowe odbicie mówi mu, że krok został odrzucony.1
  • Na telefonie moja rekomendacja to: domyślnie dotknięcie, aby iść, tak jak w mobilnej wersji Stardew, pływająca gałka jako precyzyjna alternatywa, zgodnie z zaleceniem HIG, i żadnego stałego krzyżaka.4044
  • Haptyka potwierdza zdarzenia, a nie kroki, i ma przełącznik.46

Często zadawane pytania

Jak szybko chodzi gracz w Pokémonach?

W Red, Crystal i Emerald krok w chodzie pokonuje jedną 16-pikselową komórkę w 16 klatkach przy 59,7275 klatki na sekundę: 268 milisekund na komórkę, 3,73 komórki na sekundę. Emerald przesuwa postać o 1 piksel w każdej klatce; Red i Crystal o 2 piksele co drugą klatkę. Bieg i surfowanie w Emerald oraz rowery w Red i Crystal zajmują 8 klatek na komórkę, 7,47 komórki na sekundę.1419

Ile klatek ma cykl chodu w Pokémon Emerald?

Cztery pozycje na 32 klatki: rysunek wykroku przez 8 klatek, rysunek postawy przez 8, drugi wykrok przez 8, postawa przez 8. To dwie komórki chodu, więc każdy krok pokazuje jeden wykrok i jedną postawę, a nogi zmieniają się krok po kroku. Bieg to cykl 16 klatek na dwie komórki po 8 klatek.192

Czy kamera w Pokémon Emerald zostaje w tyle za graczem?

Nie. Kamera w Emerald kopiuje pozycję gracza i przewija mapę o te same piksele w tej samej klatce, więc gracz pozostaje nieruchomy na ekranie, a porusza się świat. Nie zatrzymuje się też na krawędzi mapy: zewnętrze rysowane jest z kafelków brzegowych układu. Kamera wyprzedzająca dla roweru istnieje w kodzie, ale nigdy nie jest włączana.231112

Jak długo trwa przejście przez drzwi z wygaszaniem w Pokémon Emerald?

Drzwi otwierają się w czterech rysunkach po pięć klatek (335 milisekund), gracz robi jeden wymuszony 16-klatkowy krok do środka, drzwi zamykają się w 20 klatek, a ekran wygasa w dziewięciu poziomach, z ostatnim mieszaniem w 17. klatce wygaszania i zakończeniem w jego 22. klatce: co najmniej 79 klatek, 1,32 sekundy, zanim może zacząć się wczytywanie następnej mapy. Przy wejściu do jaskini ekran wygasa do bieli zamiast do czerni, a przy wyjściu z niej rozjaśnia się z bieli.152527

Czy gra pixel art powinna działać w 120 Hz na iPhonie z ProMotion?

Nie w przypadku ruchu o pełne piksele. Świat, który przesuwa się o jeden piksel na tik 60 Hz, przy 120 Hz tylko pokazuje każdy piksel przez dwa odświeżenia, a przy 80 z nierównymi czasami. Artykuł Apple o ProMotion mówi, że gry dostają „special priority to 30Hz and 60Hz” (specjalny priorytet dla 30 Hz i 60 Hz), że aplikacja na iPhone’a musi ustawić CADisableMinimumFrameDurationOnPhone, by przekroczyć 60, oraz że RealityKit zwykle renderuje w 60. Należy symulować na stałym tiku 60 Hz, a ekran niech pracuje, jak pracuje.67168

Dlaczego stopy mojego sprite’a ślizgają się podczas chodu?

Zwykle dlatego, że rysunki chodu chodzą na jednym zegarze, a ruch na drugim, z długościami, które się nie zgadzają. Obecny kod Kiradex odtwarza cykl sześciu rysunków w tempie 8 rysunków na sekundę przy chodzie 4 kafelków na sekundę, więc jeden cykl obejmuje 3 kafelki zamiast 2, jak pokazuje model kodu. Konsole przenośne liczą jedno i drugie w klatkach i sprawiają, że rysunki każdego kroku trwają tyle, co sam krok; wybieranie rysunku na podstawie przebytej odległości, które proponuję dla Kiradex, daje tę samą zgodność bez drugiego zegara. Tak czy inaczej kadencja nie może odjechać od ruchu; to, czy stopa pozostaje postawiona, zależy też od samych rysunków.619

Czy mobilna gra pikselowa powinna mieć wirtualny krzyżak?

Nie jako domyślny, w mojej interpretacji źródeł. Domyślnym sterowaniem w mobilnym Stardew Valley jest dotknięcie, aby się poruszyć, z niewidzialnym joystickiem wśród innych schematów do precyzyjnych zadań; HIG Apple zalecają bezpośrednie dotykanie obiektów i gałkę, która pojawia się „wherever the player lands their thumb instead of a static thumbstick position.” (tam, gdzie gracz położy kciuk, zamiast w stałym miejscu).4044

Powiązane na tej stronie: Światy pixel art na iPhonie to pierwszy przewodnik z tej serii, z chodem krok-postawa w Emerald, przepisem RealityKit, na którym działa ten świat, i lekcją o kryciu z przeglądarki kart; Ludzie w pixel art: postacie i kreator na iPhonie to drugi, z sześciorysunkowym chodem, którego odmierzanie ten artykuł przenosi z zegara na odległość; Budowle w pixel art: domy, hale i wnętrza na iPhonie to trzeci, z drzwiami, warpami i piętrami, których czasy mierzy sekcja 2; iPhone Duo dla programistów i Przygotowanie aplikacji na iPhone’a Duo omawiają dwa ekrany i zgięcie, z dala od którego trzyma się kamera z briefu; Przestrzenny model mentalny RealityKit wyjaśnia model encji i systemów stojący za SceneEvents.Update.

Źródła


  1. Pomiar autora, 4 października 2026: measure_gen3_motion.py, w folderze dowodów autora do tego artykułu, uruchomiony na pokeemerald z pret w commicie 731ad5b; parsuje tabele funkcji kroku i czasy trwania InitMoveInPlace w src/event_object_movement.c, tabele animacji w src/data/object_events/object_event_anims.h, regułę licznika opóźnienia w src/sprite.c oraz klatki drzwi w src/field_door.c i przelicza klatki przy 59,7275 Hz. Wynik zapisany obok jako measure_gen3_motion.out.txt (prędkości 3,73, 7,47, 9,95, 14,93 i 29,86 komórki na sekundę; chód w miejscu 32, 16, 8 i 4 klatki; sAnim_GoSouth 32 klatki; rysunki drzwi trzymane przez 5 aktualizacji, 83,7 ms, 335 ms dla czterech). ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  2. pret, pokeemerald/src/event_object_movement.c (sStep1Funcs do sStep8Funcs i komentarz „Over the course of the step animation, these sum to 16 pixels (one full metatile)”; sStepTimes i NpcTakeStep, które indeksuje tabelę kroków wartością sTimer, jedna pozycja na klatkę; SetStepAnimHandleAlternation, które ustawia animację trybu ruchu i naprzemienność na początku kroku; CameraObject_UpdateMove), commit 731ad5b, dostęp 4 października 2026, https://github.com/pret/pokeemerald/blob/master/src/event_object_movement.c. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  3. pret, pokeemerald/src/overworld.c (kolejność w OverworldBasic: RunTasks(); AnimateSprites(); CameraUpdate(); UpdateCameraPanning();, a dalej w tej samej funkcji UpdatePaletteFade(); VBlankCB_Field wywołujące TransferPlttBuffer; InitPlayerAvatar przed InitCameraUpdateCallback(gPlayerAvatar.spriteId)) oraz src/sprite.c (AnimateSprites uruchamia callbacki w kolejności slotów), dostęp 4 października 2026, https://github.com/pret/pokeemerald/blob/master/src/overworld.c i https://github.com/pret/pokeemerald/blob/master/src/sprite.c. Wniosek o tej samej klatce to interpretacja kodu przez autora, a nie wynik uruchomienia. ↩↩↩↩↩↩↩

  4. Pomiar autora, 4 października 2026: measure_gen12_motion.py, w folderze dowodów autora do tego artykułu, uruchomiony na pokered z pret w d2704a6 (home/overworld.asm, home/fade.asm) i pokecrystal w 5beda23 (engine/overworld/events.asm, engine/overworld/map_objects.asm, data/maps/setup_scripts.asm, engine/tilesets/timeofday_pals.asm). Wynik zapisany jako measure_gen12_motion.out.txt (Red: 16 px w 16 klatkach, rower 8 klatek, wygaszanie przy warpie 32 klatki; Crystal: chód 8 aktualizacji po 2 px, rower 4 po 4, powolny krok 16 po 1 przy 1,87 komórki na sekundę, wygaszanie przy drzwiach 8 klatek w każdą stronę). ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  5. Pomiar autora, 5 października 2026: measure_gen3_fade.py, w folderze dowodów autora do tego artykułu, port do Pythona funkcji BeginNormalPaletteFade, UpdatePaletteFade z jej flagą oczekującego transferu, UpdateNormalPaletteFade i IsSoftwarePaletteFadeFinishing z pokeemerald/src/palette.c z pret w 731ad5b, uruchomiony według harmonogramu wywołujących: zadanie drzwi uruchamia wygaszanie wewnątrz RunTasks (Task_DoDoorWarp, src/field_screen_effect.c); BeginNormalPaletteFade aktualizuje raz, kopiuje bufor do pamięci palet i czyści flagę; OverworldBasic (src/overworld.c) aktualizuje ponownie w tej samej klatce; każda kolejna klatka aktualizuje raz, a transfer w VBlank czyści flagę. Klatki liczone są od klatki zadania jako 0. Zakłada, że wcześniej nie trwało żadne wygaszanie; przy aktywnym deszczu, śniegu, mgle, cieniu lub suszy FadeScreen w src/field_weather.c kopiuje bufor zabarwiony przez pogodę, a następnie wywołuje to samo BeginNormalPaletteFade (własną procedurą pogody dla wygaszania jest DoNothing), więc harmonogram wygaszania się utrzymuje, natomiast rozjaśnianie przechodzi przez kod pogody i nie było symulowane; rozjaśnianie po warpie zaczyna się od callbacku wczytywania mapy, którego pierwszej klatki nie prześledzono, więc podano oba przypadki. Wynik zapisany jako measure_gen3_fade.out.txt (poziomy od 0 do 16 co 2; pierwsze widoczne mieszanie w klatce 1; ostatnie mieszanie, palety sprite’ów na 16, w klatce 16, 285 ms, licząc klatkę 0; nieaktywne w klatce 21, 368 ms; FadeInFromWhite z opóźnieniem 8 nieaktywne w klatce 85 lub 86, licząc od klatki 0, czyli po 86 lub 87 klatkach, 1440 lub 1457 ms; wejście przez drzwi: otwarcie 20, krok 16 i zamknięcie 20 klatek, ostatnie mieszanie wygaszania po co najmniej 73 klatkach, 1,22 s, a WarpIntoMap nie wcześniej niż w klatce 79, 1,32 s, ponieważ Task_WarpAndLoadMap czeka, aż wygaszanie stanie się nieaktywne). ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  6. Model autora, 5 października 2026: measure_kiradex_motion.py, w folderze dowodów autora do tego artykułu, odczytuje tilesPerSecond (4), framesPerSecond (8), runPace (1,6), próg biegu (6 kroków) i min(1, dt * 6) kamery z Kiradex/World/PlazaRig.swift, centre z Kiradex/World/TileMap.swift oraz sześciorysunkowe cykle walk i run z scripts/forge/rig.py i modeluje advance, place, animate i follow klatka po klatce, wykonując każdą operację Float w 32 bitach (numpy float32) w kolejności z kodu i z zaokrąglaniem Swifta połówek od zera, przy dokładnie 1/60 i 1/120 s, dla chodu na 5 kafelków, tempa chodu utrzymanego przez 12 kafelków i biegu na 12 kafelków, po każdym z nich 3 s stania; ograniczenie do mapy, zgięcie i przesunięcie stóp pominięto. To model kodu, a nie nagranie z urządzenia. Wynik zapisany jako measure_kiradex_motion.out.txt. Chód przy 60 Hz: 15 klatek na kafelek, każdy kafelek to 14 klatek po 1 px i jedna z 2; odstęp kamery 5, 6, 7, 8 i 9 px na końcach pierwszych pięciu kafelków, 13 od dziewiątego przy utrzymanym tempie chodu; pozycja na ekranie zmienia się w 8 z 73 klatek ruchu w chodzie na 5 kafelków następujących po pierwszej (chód w modelu ma 74 klatki ruchu, a ostatnia klatka ostatniego kafelka liczy się jako stanie); przesunięcie w spoczynku 4 px. Przy 120 Hz: 30 klatek na kafelek, 16 po 1 px i 14 po 0; odstęp 9; spoczynek 9. Bieg przy 60 Hz: 10 klatek na kafelek, 6,00 kafelka na sekundę, 4 klatki po 1 px i 6 po 2 na kafelek; odstęp 13 od drugiego kafelka; spoczynek 4. Bieg przy 120 Hz: 19 klatek na kafelek, 6,32 kafelka na sekundę; odstęp 9; spoczynek 9. Droga cyklu na podstawie symulowanych pozycji między kolejnymi początkami cyklu przy 60 Hz: 3,00 kafelka w chodzie, 4,50 w biegu (4,80 przy nominalnych 6,4 kafelka na sekundę); przy 120 Hz 3,00 i 2,94 w chodzie, 4,75 w biegu. Kroki po przekątnej w obecnym kodzie: 22 klatki w chodzie i 14 w biegu przy 60 Hz, 43 i 27 przy 120. W porównaniu z tym samym modelem uruchomionym z zegarem, pozycjami i kamerą w podwójnej precyzji wszystkie pozycje i wartości kamery są bez zmian, a różni się 9 rysunków chodu przy 60 Hz i 14 przy 120, wszystkie w klatkach, w których zegar pomnożony przez 8 jest liczbą całkowitą (11 takich klatek na 12 kafelkach chodu przy 60 Hz, 23 przy 120). Propozycja: stały tik 60 Hz trzyma każdy piksel przez 1 odświeżenie przy 60 Hz, przez 2 odświeżenia dla 59 z 61 pozycji przy 120 Hz oraz przez 1 lub 2 odświeżenia, 42 i 19 pozycji, przy 80 Hz. Akumulator: dziesięć sekund callbacków przy każdej z dwunastu częstotliwości iPhone’a od 120 do 10 Hz wykonuje 600 tików przy limicie zaległości 8 tików, bez odrzuceń, wobec 480 przy 12 Hz i 400 przy 10 Hz przy limicie 4 tików na callback; po przycięciu trwającym 1 s przy 60 Hz reguła 8 tików wykonuje 8 tików, potem 1 na callback, odrzucając 866,7 ms, podczas gdy limit 4 tików z zachowaniem zaległości wykonuje po 4 tiki w 19 kolejnych callbackach. Arytmetyka kamery: fit dla widoku 393 na 852 pt przy 3x daje 7 pikseli ekranu na teksel i widok 168,43 na 365,14 piksela świata, połowa szerokości 84,21, granice zaokrąglone do środka 85 i szerokość mapy minus 85; zmiana zgięcia z 0 na −37 w 9 poziomach przechodzi przez 0, −5, −9, −14, −19, −23, −28, −32, −37. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  7. Apple Developer Documentation, „Optimizing iPhone and iPad apps to support ProMotion displays” (zakres iPhone’a od 10 do 120 Hz i jego dwanaście częstotliwości; lista urządzeń; CADisableMinimumFrameDurationOnPhone; priorytet dla gier przy 30 i 60 Hz; „Prepare your app to operate at any refresh rate”; targetTimestamp), dostęp 4 października 2026, 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” do 60 klatek na sekundę), dostęp 4 października 2026, 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) oraz src/sprite.c (animDelayCounter ładowany czasem trwania klatki pomniejszonym o jeden, odliczany przez ContinueAnim, następna klatka pobierana po osiągnięciu zera), dostęp 4 października 2026, https://github.com/pret/pokeemerald/blob/master/src/data/object_events/object_event_anims.h i https://github.com/pret/pokeemerald/blob/master/src/sprite.c. ↩↩↩↩↩↩↩↩↩↩↩

  10. pret, pokeemerald/src/field_player_avatar.c (PlayerWalkNormal, PlayerRun, PlayerWalkFast z komentarzem „same speed as running”, CheckMovementInputNotOnBike i TURN_DIRECTION, PlayerTurnInPlace, odbicie od ściany), dostęp 4 października 2026, https://github.com/pret/pokeemerald/blob/master/src/field_player_avatar.c. ↩↩↩↩↩

  11. pret, pokeemerald/src/fieldmap.c (GetBorderBlockAt: brzegowe metatile’e układu 2 na 2, oznaczone jako MAPGRID_IMPASSABLE), dostęp 4 października 2026, https://github.com/pret/pokeemerald/blob/master/src/fieldmap.c. ↩↩↩

  12. pret, pokeemerald/src/field_camera.c (CameraUpdate; CameraPanningCB_PanAhead, które przesuwa sVerticalCameraPan o 2 w stronę 72 lub minus 8 od wartości spoczynkowej 32, chronione przez gUnusedBikeCameraAheadPanback i opatrzone komentarzem „this code is never reached”), dostęp 4 października 2026, 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, 3 października 2026 (klatka drzwi w Emerald trzymana przez pięć aktualizacji, około 84 milisekund; PlayerStepOutFromDoor w Red; wstrząs windy w Emerald; 70-milisekundowe drzwi i 220-milisekundowy warp Kiradex wymienione wśród luk), https://blakecrosley.com/blog/pixel-art-structures-on-iphone. ↩↩↩↩↩↩

  14. Notatki badawcze autora do artykułu o budowlach, repozytorium Kiradex (prywatne), docs/research/structures/01-structures-in-the-canon.md, 3 października 2026, które podawały klatki drzwi w Emerald jako „4 ticks each (16 frames, about 0.27 s at 59.7 Hz)”; skorygowane przez przedstawioną w tym artykule interpretację AnimateDoorFrame w field_door.c. ↩↩

  15. Apple Developer Documentation, „preferredFrameRateRange” (CADisplayLink; iOS 15.0, iPadOS 15.0, Mac Catalyst 15.0, visionOS 1.0), dostęp 4 października 2026, https://developer.apple.com/documentation/quartzcore/cadisplaylink/preferredframeraterange. ↩↩↩

  16. Apple Developer Documentation, „CADisableMinimumFrameDurationOnPhone” (klucz Information Property List; iOS 15.0, iPadOS 15.0), dostęp 4 października 2026, https://developer.apple.com/documentation/bundleresources/information-property-list/cadisableminimumframedurationonphone. ↩↩↩

  17. Odczyt repozytorium Kiradex (prywatnego) przez autora w commicie b1b78b1, 4 października 2026, tylko do odczytu: Kiradex/World/PlazaRig.swift (tilesPerSecond, framesPerSecond, runPace, walk(to:) i jego reguła biegu steps.count >= 6, advance, którego krok postępu to dt * Self.tilesPerSecond * (walker.running ? Self.runPace : 1) / length, animate, place, fit, które dzieli piksele ekranu widoku przez całkowite pixelScale, follow, które przelicza punkty zgięcia przez displayScale / pixelScale, wygładza przez min(1, dt * 6) i zaokrągla potem, i którego clamp pozwala kamerze wyjść poza krawędź mapy w las tylko wtedy, gdy Duo jest do połowy otwarty, o beyondMargin, 12 kafelków, podczas gdy build układa las niezależnie od zgięcia, ripple, które zmienia rysunek wody na podłożu 4 razy na sekundę na mapie z wodą, openDoor i jego komentarz „at about Emerald’s four ticks a frame”), Kiradex/World/PlazaStage.swift (obsługa dotknięć, 220-milisekundowe opóźnienie warpa, subskrypcja SceneEvents.Update, podmiana piętra przez .id), Kiradex/World/TileMap.swift (ośmiokierunkowe path, które bierze otwarty kafelek o najniższym dotychczasowym koszcie, dodaje 1 lub √2 na krok i nie używa żadnego szacunku odległości do celu, a którego kroki po przekątnej są pomijane, jeśli obie komórki boczne nie są przechodnie), Kiradex/World/WorldMap.swift (arkusz drzwi warpa, „closed, half open, open”), openDoor wczytujące ten arkusz przez SpriteSheet.bundled(name, columns: 3, rows: 1) i pokazujące kolumnę 1, a potem kolumnę 2, scripts/forge/kit.py (door_sheet(), „The three frames side by side, 48 × 32”), scripts/forge/town.py i scripts/forge/buildings.py (każdy warp drzwi w miasteczku leży na komórce drzwi w dolnym rzędzie budynku, a komórki budynku na lewo, na prawo i nad każdymi drzwiami są zablokowane; sprawdzone dla wszystkich siedmiorga drzwi miasteczka w wydanym town.json), Kiradex/Views/Card/CardViewer.swift (.easeOut(duration: 0.25) zasłony) i scripts/forge/rig.py (cykle). Brak UIImpactFeedbackGenerator, sensoryFeedback, CHHaptic, CADisplayLink, preferredFrameRateRange i CADisableMinimumFrameDurationOnPhone to wynik wyszukiwania w Kiradex/ i project.yml z 4 października 2026, które niczego nie znalazło; to samo wyszukiwanie nie znajduje w Kiradex/ żadnego MTKView, MTLRenderCommandEncoder ani TouchController, więc świat nie ma własnego przebiegu renderowania. Cofnięcie postaci przy dotknięciu w połowie kroku odczytano z walk(to:), a nie nagrano. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  18. pret, płytkie klony pokered (commit d2704a6, 22 września 2026), pokecrystal (5beda23, 29 września 2026) i pokeemerald (731ad5b, 1 października 2026), społecznościowe dekompilacje wydanych gier, https://github.com/pret. ↩

  19. Martin Korth, GBATEK, „LCD Dimensions and Timings” (280 896 cykli i 16,743 ms na klatkę, „ca. 59.737 Hz”), dostęp 4 października 2026, https://problemkaputt.de/gbatek-lcd-dimensions-and-timings.htm; Pan Docs, „Rendering” („One frame: 70224 dots @ 59.7 fps”), dostęp 4 października 2026, https://gbdev.io/pandocs/Rendering.html. Wartość 59,7275 to obliczenie autora: 2^24 / 280 896 oraz 4 194 304 / 70 224. ↩↩↩↩↩

  20. pret, pokeemerald/src/bike.c (sMachBikeSpeedCallbacks, bikeFrameCounter z limitem 2, AcroBikeTransition_Moving wywołujące PlayerRideWaterCurrent i jedyne przypisanie gUnusedBikeCameraAheadPanback = FALSE), dostęp 4 października 2026, https://github.com/pret/pokeemerald/blob/master/src/bike.c. ↩↩↩

  21. Wykresy narysowane skryptem autora make_figures.py (z svgkit.py), 4 października 2026, wyłącznie z danych na dysku: prędkości kanonu z measure_gen3_motion.out.txt i measure_gen12_motion.out.txt; chód i kamera Kiradex z własnej funkcji simulate() skryptu measure_kiradex_motion.py (pierwsza sekunda chodu na 5 kafelków; kamera dla chodu na 5 kafelków przy 60 i 120 Hz i biegu na 12 kafelków przy 60 Hz), a pętla tików propozycji z tego samego skryptu; wygaszanie w Emerald z run() skryptu measure_gen3_fade.py, czyli portu z harmonogramem wywołujących, a wygaszanie i najwcześniejsze wczytanie mapy na wykresie drzwi z tego samego źródła; czasy drzwi Kiradex (70 ms, 1400 ms, 220 ms) odczytane z PlazaRig.swift i PlazaStage.swift w b1b78b1. Nie wykorzystano żadnej grafiki z gier. ↩↩↩↩↩

  22. Blake Crosley, „Pixel-Art Worlds on iPhone: What the 16-Bit Masters Knew,” blakecrosley.com, 3 października 2026 (chód w Emerald „step, stand, step, stand at eight ticks each”; „stand, step, stand, step-flipped” w Red; Celeste w 320 na 180 pomnożone przez sześć; silnik RealityKit; pozycje „rounded to whole world units each frame, after easing”; lekcja o kryciu z przeglądarki kart), 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, 11 maja 2015, zmodyfikowana wersja jego wystąpienia na Independent Games Summit, GDC 2015 (position-locking, edge-snapping, camera-window, lerp-smoothing, target-focus, dual-forward-focus; cytaty), dostęp 4 października 2026, 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}; AnimateDoorFrame, które rysuje przy liczniku 0 i przechodzi dalej, gdy licznik zrówna się z czasem klatki), dostęp 4 października 2026, https://github.com/pret/pokeemerald/blob/master/src/field_door.c. ↩↩↩↩

  25. pret, pokeemerald/src/field_screen_effect.c (stany Task_DoDoorWarp, z których piąty wywołuje WarpFadeOutScreen i przekazuje zadanie do Task_WarpAndLoadMap, które przed WarpIntoMap czeka na PaletteFadeActive(), czyli gPaletteFade.active, oraz na BGMusicStopped(); Task_ExitDoor, WarpFadeOutScreen wywołujące GetMapPairFadeToType, WarpFadeInScreen wywołujące GetMapPairFadeFromType oraz FadeInFromWhite z FadeScreen(FADE_FROM_WHITE, 8)), dostęp 4 października 2026, https://github.com/pret/pokeemerald/blob/master/src/field_screen_effect.c. ↩↩↩↩↩↩↩↩↩↩

  26. pret, pokeemerald/src/palette.c (BeginNormalPaletteFade z deltaY = 2, jego własne wywołanie UpdatePaletteFade, jego CpuCopy32 do pamięci palet i sPlttBufferTransferPending = FALSE; UpdatePaletteFade, które natychmiast wraca, dopóki ta flaga jest ustawiona, i ustawia ją na podstawie gPaletteFade_selectedPalettes po każdej aktualizacji; TransferPlttBuffer, które ją czyści; UpdateNormalPaletteFade; IsSoftwarePaletteFadeFinishing), dostęp 4 października 2026, https://github.com/pret/pokeemerald/blob/master/src/palette.c. ↩↩

  27. pret, pokeemerald/src/fldeff_flash.c (sTransitionTypes z 16 wierszami do i z MAP_TYPE_UNDERGROUND, każdy z flagą wejścia, flagą wyjścia i procedurą przejścia; GetMapPairFadeToType zwracające flagę wejścia wiersza, a GetMapPairFadeFromType jego flagę wyjścia), dostęp 4 października 2026, https://github.com/pret/pokeemerald/blob/master/src/fldeff_flash.c. ↩↩↩

  28. pret, pokeemerald/src/field_specials.c (ShakeCamera, odczytujące przesunięcie pionowe, przesunięcie poziome, liczbę wstrząsów i opóźnienie z VAR_0x8004 do VAR_0x8007 i odwracające znak przesunięcia przy każdym wstrząsie), dostęp 4 października 2026, https://github.com/pret/pokeemerald/blob/master/src/field_specials.c. ↩

  29. Zliczenie autora, 4 października 2026: measure_gen3_shake.py, w folderze dowodów autora do tego artykułu, odczytuje każde wywołanie special ShakeCamera i cztery poprzedzające je wartości setvar w data/**/*.inc w pokeemerald z pret w 731ad5b. Wynik zapisany jako measure_gen3_shake.out.txt (24 wywołania w 9 plikach, osiem zestawów parametrów i ich liczby z tabeli). ↩↩↩↩↩↩

  30. pret, pokered/home/overworld.asm (OverworldLoop, OverworldLoopLessDelay, wWalkCounter, AdvancePlayerSprite, DoBikeSpeedup i komentarz do obrotu o 180 stopni), commit d2704a6, dostęp 4 października 2026, https://github.com/pret/pokered/blob/master/home/overworld.asm. Zachowanie przy obrocie odczytano z kodu, a nie uruchomiono w emulatorze. ↩↩↩↩↩↩

  31. pret, pokered/engine/overworld/movement.asm (UpdatePlayerSprite, wewnętrzny licznik animacji przełączający rysunek przy 4), dostęp 4 października 2026, https://github.com/pret/pokered/blob/master/engine/overworld/movement.asm. ↩↩

  32. pret, pokered/home/fade.asm (GBFadeOutToBlack) oraz home/overworld.asm (PlayMapChangeSound, SFX_GO_INSIDE dla kafelka drzwi $0b, w przeciwnym razie SFX_GO_OUTSIDE), dostęp 4 października 2026, https://github.com/pret/pokered/blob/master/home/fade.asm. ↩

  33. pret, pokecrystal/engine/overworld/events.asm (MaxOverworldDelay: db 2) oraz engine/overworld/map_objects.asm (StepVectors; StepFunction_Turn, którego .init1, .step1, .init2 i .step2 przechodzą jedno w drugie, oraz ObjectStep_AnonJumptable), commit 5beda23, dostęp 4 października 2026, https://github.com/pret/pokecrystal/blob/master/engine/overworld/events.asm i https://github.com/pret/pokecrystal/blob/master/engine/overworld/map_objects.asm. Trzy aktualizacje obrotu to odtworzenie procedury instrukcja po instrukcji przez autora, a nie uruchomienie w emulatorze. ↩↩↩↩↩

  34. pret, pokecrystal/data/maps/setup_scripts.asm (MapSetupScript_Door zaczynające się od FadeOutToWhite, MapSetupScript_Warp kończące się na FadeInFromWhite) oraz engine/tilesets/timeofday_pals.asm, dostęp 4 października 2026, https://github.com/pret/pokecrystal/blob/master/data/maps/setup_scripts.asm i 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, 3 października 2026 (sześcioklatkowy chód; „the walk at eight frames a second” na placu; w części „Od publikacji” 26-pikselowa postać zastąpiona w kompilacji 34 w TestFlight 30-pikselową postacią w komórce 32 na 40, którą scripts/forge/rig.py w b1b78b1 ustawia jako CELL_W, CELL_H = 32, 40), https://blakecrosley.com/blog/pixel-art-characters-on-iphone. ↩↩↩

  36. Twórcy Celeste, repozytorium NoelFB/Celeste na GitHubie, Source/Player/Player.cs (aktualizacja kamery pod „Camera (lerp by distance using delta-time)” i getter CameraTarget), dostęp 4 października 2026, https://raw.githubusercontent.com/NoelFB/Celeste/master/Source/Player/Player.cs. ↩↩↩↩

  37. Twórcy Celeste, repozytorium NoelFB/Celeste, README.md (pliki klas udostępnione „as a learning resource and for general interest”; licencja MIT obejmująca wyłącznie ten kod), dostęp 4 października 2026, https://raw.githubusercontent.com/NoelFB/Celeste/master/README.md. ↩

  38. Stardew Valley Wiki, „Speed” (bazowa prędkość gracza: 2 w chodzie, 5 w biegu, 6,6 na koniu, 7 po marchewce), dostęp 4 października 2026, https://stardewvalleywiki.com/Speed. ↩

  39. Notatki badawcze autora do tego artykułu, zebrane 4 października 2026, odnotowujące, czego szukano i czego nie znaleziono: źródła pierwotnego o kamerach w Sea of Stars, Eastward lub CrossCode; wystąpienia lub artykułu Maddy Thorson o kamerze; dokładnego schematu dotykowego w Pixel Remasterach; ruchu w mobilnej Terrarii na https://terraria.wiki.gg/wiki/Mobile_version; oraz developer.apple.com/documentation/touchcontrols, który zwrócił błąd 404. ↩↩↩

  40. Stardew Valley Wiki, „Mobile Controls” (schematy; „Tap-to-move & Auto-Attack” jako domyślny; cytaty o dotknięciu, aby się poruszyć, o podążaniu za dotykiem, o niewidzialnym joysticku i o ograniczeniach domyślnego sterowania), dostęp 4 października 2026, https://stardewvalleywiki.com/Mobile_Controls. ↩↩↩↩↩↩↩

  41. Jared Nelson, „’Stardew Valley’ is Getting A TON of New Control Options in the Next Update,” TouchArcade, 1 listopada 2018, dostęp 4 października 2026, https://toucharcade.com/2018/11/01/stardew-valley-mobile-controls-update/. ↩

  42. App Store, „FINAL FANTASY” od SQUARE ENIX, historia wersji (wersja 1.2.0, 03/11/2025, oraz uwaga o ruchu opartym na dotknięciach), dostęp 4 października 2026, https://apps.apple.com/us/app/final-fantasy/id1492041278. ↩↩

  43. Mikhail Madnani, TouchArcade, 30 stycznia 2024, o aktualizacji Final Fantasy Pixel Remaster, która wprowadziła obsługę kontrolerów na urządzeniach mobilnych, dostęp 4 października 2026, 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” (dobre praktyki sterowania dotykowego; wpis w dzienniku zmian z 9 czerwca 2025), dostęp 4 października 2026, https://developer.apple.com/design/human-interface-guidelines/game-controls. ↩↩↩↩↩↩↩

  45. Apple, sesja 209 z WWDC25 (framework Touch Controls; „the vast majority of players won’t have a controller available”; „integrates directly with Metal”), dostęp do transkrypcji 4 października 2026, https://developer.apple.com/videos/play/wwdc2025/209/. ↩↩↩

  46. Apple Human Interface Guidelines, „Playing haptics” (dobre praktyki, niestandardowa haptyka i kategoria uderzeń w iOS; cytaty), dostęp 4 października 2026, 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), dostęp 4 października 2026, 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), dostęp 4 października 2026, https://developer.apple.com/documentation/quartzcore/cadisplaylink/targettimestamp. ↩

  49. Apple, sesja 10147 z WWDC21, „Optimize for variable refresh rate displays” (ekrany Adaptive-Sync na Macu i ProMotion w iPadzie Pro; cytaty), dostęp do transkrypcji 4 października 2026, https://developer.apple.com/videos/play/wwdc2021/10147/. ↩↩↩

  50. Apple Developer Documentation, „SceneEvents.Update” (iOS 13.0, iPadOS 13.0, Mac Catalyst 13.0), dostęp 4 października 2026, https://developer.apple.com/documentation/realitykit/sceneevents/update. ↩

  51. Apple Developer Documentation, „deltaTime” (SceneEvents.Update; iOS 13.0), dostęp 4 października 2026, 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; kod wykonywany co klatkę przez System lub SceneEvents.Update), dostęp 4 października 2026, 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), dostęp 4 października 2026, https://developer.apple.com/documentation/corehaptics/chhapticpattern; strona frameworka Core Haptics, https://developer.apple.com/documentation/corehaptics. ↩

  54. Apple Developer Documentation, „CHHapticEvent” i „CHHapticEvent.EventType” (hapticTransient, hapticContinuous; iOS 13.0), dostęp 4 października 2026, https://developer.apple.com/documentation/corehaptics/chhapticevent i https://developer.apple.com/documentation/corehaptics/chhapticevent/eventtype. ↩

  55. Apple Developer Documentation, „CHHapticEvent.ParameterID” (iOS 13.0), dostęp 4 października 2026, https://developer.apple.com/documentation/corehaptics/chhapticevent/parameterid. ↩

  56. Apple Developer Documentation, „CHHapticEngine” (iOS 13.0; capabilitiesForHardware()), dostęp 4 października 2026, https://developer.apple.com/documentation/corehaptics/chhapticengine. ↩↩

  57. Apple Developer Documentation, „UIImpactFeedbackGenerator” (iOS 10.0, iPadOS 10.0, Mac Catalyst 13.1; init(style:view:) w sekcji „Initializing the feedback generator”, a init(style:) wśród elementów przestarzałych), dostęp 4 października 2026, https://developer.apple.com/documentation/uikit/uiimpactfeedbackgenerator. ↩↩

  58. Apple Developer Documentation, „UIImpactFeedbackGenerator.FeedbackStyle” (iOS 10.0), dostęp 4 października 2026, https://developer.apple.com/documentation/uikit/uiimpactfeedbackgenerator/feedbackstyle. ↩

  59. Apple Developer Documentation, „impactOccurred(intensity:)” (iOS 13.0, iPadOS 13.0, Mac Catalyst 13.1), dostęp 4 października 2026, https://developer.apple.com/documentation/uikit/uiimpactfeedbackgenerator/impactoccurred(intensity:). ↩

  60. Apple Developer Documentation, „prepare()” (UIFeedbackGenerator; iOS 10.0), dostęp 4 października 2026, https://developer.apple.com/documentation/uikit/uifeedbackgenerator/prepare(). ↩↩

  61. Apple Developer Documentation, „sensoryFeedback(:trigger:)” i „SensoryFeedback” (SwiftUI; iOS 17.0, iPadOS 17.0, Mac Catalyst 17.0, visionOS 26.0; impact(weight:intensity:)), dostęp 4 października 2026, https://developer.apple.com/documentation/swiftui/view/sensoryfeedback(:trigger:) i 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), dostęp 4 października 2026, https://developer.apple.com/documentation/touchcontroller. ↩↩↩↩

  63. Apple Developer Documentation, „TCDirectionPad” (iOS 26.0, iPadOS 26.0, Mac Catalyst 26.0), dostęp 4 października 2026, https://developer.apple.com/documentation/touchcontroller/tcdirectionpad. ↩

  64. Apple Developer Documentation, „GCVirtualController” (Game Controller; iOS 15.0, iPadOS 15.0, Mac Catalyst 15.0), dostęp 4 października 2026, https://developer.apple.com/documentation/gamecontroller/gcvirtualcontroller. ↩

  65. pret, pokeemerald/src/field_control_avatar.c (TryDoorWarp, wywoływane z komórką przed graczem, gdy przytrzymany kierunek zgadza się z kierunkiem, w który gracz jest zwrócony, i wykonujące warp tylko wtedy, gdy tym kierunkiem jest północ, a komórka jest warpem drzwi), dostęp 4 października 2026, https://github.com/pret/pokeemerald/blob/master/src/field_control_avatar.c. ↩

Powiązane artykuły

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 min czytania

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 min czytania

Przygotowanie aplikacji na iPhone Duo: przykład krok po kroku

Jak przygotować i wysłać aplikację na iPhone Duo: znacznik SDK, który włącza nowy układ, prawdziwa aplikacja w każdej po…

47 min czytania