Программирование игр на Ассемблере
(часть II)

На чем мы остановились?

А мы и не останавливались ... Рассмотрим конструкции MASM , которые являются удобоваримыми в сравнительном смысле с аналогичными конструкциями на си . Затем рассмотрим основной цикл в главных виндовских процедурах . После мы обратим свои взоры на Direct Draw . Поняв , как он работает , мы сможем построить свою собственную Direct Draw Library . Затем мы построим bitmap file library . И в конце можно будет написать небольшую Loading Game .

Для компиляции нам потребуется MASM32 , в частности его версия MASM 6.11+

Синтаксис MASM

Досовские варианты ассемблерных листингов представляют в своем большинстве собрание весьма неудобоваримых сочинений , порой непонятных даже квалифицированным программистам . Очень много меток , jmp-ов и прочей нечисти . Но asm не стоит на месте , и в 6-м MASM-е макро-конструкции становятся неотьемлемым инструментом разработки .

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-конструкции .

Далее , рассмотрим такие ключевые слова MASM-а , как PROTO и PROC. Декларирование прототипов выполняется так - объявление функций :

     ;==================================
        ; Main Program Procedures
        ;==================================
        WinMain PROTO           :DWORD,:DWORD,:DWORD,:DWORD
        WndProc PROTO           :DWORD,:DWORD,:DWORD,:DWORD

В каждой фунции задаются 4 параметра . Вообще , каждая 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
        

Обратите внимание на MOV EAX в конце - причина этого в том , что все виндовские функции фозвращают значеие в регистре 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   ;<< NOTE: macro not mnemonic
        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

        ;================================
        ; Register 
        ;================================
        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

        ;====================================
        ; cursor
        ;====================================
        INVOKE ShowCursor, FALSE

        ;===========================================
        ; выводим на экран
        ;===========================================
        INVOKE ShowWindow, hMainWnd, SW_SHOWDEFAULT

        ;=================================
        ; инициализация Game
        ;=================================
        INVOKE Game_Init

        ;========================================
        ; ошибка 
        ;========================================
        .IF EAX != TRUE
                JMP      shutdown
        .ENDIF

        ;===================================
        ; цикл
        ;===================================
        .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

                        ;===================================
                        ; message
                        ;===================================
                        INVOKE       TranslateMessage, ADDR msg
                        INVOKE       DispatchMessage, ADDR msg

                .ENDIF

                ;================================
                ; вызов главного игрового цикла 
                ;================================
                INVOKE Game_Main

        .ENDW

shutdown:

        ;=================================
        ; Shutdown the Game
        ;=================================
        INVOKE Game_Shutdown

        ;=================================
        ; Show the Cursor
        ;=================================
        INVOKE ShowCursor, TRUE

getout:
        ;===========================
        ; приплыли 
        ;===========================
        return msg.wParam

WinMain endp
;########################################################################
; End of WinMain Procedure
;########################################################################
        

Давайте проанализируем . Обратите внимание : при инициализации локальной переменной в начале функции не нужно никакой возни со стеком push/pop , как и в ее конце - TEFAL всегда думает о нас ... Это вам не ДОС-овский asm , где для перемещения данных из одного места памяти в другое все нужно делать через - чуть не сказал , через что , через регистры и стек , конечно .

Далее , мы создаем окно и прячем курсор , ибо он нам в игре не нужен . Показываем окно и вызываем Game_Init() . Вообще , при отладке asm-процедур всегда нужно помнить , что должна быть одна точка входа и одна точка выхода .

Дальше идет обработка message loop , которые могут поступать откуда угодно . Для их обработки мы используем обычную GetMessage() , поскольку вопрос о скорости пока не стоит . Если никаких сообщений не поступает , работает PeekMessage()

И в конце , когда мы получаем quit message , мы выходим из цикла .

Связь с Direct Draw

Мы не будем рассматривать сам DirectX на уровне асм-а , а расмотрим его на уровне основных концепций .

Прежде всего , необходимо понять саму концепцию Таблицы Виртуальных Функций . Делается запрос в нее в форме смещения , и получается АДРЕС расположения функции в ней . Т.е. под вызовом функции понимается обращение к таблице . Которая УЖЕ существует . Адреса функций имеются в DirectX-библиотеке .

Далее , необходимо определить адрес обьекта , для которого вызывается функция . Вычисляем виртуальный адрес и сохраняем в стеке все параметры . Для этой цели существуют различные макросы , в частности , DD4INVOKE из 4-го еще директа .

Сначала определяем имя функции , затем имя обьекта , и затем параметры :

    ;========================================
        ; Создадим primary surface
        ;========================================
        DD4INVOKE CreateSurface, lpdd, ADDR ddsd, ADDR lpddsprimary, NULL

В этом примере вызывается функция CreateSurface(). Устанавливаются указатели на обект и на поверхность . На основе этого мы построим небольшую библиотеку .

Наша Direct Draw Library

Нам понадобятся функции для инициализации и выхода из игры , для определения формата пиксела , создания и прорисовки поверхностей , загрузки в нее битмапа .

Далее код функции инициализации :


;########################################################################
; DD_Init Procedure
;########################################################################
DD_Init PROC screen_width:DWORD, screen_height:DWORD, screen_bpp:DWORD

        ;=======================================================
        ; Устанавливается полноэкранный режим
        ;=======================================================

        ;=================================
        ; Local Variables
        ;=================================
        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() . Это еще не виртуальная функция . В случае ошибки вызова мы просто прыгаем на метку err: в конце функции

Далее устанавливаем режим экрана с помощью SetCooperativeLevel() и SetDisplayMode() .

На следующем шаге создаем primary surface , и в случае успеха создаем back buffer . При этом полученную структуру надобно очистить . с помощью макроса DDINITSTRUCT .

После вызывается процедура для определения формата пиксела .

В следующей процедуре мы получим формат пиксела :


;########################################################################
; DD_Get_RGB_Format Procedure
;########################################################################
DD_Get_RGB_Format       PROC surface:DWORD

        ;=========================================================
        ; Установим несколько глобальных переменных
        ;=========================================================

        ;====================================
        ; Local variables
        ;====================================
        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 structure и делаем вызов из Direct Draw для получения surface description . Возвращенные маски мы разместим в глобальных переменных для их дальнейшего использования . В нашем случае маски используются для того , чтобы поиметь доступ к red, green, или blue - битам пиксела .

Следующая секция используется для определения числа битов для каждой цветовой компоненты . Например , если нам нужен цветовой режим 24 bpp, то на каждую компоненту нужно отводить по 8 бит . Делается это путем битового сдвига вправо и операции AND .

В случае установки 16-битного режима , переменная Is_555 становится TRUE .

Еще одна функция - для прорисовки текста . Она использует 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

        ;===========================================
        ; установим text color 
        ;===========================================
        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
;########################################################################
        

Далее , получим Device Context - это первая вещь , которую нужно получить при рисовании . Устанавливаем background mode и text color с помощью все той же Windows GDI - и мы готовы рисовать текст с помощью вызова TextOut().

Далее , запишем код для наложения bitmap .

Наша Bitmap Library

Нам нужны 2 процедуры : для загрузки битмапа и его прорисовки . В данном случае рассматривается уникальный формат файла .

Этот формат имеет 5 основных частей : Width, Height, BPP, Size of Buffer, Buffer. Первые 3 дают информацию о самом образе . В данном случае применяется режимr 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

        ;===============================
        ;  error
        ;===============================
        .IF EAX == INVALID_HANDLE_VALUE
                JMP err
        .ENDIF

        ;===============================
        ; Получим размер файла 
        ;===============================
        INVOKE GetFileSize, hFile, NULL
        PUSH     EAX

        ;================================
        ; error
        ;================================
        .IF EAX == -1
                JMP      err
        .ENDIF

        ;==============================================
        ; получим memory 
        ;==============================================
        INVOKE GlobalAlloc, GMEM_FIXED, EAX
        MOV      hSFP, EAX

        ;===================================
        ;  error
        ;===================================
        .IF EAX == 0
                JMP      err
        .ENDIF

        ;===================================
        ; положим файл в память
        ;===================================
        POP      EAX
        INVOKE ReadFile, hFile, hSFP, EAX, OFFSET Amount_Read, NULL

        ;====================================
        ; error
        ;====================================
        .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

                ;====================================
                ;  error
                ;====================================
                .IF EAX == FALSE
                        ;========================
                        ; облом 
                        ;========================
                        JMP      err

                .ENDIF

        .ELSE
                ;========================================
                ; код для 24 bit 
                ;========================================

                ;============================
                ;  err
                ;============================
                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 
                        ;========================================

                        ;============================
                        ;  err
                        ;============================
                        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 or 5-5-5 ) для буффера , после чего сохраняем ее там . Каждый пиксел битмапа мы конвертируем , для чего используется макрос .

После конвертации мы возвращаем буфер с отконвертированными пикселами .

После загрузки в память битмап можно нарисовать в back buffer :


;########################################################################
; Draw_Bitmap Procedure
;########################################################################
Draw_Bitmap PROC     surface:DWORD, bmp_buffer:DWORD, lPitch:DWORD, bpp:DWORD

        ;=========================================================
        ; Эта функция рисует BMP . 
        ; используются width и height экрана
        ;=========================================================

        ;===========================
        ; Local Variables
        ;===========================
        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
;########################################################################
        

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

Затем вычисляется число WORDS , которое делится на 2 , и получаем число DWORDS . Вообще , используем 640 x 480 x 16 . Число 320 разместим в регистре ECX. Делаем классическое REP MOVSD . Двигаем DWORD-ми, вычитаем из ECX по 1, сравнивая с ZERO , если нет , то MOVE A DWORD, до тех пор пока ECX не станет zero. Все это здорово смахивает на си-шный for со счетчиком в ECX. И все это повторим 480 раз - по количеству строк .

Теперь осталось все это вывести на экран .

Game

  Начнем с инициализации :


;########################################################################
; Game_Init Procedure
;########################################################################
Game_Init       PROC

        ;=========================================================
        ;  setup the game
        ;=========================================================

        ;============================================
        ; инициализация Direct Draw -- 640, 480, bpp
        ;============================================
        INVOKE DD_Init, 640, 480, screen_bpp

        ;====================================
        ; error
        ;====================================
        .IF EAX == FALSE
                ;========================
                ; failed 
                ;========================
                JMP      err

        .ENDIF

        ;======================================
        ; читаем bitmap и создаем buffer
        ;======================================
        INVOKE Create_From_SFP, ADDR ptr_BMP_LOAD, ADDR szLoading, screen_bpp

        ;====================================
        ;  error
        ;====================================
        .IF EAX == FALSE
                ;========================
                ; failed 
                ;========================
                JMP      err

        .ENDIF

        ;===================================
        ;  DirectDraw back buffer
        ;===================================
        INVOKE DD_Lock_Surface, lpddsback, ADDR lPitch

        ;============================
        ;  error
        ;============================
        .IF EAX == FALSE
                ;===================
                ;  err
                ;===================
                JMP      err

        .ENDIF

        ;===================================
        ; рисуем bitmap 
        ;===================================
        INVOKE Draw_Bitmap, EAX, ptr_BMP_LOAD, lPitch, screen_bpp

        ;===================================
        ;  back buffer
        ;===================================
        INVOKE DD_Unlock_Surface, lpddsback

        ;============================
        ; error
        ;============================
        .IF EAX == FALSE
                ;===================
                ;  err
                ;===================
                JMP      err

        .ENDIF

        ;=====================================
        ;  loading 
        ;======================================
        INVOKE DD_Flip

        ;============================
        ;  error
        ;============================
        .IF EAX == FALSE
                ;===================
                ;  err
                ;===================
                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
                ;===========================
                ; грохнем application
                ;===========================
                INVOKE PostQuitMessage,NULL

        .ENDIF

        ;==========================
        ; processed it
        ;==========================
        return 0

.ELSEIF uMsg == WM_DESTROY
        ;===========================
        ; грохнем application
        ;===========================
        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 . Все остальные сообщения по умолчанию обрабатываются функцией DefWindowProc().

И далее - shutdown-код :


;########################################################################
; Game_Shutdown Procedure
;########################################################################
Game_Shutdown   PROC

        ;===========================
        ; Shutdown DirectDraw
        ;===========================
        INVOKE DD_ShutDown

        ;==========================
        ; Free the bitmap memory
        ;==========================
        INVOKE GlobalFree, ptr_BMP_LOAD

done:
        ;===================
        ; We completed
        ;===================
        return TRUE

err:
        ;===================
        ; We didn't make it
        ;===================
        return FALSE

Game_Shutdown   ENDP
;########################################################################
; END Game_Shutdown
;########################################################################

        

Итак , мы выгружаем Direct Draw library , и освобождаем память .

Назад

Сайт создан в системе uCoz