::Главная страница :: С++/Си :: Статьи :: Часть 2

Основы программирования игр с использованием DirectDraw
(перевод статьи Doug Klopfenstein "Basics of DirectDraw Game Programming") (Часть 1)

Материал взят с "Мир программирования"

Введение

DirectDraw® - это часть выпущенного компанией Microsoft программного продукта Microsoft® DirectX® 5 Software Development Kit (SDK). Для тех из вас, кто живет на необитаемых островах, скажу, что DirectX 5 SDK - последняя версия инструмента разработчика, изначально называвшаяся Game SDK. DirectX 5 SDK содержит набор динамически подключаемых библиотек (DLL) для ускорения операций с графикой, сервисы для работы с 3D, ускорение обработки звука, расширенные функции связи, функции для работы с джойстиком и CD-ROM.

Хотя тем для описания в DirectX 5 SDK очень много, в этой статье я собираюсь рассказать, как начать программировать графическую часть игры с помощью DirectDraw. Этот процесс относительно прост, хотя и требует незначительных познаний интерфейсов OLE и Component Object Model (COM). Однако, не паникуйте. Вся информация, которую вам нужно знать об OLE и COM, будет изложена в статье.

После прочтения этой статьи вы будете в состоянии написать простую игру с использованием DirectDraw. Однако в статье рассматриваются лишь основные моменты программирования DirectDraw. В примерах рассматриваются только полноэкранные режимы с переключением страниц. Я не собираюсь рассказывать о применении DirectDraw в окне, наложениях текстур на 3D поверхность, использования видеоклипов на поверхностях DirectDraw, или как рендерить поверхности с использованием DirectDraw. Если вы интересуетесь этими темами, можно почитать документацию по DirectX 5. (Документация DirectX 5 находится в библиотеке MSDN™ в разделе Platform SDK/Graphics and Multimedia Services.)

Требования DirectX 5 SDK

DirectX 5 SDK может работать как под платформой Windows® 95, так и Windows NT® 5.0. Далее предполагается, что вы используете IBM-PC–совместимый компьютер под управлением Windows 95. Плюс, для программирования, компилирования, и запуска программ, использующих DirectDraw, вам нужен инсталлированный DirectX 5 SDK. Вы можете скачать SDK по адресу http://www.microsoft.com/directx/resources/devdl.htm. DirectX 5 SDK также распространяется через библиотеку MSDN (только для подписчиков уровня Professional или выше).

Также предполагается, что у вас есть C или C++ компилятор, способный откомпилировать 32-битное приложение, и что у вас есть достаточный запас знаний по программированию на C или C++. Возможно использование других языков программирования для работы с DirectX 5 SDK, но в статье они не рассматриваются. Также вам необходимы познания в программировании под Windows.

Если вы используете компилятор C, вам необходимо проинсталлировать Win32® SDK. Win32 SDK содержит библиотеки, которые нужны вам, чтобы создать исполняемые файлы из примеров, поставляемых с DirectX 5 SDK.

DirectDraw API

DirectDraw - одна из компонент DirectX 5 SDK. DirectDraw был создан для быстрой работы; библиотека позволяет получить доступ к оборудованию на самом нижнем уровне, исключая многочисленные надстройки, обычно связанные с программированием Windows-based графики. Это делает DirectDraw идеальным средством для программирования игр, где самое главное - быстрый вывод графики на экран.

Но, конечно, основное преимущество DirectDraw - это то, что предоставляется одинаковый (общий) интерфейс к видеоадаптерам различных производителей Вам не нужно беспокоиться о том, на каком оборудовании будет работать ваша программа. DirectDraw использует информацию, находящуюся на абстрактном аппаратном уровне (HAL - Hardware Abstraction Level) для определения возможностей видеоадаптера. (Информация HAL - забота производителя видеоадаптера.) HAL определяет общий интерфейс между видеоадаптерами различных производителей и приложением, использующим DirectDraw.

Однако, DirectDraw не ограничивает вас только аппаратными возможностями видеоадаптера. Если вашей игре нужно специфичное оборудование или поддержка специфичных функций, а такого оборудования нет или функция не поддерживается, ваше приложение будет использовать уровень эмуляции аппаратуры (HEL - Hardware Emulation Level), включенный в DirectDraw. В этом случае DirectDraw использует встроенную эмуляцию для "создания" тех условий, которые требуются. Конечно, у HEL есть свои недостатки, в основном связанные с быстродействием. Этот вопрос будет рассмотрен позже, в статье Determining the Capabilities of the Display Hardware.

Следующий рисунок показывает связь между DirectDraw и графическими компонентами Windows:

Рисунок 1. Связь между DirectDraw и графическими компонентами Windows

API DirectDraw состоит из объекта DirectDraw, который представляет собой индивидуальный адаптер дисплея. Кроме того, API содержит объект DirectDrawSurface, который представляет собой поверхность, объект DirectDrawPalette, представляющий палитру поверхности, и объект DirectDrawClipper, представляющий список клипов. Объект DirectDraw можно использовать для создания объектов DirectDrawSurface и DirectDrawPalette. (Также с помощью объекта DirectDraw можно создать объект DirectDrawClipper, однако обычно DirectDrawClipper создают независимо.) Единственные объекты, нужные для создания игр - DirectDraw, DirectDrawSurface, и DirectDrawPalette.

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

DirectDraw, OLE, и интерфейс COM

DirectDraw был спроектирован на основе OLE и интерфейса COM. Если вы не знакомы с программированием OLE начало программирования DirectDraw может показаться вам очень трудным. Хотя много бумаги ушло на многотомовые издания книг об OLE и интерфейсе COM, для программирования DirectDraw вам нужно знать всего лишь несколько вещей.

Для начала, дадим определение OLE и инетрфейсу COM. OLE - это объектно-ориентированная технология, разработанная Microsoft для совместного использования информации процессами. COM - Component Object Model (компонентная объектная модель) - интерфейс, используемый при программировании OLE. Теперь, когда вы знаете эти определения, можете их забыть - они не используются в программировании. Вместо этого давайте изучим самый минимум, который необходимо знать об интерфейсе COM, чтобы использовать DirectDraw.

В основном, интерфейс COM основан на трех составляющих: объектах, интерфейсах и методах. Объект - по существу, черный ящик. Вам не нужно знать ничего об устройстве этого объекта, так как все, что вам нужно для взаимодействия с этим объектом, заключено в интерфейсе. Суть взаимодействия через интерфейс - использование методов. Образно объект можно представить в виде интегральной микросхемы (для тех, кому ближе аппаратная часть). Взаимодействие с микросхемой(объектом) заключается в подаче и снятии электрических сигналов (методы) с выводов микросхемы (интерфейс).

Все интерфейсы COM выводятся из интерфейса IUnknown OLE. Интерфейс IUnknown поддерживает жизненный цикл объекта DirectDraw. В дополнение, он определяет доступные интерфейсы данного объекта. Интерфейс IUnknown состоит из трех методов: AddRef, Release, и QueryInterface.

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

Когда вы завершили использование интерфейса, вы должны вызвать метод Release для уменьшения числа ссылок на 1. Для того, чтобы удалить из памяти объект, число ссылок должно равняться 0. Как только число ссылок объекта равняется 0, он удаляется и все его интерфейсы становятся "неправильными" (invalid).

Третий метод IUnknown, QueryInterface, запрашивает объект о поддержке определенного интерфейса. Если интерфейс поддерживается, QueryInterface возвращает указатель на этот интерфейс.

Как методы AddRef, QueryInterface, и Release связаны с DirectDraw? Во-первых, нет причин использовать AddRef или QueryInterface в простых играх, использующих только DirectDraw. Функции, создающие различные типы объектов DirectDraw должны заботиться об увеличении числа ссылок и возвращении указателей на интерфейсы. Вам неоходимо выполнить Release для каждого неявно созданного указателя. Если эти требования не выполнять, ваши программы будут заканчиваться с "утечкой памяти". В следующих примерах я покажу как все делается. (Вам понадобится использовать AddRef и QueryInterface, если ваша программа использует объекты DirectDraw, уже используемые другим приложением. Если вы используете Direct3D, необходимо использовать QueryInterface для возврата указателя на интерфейс Direct3D. Оба эти случая находятся за пределами рассмотрения данной статьи.)

Теперь давайте посмотрим на пример кода на C, использующего метод интерфейса IDirectDraw:

ddrval = lpDD->lpVtbl->SetDisplayMode( lpDD, ScreenX, ScreenY, ScreenBpp );

В этой строчке кода вы используете метод SetDisplayMode для установки видеорежима и получения результата этой операции (успешно/неудачно). Это не так важно. Важно то, как получается указатель на метод, который вы используете. Вы не можете получить прямой доступ к методам интерфейса IDirectDraw. Когда создается экземпляр объекта, создается таблица виртуальных функций, называемая vtable, которая содержит указатели на все методы интерфейса. Единственная возможность вызвать методы интерфейса - использовать указатели из этой таблицы. В предыдущем примере указатель на объект DirectDraw (lpDD)указывает на указатель, содержащий адрес таблицы vtable (lpVtbl), которая в свою очередб содержит указатели на все методы объекта—в нашем случае это метод SetDisplayMode. Связь между нашим приложением и интерфейсом объекта представлена на следующем рисунке:

Рисунок 2.

Следующий пример показывает, как сделать то же с использованием C++:

ddrval = lpDD->SetDisplayMode( ScreenX, ScreenY, ScreenBpp );

Заметим, что указатель на vtable явно больше не используется. Указатель на vtable неявный, и C++ автоматически подставляет lpDD как первый параметр. Указатель this больше не нужен, так как С++ вызывает метод относительно, используя указатель на текущий объект (в нашем случае, lpDD).

Если вы хотите узнать больше об OLE и интерфейсе COM, можете почитать книгу "Inside OLE" (автор Kraig Brockschmidt, MSDN Library/Books/Inside OLE). Это лучшая книга, объясняющая как работают OLE и COM. Я рекомендую прочитать первую главу и половину второй для хорошего понимания принципов функционирования интерфейса COM. Это поможет понять связи между DirectDraw и интерфейсом COM (если вы не поняли моих объяснений).

Начало работы с DirectDraw

Я отмечал ранее, что вам нужно проинсталлировать на компьютер DirectX 5 SDK. Также вам нужно проинсталлировать компилятор C или C++. Предположим, вы используете Microsoft Visual C++® версии 5.0 и вы проинсталлировали компилятор и SDK в каталоги по умолчанию. Если вы используете другой компилятор или проинсталлировали SDK в другой каталог на жестком диске, вам нужно внести соответствующие изменения в примеры, приведенные ниже.

Так как моя цель - показать основы программирования DirectDraw, давайте используем некоторые элементарные примеры, поставляемые с DirectX 5 SDK. Они показывают, как инициализировать DirectDraw и использовать методы DirectDraw. Просмотрев эти примеры, вам будет легче понять более сложный пример игры, находящийся на CD DirectX 5 SDK.

Но перед началом работы нужно настроить окружение компилятора для работы с DirectX 5 SDK. Как это сделать, зависит от того, как вы используете Visual C++ для компиляции примеров. Я покажу вам правильную настройку в случае использования Microsoft Developer Studio, или при использовании утилиты NMAKE из командного режима.

Настройка Microsoft Developer Studio

Для тех из вас, кому нравится работать с удобным графическим интерфейсом, Visual C++ предлагает Microsoft Developer Studio. Для начала компилирования примеров из DirectX 5 SDK, вам нужно создать новый проект, подключить к нему несколько файлов и настроить окружение таким образом, чтобы компилятор нашел нужные библиотеки и включаемые файлы. Рассмотрим по пунктам, что же нужно сделать для того, чтобы откомпилировать первый пример, DDEX1.

Открыв Microsoft Developer Studio создайте новый проект:

  1. В меню File, выберите New.
  2. В диалоге New, выберите закладку Project.
  3. Из списка Projects, выберите Win32 Application.
  4. В поле Location можно выбрать путь к каталогу, содержащему проект.
  5. В поле Project name, введите DDEX1.
  6. Нажмите OK. Появится новая папка DDEX1 Classes в левой части окна.

Для подключения к проекту файлов проделайте следующие действия:.

  1. В меню Project выберите пункт Add To Project и кликните Files. Появится диалог Insert Files into Project.
  2. Откройте каталог DXSDK\SDK\SAMPLES\DDEX1. Выберите все файлы с расширением CPP. НажмитеOK.
  3. Откройте панель FileView для просмотра файлов, подключенных к проекту.
  4. Для просмотра списка файлов в папке DDEX1, нажмите на + слева от надписи DDEX1.

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

  1. В меню Tools выберите Options. Появится диалог Options.
  2. Выберите закладку Directories.
  3. В списке Show Directories For выберите категорию файлов Include.
  4. В поле Directories, дважды щелкните на пустую строку в конце списка, и введите C:\DXSDK\SDK\LIB.
  5. Нажмите Enter.
  6. В поле Directories, дважды щелкните мышью на последней (пустой) строке и введите C:\DXSDK\SDK\SAMPLES\MISC.
  7. Выделите строку C:\DXSDK\SDK\LIB. Нажимайте Move Item Up в поле Directories пока строка C:\DXSDK\SDK\LIB не станет самой верхней строкой.
  8. Выделите строку C:\DXSDK\SDK\SAMPLES\MISC. Нажимайте Move Item Up в поле Directories пока строка C:\DXSDK\SDK\SAMPLES\MISC не станет второй (прямо после строки C:\DXSDK\SDK\LIB).
  9. Нажмите OK.

Выбираем каталог, где находятся библиотеки.

  1. В списке Show Directories For, выберите категорию файлов Library.
  2. В поле Directories, дважды щелкните на самую нижнюю пустую строку и введите C:\DXSDK\SDK\LIB.
  3. Нажмите Enter.
  4. Выделите строку C:\DXSDK\SDK\LIB. Нажимайте Move Item Up в поле Directories пока строка C:\DXSDK\SDK\LIB не станет самой верхней строкой.
  5. Нажмите OK.

Наконец, подключаем нужные библиотеки:

  1. В меню Project выберите пункт Settings. Появится диалог Project Settings.
  2. Выберите закладку Link.
  3. Из выпадающего списка Category выберите General.
  4. В списке Object/library modules добавьте Ddraw.lib и Winmm.lib.
  5. Нажмите OK.

Вы наверное удивляетесь, почему C:\DXSDK\SDK\INC, C:\DXSDK\SDK\SAMPLES\MISC и C:\DXSDK\SDK\LIB нужно двигать в самый верх поля Directories. Это нужно из-за того, что Visual C++ версии 5.0 уже содержит все библиотеки и включаемые файлы от DirectX 3. Чтобы быть увереным, что используются файлы DirectX 5, мы вынуждены поставить их самыми первыми в списке. В этом случае Visual C++ 5.0 найдет эти файлы первыми и не будет использовать старые файлы DirectX 3.

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

Настройка путей для утилиты NMAKE

Если вам (как и мне) нравится утилита командной строки NMAKE, то вам нужно прописать пути к каталогам, содержащим библиотеки и включаемые файлы DirectX 5. Так как в Visual C++ 5.0 используются длинные имена, то нужно проделать дополнительную работу, чтобы зставить NMAKE работать нормально. Я дам вам несколько подсказок, чтобы вы представляли, что нужно сделать.

  1. Первое, что нужно сделать - это изменить файл Vcvars32.bat, находящийся в каталоге C:\Program Files\DevStudio\VC\bin. Здесь нужно прописать некоторые переменные окружения, определяющие как вы используете C++. (К примеру, если вы переписали все каталоги с установочного диска C++ на жесткий диск, то нужно закоментировать все строки, ссылающиеся на CD-ROM)
  2. Затем необходимо создать .BAT- файл, который будет содержать все, необходимое для работы с DirectX 5:

      1.  @echo off
      2.  set INCLUDE=%INCLUDE%;C:\DXSDK\SDK\INC
      3.  set LIB=%LIB%;C:\DXSDK\SDK\LIB

  1. Затем необходимо увеличить объем памяти, выделяемый под переменные окружения окна MS-DOS® (спасибо новым длинным именам в Visual C++ 5.0). Для этого выполните в окне MS-DOS следующую команду:

      1.  command /E:1000

что добавит тысячу байт к уже выделенному объему. Заметим, что эту строку надо выполнить первой в окне MS-DOS, так как все установки переменных окружения, выполненные до этой команды будут потеряны.

  1. Зпаустите Vcvars32.bat для установки переменных окружения Visual C++ 5.0.
  2. Наконец, запустите .BAT - файл, который вы создали на шаге 2 DirectX 5.

Для того, чтобы откомпилировать какой-нибудь пример зайдите в каталог, его содержащий (C:\DXDSK\SDK\SAMPLES\DDEX4, например) и введите:

NMAKE

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

Начальный размер памяти, выделяемый под переменные окружения окна MS-DOS в Windows 95 можно изменить следующим образом:

  1. В настройках меню Пуск Windows найдите "MS-DOS Prompt".
  2. Щелкните на иконке правой кнопкой и выберите Properties.
  3. Выберите закладку Memory и измените значение Initial на 1024.

Основы DirectDraw (DDEX1)

Для использования DirectDraw сначала нужно создать экзепляр объекта DirectDraw object, который будет представлять адаптер дисплея, а затем использовать методы интерфеса. Также необходимо создать один или несколько экземпляров объекта DirectDrawSurface для отображения вашей игры.

Для демонстрации посмотрим, как DDEX1 из DirectX 5 SDK создает объект DirectDraw, затем создает основную поверхность и затем переключается между поверхностями.

Замечание   Файлы примера DDEX написаны на C++. Если вы используете компилятор C, вам нужно модифицировать файлы чтобы они откомпилировались. (По крайней мере, вам нужно добавить vtable и указатель this для методов интерфейса.)

Инициализация DirectDraw

Инициализация DirectDraw в примере DDEX1 содержится в функции doInit.

/*
 * Создаем основной объект DirectDraw.
 */
ddrval = DirectDrawCreate( NULL, &lpDD, NULL );
if( ddrval == DD_OK )
{
  // Get exclusive mode.
  ddrval = lpDD->SetCooperativeLevel( hwnd,        DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN );
  if(ddrval == DD_OK )
  {
     ddrval = lpDD->SetDisplayMode( 640, 480, 8 );
     if( ddrval == DD_OK )
     {
        // Создаем основную поверхность.
        ddsd.dwSize = sizeof( ddsd );
        ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
        ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |
                                 DDSCAPS_FLIP |
                                 DDSCAPS_COMPLEX;
        ddsd.dwBackBufferCount = 1;
        ddrval = lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL );
        if( ddrval == DD_OK )
        {
            // Получаем указатель на бэк-буфер.
            ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
            ddrval = lpDDSPrimary->GetAttachedSurface(&ddscaps,
                      &lpDDSBack);
            if( ddrval == DD_OK )
            {
               // Напишем текст.
                if (lpDDSPrimary->GetDC(&hdc) == DD_OK)
                {
                   SetBkColor( hdc, RGB( 0, 0, 255 ) );
                   SetTextColor( hdc, RGB( 255, 255, 0 ) );
                   TextOut( hdc, 0, 0, szFrontMsg,
                            lstrlen(szFrontMsg) );
                  lpDDSPrimary->ReleaseDC(hdc);
                }
                if (lpDDSBack->GetDC(&hdc) == DD_OK)
                {
                  SetBkColor( hdc, RGB( 0, 0, 255 ) );
                   SetTextColor( hdc, RGB( 255, 255, 0 ) );
                   TextOut( hdc, 0, 0, szBackMsg,
                            lstrlen(szBackMsg) );
                  lpDDSBack->ReleaseDC(hdc);
                }
                // Создаем таймер для переключения страниц.
                if( SetTimer( hwnd, TIMER_ID, TIMER_RATE, NULL ) )
                {
                   return TRUE;
                }
            }
      }
     }
  }
}  

wsprintf(buf, "Direct Draw Init Failed (%08lx)\n", ddrval );  

.  

.  

.

Каждый из проделанных шагов необходим для создания объекта DirectDraw, а созданием и подготовкой поверхности мы займемся в следующих разделах.

Создание объекта DirectDraw

Для создания экземпляра объекта DirectDraw ваше приложение может использовать функцию API DirectDrawCreate. (Заметим, что я употребил слово "может".Есть по-крайней мере еще одна возможность создания объекта DirectDraw — с использованием функции OLE CoCreateInstance — но это не обсуждается в данной статье).В DirectDrawCreate передается глобальный идентификатор (GUID) который определяет устройство вывода и в большинстве случаев он устанавливается в NULL (что говорит о том, что используется устройство вывода по умолчанию), адрес указателя, определяющий местонахождение создаваемого объекта DirectDraw, и третий параметр всегда устанавливается в NULL (используется для будущих расширений).

Следующий пример показывает, как создавать объект DirectDraw и определять, удачно ли завершилась эта операция:

ddrval = DirectDrawCreate( NULL, &lpDD, NULL );
if( ddrval == DD_OK )
{
   // lpDD - указатель на созданный объект DirectDraw.
}
else
{
   // Объект DirectDraw не создан.
}
Тематические ссылки
Ваша ссылка Ваша ссылка

Обмен кнопками, ведение статистики, реклама.