Программирование игр на ассемблере (Часть 2)

Дата публикации 29 окт 2002

Программирование игр на ассемблере (Часть 2) — Архив WASM.RU


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

В прошлой статье были рассмотрены основы 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. Вот несколько примеров объявления прототипов для ваших функций:

Код (Text):
  1.  
  2.     ;==================================
  3.     ; Main Program Procedures
  4.     ;==================================
  5.     WinMain PROTO       :DWORD,:DWORD,:DWORD,:DWORD
  6.     WndProc PROTO       :DWORD,:DWORD,:DWORD,:DWORD

Вышеупомянутый код сообщает ассемблеру, о двух новых процедурах WinMain и WndProc. Каждая из них имеет список параметров связанных с ними. В каждой функции задаются 4 параметра (DWORD). Для тех кто использует пакет MASM32, то в нем уже есть прототипы всех Windows API функций, вам просто нужно подключить соответствующий файл. Но вам надо убедиться, что все пользовательские процедуры определены вышеупомянутым способом.

Породив прототип, мы можем породить и саму функцию с помощью ключевого слова PROC:

Код (Text):
  1.  
  2. ;########################################################################
  3. ; WinMain Function
  4. ;########################################################################
  5. WinMain PROC    hInstance   :DWORD,
  6.         hPrevInst   :DWORD,
  7.         CmdLine     :DWORD,
  8.         CmdShow     :DWORD
  9.  
  10.  
  11.     ;===========================
  12.     ; We are through
  13.     ;===========================
  14.     return msg.wParam
  15.  
  16. WinMain endp
  17. ;########################################################################
  18. ; End of WinMain Procedure
  19. ;########################################################################
  20.  

При такой записи имеется доступ ко всем параметрам функции. Не правда ли - слишком просто для Winmain?

--> Основной игровой цикл

Теперь, когда мы все знаем, как писать на ассемблере, давайте приступим к написанию основного игрового цикла.

Начнем с WinMain().

Код (Text):
  1.  
  2.   .CODE
  3.  
  4. start:
  5.     ;==================================
  6.     ; Получим экземпляр
  7.     ; приложения
  8.     ;==================================
  9.     INVOKE GetModuleHandle, NULL
  10.     MOV hInst, EAX
  11.  
  12.     ;==================================
  13.     ; Как насчет командной строки?
  14.     ;==================================
  15.     INVOKE GetCommandLine
  16.     MOV CommandLine, EAX
  17.  
  18.     ;==================================
  19.     ; Вызов WinMain
  20.     ;==================================
  21.     INVOKE WinMain,hInst,NULL,CommandLine,SW_SHOWDEFAULT
  22.  
  23.     ;==================================
  24.     ; Выход
  25.     ;==================================
  26.     INVOKE ExitProcess,EAX
  27.  

Единственное, что здесь может оказаться немного странным [ можно подумать, что данный случай - исключение, а не правило. Это не странно, а естественно - прим. ред.], так это пересылка регистра EAX в переменную (MOV ...,EAX) в конце INVOKE. Причина в том, что все функции Windows (и Си функции в том числе), возвращают значение результата функции/процедуры в регистре EAX. Этот код вы можете использовать во всех программах, которые будете писать, по крайней мере, мне никогда не приходилось его изменять.

А теперь сам код:

Код (Text):
  1.  
  2. ;########################################################################
  3. ; WinMain Function
  4. ;########################################################################
  5. WinMain PROC    hInstance   :DWORD,
  6.         hPrevInst   :DWORD,
  7.         CmdLine     :DWORD,
  8.         CmdShow     :DWORD
  9.  
  10.     ;=========================
  11.     ; локальные переменные (размещаются в стеке)
  12.     ;=========================
  13.     LOCAL wc        :WNDCLASS
  14.  
  15.     ;==================================================
  16.     ; Заполнение структуры WNDCLASS требуемыми переменными
  17.     ;==================================================
  18.     MOV wc.style, CS_OWNDC
  19.     MOV wc.lpfnWndProc,OFFSET WndProc
  20.     MOV wc.cbClsExtra,NULL
  21.     MOV wc.cbWndExtra,NULL
  22.     m2m wc.hInstance,hInst          ;<< замечание: это макрос
  23.     INVOKE GetStockObject, BLACK_BRUSH
  24.     MOV wc.hbrBackground, EAX
  25.     MOV wc.lpszMenuName,NULL
  26.     MOV wc.lpszClassName,OFFSET szClassName
  27.     INVOKE LoadIcon, hInst, IDI_ICON ; icon ID
  28.     MOV wc.hIcon,EAX
  29.     INVOKE LoadCursor,NULL,IDC_ARROW
  30.     MOV wc.hCursor,EAX
  31.  
  32.     ;================================
  33.     ; Регистрация класса, который мы создали
  34.     ;================================
  35.     INVOKE RegisterClass, ADDR wc
  36.  
  37.     ;===========================================
  38.     ; Создание главного экрана
  39.     ;===========================================
  40.     INVOKE CreateWindowEx,NULL,
  41.             ADDR szClassName,
  42.                         ADDR szDisplayName,
  43.                         WS_POPUP OR WS_CLIPSIBLINGS OR \
  44.             WS_MAXIMIZE OR WS_CLIPCHILDREN,
  45.                         0,0,640,480,
  46.                         NULL,NULL,
  47.                         hInst,NULL
  48.        
  49.     ;===========================================
  50.     ; сохранить указатель на окно (хэндл)
  51.     ;===========================================
  52.     MOV hMainWnd, EAX
  53.  
  54.     ;====================================
  55.     ; Скрыть курсор
  56.     ;====================================
  57.     INVOKE ShowCursor, FALSE
  58.  
  59.     ;===========================================
  60.     ; Вывод на экран нашего окна, которое мы создали
  61.     ;===========================================
  62.     INVOKE ShowWindow, hMainWnd, SW_SHOWDEFAULT
  63.  
  64.     ;=================================
  65.     ; Инициализация игры
  66.     ;=================================
  67.     INVOKE Game_Init
  68.  
  69.     ;========================================
  70.     ; проверка на ошибки и выход в случае чего
  71.     ;========================================
  72.     .IF EAX != TRUE
  73.         JMP shutdown
  74.     .ENDIF
  75.  
  76.     ;===================================
  77.     ; Цикл, пока не будет послано PostQuitMessage
  78.     ;===================================
  79.     .WHILE TRUE
  80.         INVOKE  PeekMessage, ADDR msg, NULL, 0, 0, PM_REMOVE
  81.         .IF (EAX != 0)
  82.             ;===================================
  83.             ; Выход из цикла
  84.             ;===================================
  85.             MOV EAX, msg.message
  86.             .IF EAX == WM_QUIT
  87.                 ;======================
  88.                 ; Выход
  89.                 ;======================
  90.                 JMP shutdown
  91.             .ENDIF
  92.  
  93.             ;===================================
  94.             ; Translate and Dispatch the message
  95.             ;===================================
  96.             INVOKE  TranslateMessage, ADDR msg
  97.             INVOKE  DispatchMessage, ADDR msg
  98.  
  99.         .ENDIF
  100.  
  101.         ;================================
  102.         ; вызов главного игрового цикла
  103.         ;================================
  104.         INVOKE Game_Main
  105.  
  106.     .ENDW
  107.  
  108. shutdown:
  109.  
  110.     ;=================================
  111.     ; Завершение игры
  112.     ;=================================
  113.     INVOKE Game_Shutdown
  114.  
  115.     ;=================================
  116.     ; Показать курсор
  117.     ;=================================
  118.     INVOKE ShowCursor, TRUE
  119.  
  120. getout:
  121.     ;===========================
  122.     ; Завершение
  123.     ;===========================
  124.     return msg.wParam
  125.  
  126. WinMain endp
  127. ;########################################################################
  128. ; End of WinMain Procedure
  129. ;########################################################################
  130.  

Давайте проанализируем. Обратите внимание: при инициализации локальной переменной (в нашем случае это структура 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.

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

Код (Text):
  1.  
  2.     ;========================================
  3.     ; Создадим primary surface
  4.     ;========================================
  5.       DD4INVOKE CreateSurface, lpdd, ADDR ddsd, ADDR lpddsprimary, NULL
  6.  

В этом примере создается поверхность, вызовом функции CreateSurface(), передавая ей в качестве параметров: указатель на объект, адрес структуры Direct Draw Surface Describe (ddsd), адрес переменной для хранения указателя на поверхность, и наконец NULL. Теперь, когда мы увидели, как делать запросы к DirectX, давайте построим небольшую библиотеку.

--> Наша Direct Draw Library

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

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

Код (Text):
  1.  
  2. ;########################################################################
  3. ; DD_Init Procedure
  4. ;########################################################################
  5. DD_Init PROC screen_width:DWORD, screen_height:DWORD, screen_bpp:DWORD
  6.  
  7.         ;=======================================================
  8.         ; Устанавливаем полноэкранный режим
  9.         ;=======================================================
  10.  
  11.         ;=================================
  12.         ; Локальные переменные
  13.         ;=================================
  14.         LOCAL        lpdd_1          :LPDIRECTDRAW
  15.  
  16.         ;=============================
  17.         ; Создаем объект
  18.         ;=============================
  19.         INVOKE DirectDrawCreate, 0, ADDR lpdd_1, 0
  20.  
  21.         ;=============================
  22.         ; Обработка ошибок
  23.         ;=============================
  24.         .IF EAX != DD_OK
  25.                 ;======================
  26.                 ; Ошибка
  27.                 ;======================
  28.                 INVOKE MessageBox, hMainWnd, ADDR szNoDD, NULL, MB_OK
  29.  
  30.                 ;======================
  31.                 ; Выход
  32.                 ;======================
  33.                 JMP      err
  34.  
  35.         .ENDIF
  36.  
  37.         ;=========================================
  38.         ; Получим DirectDraw 4 object
  39.         ;=========================================
  40.         DDINVOKE QueryInterface, lpdd_1, ADDR IID_IDirectDraw4, ADDR lpdd
  41.  
  42.         ;=========================================
  43.         ; Получили ???
  44.         ;=========================================
  45.         .IF EAX != DD_OK
  46.                 ;==============================
  47.                 ; Нет
  48.                 ;==============================
  49.                 INVOKE MessageBox, hMainWnd, ADDR szNoDD4, NULL, MB_OK
  50.  
  51.                 ;======================
  52.                 ; Выход
  53.                 ;======================
  54.                 JMP      err
  55.  
  56.         .ENDIF
  57.  
  58.         ;===================================================
  59.         ; Установка cooperative level
  60.         ;===================================================
  61.         DD4INVOKE SetCooperativeLevel, lpdd, hMainWnd, \
  62.                 DDSCL_ALLOWMODEX OR DDSCL_FULLSCREEN OR \
  63.                 DDSCL_EXCLUSIVE OR DDSCL_ALLOWREBOOT
  64.  
  65.         ;=========================================
  66.         ; Получили ???
  67.         ;=========================================
  68.         .IF EAX != DD_OK
  69.                 ;==============================
  70.                 ; Нет
  71.                 ;==============================
  72.                 INVOKE MessageBox, hMainWnd, ADDR szNoCoop, NULL, MB_OK
  73.  
  74.                 ;======================
  75.                 ; Выход
  76.                 ;======================
  77.                 JMP      err
  78.  
  79.         .ENDIF
  80.  
  81.         ;===================================================
  82.         ; Установка Display Mode
  83.         ;===================================================
  84.         DD4INVOKE SetDisplayMode, lpdd, screen_width, \
  85.                 screen_height, screen_bpp, 0, 0
  86.  
  87.         ;=========================================
  88.         ; Установили ???
  89.         ;=========================================
  90.         .IF EAX != DD_OK
  91.                 ;==============================
  92.                 ; Нет
  93.                 ;==============================
  94.                 INVOKE MessageBox, hMainWnd, ADDR szNoDisplay, NULL, MB_OK
  95.  
  96.                 ;======================
  97.                 ; Выход
  98.                 ;======================
  99.                 JMP      err
  100.  
  101.         .ENDIF
  102.  
  103.         ;================================
  104.         ;  screen info
  105.         ;================================
  106.         m2m     app_width, screen_width
  107.         m2m     app_height, screen_height
  108.         m2m     app_bpp, screen_bpp
  109.  
  110.         ;========================================
  111.         ; Зададим параметры для поверхности (surface)
  112.         ;========================================
  113.         DDINITSTRUCT OFFSET ddsd, SIZEOF(DDSURFACEDESC2)
  114.         MOV      ddsd.dwSize, SIZEOF(DDSURFACEDESC2)
  115.         MOV      ddsd.dwFlags, DDSD_CAPS OR DDSD_BACKBUFFERCOUNT;
  116.         MOV      ddsd.ddsCaps.dwCaps, DDSCAPS_PRIMARYSURFACE OR \
  117.                         DDSCAPS_FLIP OR DDSCAPS_COMPLEX
  118.         MOV      ddsd.dwBackBufferCount, 1
  119.  
  120.         ;========================================
  121.         ; Создадим первичную поверхность (primary surface)
  122.         ;========================================
  123.         DD4INVOKE CreateSurface, lpdd, ADDR ddsd, ADDR lpddsprimary, NULL
  124.  
  125.         ;=========================================
  126.         ; Создали ???
  127.         ;=========================================
  128.         .IF EAX != DD_OK
  129.                 ;==============================
  130.                 ; Нет
  131.                 ;==============================
  132.                 INVOKE MessageBox, hMainWnd, ADDR szNoPrimary, NULL, MB_OK
  133.  
  134.                 ;======================
  135.                 ; Выход
  136.                 ;======================
  137.                 JMP      err
  138.  
  139.         .ENDIF
  140.  
  141.         ;==========================================
  142.         ; Попробуем получить  backbuffer
  143.         ;==========================================
  144.         MOV      ddscaps.dwCaps, DDSCAPS_BACKBUFFER
  145.         DDS4INVOKE GetAttachedSurface, lpddsprimary, ADDR ddscaps, ADDR lpddsback
  146.  
  147.         ;=========================================
  148.         ; Получили ???
  149.         ;=========================================
  150.         .IF EAX != DD_OK
  151.                 ;==============================
  152.                 ; Нет
  153.                 ;==============================
  154.                 INVOKE MessageBox, hMainWnd, ADDR szNoBackBuffer, NULL, MB_OK
  155.  
  156.                 ;======================
  157.                 ; Выход
  158.                 ;======================
  159.                 JMP      err
  160.  
  161.         .ENDIF
  162.  
  163.         ;==========================================
  164.         ; Получим RGB format для surface
  165.         ;==========================================
  166.         INVOKE DD_Get_RGB_Format, lpddsprimary
  167.  
  168. done:
  169.         ;===================
  170.         ; Все Ок!  <img src="styles/smiles_s/smile3.gif" class="mceSmilie" alt=":smile3:" title="Smile3    :smile3:">
  171.         ;===================
  172.         return TRUE
  173.  
  174. err:
  175.         ;===================
  176.         ; Ничего не Ок! :(
  177.         ;===================
  178.         return FALSE
  179.  
  180. DD_Init ENDP
  181. ;########################################################################
  182. ; END DD_Init
  183. ;########################################################################
  184.  

Рассмотрим подробнее.

Сначала создаем так называемый default Direct Draw object с помощью функции DirectDrawCreate(). Это не более, чем простой вызов функции с несколькими параметрами. Это еще не виртуальная функция. Поэтому мы можем вызывать ее с помощью INVOKE. Также, обратите внимание, что мы потом проверяем на ошибку. Это очень важно в DirectX!!! В случае ошибки, мы просто выводим сообщение, и переходим на метку err: в конце процедуры.

Далее делаем запрос на получение DirectDraw4 object. После чего устанавливаем режимы экрана с помощью SetCooperativeLevel() и SetDisplayMode(). Не забывайте проверять на ошибки, после каждого вызова функций.

На следующем шаге создаем первичную поверхность (primary surface), и в случае успеха создаем back buffer. При этом полученную структуру надо очистить с помощью макроса DDINITSTRUCT, который я включил в файл Ddraw.inc.

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

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

Код (Text):
  1.  
  2. ;########################################################################
  3. ; DD_Get_RGB_Format Procedure
  4. ;########################################################################
  5. DD_Get_RGB_Format       PROC surface:DWORD
  6.  
  7.         ;=========================================================
  8.         ; Установим несколько глобальных переменных
  9.         ;=========================================================
  10.  
  11.         ;====================================
  12.         ; Локальные переменные
  13.         ;====================================
  14.         LOCAL        shiftcount      :BYTE
  15.  
  16.         ;================================
  17.         ; получим  surface despriction
  18.         ;================================
  19.         DDINITSTRUCT ADDR ddsd, sizeof(DDSURFACEDESC2)
  20.         MOV      ddsd.dwSize, sizeof(DDSURFACEDESC2)
  21.         MOV      ddsd.dwFlags, DDSD_PIXELFORMAT
  22.         DDS4INVOKE GetSurfaceDesc, surface, ADDR ddsd
  23.  
  24.         ;==============================
  25.         ; маски
  26.         ;==============================
  27.         m2m     mRed, ddsd.ddpfPixelFormat.dwRBitMask      ; Red Mask
  28.         m2m     mGreen, ddsd.ddpfPixelFormat.dwGBitMask    ; Green Mask
  29.         m2m     mBlue, ddsd.ddpfPixelFormat.dwBBitMask     ; Blue Mask
  30.  
  31.         ;====================================
  32.         ; определим red mask
  33.         ;====================================
  34.         MOV      shiftcount, 0
  35.         .WHILE (!(ddsd.ddpfPixelFormat.dwRBitMask &amp; 1))
  36.                 SHR      ddsd.ddpfPixelFormat.dwRBitMask, 1
  37.                 INC      shiftcount
  38.         .ENDW
  39.         MOV      AL, shiftcount
  40.         MOV      pRed, AL
  41.  
  42.         ;=======================================
  43.         ; определим green mask
  44.         ;=======================================
  45.         MOV      shiftcount, 0
  46.         .WHILE (!(ddsd.ddpfPixelFormat.dwGBitMask &amp; 1))
  47.                 SHR      ddsd.ddpfPixelFormat.dwGBitMask, 1
  48.                 INC      shiftcount
  49.         .ENDW
  50.         MOV      AL, shiftcount
  51.         MOV      pGreen, AL
  52.  
  53.         ;=======================================
  54.         ; определим blue mask
  55.         ;=======================================
  56.         MOV      shiftcount, 0
  57.         .WHILE (!(ddsd.ddpfPixelFormat.dwBBitMask &amp; 1))
  58.                 SHR      ddsd.ddpfPixelFormat.dwBBitMask, 1
  59.                 INC      shiftcount
  60.         .ENDW
  61.         MOV      AL, shiftcount
  62.         MOV      pBlue, AL
  63.  
  64.         ;===========================================
  65.         ; определим специальную переменную для 16 bit mode
  66.         ;===========================================
  67.         .IF app_bpp == 16
  68.                 .IF pRed == 10
  69.                         MOV      Is_555, TRUE
  70.                 .ELSE
  71.                         MOV      Is_555, FALSE
  72.                 .ENDIF
  73.         .ENDIF
  74.  
  75. done:
  76.         ;===================
  77.         ; все
  78.         ;===================
  79.         return TRUE
  80.  
  81. DD_Get_RGB_Format       ENDP
  82. ;########################################################################
  83. ; END DD_Get_RGB_Format
  84. ;########################################################################
  85.  

Сначала, инициализируем структуру description, а затем делаем вызов из Direct Draw для получения surface description. Возвращенные маски мы разместим в глобальных переменных для их дальнейшего использования. Маска это значение, которое мы можем использовать для установки или очистки некоторых бит в переменной или регистре. В нашем случае маски используются для того, чтобы получить доступ к red, green, и blue - битам пикселя.

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

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

И еще одна функция - для прорисовки текста. Она использует GDI:

Код (Text):
  1.  
  2. ;########################################################################
  3. ; DD_Draw_Text Procedure
  4. ;########################################################################
  5. DD_Draw_Text PROC    surface:DWORD, text:DWORD, num_chars:DWORD,
  6.                         x:DWORD, y:DWORD, color:DWORD
  7.  
  8.         ;=======================================================
  9.         ; Эта функция будет рисовать текст
  10.         ; с помощью GDI
  11.         ;=======================================================
  12.  
  13.         ;===========================================
  14.         ; Для начала получим  DC
  15.         ;===========================================
  16.         DDS4INVOKE GetDC, surface, ADDR hDC
  17.  
  18.         ;===========================================
  19.         ; установим цвет текста
  20.         ;===========================================
  21.         INVOKE SetTextColor, hDC, color
  22.         INVOKE SetBkMode, hDC, TRANSPARENT
  23.  
  24.         ;===========================================
  25.         ; запишем текст в позицию
  26.         ;===========================================
  27.         INVOKE TextOut, hDC, x, y, text, num_chars
  28.  
  29.         ;===========================================
  30.         ; release DC
  31.         ;===========================================
  32.         DDS4INVOKE ReleaseDC, surface, hDC
  33.  
  34. done:
  35.         ;===================
  36.         ; все
  37.         ;===================
  38.         return TRUE
  39.  
  40. DD_Draw_Text    ENDP
  41. ;########################################################################
  42. ; END DD_Draw_Text
  43. ;########################################################################
  44.  

Далее, получаем контекст устройства (DC) для нашей поверхности - это первая вещь, которую нужно получить при рисовании. Устанавливаем background mode и цвет текста с помощью все той же Windows GDI - и мы готовы рисовать текст с помощью вызова TextOut(). После чего освобождаем DC.

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

--> Наша Bitmap Library

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

Этот формат, вероятно один из самых простых, с которым вы когда-либо столкнетесь. Он состоит из 5 основных частей: Width, Height, BPP, Size of Buffer, Buffer. Первые 3 дают информацию о самом образе. В данном случае применяется режим 16 bpp.

Код (Text):
  1.  
  2. ;########################################################################
  3. ; Create_From_SFP Procedure
  4. ;########################################################################
  5. Create_From_SFP PROC ptr_BMP:DWORD, sfp_file:DWORD, desired_bpp:DWORD
  6.  
  7.         ;=========================================================
  8.         ; битмап будет загружаться из SFP file.  
  9.         ;=========================================================
  10.  
  11.         ;=================================
  12.         ; Local Variables
  13.         ;=================================
  14.         LOCAL        hFile           :DWORD
  15.         LOCAL        hSFP            :DWORD
  16.         LOCAL        Img_Left        :DWORD
  17.         LOCAL        Img_Alias       :DWORD
  18.         LOCAL        red             :DWORD
  19.         LOCAL        green           :DWORD
  20.         LOCAL        blue            :DWORD
  21.         LOCAL        Dest_Alias      :DWORD
  22.  
  23.         ;=================================
  24.         ; Создадим этот SFP file
  25.         ;=================================
  26.         INVOKE CreateFile, sfp_file, GENERIC_READ,FILE_SHARE_READ, \
  27.                 NULL,OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,NULL
  28.         MOV      hFile, EAX
  29.  
  30.         ;===============================
  31.         ;  ошибка
  32.         ;===============================
  33.         .IF EAX == INVALID_HANDLE_VALUE
  34.                 JMP err
  35.         .ENDIF
  36.  
  37.         ;===============================
  38.         ; Получим размер файла
  39.         ;===============================
  40.         INVOKE GetFileSize, hFile, NULL
  41.         PUSH     EAX
  42.  
  43.         ;================================
  44.         ; ошибка
  45.         ;================================
  46.         .IF EAX == -1
  47.                 JMP      err
  48.         .ENDIF
  49.  
  50.         ;==============================================
  51.         ; получим память
  52.         ;==============================================
  53.         INVOKE GlobalAlloc, GMEM_FIXED, EAX
  54.         MOV      hSFP, EAX
  55.  
  56.         ;===================================
  57.         ;  ошибка
  58.         ;===================================
  59.         .IF EAX == 0
  60.                 JMP      err
  61.         .ENDIF
  62.  
  63.         ;===================================
  64.         ; положим файл в память
  65.         ;===================================
  66.         POP      EAX
  67.         INVOKE ReadFile, hFile, hSFP, EAX, OFFSET Amount_Read, NULL
  68.  
  69.         ;====================================
  70.         ; ошибка
  71.         ;====================================
  72.         .IF EAX == FALSE
  73.                 ;========================
  74.                 ; failed
  75.                 ;========================
  76.                 JMP      err
  77.  
  78.         .ENDIF
  79.  
  80.         ;===================================
  81.         ; Определим размер
  82.         ;===================================
  83.         MOV      EBX, hSFP
  84.         MOV      EAX, DWORD PTR [EBX]
  85.         ADD      EBX, 4
  86.         MOV      ECX, DWORD PTR [EBX]
  87.         MUL      ECX
  88.         PUSH     EAX
  89.  
  90.         ;======================================
  91.         ; Разберемся с типом буфера
  92.         ;======================================
  93.         .IF desired_bpp == 16
  94.                 ;============================
  95.                 ; просто установим 16-bit
  96.                 ;============================
  97.                 POP      EAX
  98.                 SHL      EAX, 1
  99.                 INVOKE GlobalAlloc, GMEM_FIXED, EAX
  100.                 MOV      EBX, ptr_BMP
  101.                 MOV      DWORD PTR [EBX], EAX
  102.                 MOV      Dest_Alias, EAX
  103.  
  104.                 ;====================================
  105.                 ;  ошибка
  106.                 ;====================================
  107.                 .IF EAX == FALSE
  108.                         ;========================
  109.                         ; облом
  110.                         ;========================
  111.                         JMP      err
  112.  
  113.                 .ENDIF
  114.  
  115.         .ELSE
  116.                 ;========================================
  117.                 ; код для 24 bit
  118.                 ;========================================
  119.  
  120.                 ;============================
  121.                 ;  ошибка
  122.                 ;============================
  123.                 JMP      err
  124.  
  125.         .ENDIF
  126.  
  127.         ;====================================
  128.         ; подготовим чтение
  129.         ;====================================
  130.         MOV      EBX, hSFP
  131.         ADD      EBX, 10
  132.         MOV      EAX, DWORD PTR[EBX]
  133.         MOV      Img_Left, EAX
  134.         ADD      EBX, 4
  135.         MOV      Img_Alias, EBX
  136.  
  137.         ;====================================
  138.         ; конвертация
  139.         ;====================================
  140.         .WHILE Img_Left &gt; 0
  141.                 ;==================================
  142.                 ; создадим color word  
  143.                 ;==================================
  144.                 .IF desired_bpp == 16
  145.                         ;==========================================
  146.                         ; прочтем по байту для blue, green , red
  147.                         ;==========================================
  148.                         XOR      ECX, ECX
  149.                         MOV      EBX, Img_Alias
  150.                         MOV      CL, BYTE PTR [EBX]
  151.                         MOV      blue, ECX
  152.                         INC      EBX
  153.                         MOV      CL, BYTE PTR [EBX]
  154.                         MOV      green, ECX
  155.                         INC      EBX
  156.                         MOV      CL, BYTE PTR [EBX]
  157.                         MOV      red, ECX
  158.  
  159.                         ;=======================
  160.                         ; Img_Alias
  161.                         ;=======================
  162.                         ADD      Img_Alias, 3
  163.  
  164.                         ;================================
  165.                         ;  555 или 565 ?
  166.                         ;================================
  167.                         .IF Is_555 == TRUE
  168.                                 ;============================
  169.                                 ;  555
  170.                                 ;============================
  171.                                 RGB16BIT_555 red, green, blue
  172.                         .ELSE
  173.                                 ;============================
  174.                                 ;  565
  175.                                 ;============================
  176.                                 RGB16BIT_565 red, green, blue
  177.  
  178.                         .ENDIF
  179.  
  180.                         ;================================
  181.                         ; перевод в  buffer
  182.                         ;================================
  183.                         MOV      EBX, Dest_Alias
  184.                         MOV      WORD PTR [EBX], AX
  185.  
  186.                         ;============================
  187.                         ; делим на 2
  188.                         ;============================
  189.                         ADD      Dest_Alias, 2
  190.  
  191.                 .ELSE
  192.                         ;========================================
  193.                         ; код для 24 bit
  194.                         ;========================================
  195.  
  196.                         ;============================
  197.                         ;  ошибка
  198.                         ;============================
  199.                         JMP      err
  200.  
  201.                 .ENDIF
  202.  
  203.                 ;=====================
  204.                 ; Sub amount left by 3
  205.                 ;=====================
  206.                 SUB      Img_Left, 3
  207.  
  208.         .ENDW
  209.  
  210.         ;====================================
  211.         ; Почистим память
  212.         ;====================================
  213.         INVOKE GlobalFree, hSFP
  214.  
  215. done:
  216.         ;===================
  217.         ; Вроде все хорошо
  218.         ;===================
  219.         return TRUE
  220.  
  221. err:
  222.         ;====================================
  223.         ; Почистим SFP Memory
  224.         ;====================================
  225.         INVOKE GlobalFree, hSFP
  226.  
  227.         ;===================
  228.         ; Не получилось
  229.         ;===================
  230.         return FALSE
  231.  
  232. Create_From_SFP ENDP
  233. ;########################################################################
  234. ; END Create_From_SFP
  235. ;########################################################################
  236.  

Сначала создаем файл, а потом выделяем для него память и читаем данные. После размещения файла в памяти определяем размер буфера.

Далее функция загрузки. Читаем 3 байта и определяем значение переменной ( 5-6-5 или 5-5-5 ) для буфера, после чего сохраняем ее там. Каждый пиксель битмапа конвертируем, для чего используется макрос.

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

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

Код (Text):
  1.  
  2. ;########################################################################
  3. ; Draw_Bitmap Procedure
  4. ;########################################################################
  5. Draw_Bitmap PROC     surface:DWORD, bmp_buffer:DWORD, lPitch:DWORD, bpp:DWORD
  6.  
  7.         ;=========================================================
  8.         ; Эта функция рисует BMP .
  9.         ; используются width и height экрана
  10.         ;=========================================================
  11.  
  12.         ;===========================
  13.         ; Локальные переменные
  14.         ;===========================
  15.         LOCAL        dest_addr       :DWORD
  16.         LOCAL        source_addr     :DWORD
  17.  
  18.         ;===========================
  19.         ; инициализация
  20.         ;===========================
  21.         MOV      EAX, surface
  22.         MOV      EBX, bmp_buffer
  23.         MOV      dest_addr, EAX
  24.         MOV      source_addr, EBX
  25.  
  26.         MOV      EDX, 480
  27.  
  28.         ;=================================
  29.         ; 16 bit mode
  30.         ;=================================
  31.  
  32.         copy_loop1:
  33.         ;=============================
  34.         ; Setup num of bytes in width
  35.         ; 640*2/4 = 320.
  36.         ;=============================
  37.         MOV      ECX, 320
  38.  
  39.         ;=============================
  40.         ; установим source и dest
  41.         ;=============================
  42.         MOV      EDI, dest_addr
  43.         MOV      ESI, source_addr
  44.  
  45.         ;======================================
  46.         ; Move с помощью dwords
  47.         ;======================================
  48.         REP      movsd
  49.  
  50.         ;==============================
  51.         ;  variables
  52.         ;==============================
  53.         MOV      EAX, lPitch
  54.         MOV      EBX, 1280
  55.         ADD      dest_addr, EAX
  56.         ADD      source_addr, EBX
  57.  
  58.         ;========================
  59.         ; декремент
  60.         ;========================
  61.         DEC EDX
  62.  
  63.         ;========================
  64.         ; Конец ?
  65.         ;========================
  66.         JNE copy_loop1
  67.  
  68. done:
  69.         ;===================
  70.         ; Да
  71.         ;===================
  72.         return TRUE
  73.  
  74. err:
  75.         ;===================
  76.         ; Нет
  77.         ;===================
  78.         return FALSE
  79.  
  80. Draw_Bitmap     ENDP
  81. ;########################################################################
  82. ; END Draw_Bitmap
  83. ;########################################################################
  84.  

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

Затем вычисляем количество 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 ...

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

Код (Text):
  1.  
  2. ;########################################################################
  3. ; Game_Init Procedure
  4. ;########################################################################
  5. Game_Init       PROC
  6.  
  7.         ;=========================================================
  8.         ;  setup the game
  9.         ;=========================================================
  10.  
  11.         ;============================================
  12.         ; инициализация Direct Draw -- 640, 480, bpp
  13.         ;============================================
  14.         INVOKE DD_Init, 640, 480, screen_bpp
  15.  
  16.         ;====================================
  17.         ; ошибка
  18.         ;====================================
  19.         .IF EAX == FALSE
  20.                 ;========================
  21.                 ; облом
  22.                 ;========================
  23.                 JMP      err
  24.  
  25.         .ENDIF
  26.  
  27.         ;======================================
  28.         ; читаем битмап и создаем буффер
  29.         ;======================================
  30.         INVOKE Create_From_SFP, ADDR ptr_BMP_LOAD, ADDR szLoading, screen_bpp
  31.  
  32.         ;====================================
  33.         ;  ошибка
  34.         ;====================================
  35.         .IF EAX == FALSE
  36.                 ;========================
  37.                 ; облом
  38.                 ;========================
  39.                 JMP      err
  40.  
  41.         .ENDIF
  42.  
  43.         ;===================================
  44.         ;  DirectDraw back buffer
  45.         ;===================================
  46.         INVOKE DD_Lock_Surface, lpddsback, ADDR lPitch
  47.  
  48.         ;============================
  49.         ;  ошибка
  50.         ;============================
  51.         .IF EAX == FALSE
  52.                 ;===================
  53.                 ;  облом
  54.                 ;===================
  55.                 JMP      err
  56.  
  57.         .ENDIF
  58.  
  59.         ;===================================
  60.         ; рисуем битмап
  61.         ;===================================
  62.         INVOKE Draw_Bitmap, EAX, ptr_BMP_LOAD, lPitch, screen_bpp
  63.  
  64.         ;===================================
  65.         ;  back buffer
  66.         ;===================================
  67.         INVOKE DD_Unlock_Surface, lpddsback
  68.  
  69.         ;============================
  70.         ; ошибка
  71.         ;============================
  72.         .IF EAX == FALSE
  73.                 ;===================
  74.                 ;  облом
  75.                 ;===================
  76.                 JMP      err
  77.  
  78.         .ENDIF
  79.  
  80.         ;=====================================
  81.         ;  loading
  82.         ;======================================
  83.         INVOKE DD_Flip
  84.  
  85.         ;============================
  86.         ;  ошибка
  87.         ;============================
  88.         .IF EAX == FALSE
  89.                 ;===================
  90.                 ;  облом
  91.                 ;===================
  92.                 JMP      err
  93.  
  94.         .ENDIF
  95.  
  96. done:
  97.         ;===================
  98.         ; да
  99.         ;===================
  100.         return TRUE
  101.  
  102. err:
  103.         ;===================
  104.         ; нет
  105.         ;===================
  106.         return FALSE
  107.  
  108. Game_Init       ENDP
  109. ;########################################################################
  110. ; END Game_Init
  111. ;########################################################################
  112.  

Эта функция играет важную роль в нашей игре. В этой функции мы делаем вызов процедуры инициализации Direct Draw и в случае успеха загружаем с диска наш битмап. Далее беремся за back buffer и рисуем в него наш битмап. После чего делаем флиппинг видимого и невидимого буферов.

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

Код (Text):
  1.  
  2. ;########################################################################
  3. ; Main Window Callback Procedure -- WndProc
  4. ;########################################################################
  5. WndProc PROC hWin   :DWORD,
  6.                 uMsg   :DWORD,
  7.                 wParam :DWORD,
  8.                 lParam :DWORD
  9.  
  10. .IF uMsg == WM_COMMAND
  11.         ;===========================
  12.         ; без меню
  13.         ;===========================
  14.  
  15. .ELSEIF uMsg == WM_KEYDOWN
  16.         ;=======================================
  17.         ; не будем программировать Direct input
  18.         ;=======================================
  19.         MOV      EAX, wParam
  20.         .IF EAX == VK_ESCAPE
  21.                 ;===========================
  22.                 ; закрыть программу
  23.                 ;===========================
  24.                 INVOKE PostQuitMessage,NULL
  25.  
  26.         .ENDIF
  27.  
  28.         ;==========================
  29.         ; processed it
  30.         ;==========================
  31.         return 0
  32.  
  33. .ELSEIF uMsg == WM_DESTROY
  34.         ;===========================
  35.         ; закрыть программу
  36.         ;===========================
  37.         INVOKE PostQuitMessage,NULL
  38.         return 0
  39.  
  40. .ENDIF
  41.  
  42. ;=================================================
  43. ; procedure handle the message
  44. ;=================================================
  45. INVOKE DefWindowProc,hWin,uMsg,wParam,lParam
  46.  
  47. RET
  48.  
  49. WndProc endp
  50. ;########################################################################
  51. ; End of Main Windows Callback Procedure
  52. ;########################################################################
  53.  

Я думаю этот код не требует пояснений. Пока мы имеем дело только с 2-мя сообщениями - WM_KEYDOWN и WM_DESTROY. Мы обрабатываем сообщение WM_KEYDOWN для того, чтобы пользователь мог выйти из игры по нажатию клавиши escape. Обратите внимание, что те сообщения, которые мы не обрабатываем, обрабатываются функцией DefWindowProc(). Эта функция уже определена в Windows. Вы только должны вызвать ее всякий раз, когда не обрабатываете сообщение.

И далее процедура завершения:

Код (Text):
  1.  
  2. ;########################################################################
  3. ; Game_Shutdown Procedure
  4. ;########################################################################
  5. Game_Shutdown   PROC
  6.  
  7.         ;===========================
  8.         ; Shutdown DirectDraw
  9.         ;===========================
  10.         INVOKE DD_ShutDown
  11.  
  12.         ;==========================
  13.         ; освобождаем память битмап'а
  14.         ;==========================
  15.         INVOKE GlobalFree, ptr_BMP_LOAD
  16.  
  17. done:
  18.         ;===================
  19.         ; Завершено успешно
  20.         ;===================
  21.         return TRUE
  22.  
  23. err:
  24.         ;===================
  25.         ; Мы не завершились
  26.         ;===================
  27.         return FALSE
  28.  
  29. Game_Shutdown   ENDP
  30. ;########################################################################
  31. ; END Game_Shutdown
  32. ;########################################################################
  33.  

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

--> В следующей статье...

В следующей статье мы познакомимся с программированием Direct Input. А также создадим свое меню.

Счастливого кодирования!!! © Chris Hobbs, пер. UniSoft


0 1.161
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532