Создаем игру

Материал из PocketZ_wiki
Перейти к: навигация, поиск

Содержание

Вступление


Не секрет что поиграв в компьютерные игры на КПК многие думают "а не сделать ли мне свою собственную?", но столкнувшись с теми или иными трудностями бросают это дело. Трудности конечно бывают разные, зачастую и не связанные (или отдаленно связанные) с программированием - например абсолютное неумение нарисовать хотя бы примитивную картинку или сделать простейший звуковой эффект. (Здесь не рассматриваю такой момент когда человек всё это может, но просто наконец осознает нереальный объем работ).
Однако если научить рисовать довольно тяжело и не знаю даже возможно ли (аналогично с музыкой и звуками), то уж преодолеть хотя бы некоторые трудности программирования не так и сложно. Вот... Собственно поэтому я думаю в данной статье представить некоторую полезную информацию по изготовлению хотя бы самой что ни на есть простейшей игрушки (в плане программирования), а конкретнее - отображения графики. Возможно, кто-то здесь чему-то научится, а кто-то наоборот - поможет советами и научит других.
Итак... Начнем делать игру ! Во-первых на чем будем писать: я выбрал обычный C++ . Также, я не стал использовать готовые библиотеки работы с графикой, ибо думаю что освоив досконально простые вещи в простой игре можно будет спокойно браться за более продвинутые технологии и соответственно более сложные игры.
Во-вторых - что это будет за игра : Ясное дело игра будет совсем простой, но тетрис это будет уж слишком банально, поэтому возьмем скажем что-то вроде космической леталки-стрелялки - сделать такую вещь на самом деле совсем не сложно с программной точки зрения, а также графику для неё нарисовать довольно просто даже для не слишком искушенных в этом деле.
В третьих - не пинайте за ошибки в коде или в объяснении поскольку я не монстр программирования на КПК, много чего не знаю и это для меня всего лишь хобби в свободное время - лучше объясните обоснованно свою точку зрения, приводите свой код и так далее.

Часть 1. Как хоть что-нибудь отобразить на экране КПК


Я рассмотрю два способа вывода на экран - через gapi и через rawframebuffer. И в том и в другом случае экран как правило представляет собой адресное пространство (фреймбуфер) размером [ширина] x [высота] x [bpp], где bpp - количество бит на пиксель. В обеих случаях левый верхний пиксель имеет смещение = 0 относительно начала фреймбуфера, следующий справа пиксель имеет смещение = 1 х [bpp] и так далее до нижнего правого. Под словами левый верхний я здесь и в дальнейшем буду иметь ввиду "стандартную" ориентацию экрана КПК, то есть кнопками вниз. Если например мы будем проектировать игру с ориентацией "кнопки слева" - то смещение = 0 будет иметь правый верхний пиксель, а смещение = 1 х [bpp] соответственно тот который прямо снизу под ним. Также я всегда буду рассматривать bpp = 16, с маской 565 (поскольку это самое распространенное значение для КПК последних лет, понятное дело бывают и другие значения). То есть один пиксель занимает ровно 16 бит (один word) и интенсивности цветов R, G, B в этих 16-ти битах занимают соответственно 5 старших бит - красный, затем 6 бит - зеленый и последние младшие 5 бит - интенсивность красного. Исходя из вышеизложенного скажем для того чтобы вывести зеленую точку в левом верхнем углу экрана необходимо по смещению = 0 записать во фреймбуфер число (двоичное) 00000 111111 00000.
Или используя код:

LPWORD framebuffer = ... (инициализация разная) ...
framebuffer[0] = 0x7E0; 

Аналогично чтобы вывести точку с координатами (x,y) будет следующий код:

LPWORD framebuffer = ... (инициализация разная) ...
framebuffer[y*screen_width + x] = 0x7E0;


Здесь - screen_width = ширина экрана фреймбуфера. Именно фреймбуфера а не физического экрана, ибо например на VGA-устройствах фреймбуфер при работе через gapi может запросто иметь размеры 240 x 320.
Теперь о том как инициализировать фреймбуфер и получить адрес его начала.
Через библиотеку gapi.
Это довольно старый, но тем не менее использующийся до сих пор способ. Для его работы необходимо наличие библиотеки gx.dll которая является стандартной в системах начиная с windows mobile 2003 (???) и выше.
Сначала необходимо с помощью функции GXOpenDisplay инициализировать работу с данной библиотекой, затем с помощью функции GXGetDisplayProperties получаю информацию о свойствах фреймбуфера экрана соответственно проверяя наличие необходимого режима. Этот код должен выполняться один раз в самом начале программы сразу после создания основного окна, которое у меня тут имеет handle: g_hMainWindow.

GXDisplayProperties dispprop;
if (GXOpenDisplay(g_hMainWindow, GX_FULLSCREEN) == 0) {
MessageBox(g_hMainWindow, L"GAPI: Unable to init display.", L"ERROR", MB_OK);
return FALSE;
}
dispprop = GXGetDisplayProperties();

if ((dispprop.cBPP != 16) || (dispprop.cxWidth != 240) || (dispprop.cyHeight != 320))
{
GXCloseDisplay();
MessageBox(g_hMainWindow, L"GAPI: Unable to init 240x320 16bpp mode.", L"ERROR", MB_OK);
return FALSE;
}


После успешного выполнения данного куска кода можно собственно начинать отображать графику во фреймбуфер с помощью функций GXBeginDraw и GXEndDraw:

LPWORD framebuffer = (LPWORD) GXBeginDraw();
// начинаем "рисовать"
memset(framebuffer, 0xFF, 50*sizeof(WORD)); // верхняя линия в 50 пикселей белым цветом
framebuffer[10*240+20] = 0x7E0; // зеленая точка с координатами (20,10). 
// закончили рисование - вызовем функцию завершения
GXEndDraw();


Также перед выходом из программы необходимо вызвать функцию GXCloseDisplay чтобы высвободить ресурсы gapi которые мы тут "одолжили" у системы.
Через rawframebuffer.
Более современный способ введенный майкрософтом начиная с версий windows mobile 2003SE и выше. Особенно не отличается от gapi разве что ему не нужна библиотека gx.dll, но зато он не будет работать на старых устройствах. А также его большой плюс так это то что на VGA-устройствах с его помощью можно будет выводить именно vga-картинку ибо через gapi мы получим только qvga...
Сначала как обычно - необходима инициализация и проверка параметров фреймбуфера экрана:

#define GETRAWFRAMEBUFFER 0x00020001

#define FORMAT_565 1
#define FORMAT_555 2
#define FORMAT_OTHER 3

typedef struct _RawFrameBufferInfo
{
WORD wFormat;
WORD wBPP;
VOID *pFramePointer;
int cxStride;
int cyStride;
int cxPixels;
int cyPixels;
} RawFrameBufferInfo;

RawFrameBufferInfo screen_rfbi;
HDC hdc = GetDC(NULL);
ExtEscape(hdc, GETRAWFRAMEBUFFER, 0, NULL, sizeof(RawFrameBufferInfo), (char *)  &screen_rfbi);
ReleaseDC(NULL, hdc);

if ((screen_rfbi.wBPP != 16) || (screen_rfbi.wFormat != FORMAT_565) || (screen_rfbi.cxPixels != 480) ||  (screen_rfbi.cyPixels != 640))
{
MessageBox(g_hMainWindow, L"RAW: Unable to init 480x640 16bpp mode.", L"ERROR", MB_OK);
return FALSE;
}


Далее аналогично - берем ссылку на фреймбуфер из нашей структуры и заполняя его наблюдаем прорисовку всего этого на экране:

LPWORD framebuffer = (LPWORD) screen_rfbi.pFramePointer;
// начинаем "рисовать"
memset(framebuffer, 0xFF, 50*sizeof(WORD)); // верхняя линия в 50 пикселей белым цветом
framebuffer[10*480+20] = 0x7E0; // зеленая точка с координатами (20,10). 
// закончили рисование - не надо никаких функций вызывать ...


Здесь надо добавить что по-хорошему надо использовать значения cxStride и cyStride (а если используем gapi - cbxPitch, cbyPitch в структуре GXDisplayProperties) которые показывают сколько необходимо добавить к адресу во фреймбуфере чтобы перейти на соседнюю точку соответственно по-вертикали и по-горизонтали. И соответственно более верный код рисования точки по координатам (x,y) будет таким:

 ((LPWORD)screen_rfbi.pFramePointer)[(y*screen_rfbi.cyStride+x*screen_rfbi.cxStride)>>1] = 0x7E0;


Перед закрытием программы не требуется вызов каких-либо функций завершения работы. (во всяком случае я не нашел - если кто больше знает - подскажите)
Как сделать чтобы картинка двигалась - вроде довольно просто - стираем всё во фреймбуфере, рисуем картинку... потом опять стираем и опять рисуем картинку но сдвинутую на некоторое количество пикселей. Однако данный способ абсолютно неприемлем! Нельзя сразу рисовать сложную динамическую сцену во фреймбуфер. На экране при движении объектов будет наблюдаться мерцание или дергание. Поэтому чтобы отобразить на экране сложную динамически изменяющуюся картинку самым простым способом будет использовать копию фреймбуфера (такого же размера) в которой мы сначала будем рисовать всю графику, а затем быстро копировать сразу весь подготовленный экран в настоящий фреймбуфер.
Далее - чтобы объекты двигались равномерно и плавно - необходимо производить обновление картинки со стабильным интервалом сколько-то раз в секунду - как показывает практика где-то около 20 и больше уже можно считать нормальной скоростью. Итого - за 1/20 секунды программа должна произвести необходимые действия по обработке объектов игры, подготовить всю графику в копии фреймбуфера и вывести его на экран, затем следующий цикл аналогичных действий и т.д.
Итак... закончили с теорией... Теперь более практически: сделаем простую программу которая будет рисовать примитивный графический объект на экране двигая его с помощью стилуса... Да-да на этом всё - для начала будет достаточно разобраться хотя бы и в этом ибо в последующих версиях будет всё гораздо сложнее и меньшее количество комментариев и объяснений...
Сама структура программы очень простая:
Сначала:
инициализация окон, переменных и прочего, также инициализация графического режима и проверка его параметров.
Далее в цикле по таймеру с постоянным интервалом в 1/20 сек.:
обработка нажатий кнопок и стилуса
отработка логики объектов игры
очищаем копию фреймбуфера
рисуем туда всё что необходимо
копируем фреймбуфер на экран
При выходе:
очищаем, освобождаем память и т.д.
Теперь подумаем о разрешении графики в игре. Большинство КПК имеют разрешение экрана либо 240 х 320 либо 480 х 640. Ну и поскольку программа должна работать на обеих типах КПК - имеем несколько выходов:

  1. нарисовать разные картинки графики для разных разрешений и сделать либо две версии программы, либо одну, но в ней при инициализации графики выбирать нужные картинки - получаем просто увеличение объемов работы над программой но зато лучший вид при отображении на тех и других типах устройств.
  2. нарисовать всю графику в большом (vga) разрешении и работать с копией фреймбуфера как будто бы на vga-устройстве вне зависимости от реального девайса, но при окончательном выводе копии в актуальный фреймбуфер либо просто её копировать (для vga-устройств), либо копировать с одновременным масштабированием (для qvga) - я пробовал такой способ когда делал простой тетрис - вполне работоспособно хотя конечно vga графика теряет в качестве на qvga устройствах после масштабирования.
  3. нарисовать всю графику для qvga разрешения и соответственно на vga устройствах при окончательном выводе масштабировать её - здесь я буду использовать именно этот метод ибо хоть картинка будет терять в качестве на vga-устройствах - но зато мы сильно выиграем в скорости ибо подготовить и прорисовать qvga фреймбуфер в 4 раза быстрее чем vga, также графика в памяти будет занимать в 4 раза меньше места - а ведь скорость для КПК, как и размеры графики/занимаемой памяти это очень и очень важный параметр.


В нижеприведенном исходнике я использовал только gapi метод вывода картинки ибо удобен тем что при выводе через него - на vga-устройствах не требуется самостоятельно масштабировать изображение - gapi изначально предлагает qvga фреймбуфер и сам масштабирует при выводе на экран.
Второй момент - получение координат стилуса в программе. Windows передает координаты относительно текущего виртуального разрешения программы. Например если на vga-устройстве в ресурсы программы не включить HI_RES_AWARE - координаты будут передаваться как будто для qvga. Поэтому в своей программе я добавил этот ресурс и поскольку фреймбуфер у меня qvga - переданные мне vga-координаты мыши я просто делю на 2.
Ещё я обнаружил такой момент - когда запускаешь программу из ландшафтного режима - то передаваемые ей координаты могут как отличаться от "портретного запуска", так и не отличаться... Во всяком случае я заметил что при использовании gapi (функции GXOpenDisplay я так подозреваю) - координаты не отличаются, а например если использовать rawframebuffer - координаты будут передаваться соответственно "повернутыми".

Часть 2. Организация графических файлов и более совершенный вывод графики.


Итак - потренировавшись выводить примитивные точки/линии, крашенные прямоугольники и т.д., можно переходить к более продвинутым картинкам растровой графики (спрайтам).
Для начала определимся, куда и в каком формате будем выводить эти спрайты. Поскольку как известно большинство КПК для хранения одной точки требует 16 бит с цветовой маской 565 (описано в части 1.), а также имея ввиду что всю графику сначала требуется вывести в некий "теневой" фреймбуфер - логично будет выбрать такую структуру этого самого теневого фреймбуфера: одномерный массив из WORD размером 240 х 320 х sizeof(WORD), где по нулевому смещению будет располагаться верхняя левая точка, следующая за ней по горизонтали справа будет иметь смещение 2 (в байтах), а нижестоящая будет иметь смещение 480 (в байтах). Эта структура будет одна для всех типов КПК на которых будет запускаться наша игра вне зависимости от их разрешения или метода вывода теневого фреймбуфера на экран - просто при окончательном выводе теневого фреймбуфера на экран будут использоваться разные функции. Это позволит нам хранить все спрайты в одном формате. Соответственно формат хранения спрайтов будет такой-же как и формат самого теневого фреймбуфера - а именно одномерный массив из WORD размером [ширина спрайта] x [высота спрайта] x sizeof(WORD). Собственно в этом формате я и буду хранить спрайты на диске только добавив вначале файла ещё два WORD - размеры спрайта: ширина и высота.
Здесь продвинутый юзер подумает: "блин, ведь мы храним только R,G,B цветовые составляющие на одну точку - а где ж е-мое (полу)прозрачность?". Что-ж пока у нас полупрозрачности не будет - ограничимся тем что скажем цвет = 0 (я выбрал такой) будет считаться полностью прозрачным. Относительно реализации полупрозрачности я ещё напишу статью, но пока для нашей простейшей игрушки я её не использовал. Забегая вперед могу сказать что для полной и относительно быстрой реализации полупрозрачности каждую точку также можно хранить в 16-ти битах, но цветопередачей придется пожертвовать и цветовая маска будет 4-4-4-4 (ARGB) - то есть 4 (верхние) бита на степень прозрачности (alpha channel), и последующие 12 бит на цвета R,G,B. Также теневой фрейбуфер будет содержать точки с маской не 565, а 0444 - то есть 12-ти битный цвет (степень прозрачности во фреймбуфере хранить незачем).
Итого на диске спрайты у меня будут храниться в виде:

0000 WORD - ширина
0002 WORD - высота
0004 WORD - первая точка
... и т.д.


Как сделать данные файлы? А собственно как вам удобно - я например рисовал картинки в GIMP - сохранял их в .png файлах, а затем, используя php-скрипт конвертировал их в данный формат. (этот скрипт внутри прикрепленного файла есть).
Вся графика у меня хранится в этих файлах, вся эта куча файлов должна лежать в определенном каталоге рядом с исполняемым файлом программы. Также все графические файлы загружаются сразу в память при запуске программы... Здесь есть разные пути усовершенствования: 1) Паковать файлы скажем алгоритмом gzip и при считывании распаковывать 2) Считывать не все файлы сразу, а только те которые нужны для определенного уровня игры 3) Считывать определенное число файлов (пока не кончится определенный отведенный для графики кусок памяти) и затем по-необходимости считывать требуемые, одновременно выбрасывая из памяти редко- или последние использованные
Далее ... вывод спрайта в теневой фреймбуфер, а также некоторые функции копирования теневого фреймбуфера в настоящий я написал на arm-ассемблере для ускорения работы.
Как "засунуть" ассемблерную программу в eVC можно почитать тут:
pocketmatrix.com
Довольно большая книжка по ассемблеру:
Книжка по Ассеблеру
Все графические объекты игры (а там их пока всего три - звезды, корабль и выстрелы) описаны своими классами со своими функциями вывода спрайта в теневой фреймбуфер - соответственно основной цикл графики выглядит так :

  1. очищаем теневой фреймбуфер
  2. проходим по всем объектам на игровом поле вызывая для каждого функцию отрисовки спрайта
  3. копируем теневой фреймбуфер в настоящий используя преобразование если надо (если например реальный фреймбуфер разрешения 480 х 640, а теневой у меня 240 х 320 - соответственно я его растягиваю)

Пример реализации :
Изображение:Dark_star1.png

Часть 3. Примитивная полупрозрачность


Сейчас надо будет сказать пару слов о реализации примитивной полупрозрачности спрайтов. Зачем вообще она нужна? А хотя бы для того чтобы более красиво смотрелись выводимые на экран элементы интерфейса (кнопки типа "выход", "опции") и игровые элементы например текущий счет в игре, энергия нашего космического корыта и так далее ибо я их вывожу прямо поверх игрового поля. Наиболее просто реализовать 50% прозрачность всего спрайта (то есть все точки спрайта выводятся с одинаковой прозрачностью). О хорошей реализации прозрачности (когда любую точку спрайта можно будет вывести с любым коэффициентом прозрачности речь ещё пойдет, но пока рассмотрим такой вот самый простой вариант).
Теперь - что такое полупрозрачность?
Как мы уже знаем - каждая наша точка на экране представлена тремя цветами R,G,B. Допустим на экране уже нарисована точка с коэффициентами R1,G1,B1 и мы хотим поверх неё нарисовать ещё одну полупрозрачную с коэффициентами R2,G2,B2. Действие довольно простое - для 50% прозрачности надо поделить все цветовые коэффициенты на 2 и сложить соответствующие получившиеся значения. То есть результирующая точка будет иметь коэффициенты :

R = 0.5*R1 + 0.5*R2, G = 0.5*G1 + 0.5*G2, B = 0.5*B1 + 0.5*B2. 


Всё! Как видите - ничего сложного и если таким образом мы отобразим целый спрайт на экран - он будет выглядеть как раз полупрозрачным.
Осталось теперь это реализовать программно.
У меня теневой буфер экрана как уже говорил имеет цветовую маску 565. Можно конечно выделять из каждой точки по-очереди три цвета, делить их на 2, но это будет нерационально. Немного подумав - можно понять что выделять отдельно цвета не требуется, можно разделить на 2 сразу все 16 бит одной точки а те цветовые биты которые "уползут" за границы - просто убрать. Итого код будет такой:

WORD color1 = ...; // цвет исходной точки
WORD color2 = ...; // цвет точки которую мы хотим наложить
// результирующий цвет, 0x7BEF = двоичное 01111 011111 01111
// чтобы убрать верхние биты цветов которые "сползли" при сдвиге
WORD color = ((color1>>1) & 0x7BEF) + ((color2>>1) & 0x7BEF);


Или на ассемблере:

; по адресу [r0] - исходный цвет (теневой фреймбуфер)
; по адресу [r1] - цвет который хотим наложить
; в r4 наша маска для сдвига - число 0x7BEF
...
ldrh r12, [r1], #2 ; получаем в r12 цвет2 - точка спрайта (одновременно передвигая указатель r1 на след. точку)
tst r12, r12 ; сравниваем цвет2 с нулевым - нулевой цвет у меня полностью прозрачный и я его не рисую
beq label1 ; соответственно если рисовать точку совсем не надо - прыгаем дальше
and r12, r4, r12, LSR #1 ; сдвигаем цвет2 в r12 на 1 (делим на 2) и "вычищаем" верхние биты маской из r4 (0x7BEF)
ldrh r9, [r0] ; аналогичная операция с цветом точки фреймбуфера
and r9, r4, r9, LSR #1 
add r12, r12, r9 ; суммируем две точки в r12
strh r12, [r0] ; записываем в теневой фреймбуфер
label1
...

"Прокрутив" эти операции в цикле по всему спрайту - получим его вывод наполовину прозрачным.
Изображение:Dark_star2.png
Ещё пару слов можно сказать о том как обработать такие моменты когда система в момент работы игры выбрасывает какое-либо окно например на передний план - всё довольно просто - необходимо обрабатывать сообщения WM_ACTIVATE и WM_SETTINGCHANGE и если наше окно стало неактивным например - ставить игру на паузу и прекращать прорисовку графики, соответственно при активизации окна - убирать паузу (или не убирать) и рисовать графику дальше. У меня в исходнике встроена небольшая обработка WM_ACTIVATE, даже как-то худо-бедно работает...

Часть 4. "Полноценная" полупрозрачность (alpha-blending)

Итак, как сделать 50% прозрачность при выводе всего спрайта мы уже знаем, теперь попробуем реализовать полноценную полупрозрачность для каждой точки спрайта.

Ну во-первых - зачем оно надо? К примеру, мы рисуем объект с расплывающимися границами - скажем взрыв или пламя двигателя нашей ракеты - на черном фоне выглядеть это будет так: в центре - яркий (белый) цвет, возле самой границы цвет должен быть темным ... но вот например сменился background и теперь фон стал светлым ... ага ... засада - на светлом фоне края взрыва остались черными и выглядеть это стало совсем не ахти... Для примера смотрите на рисунок (ниже) - выстрелы (желтые шары) отображаются без использования полупрозрачности и заметен их странный вид на светлом фоне (появляется темная граница) поскольку они были нарисованы для темного фона, с другой стороны взрыв рисуется с использованием полупрозрачности - и он никаких артефактов на рядом-стоящих объектах не показывает... Кроме нормального отображения плавных границ объектов полупрозрачность также пригодится для разнообразных эффектов типа плывущих поверх всей игровой сцены облаков, отображения теней от объектов...

Технически отображение полупрозрачной точки можно описать так:
Допустим мы хотим нарисовать точку с цветами R1,G1,B1 поверх точки с цветами R0,G0,B0 с коэффициентом прозрачности а = 0..1, 0 - непрозрачный цвет, 1 - полностью прозрачный. Формула очень простая:

R = R0 * a + R1 * (1-a); 
G = G0 * a + G1 * (1-a);
B = B0 * a + B1 * (1-a);

Для каждой отображаемой точки будет свой коэффициент a = 0..1.

При кажущейся простоте тем не менее возникает такая проблема что для отображения одной единственной точки требуется произвести 6 операций умножения чисел с плавающей запятой а это довольно накладное занятие ибо точек этих отображать в игре приходится очень много ... Будем оптимизировать это дело...

  • Во-первых - умножения в правой части вроде R1*(1-a) можно вообще не делать при отображении спрайта, ибо значения (a,R1,G1,B1) описывают точку спрайта и не меняются для неё - поэтому эти умножения можно просто сделать заранее на этапе формирования наших графических файлов и в них хранить для каждой точки спрайта значения (a,R2,G2,B2)
    где R2=R1*(1-a), G2=G1*(1-a), B2=B1*(1-a).
  • Во-вторых - разберемся как у нас будут храниться цвета в графическом файле. Раньше на одну точку использовалось 16-бит = 5+6+5 бит соответственно интенсивности красного, зеленого и синего цветов. Я решил оставить общее число бит прежним, но сократить количество бит для цветов, добавив туда взамен биты полупрозрачности - а именно выбрал 3+4+5+4 бит соответственно полупрозрачность+красный+зеленый+синий цвета. Да - увы -за счет этого я сильно потерял в цветопередаче, но мне была важна скорость отображения графики - поэтому вот выбрал такой путь. Также в теневом буфере будут храниться точки именно с такой цветовой маской, только без коэффициента прозрачности (без верхних 3-х бит) и при отображении теневого буфера в реальный фреймбуфер - эти цвета будут трансформироваться в требуемые КПК цвета с маской 565 посредством заранее подготовленного массива преобразования по типу: цвет_фреймбуфера = массив_преобразования[цвет_теневого_буфера];

Итак - теперь коэффициент прозрачности у нас хранится в целочисленном виде: a = 0 (непрозрачный) .. 7 (полностью прозрачный), а формула получения цветов становится такой: (для красного цвета) R = (R0 * a / 7) + R2 (где R2 - заранее вычисленное R2=R1*(1 - a/7), размерности: R0 - 4 бита, а - 3 бита, R2 - 4 верхних бита после вычислений, а R1 в принципе любой размерности. Аналогично будет для зеленого и синего цветов.
Тут можно заметить что поскольку размерности у нас маленькие (3,4,5 бит...) - можно вообще ничего не умножать и не делить, а использовать массивы заранее посчитанных значений. То есть кусок (R0 * a / 7) можно взять из массива R_array[a<<4 + R0]. Как видим - умножение и деление заменяются на сдвиг и сложение - это очень большой выигрыш в скорости (ну не любят процессоры умножать и делить).
Пойдем ещё дальше - не будем по-отдельности считать каждый цвет - а аналогично получим из заранее подготовленного массива все 3 цвета сразу:

Color = color_array[a<<13 + Color0] + Color2; 

где:
Color - результирующая точка (WORD) с цветовой маской 454 (верхние 3 бита = 0)
Color0 - точка нашего теневого буфера с такой же цветовой маской (454) или (R0<<9+G0<<4+B0)
Color2 - 13 бит заранее подготовленной точки спрайта (R2<<9+G2<<4+B2)
a - 3 бита коэффициент прозрачности данной точки спрайта
Ну и поскольку коэффициент прозрачности у меня хранится в верхних 3-х битах цвета спрайта - получится вот так:

Color = color_array[(Color2 & 0xE000) + Color0] + (Color2 & 1FFF); 

Заранее посчитанный массива color_array[] будет содержать 0x10000 элементов WORD, не так чтобы мало - но как уже сказал - скорость мне была важнее...
Здесь главное правильно (точно) сформировать этот массив (color_array) и правильно подготовить цвета в спрайте (Color2) так чтобы после сложения цвета не "вылезли" за границы цветовых масок - ведь все цвета складываются одновременно...
Ну да ладно, теперь по поводу файлов изображений - я как уже говорил их делаю (с помощью php) из файлов .png - в .png можно хранить коэффициент прозрачности для каждой точки - соответственно в исходниках у меня есть скрипт conv_png_454.php который из кучи файлов вида xxx_yyy_description.png (ххх - номер изображения, ууу - номер спрайта внутри изображения) в каталоге делает мне нужные файлы моего формата .454 который собственно довольно простой:

(WORD) sprite_count - количество спрайтов в данном файле
(WORD) sprite1_dx - ширина первого спрайта
(WORD) sprite1_dy - высота первого спрайта
(WORD) sprite1_isalpha - используется ли полупрозрачность в данном спрайте - если 1 - значит этот спрайт использует полупрозрачность и его цвета уже подготовлены, если 0 - значит надо выводить этот спрайт простым копированием цветов в теневой буфер
(WORD) ... data ...
...
(WORD) sprite2_dx 
(WORD) sprite2_dy 
и т.д.

Изображение:Dark_star3.png
Исходные тексты программы (C++, Asm); исходные и итоговые изображения, готовый релиз для КПК : DarkStar

Автор статьи и исходных кодов : chamine
(под редакцией maskin)

Личные инструменты