|
--> На чем мы остановились?
В прошлой статье были рассмотрены основы Win32 ASM программирования,
основы создания игр, и сам процесс разработки. Пришло время зайти немного дальше.
Сначала я расскажу о высокоуровневых конструкциях MASM, которые являются удобоваримыми
в сравнительном смысле с аналогичными конструкциями на
Си. Затем рассмотрим основной цикл и
главные оконные процедуры. После чего обратим внимание на Direct Draw и вызовы связанные с ним.
Поняв, как это работает, мы сможем построить свою собственную Direct Draw
Library, после чего построим свою bitmap file library и в конце напишем программу,
которая отображает экран 'Loading Game' и выходит из нее по нажатию клавиши Esc.
Для компиляции вам потребуется пакет MASM32, или по крайней мере MASM 6.11+.[Можно
взять здесь http://wasm.ru/tools/7/masm32v7.zip и здесь http://wasm.ru/tools/7/masm615.zip- прим. ред.]
--> Синтаксис MASM
[рекомендуем обратиться к источникам
на http://www.wasm.ru, в частности, посмотреть туториалы Iczelion'a и др.- прим. ред.]
Досовские варианты ассемблерных листингов представляют в своем большинстве собрание
весьма неудобоваримых сочинений, порой непонятных даже квалифицированным программистам.
Очень много меток, jmp-ов и прочей нечисти. Но asm не стоит на месте,
и в MASM 6.0 макро-конструкции становятся неотъемлемым
инструментом разработки.
MASM ныне - такой же легкий в чтении язык, что и
Си. Это, конечно, только мое мнение.
Давайте теперь рассмотрим некоторые Си-шные конструкции и их аналоги в MASM.
- IF - ELSE IF - ELSE
The C version:if ( var1 == var2 )
{
// Code goes here
}
else
if ( var1 == var3 )
{
// Code goes here
}
else
{
// Code goes here
} |
The MASM version:.if ( var1 == var2 )
; Code goes here
.elseif ( var1 == var3 )
; Code goes here
.else
; Code goes here
.endif |
- DO - WHILE
The C version:do
{
// Code goes here
}
while ( var1 == var2 ); |
The MASM version:.repeat
; Code goes here
.until ( var1 != var2 ) |
- WHILE
The C version:while ( var1 == var2 )
{
// Code goes here
} |
The MASM version:.while ( var1 == var2 )
; Code goes here
.endw |
Это все - примеры рабочих конструкций, и как вы видите они чрезвычайно просты.
При компиляции, MASM скомпилирует в коде, все те же нужные метки, jmp-ы, cmp-конструкции.
Есть еще и другие вещи, которые нам следует обсудить, это псевдо-операторы
которые позволяют нам легко определить процедуры/функции,
такие как PROTO и PROC. Использовать их очень легко. Для начала, также как и в
Си,
вам нужно иметь прототип. В MASM это делается с помощью ключевого слова PROTO.
Вот несколько примеров объявления прототипов для ваших функций:
;==================================
; Main Program Procedures
;==================================
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
WndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
Вышеупомянутый код сообщает ассемблеру, о двух новых процедурах WinMain и WndProc.
Каждая из них имеет список параметров связанных с ними.
В каждой функции задаются 4 параметра (DWORD). Для тех кто использует пакет MASM32,
то в нем уже есть прототипы всех Windows API функций, вам просто нужно подключить соответствующий файл.
Но вам надо убедиться, что все пользовательские процедуры определены вышеупомянутым способом.
Породив прототип, мы можем породить и саму функцию с помощью ключевого слова PROC:
;########################################################################
; WinMain Function
;########################################################################
WinMain PROC hInstance :DWORD,
hPrevInst :DWORD,
CmdLine :DWORD,
CmdShow :DWORD
;===========================
; We are through
;===========================
return msg.wParam
WinMain endp
;########################################################################
; End of WinMain Procedure
;########################################################################
При такой записи имеется доступ ко всем параметрам функции.
Не правда ли - слишком просто для Winmain?
--> Основной игровой цикл
Теперь, когда мы все знаем, как писать на ассемблере,
давайте приступим к написанию основного игрового цикла.
Начнем с WinMain().
.CODE
start:
;==================================
; Получим экземпляр
; приложения
;==================================
INVOKE GetModuleHandle, NULL
MOV hInst, EAX
;==================================
; Как насчет командной строки?
;==================================
INVOKE GetCommandLine
MOV CommandLine, EAX
;==================================
; Вызов WinMain
;==================================
INVOKE WinMain,hInst,NULL,CommandLine,SW_SHOWDEFAULT
;==================================
; Выход
;==================================
INVOKE ExitProcess,EAX
Единственное, что здесь может оказаться немного странным
[ можно подумать, что данный случай -
исключение, а не правило. Это не странно, а
естественно - прим. ред.], так это пересылка регистра EAX
в переменную (MOV ...,EAX) в конце INVOKE. Причина в том,
что все функции Windows (и Си функции в том числе), возвращают значение результата
функции/процедуры в регистре EAX. Этот код вы можете использовать во всех программах,
которые будете писать, по крайней мере, мне никогда не приходилось его изменять.
А теперь сам код:
;########################################################################
; WinMain Function
;########################################################################
WinMain PROC hInstance :DWORD,
hPrevInst :DWORD,
CmdLine :DWORD,
CmdShow :DWORD
;=========================
; локальные переменные (размещаются в стеке)
;=========================
LOCAL wc :WNDCLASS
;==================================================
; Заполнение структуры WNDCLASS требуемыми переменными
;==================================================
MOV wc.style, CS_OWNDC
MOV wc.lpfnWndProc,OFFSET WndProc
MOV wc.cbClsExtra,NULL
MOV wc.cbWndExtra,NULL
m2m wc.hInstance,hInst ;<< замечание: это макрос
INVOKE GetStockObject, BLACK_BRUSH
MOV wc.hbrBackground, EAX
MOV wc.lpszMenuName,NULL
MOV wc.lpszClassName,OFFSET szClassName
INVOKE LoadIcon, hInst, IDI_ICON ; icon ID
MOV wc.hIcon,EAX
INVOKE LoadCursor,NULL,IDC_ARROW
MOV wc.hCursor,EAX
;================================
; Регистрация класса, который мы создали
;================================
INVOKE RegisterClass, ADDR wc
;===========================================
; Создание главного экрана
;===========================================
INVOKE CreateWindowEx,NULL,
ADDR szClassName,
ADDR szDisplayName,
WS_POPUP OR WS_CLIPSIBLINGS OR \
WS_MAXIMIZE OR WS_CLIPCHILDREN,
0,0,640,480,
NULL,NULL,
hInst,NULL
;===========================================
; сохранить указатель на окно (хэндл)
;===========================================
MOV hMainWnd, EAX
;====================================
; Скрыть курсор
;====================================
INVOKE ShowCursor, FALSE
;===========================================
; Вывод на экран нашего окна, которое мы создали
;===========================================
INVOKE ShowWindow, hMainWnd, SW_SHOWDEFAULT
;=================================
; Инициализация игры
;=================================
INVOKE Game_Init
;========================================
; проверка на ошибки и выход в случае чего
;========================================
.IF EAX != TRUE
JMP shutdown
.ENDIF
;===================================
; Цикл, пока не будет послано PostQuitMessage
;===================================
.WHILE TRUE
INVOKE PeekMessage, ADDR msg, NULL, 0, 0, PM_REMOVE
.IF (EAX != 0)
;===================================
; Выход из цикла
;===================================
MOV EAX, msg.message
.IF EAX == WM_QUIT
;======================
; Выход
;======================
JMP shutdown
.ENDIF
;===================================
; Translate and Dispatch the message
;===================================
INVOKE TranslateMessage, ADDR msg
INVOKE DispatchMessage, ADDR msg
.ENDIF
;================================
; вызов главного игрового цикла
;================================
INVOKE Game_Main
.ENDW
shutdown:
;=================================
; Завершение игры
;=================================
INVOKE Game_Shutdown
;=================================
; Показать курсор
;=================================
INVOKE ShowCursor, TRUE
getout:
;===========================
; Завершение
;===========================
return msg.wParam
WinMain endp
;########################################################################
; End of WinMain Procedure
;########################################################################
Давайте проанализируем. Обратите внимание:
при инициализации локальной переменной (в нашем случае это структура WNDCLASS), в начале
функции не нужно никакой возни со стеком push/pop, также как и в ее конце,
за вас все сделает компилятор. Вы должны только объявить локальные переменные, как в
Си.
Далее, заполняем структуру значениями. Обратите внимание на использование макроса m2m.
Это потому, что ASM не позволяет напрямую копировать из памяти в память,
без использования регистра или стека в качестве посредника.
Далее, создаем окно и прячем курсор, так как он нам в игре не нужен.
Показываем окно и вызываем Game_Init(). Если внутри процедуры Game_Init() произошла ошибка,
то ее результат будет FALSE. Проверяем результат процедуры Game_Init() и в случае ошибки выходим
из программы (прыгаем на метку shutdown). Вообще, при отладке asm-процедур всегда нужно помнить,
что должна быть одна точка входа и одна точка выхода.
Дальше идет цикл сообщений (message loop), которые могут поступать откуда угодно. Если бы это была обычная программа, а не игра, то мы бы использовали GetMessage() для приема сообщений из очереди. Но здесь есть одна проблема, если нет никаких сообщений, то функция будет ждать, пока придет какое-нибудь сообщение. Это совершенно не подходит для игры. Нам нужно постоянно выполнять главный игровой цикл, независимо от того, придут ли какие-либо сообщения или нет. Есть один путь обойти это - использовать PeekMessage(). PeekMessage() возвращает ноль, если нет никаких сообщений, иначе вернет сообщение из очереди.
Обратите внимание, что главный игровой цикл (Game_Main), будет вызываться всегда, независимо от того,
пришло ли какое-либо сообщение или нет. Если бы мы этого не сделали, то Windows мог бы обработать кучу сообщений, в то время, как
главный игровой цикл - ни разу.
И в конце, когда мы получаем сообщение quit, мы выходим из цикла и соответственно завершаем программу.
--> Связь с Direct Draw
Мы не будем рассматривать сам DirectX на уровне асм-а, а рассмотрим его на уровне основных концепций.
Прежде всего, необходимо понять саму концепцию Таблицы Виртуальных Функций. Делается запрос в нее в форме смещения, и получается АДРЕС расположения функции. Т.е. под вызовом функции понимается обращение к таблице, которая УЖЕ существует. Адреса функций имеются в DirectX-библиотеке.
Далее, необходимо определить адрес объекта, для которого вызывается функция. Вычисляем виртуальный адрес и сохраняем в стеке все параметры. Для этой цели существуют различные макросы, в частности, DD4INVOKE еще из 4-го DirextX.
Сначала определяем имя функции, затем имя объекта и параметры:
;========================================
; Создадим primary surface
;========================================
DD4INVOKE CreateSurface, lpdd, ADDR ddsd, ADDR lpddsprimary, NULL
В этом примере создается поверхность, вызовом функции CreateSurface(),
передавая ей в качестве параметров: указатель на объект,
адрес структуры Direct Draw Surface Describe (ddsd), адрес переменной для хранения указателя на поверхность, и наконец NULL. Теперь, когда мы увидели, как делать запросы к DirectX, давайте построим небольшую библиотеку.
--> Наша Direct Draw Library
Нам понадобятся функции для инициализации и выхода из игры, для определения формата пикселя, создания и прорисовки поверхностей, и загрузки в нее битмапа.
Далее код функции инициализации:
;########################################################################
; DD_Init Procedure
;########################################################################
DD_Init PROC screen_width:DWORD, screen_height:DWORD, screen_bpp:DWORD
;=======================================================
; Устанавливаем полноэкранный режим
;=======================================================
;=================================
; Локальные переменные
;=================================
LOCAL lpdd_1 :LPDIRECTDRAW
;=============================
; Создаем объект
;=============================
INVOKE DirectDrawCreate, 0, ADDR lpdd_1, 0
;=============================
; Обработка ошибок
;=============================
.IF EAX != DD_OK
;======================
; Ошибка
;======================
INVOKE MessageBox, hMainWnd, ADDR szNoDD, NULL, MB_OK
;======================
; Выход
;======================
JMP err
.ENDIF
;=========================================
; Получим DirectDraw 4 object
;=========================================
DDINVOKE QueryInterface, lpdd_1, ADDR IID_IDirectDraw4, ADDR lpdd
;=========================================
; Получили ???
;=========================================
.IF EAX != DD_OK
;==============================
; Нет
;==============================
INVOKE MessageBox, hMainWnd, ADDR szNoDD4, NULL, MB_OK
;======================
; Выход
;======================
JMP err
.ENDIF
;===================================================
; Установка cooperative level
;===================================================
DD4INVOKE SetCooperativeLevel, lpdd, hMainWnd, \
DDSCL_ALLOWMODEX OR DDSCL_FULLSCREEN OR \
DDSCL_EXCLUSIVE OR DDSCL_ALLOWREBOOT
;=========================================
; Получили ???
;=========================================
.IF EAX != DD_OK
;==============================
; Нет
;==============================
INVOKE MessageBox, hMainWnd, ADDR szNoCoop, NULL, MB_OK
;======================
; Выход
;======================
JMP err
.ENDIF
;===================================================
; Установка Display Mode
;===================================================
DD4INVOKE SetDisplayMode, lpdd, screen_width, \
screen_height, screen_bpp, 0, 0
;=========================================
; Установили ???
;=========================================
.IF EAX != DD_OK
;==============================
; Нет
;==============================
INVOKE MessageBox, hMainWnd, ADDR szNoDisplay, NULL, MB_OK
;======================
; Выход
;======================
JMP err
.ENDIF
;================================
; screen info
;================================
m2m app_width, screen_width
m2m app_height, screen_height
m2m app_bpp, screen_bpp
;========================================
; Зададим параметры для поверхности (surface)
;========================================
DDINITSTRUCT OFFSET ddsd, SIZEOF(DDSURFACEDESC2)
MOV ddsd.dwSize, SIZEOF(DDSURFACEDESC2)
MOV ddsd.dwFlags, DDSD_CAPS OR DDSD_BACKBUFFERCOUNT;
MOV ddsd.ddsCaps.dwCaps, DDSCAPS_PRIMARYSURFACE OR \
DDSCAPS_FLIP OR DDSCAPS_COMPLEX
MOV ddsd.dwBackBufferCount, 1
;========================================
; Создадим первичную поверхность (primary surface)
;========================================
DD4INVOKE CreateSurface, lpdd, ADDR ddsd, ADDR lpddsprimary, NULL
;=========================================
; Создали ???
;=========================================
.IF EAX != DD_OK
;==============================
; Нет
;==============================
INVOKE MessageBox, hMainWnd, ADDR szNoPrimary, NULL, MB_OK
;======================
; Выход
;======================
JMP err
.ENDIF
;==========================================
; Попробуем получить backbuffer
;==========================================
MOV ddscaps.dwCaps, DDSCAPS_BACKBUFFER
DDS4INVOKE GetAttachedSurface, lpddsprimary, ADDR ddscaps, ADDR lpddsback
;=========================================
; Получили ???
;=========================================
.IF EAX != DD_OK
;==============================
; Нет
;==============================
INVOKE MessageBox, hMainWnd, ADDR szNoBackBuffer, NULL, MB_OK
;======================
; Выход
;======================
JMP err
.ENDIF
;==========================================
; Получим RGB format для surface
;==========================================
INVOKE DD_Get_RGB_Format, lpddsprimary
done:
;===================
; Все Ок! :)
;===================
return TRUE
err:
;===================
; Ничего не Ок! :(
;===================
return FALSE
DD_Init ENDP
;########################################################################
; END DD_Init
;########################################################################
Рассмотрим подробнее.
Сначала создаем так называемый default Direct Draw object с помощью функции DirectDrawCreate(). Это
не более, чем простой вызов функции с несколькими параметрами. Это еще не виртуальная функция.
Поэтому мы можем вызывать ее с помощью INVOKE.
Также, обратите внимание, что мы потом проверяем на ошибку. Это очень важно в DirectX!!! В случае ошибки, мы просто выводим сообщение, и переходим на метку err: в конце процедуры.
Далее делаем запрос на получение DirectDraw4 object. После чего устанавливаем режимы экрана с помощью SetCooperativeLevel() и SetDisplayMode(). Не забывайте проверять на ошибки, после каждого вызова функций.
На следующем шаге создаем первичную поверхность (primary surface), и в случае успеха создаем back buffer. При этом полученную структуру надо очистить с помощью макроса DDINITSTRUCT, который я включил в файл Ddraw.inc.
После, вызывается процедура для определения формата пикселя для нашей поверхности.
В следующей процедуре мы получаем формат пикселя:
;########################################################################
; DD_Get_RGB_Format Procedure
;########################################################################
DD_Get_RGB_Format PROC surface:DWORD
;=========================================================
; Установим несколько глобальных переменных
;=========================================================
;====================================
; Локальные переменные
;====================================
LOCAL shiftcount :BYTE
;================================
; получим surface despriction
;================================
DDINITSTRUCT ADDR ddsd, sizeof(DDSURFACEDESC2)
MOV ddsd.dwSize, sizeof(DDSURFACEDESC2)
MOV ddsd.dwFlags, DDSD_PIXELFORMAT
DDS4INVOKE GetSurfaceDesc, surface, ADDR ddsd
;==============================
; маски
;==============================
m2m mRed, ddsd.ddpfPixelFormat.dwRBitMask ; Red Mask
m2m mGreen, ddsd.ddpfPixelFormat.dwGBitMask ; Green Mask
m2m mBlue, ddsd.ddpfPixelFormat.dwBBitMask ; Blue Mask
;====================================
; определим red mask
;====================================
MOV shiftcount, 0
.WHILE (!(ddsd.ddpfPixelFormat.dwRBitMask & 1))
SHR ddsd.ddpfPixelFormat.dwRBitMask, 1
INC shiftcount
.ENDW
MOV AL, shiftcount
MOV pRed, AL
;=======================================
; определим green mask
;=======================================
MOV shiftcount, 0
.WHILE (!(ddsd.ddpfPixelFormat.dwGBitMask & 1))
SHR ddsd.ddpfPixelFormat.dwGBitMask, 1
INC shiftcount
.ENDW
MOV AL, shiftcount
MOV pGreen, AL
;=======================================
; определим blue mask
;=======================================
MOV shiftcount, 0
.WHILE (!(ddsd.ddpfPixelFormat.dwBBitMask & 1))
SHR ddsd.ddpfPixelFormat.dwBBitMask, 1
INC shiftcount
.ENDW
MOV AL, shiftcount
MOV pBlue, AL
;===========================================
; определим специальную переменную для 16 bit mode
;===========================================
.IF app_bpp == 16
.IF pRed == 10
MOV Is_555, TRUE
.ELSE
MOV Is_555, FALSE
.ENDIF
.ENDIF
done:
;===================
; все
;===================
return TRUE
DD_Get_RGB_Format ENDP
;########################################################################
; END DD_Get_RGB_Format
;########################################################################
Сначала, инициализируем структуру description, а затем делаем вызов из Direct Draw для получения surface description. Возвращенные маски мы разместим в глобальных переменных для их дальнейшего использования.
Маска это значение, которое мы можем использовать для установки или очистки некоторых бит в переменной или регистре. В нашем случае маски используются для того, чтобы получить доступ к red, green, и blue - битам пикселя.
Следующие три секции кода используется для определения числа битов для каждой цветовой компоненты. Например, если нам нужен цветовой режим 24 bpp, то на каждую компоненту нужно отводить по 8 бит. Делается это путем битового сдвига вправо и операции AND.
В случае установки 16-битного режима, переменная Is_555 становится TRUE для режима 5-5-5, или FALSE для режима 5-6-5.
И еще одна функция - для прорисовки текста. Она использует GDI:
;########################################################################
; DD_Draw_Text Procedure
;########################################################################
DD_Draw_Text PROC surface:DWORD, text:DWORD, num_chars:DWORD,
x:DWORD, y:DWORD, color:DWORD
;=======================================================
; Эта функция будет рисовать текст
; с помощью GDI
;=======================================================
;===========================================
; Для начала получим DC
;===========================================
DDS4INVOKE GetDC, surface, ADDR hDC
;===========================================
; установим цвет текста
;===========================================
INVOKE SetTextColor, hDC, color
INVOKE SetBkMode, hDC, TRANSPARENT
;===========================================
; запишем текст в позицию
;===========================================
INVOKE TextOut, hDC, x, y, text, num_chars
;===========================================
; release DC
;===========================================
DDS4INVOKE ReleaseDC, surface, hDC
done:
;===================
; все
;===================
return TRUE
DD_Draw_Text ENDP
;########################################################################
; END DD_Draw_Text
;########################################################################
Далее, получаем контекст устройства (DC) для нашей поверхности
- это первая вещь, которую нужно получить при рисовании. Устанавливаем background mode и цвет текста с помощью все той же Windows GDI - и мы готовы рисовать текст с помощью вызова TextOut(). После чего освобождаем DC.
Далее, напишем код для наложения bitmap.
--> Наша Bitmap Library
Нам нужны 2 процедуры: для загрузки битмапа и его прорисовки. В данном случае рассматривается уникальный формат файла.
Этот формат, вероятно один из самых простых, с которым вы когда-либо столкнетесь. Он состоит из 5 основных частей: Width, Height, BPP, Size of Buffer, Buffer. Первые 3 дают информацию о самом образе. В данном случае применяется режим 16 bpp.
;########################################################################
; Create_From_SFP Procedure
;########################################################################
Create_From_SFP PROC ptr_BMP:DWORD, sfp_file:DWORD, desired_bpp:DWORD
;=========================================================
; битмап будет загружаться из SFP file.
;=========================================================
;=================================
; Local Variables
;=================================
LOCAL hFile :DWORD
LOCAL hSFP :DWORD
LOCAL Img_Left :DWORD
LOCAL Img_Alias :DWORD
LOCAL red :DWORD
LOCAL green :DWORD
LOCAL blue :DWORD
LOCAL Dest_Alias :DWORD
;=================================
; Создадим этот SFP file
;=================================
INVOKE CreateFile, sfp_file, GENERIC_READ,FILE_SHARE_READ, \
NULL,OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,NULL
MOV hFile, EAX
;===============================
; ошибка
;===============================
.IF EAX == INVALID_HANDLE_VALUE
JMP err
.ENDIF
;===============================
; Получим размер файла
;===============================
INVOKE GetFileSize, hFile, NULL
PUSH EAX
;================================
; ошибка
;================================
.IF EAX == -1
JMP err
.ENDIF
;==============================================
; получим память
;==============================================
INVOKE GlobalAlloc, GMEM_FIXED, EAX
MOV hSFP, EAX
;===================================
; ошибка
;===================================
.IF EAX == 0
JMP err
.ENDIF
;===================================
; положим файл в память
;===================================
POP EAX
INVOKE ReadFile, hFile, hSFP, EAX, OFFSET Amount_Read, NULL
;====================================
; ошибка
;====================================
.IF EAX == FALSE
;========================
; failed
;========================
JMP err
.ENDIF
;===================================
; Определим размер
;===================================
MOV EBX, hSFP
MOV EAX, DWORD PTR [EBX]
ADD EBX, 4
MOV ECX, DWORD PTR [EBX]
MUL ECX
PUSH EAX
;======================================
; Разберемся с типом буфера
;======================================
.IF desired_bpp == 16
;============================
; просто установим 16-bit
;============================
POP EAX
SHL EAX, 1
INVOKE GlobalAlloc, GMEM_FIXED, EAX
MOV EBX, ptr_BMP
MOV DWORD PTR [EBX], EAX
MOV Dest_Alias, EAX
;====================================
; ошибка
;====================================
.IF EAX == FALSE
;========================
; облом
;========================
JMP err
.ENDIF
.ELSE
;========================================
; код для 24 bit
;========================================
;============================
; ошибка
;============================
JMP err
.ENDIF
;====================================
; подготовим чтение
;====================================
MOV EBX, hSFP
ADD EBX, 10
MOV EAX, DWORD PTR[EBX]
MOV Img_Left, EAX
ADD EBX, 4
MOV Img_Alias, EBX
;====================================
; конвертация
;====================================
.WHILE Img_Left > 0
;==================================
; создадим color word
;==================================
.IF desired_bpp == 16
;==========================================
; прочтем по байту для blue, green , red
;==========================================
XOR ECX, ECX
MOV EBX, Img_Alias
MOV CL, BYTE PTR [EBX]
MOV blue, ECX
INC EBX
MOV CL, BYTE PTR [EBX]
MOV green, ECX
INC EBX
MOV CL, BYTE PTR [EBX]
MOV red, ECX
;=======================
; Img_Alias
;=======================
ADD Img_Alias, 3
;================================
; 555 или 565 ?
;================================
.IF Is_555 == TRUE
;============================
; 555
;============================
RGB16BIT_555 red, green, blue
.ELSE
;============================
; 565
;============================
RGB16BIT_565 red, green, blue
.ENDIF
;================================
; перевод в buffer
;================================
MOV EBX, Dest_Alias
MOV WORD PTR [EBX], AX
;============================
; делим на 2
;============================
ADD Dest_Alias, 2
.ELSE
;========================================
; код для 24 bit
;========================================
;============================
; ошибка
;============================
JMP err
.ENDIF
;=====================
; Sub amount left by 3
;=====================
SUB Img_Left, 3
.ENDW
;====================================
; Почистим память
;====================================
INVOKE GlobalFree, hSFP
done:
;===================
; Вроде все хорошо
;===================
return TRUE
err:
;====================================
; Почистим SFP Memory
;====================================
INVOKE GlobalFree, hSFP
;===================
; Не получилось
;===================
return FALSE
Create_From_SFP ENDP
;########################################################################
; END Create_From_SFP
;########################################################################
Сначала создаем файл, а потом выделяем для него память и читаем данные.
После размещения файла в памяти определяем размер буфера.
Далее функция загрузки. Читаем 3 байта и определяем значение переменной ( 5-6-5 или 5-5-5 ) для буфера, после чего сохраняем ее там. Каждый пиксель битмапа конвертируем, для чего используется макрос.
После конвертации мы возвращаем буфер с отконвертированными пикселами.
После загрузки в память битмап можно нарисовать в back buffer:
;########################################################################
; Draw_Bitmap Procedure
;########################################################################
Draw_Bitmap PROC surface:DWORD, bmp_buffer:DWORD, lPitch:DWORD, bpp:DWORD
;=========================================================
; Эта функция рисует BMP .
; используются width и height экрана
;=========================================================
;===========================
; Локальные переменные
;===========================
LOCAL dest_addr :DWORD
LOCAL source_addr :DWORD
;===========================
; инициализация
;===========================
MOV EAX, surface
MOV EBX, bmp_buffer
MOV dest_addr, EAX
MOV source_addr, EBX
MOV EDX, 480
;=================================
; 16 bit mode
;=================================
copy_loop1:
;=============================
; Setup num of bytes in width
; 640*2/4 = 320.
;=============================
MOV ECX, 320
;=============================
; установим source и dest
;=============================
MOV EDI, dest_addr
MOV ESI, source_addr
;======================================
; Move с помощью dwords
;======================================
REP movsd
;==============================
; variables
;==============================
MOV EAX, lPitch
MOV EBX, 1280
ADD dest_addr, EAX
ADD source_addr, EBX
;========================
; декремент
;========================
DEC EDX
;========================
; Конец ?
;========================
JNE copy_loop1
done:
;===================
; Да
;===================
return TRUE
err:
;===================
; Нет
;===================
return FALSE
Draw_Bitmap ENDP
;########################################################################
; END Draw_Bitmap
;########################################################################
Общеизвестно, что обращение к регистрам осуществляется
намного быстрее, чем к памяти.
Поэтому адреса источника и приемника мы размещаем в регистрах. Затем вычисляем
количество WORD-значений, которое делим на 2, и получаем
количество DWORD-значений. Вообще, используем 640 x 480 x 16. Число 320 разместим в регистре ECX. Делаем классическое REP MOVSD. Двигаем DWORD-ми, вычитаем из ECX по 1, сравнивая с ZERO, если нет, то MOVE A DWORD, до тех пор пока ECX не станет равен 0. Все это здорово смахивает на
Си-шный for со счетчиком в ECX. Повторяем 480 раз - по количеству строк.
Теперь осталось все это вывести на экран.
--> A Game ...
Итак, функции библиотек мы написали, и теперь готовы приступать к основному коду игры. Начнем с инициализации, так как она выполняется в начале нашей программы:
;########################################################################
; Game_Init Procedure
;########################################################################
Game_Init PROC
;=========================================================
; setup the game
;=========================================================
;============================================
; инициализация Direct Draw -- 640, 480, bpp
;============================================
INVOKE DD_Init, 640, 480, screen_bpp
;====================================
; ошибка
;====================================
.IF EAX == FALSE
;========================
; облом
;========================
JMP err
.ENDIF
;======================================
; читаем битмап и создаем буффер
;======================================
INVOKE Create_From_SFP, ADDR ptr_BMP_LOAD, ADDR szLoading, screen_bpp
;====================================
; ошибка
;====================================
.IF EAX == FALSE
;========================
; облом
;========================
JMP err
.ENDIF
;===================================
; DirectDraw back buffer
;===================================
INVOKE DD_Lock_Surface, lpddsback, ADDR lPitch
;============================
; ошибка
;============================
.IF EAX == FALSE
;===================
; облом
;===================
JMP err
.ENDIF
;===================================
; рисуем битмап
;===================================
INVOKE Draw_Bitmap, EAX, ptr_BMP_LOAD, lPitch, screen_bpp
;===================================
; back buffer
;===================================
INVOKE DD_Unlock_Surface, lpddsback
;============================
; ошибка
;============================
.IF EAX == FALSE
;===================
; облом
;===================
JMP err
.ENDIF
;=====================================
; loading
;======================================
INVOKE DD_Flip
;============================
; ошибка
;============================
.IF EAX == FALSE
;===================
; облом
;===================
JMP err
.ENDIF
done:
;===================
; да
;===================
return TRUE
err:
;===================
; нет
;===================
return FALSE
Game_Init ENDP
;########################################################################
; END Game_Init
;########################################################################
Эта функция играет важную роль в нашей игре.
В этой функции мы делаем вызов процедуры инициализации Direct
Draw и в случае успеха загружаем с диска наш битмап. Далее беремся за back buffer и рисуем в него наш битмап. После чего делаем флиппинг видимого и невидимого буферов.
Далее у нас на пути функция WndProc, которая, как известно, обрабатывает сообщения. Если мы захотим добавить обработку еще какого-либо сообщения, то код его обработчика войдет именно в эту функцию.
;########################################################################
; Main Window Callback Procedure -- WndProc
;########################################################################
WndProc PROC hWin :DWORD,
uMsg :DWORD,
wParam :DWORD,
lParam :DWORD
.IF uMsg == WM_COMMAND
;===========================
; без меню
;===========================
.ELSEIF uMsg == WM_KEYDOWN
;=======================================
; не будем программировать Direct input
;=======================================
MOV EAX, wParam
.IF EAX == VK_ESCAPE
;===========================
; закрыть программу
;===========================
INVOKE PostQuitMessage,NULL
.ENDIF
;==========================
; processed it
;==========================
return 0
.ELSEIF uMsg == WM_DESTROY
;===========================
; закрыть программу
;===========================
INVOKE PostQuitMessage,NULL
return 0
.ENDIF
;=================================================
; procedure handle the message
;=================================================
INVOKE DefWindowProc,hWin,uMsg,wParam,lParam
RET
WndProc endp
;########################################################################
; End of Main Windows Callback Procedure
;########################################################################
Я думаю этот код не требует пояснений. Пока мы имеем дело только с 2-мя сообщениями - WM_KEYDOWN и WM_DESTROY. Мы обрабатываем сообщение WM_KEYDOWN для того,
чтобы пользователь мог выйти из игры по нажатию клавиши escape.
Обратите внимание, что те сообщения, которые мы не обрабатываем, обрабатываются функцией DefWindowProc(). Эта функция уже определена в Windows. Вы только должны вызвать ее всякий раз, когда не обрабатываете сообщение.
И далее процедура завершения:
;########################################################################
; Game_Shutdown Procedure
;########################################################################
Game_Shutdown PROC
;===========================
; Shutdown DirectDraw
;===========================
INVOKE DD_ShutDown
;==========================
; освобождаем память битмап'а
;==========================
INVOKE GlobalFree, ptr_BMP_LOAD
done:
;===================
; Завершено успешно
;===================
return TRUE
err:
;===================
; Мы не завершились
;===================
return FALSE
Game_Shutdown ENDP
;########################################################################
; END Game_Shutdown
;########################################################################
Итак, здесь мы выгружаем Direct Draw library, и освобождаем память, которую выделяли под битмап.
Счастливого кодирования!!!
[C] Chris Hobbs, пер. UniSoft
|
|