Реактивное введение в программирование Game Boy Advance (часть 5 из 8)

Вращающиеся фоны и прозрачность…



Оглавление


Часть 1: Инструментарий, основы, кнопки, таймеры
Часть 2: Пиксельные видеорежимы
Часть 3: Тайловые видеорежимы
Часть 4: Спрайты
Часть 5: Вращающиеся фоны и прозрачность
Часть 6: Прерывания, DMA
Часть 7: Звук (Direct Sound)
Часть 8: Сохранения

7. Вращающиеся фоны и полупрозрачность


Если побольше поэкспериментировать с вращающимися спрайтами и фонами, то можно обнаружить, что когда я в прошлой главе говорил о медленности программной эмуляции float в GBA, то это было скорее преуменьшением, чем преувеличением. Всего несколько вычислений синуса (и под «несколько» тут понимается даже такое небольшое число как «четыре») могут не вписываться в интервал VBlank на который полагались все примеры выше и приводить к порче изображения в связи с тем, что крайне не рекомендуется менять видеоадаптеру параметры картинки пока он рисует кадр. Наиболее разумный выход — перейти и в программе на вычисления с фиксированной запятой. Как я уже говорил это совсем нетрудно. Такие числа будем размещать в обычных целых числах, но надо держать в уме сколько бит у них отведено под дробную часть (назовём это точностью). Фактически при N бит дробной части (точности) мельчайшая часть отличная от нуля в таком числе равна ровно 1 / 2^N. Отбросить дробную часть можно просто сдвигом на N бит вправо. Привести же обычное целое к такому числу можно наоборот сдвинув его на N бит влево. При сложении/вычитании чисел одинаковой точности никаких специальных действий дополнительно производить не нужно — результат будет числом такой же точности. При этом не имеет смысла складывать или вычитать числа разной точности. Перемножать же можно числа любой точности, даже разной — результат в любом случае получит точность равную сумме точностей исходных чисел. Поэтому чаще всего его тут же сдвигают вправо на число бит точности одного из множителей, чтобы привести результат к точности другого. Впредь, упоминая числа с фиксированным числом бит после запятой я буду описывать их так: fixed(X:Y), где X это число бит до запятой, а Y — число бит после запятой. Если число бит до запятой (целая часть) не играет роли или очевидна (особенно если работа идёт с типом int), то пишется только число бит после запятой, например: большинство регистров видеочипа с фиксированной запятой имеет точность fixed(:8).

Попробовав вращение и масштабирование спрайтов мы теперь готовы перейти к чуть более сложному вращению и масштабированию фонов, которые мы пропустили в главе про тайловые видеорежимы. Но сперва подготовим почву в виде функций по работе с числами с фиксированной запятой. Основная тяжесть при вычислении афинных матриц ложилась на вычисление синусов и косинусов. Одним из классических способов разгрузить такие вычисления — это огрубление их и сведение к выборке из таблицы заранее подготовленных значений. Сделаем такую таблицу и такие функции, разместив их в новом модуле (здесь мы впервые сделаем несколько модулей в проекте — до сих пор мы обходились одним единственным).
Создадим файл fixed.h:


#ifndef _FIXED_H
#define _FIXED_H

#include "gba_defs.h"

// Таблица с предварительно вычисленными значениями синусов углов от 0 до 255.
// Предполагается система измерения углов когда в полной окружности их ровно 256.
// Значения синусов хранятся в формате fixed(4:12) для точности.
extern const u16 sin_table[ 256 ];

// Синус угла ang. Угол предполагается в формате fixed[:8] и в системе 
// измерения углов, где в полной окружности (2*Pi радиан) содержится 256 градусов.
// Результат возвращается в формате fixed(:12). Целые углы возвращаются за одно
// быстрое извлечение из таблицы, промежуточные углы считаются интерполяцией
// между двумя соседними значениями с вовлечением двух умножений и сдвига.
inline int sinfx12( int ang )
{
	int res;
	int frac = ang & 255;
	ang >>= 8;
	if ( frac == 0 )
	{
		// Точная и быстрая выборка целого угла
		res = ((i16)(sin_table[ ang & 255 ]));
	}
	else
	{
		// Интерполяция между соседями
		int r1 = ((i16)(sin_table[ ang & 255 ]));
		int r2 = ((i16)(sin_table[ (ang + 1) & 255 ]));
		res = (r1 * (256 - frac) + r2 * (frac)) >> 8;
	};
	return res;
};

// Косинус угла ang.
inline int cosfx12( int ang )
{
	return sinfx12( ang + (64 << 8) );
};

#endif // _FIXED_H


Обратите внимание, что выбрав систему измерения углов в которой в окружности 256 градусов мы облегчили себе реализацию периодичности синусов. Помните так же, что функции ожидают параметр в формате fixed(:8), а возвращают результат в формате fixed(:12).
Этому заголовочному файлу еще нужен файл с телом модуля для содержимого таблицы. Создадим рядом файл fixed.cpp:


#include "fixed.h"

const u16 sin_table[ 256 ] = {
0x0000, 0x0064, 0x00c8, 0x012d, 0x0191, 0x01f5, 0x0259, 0x02bc, 
0x031f, 0x0381, 0x03e3, 0x0444, 0x04a5, 0x0504, 0x0563, 0x05c2, 
0x061f, 0x067b, 0x06d7, 0x0731, 0x078a, 0x07e2, 0x0839, 0x088f, 
0x08e3, 0x0936, 0x0987, 0x09d7, 0x0a26, 0x0a73, 0x0abe, 0x0b08, 
0x0b50, 0x0b96, 0x0bda, 0x0c1d, 0x0c5e, 0x0c9d, 0x0cd9, 0x0d14, 
0x0d4d, 0x0d84, 0x0db9, 0x0deb, 0x0e1c, 0x0e4a, 0x0e76, 0x0ea0, 
0x0ec8, 0x0eed, 0x0f10, 0x0f31, 0x0f4f, 0x0f6b, 0x0f85, 0x0f9c, 
0x0fb1, 0x0fc3, 0x0fd3, 0x0fe1, 0x0fec, 0x0ff4, 0x0ffb, 0x0ffe, 
0x1000, 0x0ffe, 0x0ffb, 0x0ff4, 0x0fec, 0x0fe1, 0x0fd3, 0x0fc3, 
0x0fb1, 0x0f9c, 0x0f85, 0x0f6b, 0x0f4f, 0x0f31, 0x0f10, 0x0eed, 
0x0ec8, 0x0ea0, 0x0e76, 0x0e4a, 0x0e1c, 0x0deb, 0x0db9, 0x0d84, 
0x0d4d, 0x0d14, 0x0cd9, 0x0c9d, 0x0c5e, 0x0c1d, 0x0bda, 0x0b96, 
0x0b50, 0x0b08, 0x0abe, 0x0a73, 0x0a26, 0x09d7, 0x0987, 0x0936, 
0x08e3, 0x088f, 0x0839, 0x07e2, 0x078a, 0x0731, 0x06d7, 0x067b, 
0x061f, 0x05c2, 0x0563, 0x0504, 0x04a5, 0x0444, 0x03e3, 0x0381, 
0x031f, 0x02bc, 0x0259, 0x01f5, 0x0191, 0x012d, 0x00c8, 0x0064, 
0x0000, 0xff9c, 0xff38, 0xfed3, 0xfe6f, 0xfe0b, 0xfda7, 0xfd44, 
0xfce1, 0xfc7f, 0xfc1d, 0xfbbc, 0xfb5b, 0xfafc, 0xfa9d, 0xfa3e, 
0xf9e1, 0xf985, 0xf929, 0xf8cf, 0xf876, 0xf81e, 0xf7c7, 0xf771, 
0xf71d, 0xf6ca, 0xf679, 0xf629, 0xf5da, 0xf58d, 0xf542, 0xf4f8, 
0xf4b0, 0xf46a, 0xf426, 0xf3e3, 0xf3a2, 0xf363, 0xf327, 0xf2ec, 
0xf2b3, 0xf27c, 0xf247, 0xf215, 0xf1e4, 0xf1b6, 0xf18a, 0xf160, 
0xf138, 0xf113, 0xf0f0, 0xf0cf, 0xf0b1, 0xf095, 0xf07b, 0xf064, 
0xf04f, 0xf03d, 0xf02d, 0xf01f, 0xf014, 0xf00c, 0xf005, 0xf002, 
0xf000, 0xf002, 0xf005, 0xf00c, 0xf014, 0xf01f, 0xf02d, 0xf03d, 
0xf04f, 0xf064, 0xf07b, 0xf095, 0xf0b1, 0xf0cf, 0xf0f0, 0xf113, 
0xf138, 0xf160, 0xf18a, 0xf1b6, 0xf1e4, 0xf215, 0xf247, 0xf27c, 
0xf2b3, 0xf2ec, 0xf327, 0xf363, 0xf3a2, 0xf3e3, 0xf426, 0xf46a, 
0xf4b0, 0xf4f8, 0xf542, 0xf58d, 0xf5da, 0xf629, 0xf679, 0xf6ca, 
0xf71d, 0xf771, 0xf7c7, 0xf81e, 0xf876, 0xf8cf, 0xf929, 0xf985, 
0xf9e1, 0xfa3e, 0xfa9d, 0xfafc, 0xfb5b, 0xfbbc, 0xfc1d, 0xfc7f,
0xfce1, 0xfd44, 0xfda7, 0xfe0b, 0xfe6f, 0xfed3, 0xff38, 0xff9c };


Для подключения этого файла нам надо будет дополнить сборочный батник build_07_rot_bg.bat в строке с MODULES следующим образом:

@set MODULES=fixed.cpp
@set PROGNAME=07_rot_bg_alpha_spr

@call build_gba.bat

(файл с основной программой будет называться 07_rot_bg_alpha_spr.cpp).

7.1 Вращение и масштабирование фонов


Допишем в gba_defs.h описания регистров по работе с вращающимися и масштабируемыми фонами:


// *** Регистры горизонтального и вертикального смещений вращающихся фонов,
// а так же коэффициентов матриц афинных преобразований для них.
// Работают только для BG2 и BG3 в режиме вращения/масштабирования.
// Коэффиценты афинной матрицы BG2 (PA, PB, PC, PD):
#define REG_BG2PA   (*((volatile i16 *) 0x4000020))
#define REG_BG2PB   (*((volatile i16 *) 0x4000022))
#define REG_BG2PC   (*((volatile i16 *) 0x4000024))
#define REG_BG2PD   (*((volatile i16 *) 0x4000026))
// Координаты BG2 (PX, PY):
#define REG_BG2X    (*((volatile u32 *) 0x4000028))
#define REG_BG2Y    (*((volatile u32 *) 0x400002C))
// Коэффициенты афинной матрицы BG3 (PA, PB, PC, PD):
#define REG_BG3PA   (*((volatile i16 *) 0x4000030))
#define REG_BG3PB   (*((volatile i16 *) 0x4000032))
#define REG_BG3PC   (*((volatile i16 *) 0x4000034))
#define REG_BG3PD   (*((volatile i16 *) 0x4000036))
// Координаты BG3 (PX, PY):
#define REG_BG3X    (*((volatile u32 *) 0x4000038))
#define REG_BG3Y    (*((volatile u32 *) 0x400003C))


Как вы помните в GBA в видережиме 0x01 фон BG2 переходит в режим вращающегося/масштабируемого, а в режиме 0x02 таковыми становятся два фона — BG2 и BG3. При этом регистры, управляющие прокруткой этих фонов в тайловых режимах уже перестают играть какую либо роль — и прокрутку и вращение/масштабирование надо делать с помощью шести параметров с фиксированной запятой (fixed(:8)) — PA, PB, PC, PD (они имеют формат fixed(8:8)) и PX, PY (fixed(24:8)). В отличие от вращающихся спрайтов, где видеоадаптер сам поддерживал внутреннюю систему координат так, чтобы спрайт вращался вокруг своего центра на экране матрицей 2x2, здесь мы имеем более тонкий контроль над осуществляемыми преобразованиями. Фактически эти 6 параметров можно объединить для математической простоты в одну матрицу размерности 3x3:

| PA, PB, PX |
| PC, PD, PY |
|  0,  0,  1 |

Чем, опять таки, тут занимается видеоадаптер — проходя по пикселям экрана он преобразует их координаты (экранные) посредством умножения (как вектора) на эту матрицу. Если пиксель на экране имеет координаты (scr_x, scr_y), то получающиеся преобразованием координаты (bg_x, bg_y) будут вычислятся следующим образом:

bg_x = PA * scr_x + PB * scr_y + PX
bg_y = PC * scr_x + PD * scr_y + PY

После чего цветом текущего пикселя становится цвет по этим координатам из заднего фона — координаты в нём соответствуют тому как если бы фон без искажений и вращений находился бы в левом-верхнем углу экрана своим левым-верхним же углом. Так, например если PA=PD=1, а PB=PC=PX=PY=0, то фон и будет находится в левом-верхнем углу экрана без искажений. Не забывайте, что все эти параметры имеют формат fixed(:8), поэтому если вы захотите этот фон сдвигать, то PX и PY надо увеличивать на 256 (как целое число) для сдвига на 1 пиксель. Тут есть еще одно важное замечание — если получающиеся промежуточные фоновые координаты (bg_x, bg_y) выходят за границы фона, то возможно два исхода ситуации в зависимости от значения бита BG_ROT_OVERLAP в регистре управления фоном. Если он равен 0, то возвращается прозрачный цвет 0, т.е. фон обрезается по своим краям и по сути начинает играть роль спрайта огромного размера. Если же бит BG_ROT_OVERLAP включен, то у координат просто обрезаются верхние биты, так чтобы они влезли ровно в размеры заднего фона (недаром эти размеры всегда кратны степени двойки), что выглядит как режим бесконечного замощения плиткой из фонов.

Математика того как вращающиеся фоны собственно вращать, масштабировать относительно произвольного центра и сдвигать как угодно может быть несколько запутанной, поэтому я приведу уже готовые формулы как вычислять все эти параметры для поворота фона на угол alpha относительно центра (внутри самого фона) с координатами (cx, cy), масштаба по осям с коэффициентами x_scale, y_scale (относительно этого же центра фона) так, чтобы этот центр в итоге оказался в координатах (sx, sy) экрана:


PA = x_scale * cos( alpha )
PB = x_scale * sin( alpha )
PC = -y_scale * sin( alpha )
PD = y_scale * cos( alpha )
PX = PA * sx + PB * sy + cx
PY = PC * sx + PD * sy + cy


При этом, как и в предыдущей главе про спрайты — x_scale и y_scale это на самом деле делители размера, а поворот осуществляется по часовой стрелке.

7.2 Полупрозрачность


В GBA есть ряд регистров для управления пиксельными «спец-эффектами». Полупрозрачность реализуюется с помощью следующих регистров и их флагов (опять таки — описание из gba_defs.h):


// *** Регистр управления альфа-прозрачностью, затенением и просветлением
#define REG_BLENDCNT        (*((volatile u16 *) 0x4000050))
// Флаги:
// Биты 0-5 - флаги что является источником для смешивания:
// BG0-BG3 - фоны, SPR - спрайты, BACK - задний фон.
#define BLEND_SRC_BG0       0x01
#define BLEND_SRC_BG1       0x02
#define BLEND_SRC_BG2       0x04
#define BLEND_SRC_BG3       0x08
#define BLEND_SRC_SPR       0x10
#define BLEND_SRC_BACK      0x20
// Биты 6-7 режим смешивания: OFF - выкл, BLEND - полупрозрачность, 
// LIGHT - просветление, DARK - затемнение.
#define BLEND_MODE_OFF      0x0000
#define BLEND_MODE_ALPHA    0x0040
#define BLEND_MODE_LIGHT    0x0080
#define BLEND_MODE_DARK     0x00C0
// Биты 8-13 - флаги что является целью для смешивания:
// BG0-BG3 - фоны, SPR - спрайты, BACK - задний фон.
#define BLEND_DEST_BG0      0x0100
#define BLEND_DEST_BG1      0x0200
#define BLEND_DEST_BG2      0x0400
#define BLEND_DEST_BG3      0x0800
#define BLEND_DEST_SPR      0x1000
#define BLEND_DEST_BACK     0x2000

// *** Регистр управления коэффициентами альфа-прозрачности
#define REG_BLENDALPHA      (*((volatile u16 *) 0x4000052))
#define BLEND_SRC_A( x )    (x)
#define BLEND_DEST_A( x )   ((x) << 8)
// *** Регистр управления величиной затенения/просветления
#define REG_BLENDCOLY       (*((volatile u16 *) 0x4000054))


Для полупрозрачности надо включить режим альфа-блендинга (BLEND_MODE_ALPHA) и выставить то, что надо сделать полупрозрачным как источник (SRC) для смешивания, а то что лежит непосредственно «за ним» на экране — как цель (DEST) для смешивания. Здесь крайне важен порядок вывода вещей на экран (PRIORITY) — он обязан соответствовать тому что происходит — смешивание делается только если пиксель источника находится ровно над пикселем цели, без прочих заслонений и пересечений. Флаг BLEND_SRC_SPR является несколько избыточным — какое бы значение у него не было — если в самом спрайте включен режим ATTR0_MODE_TRANSPARENT, то такой спрайт в любом случае будет являться источником для смешивания. Таким образом делать полупрозрачные спрайты лучше именно флагами в спрайтах. Однако в процессе отображения картинки видеоконтроллер сперва как бы «сплавляет» спрайты в один слой пикселей и только потом уже проводит операции по полупрозрачности с ним как с остальными фонами, поэтому невозможно сделать полупрозрачные спрайты на фоне других спрайтов. В разных документациях, которые я читаю, информация о случае, когда включены сразу несколько источников и/или целей смешивания иногда неполны, а иногда несколько противоречивы, поэтому самый общий и полный знаменатель я пока выражу так: если (с учетом полностью прозрачных (нулевых) пикселей — они не считаются препятствиями) видеочип определяет в пикселе экрана, что первый видимый (самый ближний) слой является источником смешивания, а следующий за ним слой — целью смешивания, то он производит смешивание (один раз — только для них). Во всех остальных случаях рисуется только пиксель самого верхнего слоя. Таким образом нельзя сделать 3 просвечивающих сквозь друг друга слоя — смешать можно только 2, при этом источник должен быть сверху, цель должна быть снизу и между ними ничего не должно быть. Именно в связи с этими правилами в регистре REG_BLENDCNT есть флаги для «самого заднего фона» — (BACK) — его может иметь смысл выставлять как цель, если фоны и спрайты не покрывают всю площадь экрана — тогда полупрозрачность на ничем не занятых местах может вести себя неправильно.
Смешивание цветов происходит в модели RGB с помощью весовых коэффициентов заданных в регистре REG_BLENDALPHA. Значение в первом байте (SRC_A) определяет вес источника, а значение во втором байте (DEST_A) — вес цели. Значащими в обоих байтах являются всего 5 бит при этом значение 16 является «единицей» — оно соответствует неизмененному значению цвету приёмника или источника. Например, чтобы задать режим смешивания как среднее арифметическое цветов источника и цели, то следует оба коэффициента задать равными 8. Сумма SRC_A и DEST_A может отличаться от 16, это произведет некоторый «затемняющий» или «просветляющий» эффект.
Режимы смешивания BLEND_MODE_LIGHT и BLEND_MODE_DARK делают осветление или затемнение слоёв помеченных как источники на коэффициент указанный в регистре REG_BLENDCOLY. При этом так же значащие биты в нём — только первые 5 и значение 16 считается «единицей» или, точнее — «максимумом» затенения/просветления, все значения выше отобразят чисто черный или чисто белый цвета на экране.

Итак, напишем программу в которой мы сможем свободно вращать и масштабировать один слой на фоне которого в центре экрана можно будет пронаблюдать циклически меняющий коэффициент альфа-смешивания квадратный спрайт (07_rot_bg_alpha_spr.cpp):


#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <math.h>

#include "gba_defs.h"
#include "fixed.h"

// Начало тайловых данных для фона
#define TILE_DATA   ((u16*) (VID_RAM_START + 0x0000) )
// Начало тайловой карты
#define TILE_MAP2   ((u16*) BG_TILE_MAP_ADDR( 16 ) )

int main(void)
{
	// Инициализируем палитру так, чтобы 8-битный индекс цвета 
	// совпадал с цветовой маской BBGGGRRR
	for ( unsigned int i = 0; i < 256; i++ )
	{
		BGR_PALETTE[ i ] = RGB( (i & 7) << 2, (i & 56) >> 1, (i & 192) >> 3 );
		SPR_PALETTE[ i ] = RGB( (i & 7) << 2, (i & 56) >> 1, (i & 192) >> 3 );
	};

	// Очистим видеопамять
	memset( (void*) VID_RAM_START, 0, VID_RAM_SIZE );

	// Включаем MODE_2 и BG2 (с поворотами и масштабированием)
	REG_DISPCNT = MODE_2 | BG2_ENABLE | SPR_ENABLE;

	// Параметры фона BG2
	REG_BG2CNT =	BG_PRIORITY( 2 ) | BG_TILE_DATA_AT_0000 | BG_COLOR_256 | 
			BG_TILE_MAP_AT( 16 ) | BG_ROT_SIZE_128x128;

	// Заполним 256 тайлов фона и спрайтов специальным образом
	// (помним, что запись в видепамять должна производится
	// минимум 2-байтными порциями, поэтому циклы подчинены
	// этой логике)
	for ( int i = 0; i < 256; i++ )
	{
		// сперва зальём весь тайл белым цветом
		u16 *bg_tile_base = TILE_DATA + i * 8 * 4;
		u16 *spr_tile_base = SPR_TILES + i * 8 * 4;
		for ( int j = 0; j < 8 * 4; j++ )
		{
			bg_tile_base[ j ] = (255 << 8) + (255);
			spr_tile_base[ j ] = (255 << 8) + (255);
		};
		// выведем в верхней половине столько зеленых пикселей
		// чему равен первый разряд индекса как 16-чного числа
		int cnt = i & 15;
		for ( int j = 0; j < cnt; j++ )
		{
			bg_tile_base[ j ] = (255 << 8) + (48 + j);
			spr_tile_base[ j ] = (255 << 8) + (48 + j);
		};
		// выведем в нижней половине столько красных пикселей
		// чему равен второй разряд индекса как 16-чного числа
		bg_tile_base = TILE_DATA + i * 8 * 4 + 16;
		spr_tile_base = SPR_TILES + i * 8 * 4 + 16;
		cnt = (i >> 4) & 15;
		for ( int j = 0; j < cnt; j++ )
		{
			bg_tile_base[ j ] = (255 << 8) + (192 + j);
			spr_tile_base[ j ] = (255 << 8) + (192 + j);
		};
	};
	// Заполним тайловую карту нарастающими индексами
	// (для вращающихся фонов ячейки тайловой карты есть байты-индексы)
	int d = 0;
	for ( int y = 0; y < 16; y++ )
	{
		for ( int x = 0; x < 8; x++ )
		{
			TILE_MAP2[ x + 8 * y ] = ((d + 1) << 8) + d;
			d += 2;
		};
	};

	// Уберем все спрайты за экран, отключим их и придадим
	// им минимальные размеры
	for ( int i = 0; i < 128; i++ )
	{
		SPR_BUFFER[ i ].attr0 = ATTR0_Y( -16 ) | ATTR0_DISABLED | ATTR0_SHAPE_SQ;
		SPR_BUFFER[ i ].attr1 = ATTR1_X( -16 ) | ATTR1_SQ_8x8;
		SPR_BUFFER[ i ].attr2 = ATTR2_PRIORITY( 1 );
	};

	// Пусть первый спрайт будет размером 64x64 и в центре экрана
	SPR_BUFFER[ 0 ].attr0 = ATTR0_Y( 64 ) | ATTR0_MODE_TRANSPARENT 
				| ATTR0_COLOR_256 | ATTR0_SHAPE_SQ;
	SPR_BUFFER[ 0 ].attr1 = ATTR1_X( 64 ) | ATTR1_SQ_64x64;
	SPR_BUFFER[ 0 ].attr2 = ATTR2_TILE( 0 << 1 ) | ATTR2_PRIORITY( 1 );

	// Включим режим полупрозрачности MODE_ALPHA с целями - фоном BG2 и задним фоном
	REG_BLENDCNT = BLEND_MODE_ALPHA | BLEND_DEST_BG2 | BLEND_DEST_BACK;

	// "Бегущий" коэффициент альфа-прозрачности...
	int blend_coef = 0;
	// Координаты центра вращения фона на экране (числа fixed(:8))
	int bk_x = 0, bk_y = 0;
	// Угол вращения фона (число fixed(:8))
	int bk_angle = 0;
	// Масштабные коэффициенты по каждой из осей (числа fixed(:8))
	int bk_xscale = 0x0100;
	int bk_yscale = 0x0100;
	// Бесконечный цикл
	while ( true )
	{
		// Дождёмся выхода в VBLANK
		while ( REG_VCOUNT < 160 );

		// Считаем текущее состояние кнопок и сразу
		// инвертируем биты, чтобы нажатые стали единицами.
		int keys = ~REG_KEYS;

		// Реагируем на нажатые кнопки.
		int stepc = 0x0100; // шаг движения (число fixed(:8))
		int stepa = 0x0010; // шаг вращения (число fixed(:8))
		// Если кнопка B зажата - ускоряем движения и повороты
		if ( keys & KEY_B )
		{
			stepc *= 8;
			stepa *= 8;
		};
		// Если зажата A, то DPAD управляет масштабами, иначе - позицией.
		if ( keys & KEY_A )
		{
			// Управляем масштабами
			if ( keys & KEY_LEFT )
				bk_xscale -= stepa >> 4;
			if ( keys & KEY_RIGHT )
				bk_xscale += stepa >> 4;
			if ( keys & KEY_UP )
				bk_yscale -= stepa >> 4;
			if ( keys & KEY_DOWN )
				bk_yscale += stepa >> 4;
		}
		else
		{
			// Управляем позицией
			if ( keys & KEY_LEFT )
				bk_x -= stepc;
			if ( keys & KEY_RIGHT )
				bk_x += stepc;
			if ( keys & KEY_UP )
				bk_y -= stepc;
			if ( keys & KEY_DOWN )
				bk_y += stepc;
		};
		// По нажатию L или R изменяем угол вращения
		if ( keys & KEY_L )
			bk_angle -= stepa;
		if ( keys & KEY_R )
			bk_angle += stepa;

		// Обновляем коэффициенты полупрозрачности так чтобы они
		// циклически пробегали от 0 до 100% для спрайта:
		int bc = (blend_coef >> 4) % 17;
		REG_BLENDALPHA = BLEND_SRC_A( bc ) | BLEND_DEST_A( 16 - bc );
		blend_coef++;

		// Обновляем параметры фона

		// Предварительно вычисляем синус и косинус угла.
		// Помним, что функции *fx12 возвращают результат в формате fixed(:12).
		int bk_sina = sinfx12( bk_angle );
		int bk_cosa = cosfx12( bk_angle );
		// Вычисляем PA, PB, PC и PD. Т.к. fixed(:8) умножаем на fixed(:12), то
		// нужно сдвинуть результат вправо на 8 бит чтобы он стал fixed(:12).
		// Здесь мы выберем 12 бит после запятой от синусов/косинусов для точности.
		int pa = ( bk_xscale * bk_cosa ) >> 8;
		int pb = ( bk_xscale * bk_sina ) >> 8;
		int pc = ( -bk_yscale * bk_sina ) >> 8;
		int pd = ( bk_yscale * bk_cosa ) >> 8;
		// cx и cy - координаты точки на фоне, которая станет его центром и
		// осью вращения. Для фона 128x128 выбираем геометрический центр - (64,64)
		// Сдвигаем влево на 8 бит, чтобы привести к формату fixed(:8).
		int cx = (64 << 8);
		int cy = (64 << 8);
		// sx и sy - матричные параметры сдвига. Фактически bk_x и bk_y являются
		// экранными координатами где будет находится центр фона (точка вращения).
		// Но чтобы создавалось впечатление движения фона в направлении нажатия 
		// стрелок DPAD-а нужно взять их со знаком минус.
		int sx = -bk_x;
		int sy = -bk_y;
		// Вычисляем PX и PY. Т.к. fixed(:12) умножаются на fixed(:8), то результат
		// надо сдвинуть вправо на 12 бит, чтобы он стал fixed(:8). При этом эту
		// операцию можно выполнить один раз для промежуточной суммы, которая получается 
		// в формате fixed(:20).
		int px = ((pa * sx + pb * sy) >> 12) + cx;
		int py = ((pc * sx + pd * sy) >> 12) + cy;

		// Применяем вычисленные коэффициенты к матрице преобразований фона
		// Т.к. параметры pa-pb имеют формат fixed(:12), то их нужно сдвинуть вправо
		// на 4 бита, чтобы привести к формату fixed(:8).
		REG_BG2PA = pa >> 4;
		REG_BG2PB = pb >> 4;
		REG_BG2PC = pc >> 4;
		REG_BG2PD = pd >> 4;
		REG_BG2X = px;
		REG_BG2Y = py;

		// Доводим синхронизацию по VLANK до конца.
		while ( REG_VCOUNT >= 160 );
	};

	return 0;
}


В следующей главе мы научимся обработки прерываний, чтобы далее перейти к звукам.

Продолжение...

0 комментариев

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.