Вступление:Не секрет что поиграв в компьютерные игры на КПК многие думают "а не сделать ли мне свою собственную?", но столкнувшись с теми или иными трудностями бросают это дело. Трудности конечно бывают разные, зачастую и не связанные (или отдаленно связанные) с программированием - например абсолютное неумение нарисовать хотя бы примитивную картинку или сделать простейший звуковой эффект. (Здесь не рассматриваю такой момент когда человек всё это может, но просто наконец осознает нереальный объем работ).
Однако если научить рисовать довольно тяжело и не знаю даже возможно ли (аналогично с музыкой и звуками), то уж преодолеть хотя бы некоторые трудности программирования не так и сложно.
Вот... Собственно поэтому я думаю собрать в данном топике некоторую полезную информацию по изготовлению хотя бы самой что ни на есть простейшей игрушки в плане именно программирования, а именно отображения графики. Возможно кто-то здесь чему-то научится, а кто-то наоборот поможет советами и научит других.
Итак... Начнем делать игру :)
Во-первых на чем будем писать: я выбрал обычный c++ ибо vb просто не знаю, а .net просто не люблю... также я не стал использовать готовые библиотеки работы с графикой, ибо думаю что освоив досконально простые вещи в простой игре можно будет спокойно браться за более продвинутые технологии и соответственно более сложные игры.
Во-вторых - что это будет за игра... Ясное дело игра будет совсем простой, но тетрис это будет уж слишком банально, поэтому возьмем скажем что-то вроде космической леталки-стрелялки - сделать такую вещь на самом деле совсем не сложно с программной точки зрения, а также графику для неё нарисовать довольно просто даже для не слишком искушенных в этом деле.
В третьих - не пинайте за ошибки в коде или в объяснении поскольку я не монстр программирования на КПК, много чего не знаю и это для меня всего лишь хобби в свободное время - лучше объясните обоснованно свою точку зрения, приводите свой код и так далее.
-------------------
Часть 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.
Теперь о том как инициализировать фреймбуфер и получить адрес его начала.
1) Через библиотеку 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 которые мы тут "одолжили" у системы.
2). Через 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 фреймбуфер и сам масштабирует при выводе на экран.
Второй момент - получение координат стилуса в программе. Виндовс передает координаты относительно текущего виртуального разрешения программы. Например если на vga-устройстве в ресурсы программы не включить HI_RES_AWARE - координаты будут передаваться как будто для qvga. Поэтому в своей программе я добавил этот ресурс и поскольку фреймбуфер у меня qvga - переданные мне vga-координаты мыши я просто делю на 2.
Ещё я обнаружил такой момент - когда запускаешь программу из ландшафтного режима - то передаваемые ей координаты могут как отличаться от "портретного запуска", так и не отличаться... Во всяком случае я заметил что при использовании gapi (функции GXOpenDisplay я так подозреваю) - координаты не отличаются, а например если использовать rawframebuffer - координаты будут передаваться соответственно "повернутыми".
Уфффф... эдакое долгое объяснение вышло самых базовых моментов... впрочем ладно - основа положена...
Исходник я компилировал Embedded VC 4.0 c сервис-паком 3 и установленным Pocket PC 2003 SDK.
Прикрепленные файлы
Test0.zip ( 137.41 КБ )
Сообщение отредактировал chamine - 24.01.07, 15:33