Ако вече сте програмирали за ретро устройства, вероятно сте забелязали, че математическите възможности на ретро процесорите не са много богати и нашите NES или ZX-Spectrum могат да извършват само няколко математически операции: събиране и изваждане. В същото време нашият компютър работи само с числа от един тип: цели числа. Поддръжката за реални числа и операции с плаваща запетая се появи в компютрите много по-късно, така че трябва да приемем това и да го заобиколим.

Работата в рамките на тези математически ограничения може да бъде трудна, но също така ще ви направи по-креативен програмист. Всичко, от което се нуждаете, са правилните техники, като месар и неговото острие. Нека разгледаме как можем да създадем механика на движение в ретро ограничения.

Нарежете пържолите — движение

Ограничените математически възможности на ретро процесорите често са достатъчно добри за прости задачи, като движението на герой. Много е просто: трябва да добавим 1 към текущата стойност на координатата X или да извадим едно от нея:

На език C това може да изглежда така:

неподписан знак x; // един байт за x while (1) { x=x+1; }

Дефинирах променливата x като байт. Тъй като говорим за NES/Famicom, това ще ни е достатъчно, защото ширината на нашия екран е 256 пиксела, което е равно на максималната стойност на байт: 255 (0 също е стойност). След това увеличавам всеки кадър x с 1. Нека да видим как изглеждат стойностите x за първите пет кадъра:

Скоростта на опресняване на рамката на нашата конзола е 60 кадъра в секунда (NTSC), което означава, че Бил може да се премества с 60 пиксела в секунда напред или назад.

Но това може да не е достатъчно. Ами ако трябва да добавим не един пиксел на кадър, а половин или една десета от пиксела?

Тук скоростта на движение на боеца зависи от това колко дълго сте държали десния или левия бутон. Колкото по-дълго го държите, толкова по-бързо лети изтребителят. Понякога скоростта на полета може да бъде много по-бавна от един пиксел на кадър.

Очевидно за това трябва да използваме реални числа. Но, както вече знаем, нашата конзола може да работи само с цели числа. Така че трябва да изневерим малко.

Фино нарязване — ускорение

Нека дефинираме два байта за координатата x вместо един. Това означава, че нямаме 256 стойности, а два пъти за 256 стойности.

Ще използваме старшия байт като цяло число на координатата, а малкия байт като нейната дробна част.

докато (1) { x=x+1; }

Какво се промени? Абсолютно нищо. Ниската част от нашата стойност все още се увеличава с 1. Но какво се случва с високата? Всички тези пет кадъра все още са нула. Но нещо ще се случи, ако повторим събирането 256 пъти?

Кадър 1: x = x + 1; // x високо = 0, x ниско = 1

Кадър 2: x = x + 1; // x високо = 0, x ниско = 2

Кадър 3: x = x + 1; // x високо = 0, x ниско = 3

Кадър 4: x = x + 1; // x високо = 0, x ниско = 4

Кадър 5: x = x + 1; // x високо = 0, x ниско = 5

Кадър 255: x = x + 1; // x високо = 0, x ниско = 255

Какво се случи с нашата стойност в кадър 256? Лесно е, ниският байт достигна своя максимум (или препълни), след което падна до нула, а високият байт се увеличи с 1. Ако направим това още 256 пъти, се случва същото — ниската част от стойността ще бъде нулирана на нула и високата част ще се увеличи отново. Ще използваме само старшия байт на нашата стойност като x координата. Тоест нашата координата x се увеличава с 1 на всеки 256 кадъра. С други думи, разделихме рамката на 256 части.

И така, за да преместим нашия герой на „половин“ пиксел в рамка, трябва да добавим не 1, а 128 (256/2 или половината от рамката):

unsigned int x; // два байта за x while (1) { }

За да опростя разбирането, ще покажа как това се случва с калкулатор.

Както можете да видите, всяко щракване върху бутона (всеки кадър) добавя 128 към текущата стойност (80 в шестнадесетичен). И всеки път, когато ниският байт се препълни (всеки втори кадър), високият байт се увеличава. Ще използваме този байт като цяло число, за да позиционираме нашия спрайт. Що се отнася до ниския байт, ние не се нуждаем от него.

Това означава, че сме постигнали целта - нашият герой се движи със скорост от половин пиксел на кадър. Без магия, само математика.

Така че, ако искате да промените стойността на всеки 4 кадъра, просто разделете 256 на 4 и ще получите желаната стойност за увеличението: 64. Добавете 64 към вашата двубайтова стойност 64 и използвайте старшия байт като x координата. Сега нашият герой лети 4 пъти по-бавно от 60 пиксела в секунда.

Вътре в ядрото — ASM и C

Сега обаче има едно нещо. Ако използвате асемблер, решението изглежда малко по-различно. Например ZX Spectrum има 16-битови регистри, така че увеличаването на двубайтовото число е много лесно:

Всеки път, когато ниският 8-битов регистър C се препълни, ZX Spectrum автоматично увеличава високия регистър B:

Но NES няма 16-битови регистри, така че трябва да го предоставите сами:

Математика

Надяваме се, че тази проста математика ще направи работата ви много по-лесна. Този метод може да се използва навсякъде, където имате нужда от по-точни изчисления, като интерполация на интензитета на оцветяване, симулиране на инерция и изграждане на проста векторна графика (ако вашето ретро устройство може да си го позволи). Честита резба.

Алекс е разработчикът зад The Meating, нашата платформинг игра за минотавъри призраци. Той е запален месояден и няма говеждо с говеждо.

Първоначално публикувано на https://megacatstudios.com.