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

Спрайты…



Оглавление


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

6. Спрайты


Видеоадаптер GBA поддерживает до 128 аппаратных спрайтов (от номерами от 0 до 127), размерами от 8x8 (1 тайл) до 64x64 пикселей (8x8 тайлов). Размеры по каждой из осей могут быть 8, 16, 32 или 64 пикселя. Спрайты могут вращаться и масштабироваться. Как и в видеоадаптерах консолей NES или SNES существует ограничение того сколько спрайтов может отобразится одновременно на одной строке дисплея — при превышении лимита какие то из них могут пропадать. Лимит этот плавающий и зависит от многих факторов — от размеров спрайтов и того включены ли у них повороты и масштабирования до некоторых бит в регистре управления видеоконтроллером. В самом оптимистичном случае — 128 простых спрайтов 8x8 пикселей могут быть отображены одновременно в одной строке.
Каждый спрайт как и фон имеет параметр — приоритет отрисовки — число от 0 до 3. Спрайты с меньшим значением приоритета рисуются поверх спрайтов с большим значением приоритета. Так же спрайты рисуются поверх фонов с равным или большим значением приоритета. У спрайтов с одинаковым приоритетом поверху рисуются спрайты с меньшим порядковым номером.
Кроме того руководство от GBATEK содержит замечание, что даже неотображаемые (offscreen) спрайты с порядком отрисовки меньше, чем отображаемые всё равно тратят ресурсы видеоадаптера при выводе каждой строки дисплея, поэтому хорошей стратегией является размещение рисуемых спрайтов в первых ячейки таблицы спрайтов (OAM) и/или выставление неотображаемым спрайтам размера 8x8 пикселей без вращения и масштабирования.

Тайловые данные (tile data) спрайтов располагаются в верхних 32Кб видеопамяти с адреса VID_RAM_START + 0x10000. Таким образом можно сказать, что из 96Кб видеопамяти первые 64Кб отведены под тайлы и карты фонов, а верхние 32Кб — под тайлы спрайтов. Спрайты не могут ссылаться на тайлы размещенные в первых 64Кб видеопамяти, а фоны не могут ссылаться на тайлы в последних 32Кб видепамяти. Кроме того индексы тайлов в спрайтах отличаются от таковых в фонах — числовое значение индекса всегда указывает на ячейку памяти VID_RAM_START + 65536 + 32 * index. То есть неважно какой режим цветности у тайлов — 256 или 16 цветов — индексы тайлов в спрайтах указаны как бы для 16-цветной модели, как будто тайловые данные всегда являются массивом из 32-байтных значений. Это приводит к тому, что для 256-цветных тайлов спрайтов надо указывать только четные номера индексов — 0, 2, 4 и т. д. Индексы так же как и у фонов 10-битные, т.е. принимают значение от 0 до 1023, что как раз позволяет полностью охватить 32Кб памяти спрайтовых тайлов, если они будут 16-цветными или 512 256-цветных тайлов с пропусками нечетных индексов.
Спрайты можно включить и в пиксельных видеорежимах, но в них данные экранных областей вылазят за пределы первых 64Кб видепамяти, поэтому у тайловых данных «откушена» ровно первая половина — индексы спрайтовых тайлов должны начинаться с 512.
Так же как и у фоновых спрайтов, цвета спрайтовых тайлов это 8-битные индексы в палитре, но палитра у спрайтов уже своя и находится по адресу SPR_PAL_RAM_START (0x05000200). Так же в случае использования 16-цветных тайлов (4 бита на пиксель) в самих тайлах хранятся нижние 4 бита палитрового индекса, а верхние будут браться из описания спрайта, использующего тайл.

На то как компонуются из тайлов многотайловые спрайты влияет флаг MODE_LINEAR_SPRITES из регистра REG_DISPCNT. В самих спрайтах всегда указывается индекс левого-верхнего тайла. Если бит MODE_LINEAR_SPRITIES в REG_DISPCNT включен, то остальные тайлы получают линейно растущие индексы слева-направо, сверху-вниз. При этом у 16-цветного спрайта индексы тайлов будут увеличиваться на единицу, а у 256-цветного — на двойку. Таким образом спрайт из 16x16 пикселей (2x2 тайла) с заданным индексом 16 будет скомпонован следующим образом:

16 цветов     256 цветов
+--+--+       +--+--+
|16|17|       |16|18|
+--+--+       +--+--+
|18|19|       |20|22|
+--+--+       +--+--+

В случае же, если бит MODE_LINEAR_SPRITIES в REG_DISPCNT отключен тайловые данные трактуются как двумерный массив тайлов размерами 32x32 16-цветных тайла или 16x32 256-цветных тайла (в обоих случаях начало каждой следующей строки тайлов отстоит от начала предыдущей на 8*4*32=1024 байта в видеопамяти или, что тоже самое, на 32 индекса). Тогда индексы тайлов будут браться последовательно в каждой строке многотайлового спрайта, но новая строка будет «съезжать» на 32 (для любой из двух цветностей) от начала предыдущей. Таким образом спрайт 2x2 тайла получит следующую раскладку в 2D-режиме:

16 цветов     256 цветов
+--+--+       +--+--+
|16|17|       |16|18|
+--+--+       +--+--+
|48|49|       |48|50|
+--+--+       +--+--+

Описания спрайтов располагаются в следующем за видеопамятью сегментом ОЗУ GBA — OAM (Object Attribute Memory, память атрибутов объектов). Этот сегмент начинается с адреса OAM_RAM_START и содержит всего 1Кб (1024 байт) памяти — по 8 байт на каждый спрайт. Обычно видеоконтроллер плотно блокирует доступ к OAM во время всего интервала VDraw, но если включить флаг MODE_HBLANK_IS_FREE в регистре REG_DISPCNT, то становится возможным изменять таблицу спрайтов прямо во время отрисовки кадра в короткие интервалы HBlank, однако это несколько уменьшает количество одновременно отображаемых на одной строке спрайтов (видеоадаптеру остаётся 954 рабочих циклов на каждую строку вместо 1210 по умолчанию).
Из восьми байт каждой ячейки в таблице OAM только первые шесть составляют три 16-битных целочисленных значения, относящихся к самому спрайту. Эти значения содержат в себе такую сборную солянку из битовых полей и флагов каждое, что затруднительно дать им осмысленные названия, поэтому мы будем называть их attr0, attr1 и attr2. Последнее же 16-битное число в каждом спрайте (attr3) используется чтобы хранить 32 матрицы 2x2 из чисел с фиксированной запятой — эти матрицы образуют массив коэффициентов (A, B, C, D) для вращения и масштабирования спрайтов. Образуют они его довольно линейно — поля attr3 из первых четырёх спрайтов образуют по порядку коэффиценты первой матрицы (A0, B0, C0, D0), следующие — второй (A1, B1, C1, D1) и так далее.
Выразим накопленные знания в gba_defs.h и перейдем к описанию битовых полей и флагов из OAM_Entry:

// Адрес палитры, используемой спрайтами (256 ячеек по 2 байта RGB)
#define SPR_PALETTE ((u16 *) SPR_PAL_RAM_START)
// Начало спрайтовых тайлов
#define SPR_TILES ((u16 *) (VID_RAM_START + 0x10000))
// Структура описания спрайта
struct OAM_Entry
{
    u16 attr0, attr1, attr2, attr3;
};
// Массив описаний спрайтов (от 0 до 127)
#define SPR_BUFFER ((OAM_Entry *) (OAM_RAM_START))
// Выделим из массива спрайтов коэффициенты матриц
// вращения спрайтов в виде параметризированных
// макросов с параметрами от 0 до 31.
#define SPR_ROT_A( x ) (SPR_BUFFER[(x) * 4 + 0].attr3)
#define SPR_ROT_B( x ) (SPR_BUFFER[(x) * 4 + 1].attr3)
#define SPR_ROT_C( x ) (SPR_BUFFER[(x) * 4 + 2].attr3)
#define SPR_ROT_D( x ) (SPR_BUFFER[(x) * 4 + 3].attr3)

Поля attr0, attr1 и attr3 будут компоноваться из битовых масок, которые тоже проще будет описать поверхностно кодом и после пояснить детали:

// БИТОВЫЕ ФЛАГИ АТРИБУТА 0:
// Биты 0-7: Координата Y спрайта
#define ATTR0_Y( y )        ((y) & 255)
// Бит 8: Включить поворот/масштабирование
#define ATTR0_ROTATED       0x0100
// Бит 9 (1): Включить двойной размер (только для ROTATED=1)
#define ATTR0_DOUBLED       0x0200
// Бит 9 (2): Отключить отображение спрайта (только для ROTATED=0)
#define ATTR0_DISABLED      0x0200
// Биты 10-11: Режим спрайта (один из трёх): обычный, прозрачный, маска окна.
#define ATTR0_MODE_NORMAL   0x0000
#define ATTR0_MODE_TRANSPARENT  0x0400
#define ATTR0_MODE_WINDOW   0x0800
// Бит 12: Включить эффект мозаики
#define ATTR0_MOSAIC        0x1000
// Бит 13: 256-цветный режим (иначе 16-цветный)
#define ATTR0_COLOR_256     0x2000
// Бит 14-15: Форма спрайта (одна из трёх). Выбирает вариант формы спрайта, 
// которая в дальнейшем уточняется в параметре ATTR1.
// (SQ - квадратная, H - горизонтальная, V - вертикальная)
#define ATTR0_SHAPE_SQ      0x0000
#define ATTR0_SHAPE_H       0x4000
#define ATTR0_SHAPE_V       0x8000

// БИТОВЫЕ ФЛАГИ АТРИБУТА 1:
// Биты 0-8: Координата X спрайта
#define ATTR1_X( x )        ((x) & 511)
// Биты 9-13 (только для ROTATED=0): Индекс матрицы поворота (от 0 до 31)
#define ATTR1_ROT_INDEX( x )    (((x) & 31) << 9)
// Бит 12 (только для ROTATED=1): Отзеркалить горизонтально
#define ATTR1_H_FLIP        0x1000
// Бит 13 (только для ROTATED=1): Отзеркалить вертикально
#define ATTR1_V_FLIP        0x2000
// Биты 14-15 (только при ATTR0_SHAPE_SQ): Размер спрайта в пикселях (один вариант)
#define ATTR1_SQ_8x8        0x0000
#define ATTR1_SQ_16x16      0x4000
#define ATTR1_SQ_32x32      0x8000
#define ATTR1_SQ_64x64      0xC000
// Биты 14-15 (только при ATTR0_SHAPE_H): Размер спрайт в пикселях (один вариант)
#define ATTR1_H_16x8        0x0000
#define ATTR1_H_32x8        0x4000
#define ATTR1_H_32x16       0x8000
#define ATTR1_H_64x32       0xC000
// Биты 14-15 (только при ATTR0_SHAPE_V): Размер спрайта в пикселях (один вариант)
#define ATTR1_V_8x16        0x0000
#define ATTR1_V_8x32        0x4000
#define ATTR1_V_16x32       0x8000
#define ATTR1_V_32x64       0xC000

// БИТОВЫЕ ФЛАГИ АТРИБУТА 2:
// Биты 0-9: Индекс тайла (в отличие от тайлов фонов индекс тайла в
// спрайте всегда ссылается байт 32*индекс от начала тайловых данных
// спрайтов, т.е. для 256-цветных следует указывать четные индексы.
#define ATTR2_TILE( x )     ((x) & 1023)
// Биты 10-11: Приоритет (так же как у фонов от 0 до 3).
#define ATTR2_PRIORITY( x ) (((x) & 3) << 10)
// Биты 12-15 (только для 16-цветных): верхние 4 бита селектора палитры
#define ATTR2_PALETTE( x )  (((x) & 15) << 12)

Каждое из полей attr0-attr2 компонуется по битовому xor-у из соответствующих макросов ATTR0...-ATTR2…
Когда флаги охватывают несколько бит, то они часто хранят один из нескольких вариантов значений (перечисление) — тогда среди этих вариантов присутствует нулевое значение (0x0000). Получается, что такую маску совсем необязательно упоминать в коде, кроме как из соображений его человекочитаемости. Таковыми флагами являются: ATTR0_MODE_NORMAL, ATTR0_SHAPE_SQ, ATTR1_SQ_8x8, ATTR1_H_16x8, ATTR1_V_8x16.
Координаты x и y спрайта имеют всего 9 и 8 бит соответственно, но при этом отображение спрайтов зацикливается наподобие отображения текстовых фонов вокруг границ в 512 по горизонтали и 256 по вертикали, что выглядит так, как будто бы координаты имеют знак. Например, выставление всех бит координат спрайта в 1 (511 для x и 255 для y) отобразит его так, как будто бы его начало лежит в координатах (-1;-1), т.е. он на один пиксель выйдет за левый и верхний края экрана. Координаты спрайта всегда указывают на верхний-левый угол спрайта (это верно и для вращающихся — тогда координаты задают верхний-левый угол их ограничивающей рамки, см. ниже).
Если включен бит ATTR0_ROTATED, то спрайт можно вращать и масштабировать. Делается это афинной матрицей 2x2, коэффициенты которой (слева-направо, сверху-вниз) именуются нами A, B, C и D. К примеру, если нужно повернуть спрайт на угол alpha и отмасштабировать на коэффициент scale, то следует задать следующие параметры матрицы:

A =  scale * cos( alpha );
B =  scale * sin( alpha );
C = -scale * sin( alpha );
D =  scale * cos( alpha );

Так, если scale=1.0, а alpha=0.0, т.е. спрайт не повернут и имеет обычный размер, то A и D должны быть равны ровно единице, а B и C — нулю. Если угол alpha увеличивается, то спрайт вращается по часовой стрелке. Масштабный коэффициент на самом деле представляет собой делитель размера спрайта — т.е. если он равен 0.5, то спрайт увеличен в 2 раза, а если 2.0, то уменьшен в 2 раза.
Важно, что вращение и масштабирование происходят относительно центра спрайта — видеоадаптер сам транслирует координаты перед применением этих афинных преобразований как проще, поэтому те, кто знаком с афинными преобразованиями не должны удивлятся, что матрица имеет размеры 2x2, а не 3x3 — центр координат при применении матриц выставляется в центр спрайта. Тем не менее это всё те же матричные преобразования точек, что повсеместно встречаются в 3D-графике, поэтому можно преобразовать формулу в, допустим, независимое масштабирование по каждой из осей спрайта (до поворота):

A =  xscale * cos( alpha );
B =  xscale * sin( alpha );
C = -yscale * sin( alpha );
D =  yscale * cos( alpha );

Однако, если вы возьмёте квадратный спрайт, включите ему режим поворота/масштабирования и повернёте хоть на чуть-чуть, то внезапно обнаружите, что пиксели, вышедшие за квадратные границы неповёрнутого спрайта… просто исчезают. И так действительно GBA работает — каждый спрайт как бы обведен рамкой размером его оригинального неповёрнутого варианта и всё что выходит за эти рамки просто не рисуется. Разумеется чаще всего это не то, что мы хотим от повёрнутых спрайтов и поэтому существует следующий битовый флаг — ATTR0_DOUBLED.
Флаг ATTR0_DOUBLED имеет смысл только для спрайтов с флагом ATTR0_ROTATED (для обычных спрайтов этот бит превращается во флаг ATTR0_DISABLED). Если он установлен, то размеры этой «невидимой рамки» вокруг спрайта увеличиваются в 2 раза. При этом центр самого спрайта смещается так, чтобы совпасть с центром этой удвоенной области, но размер его не изменяется, т.е. он оказывается вписан по центру в рамку вдвое большего размера. Это позволяет при вращении квадратного спрайта полностью оказаться внутри обозначенной границы и избежать обрезки. Однако всё так же попытки вращать и масштабировать спрайт больше удвоенной области обрежутся о её границы. Так же в результате такого подхода невозможно без обрезки повернуть на большой угол вытянутые по горизонтали или вертикали спрайты, имейте это ввиду.
Как уже было написано выше сам спрайт содержит только индекс афинной матрицы — число от 0 до 31. Таким образом возможно не более 32-ух разных трансформаций для вращающихся спрайтов одновременно. Сами матрицы удобно задавать через макросы SPR_ROT_A/B/C/D( индекс ). Однако ячейками матрицы являются 16-битные целые числа, а нам нужно задавать в них числа вещественные. И вот тут становится интересно, потому что аппаратная начинка GBA просто не содержит устройств для вычислений с плавающей точкой — тот же CPU не имеет FPU и поэтому нужно поостеречься использовать типы float или double — все вычисления с ними происходят программно и довольно таки медленно. Имеет смысл использовать предварительно вычисленные таблицы для тригонометрических функций и числа с фиксированной запятой. Такое положение вещей отражено и в спрайтах — коэффиценты A, B, C и D афинных матриц представляют из себя вещественные числа с фиксированной запятой формата 8:8. То есть верхние 8 бит отведены под целую часть, а нижние — под дробную. Хранить такие числа нужно в обычных целых числах подходящей разрядности, арифметика с ними совсем не сложна — суммировать и вычитать их можно абсолютно так же как целые числа в которых они хранятся, а после умножения на такое число нужно сдвинуть результат вправо на количество разрядов после запятой в нём. Вообще нетрудно смешивать вычисления чисел и с разным числом разрядов после запятой — для сложения/вычитания нужно приводить их к одной точности сдвигами перед операцией, а для умножения/деления нужно приводить результат к нужной разрядности после операции — сам результат умножения имеет автоматически разрядность после запятой равную сумме разрядностей исходных чисел. Получить число формата fixed 8:8 из обычного вещественного очень легко — нужно просто умножить его на 256 и преобразовать в целое:

inline int fl2fx8( float fl )
{
    return ((int) (fl * 256.0f));
};

Следующий параграф можно пропустить, если вам неинтересны технические подробности! Если вы занимались ранее 3D-графикой и афинными преобразованиями, то вас (как и меня) может сперва смутить то, что масштабные коэффициенты матрицы являются не множителями, а делителями размера спрайта и то, что вращение спрайта происходит по часовой стрелке при возрастании угла поворота. Это больше похоже (и так и есть) на поведение обратной матрицы настоящего преобразования, чем то, что мы видим на экране. Ответ на то, что тут на самом деле происходит можно понять, если скрестить это с другим, на первый взгляд странным, поведением — невидимой обрезающей рамкой вокруг спрайтов, которую надо удваивать, чтобы вращать квадрат без потерь. На деле именно от этой «рамки» и отталкивается чип, когда рисует такие спрайты. Он обходит именно её пиксели и для каждого из них выполняет следующее:
а) вычисляет текущие координаты пикселя экрана (x_rel, y_rel) относительно центра спрайта.
б) Пропускает их через матричное преобразование, чтобы получить координаты внутри спрайта:

x_spr = A * x_rel + B * y_rel;
y_spr = C * x_rel + D * y_rel;

в) и по этим координатам (относительно центра спрайта) извлекает из спрайта цвет (а если они выходят за его границы — возвращает прозрачный 0). Программисты 3D-графики в этом месте сказали бы «осуществляет текстурную выборку». И вот полученный цвет и является цветом текущего пикселя.
Странно, но ни в одном руководстве, что я читал не написано толком что тут происходит, ни даже не используются слова ни «матрица» ни «афинное (преобразование)».

Напишем теперь программу 06_sprites.cpp с несколькими «летающими вокруг» спрайтами разного размера и одним большим спрайтом с вращением и позицией управляемыми с клавиатуры:

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

#include "gba_defs.h"

// Коверсия из float в число с фиксированной точкой 8:8
// для коэффициентов матрицы афинных преобразований
inline int fl2fx8( float fl )
{
	return ((int) (fl * 256.0f));
};

// Описание спрайта
struct simple_sprite
{
	// Координаты
	int x, y;
	// Скорость (пикселей за кадр)
	int vx, vy;
	// Индекс отображаемого тайла
	int tile;
	// Флаг размера (из квадратных)
	int size;
};

// Таблица спрайтов
// Первый спрайт управляется вручную
simple_sprite sprites[] = {
	{ 0, 0, 0, 0, 0, ATTR1_SQ_64x64 },
	{ 10, 10, 1, 0, 7, ATTR1_SQ_8x8 },
	{ 20, 20, 0, 1, 56, ATTR1_SQ_16x16 },
	{ 50, 100, 1, 1, 192, ATTR1_SQ_32x32 },
	{ 100, 50, -1, 1, 63, ATTR1_SQ_64x64 } };

// Количество спрайтов в таблице
const int sprites_count = sizeof( sprites ) / sizeof( sprites[ 0 ] );

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_0 со спрайтами с обычной компоновкой при многотайловости
	REG_DISPCNT = MODE_0 | SPR_ENABLE; // | MODE_LINEAR_SPRITES;

	// Инициализирум 512 тайлов спрайтов специальным кодированием:
	// верхняя половина тайла содержит точки зеленого оттенка, через
	// прозрачные разделители в количестве равном младшей цифре
	// индекса тайла в 16-ричной системе исчисления.
	// Нижняя же половина содержит то же самое, но в красных оттенках
	// и для вернего разряда.
	// Помним, что SPR_TILES это массив u16, поэтому 256-цветный
	// тайл занимает 8*4 ячеек, т.е. 8*8 байт.
	for ( int i = 0; i < 512; i++ )
	{
		// пиксели верхней половины...
		u16 *tile_base = SPR_TILES + i * 8 * 4;
		int cnt = i & 15;
		for ( int j = 0; j < cnt; j++ )
		{
			tile_base[ j ] = (0 << 8) + (48 + j);
		};
		// пиксели нижней половины...
		tile_base = SPR_TILES + i * 8 * 4 + 16;
		cnt = (i >> 4) & 15;
		for ( int j = 0; j < cnt; j++ )
		{
			tile_base[ j ] = (0 << 8) + (192 + j);
		};		   
	};

	// Позиция спрайта
	int sp_x = 0, sp_y = 0;
	// Поворот спрайта
	float sp_angle = 0.0f;
	// Масштабные коэффициенты по осям спрайта
	float sp_xscale = 1.0f;
	float sp_yscale = 1.0f;

	// Бесконечный цикл
	while ( true )
	{
		// Дождёмся выхода в VBLANK
		while ( REG_VCOUNT < 160 );

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

		// Реагируем на нажатые кнопки.
		int stepc = 1; // скорость движения
		float stepa = 0.01f; // скорость поворотов
		// Если зажата B - ускоряем движения и повороты в 10 раз
		if ( keys & KEY_B )
		{
			stepc *= 10;
			stepa *= 10;
		};
		if ( keys & KEY_A )
		{
			// Если зажата A - крестовина управляет масштабами
			if ( keys & KEY_LEFT )
				sp_xscale -= stepa;
			if ( keys & KEY_RIGHT )
				sp_xscale += stepa;
			if ( keys & KEY_UP )
				sp_yscale -= stepa;
			if ( keys & KEY_DOWN )
				sp_yscale += stepa;
		}
		else
		{
			// Если A отжата - крестовина управляет движением
			if ( keys & KEY_LEFT )
				sp_x -= stepc;
			if ( keys & KEY_RIGHT )
				sp_x += stepc;
			if ( keys & KEY_UP )
				sp_y -= stepc;
			if ( keys & KEY_DOWN )
				sp_y += stepc;
		};
		// L и R поворачивают спрайт в разные стороны
		if ( keys & KEY_L )
			sp_angle -= stepa;
		if ( keys & KEY_R )
			sp_angle += stepa;

		// Обновляем спрайты
		// 0-ой спрайт управляется вручную...
		sprites[ 0 ].x = sp_x;
		sprites[ 0 ].y = sp_y;
		SPR_BUFFER[ 0 ].attr0 = ATTR0_Y( sprites[ 0 ].y ) | ATTR0_ROTATED | ATTR0_DOUBLED | 
					ATTR0_COLOR_256 | ATTR0_SHAPE_SQ;
		SPR_BUFFER[ 0 ].attr1 = ATTR1_X( sprites[ 0 ].x ) | ATTR1_ROT_INDEX( 0 ) | sprites[ 0 ].size;
		SPR_BUFFER[ 0 ].attr2 = ATTR2_TILE( sprites[ 0 ].tile << 1 ) | ATTR2_PRIORITY( 3 );

		// Вычислим тригонометрию и обновим матрицу преобразований
		float sp_sina = sinf( sp_angle );
		float sp_cosa = cosf( sp_angle );
		SPR_ROT_A( 0 ) = fl2fx8(sp_xscale * sp_cosa);
		SPR_ROT_B( 0 ) = fl2fx8( sp_xscale * sp_sina );
		SPR_ROT_C( 0 ) = fl2fx8( -sp_yscale * sp_sina );
		SPR_ROT_D( 0 ) = fl2fx8( sp_yscale * sp_cosa );

		// Остальные спрайты 
		for ( int i = 1; i < 128; i++ )
		{
			if ( i < sprites_count )
			{
				// обновляем отображение
				SPR_BUFFER[ i ].attr0 = ATTR0_Y( sprites[ i ].y ) | ATTR0_MODE_NORMAL | 
							ATTR0_COLOR_256 | ATTR0_SHAPE_SQ;
				SPR_BUFFER[ i ].attr1 = ATTR1_X( sprites[ i ].x ) | sprites[ i ].size;
				SPR_BUFFER[ i ].attr2 = ATTR2_TILE( sprites[ i ].tile << 1 ) | ATTR2_PRIORITY( 0 );
				// обновляем координаты
				sprites[ i ].x += sprites[ i ].vx;
				sprites[ i ].y += sprites[ i ].vy;
			}
			else
			{
				// Остальные спрайты отключим, выведем за экран и придадим им
				// самую простую форму и самый низкий приоритет
				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( 2 );
			}
		};

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

	return 0;
}

Как обычно создаём батник для компиляции build_06_sprites.bat:

@SET MODULES=
@set PROGNAME=06_sprites

@call build_gba.bat

В следующей части рассмотрим вращающиеся задние фоны и спец-эффект полупрозрачности.

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

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

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