Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Создание компьютерных игр выпуск 16


Информационный Канал Subscribe.Ru

Cоздание компьютерных игр.
Выпускается еженедельно по пятницам.
Автор рассылки - Евгений Казеко.

Выпуск 16. (от 21 ноября 2003 года)
Работа с графикой с использованием GDI.

--------------------------------------------------------------

Этот выпуск - второй из "серии выпусков" специально для тех, кто не очень хорошо ориентировался в программном коде Windows приложений. Через пару выпусков я продолжу рассказывать об аспектах создания компьютерных игр, еще не упоминаемых в рассылке, но и сейчас, наряду с уже раскрытыми вопросами, я покажу кое-что новое.

Сегодня мы еще раз рассмотрим, что такое GDI, контекст устройства, и как все это работает - на этот раз подробнее. В конце выпуска, как всегда, я приведу пример простой программы, демонстрирующей эти понятия.

Итак, что же такое GDI? Это интерфейс графического устройства (Graphic Device Interface), встроенный в саму систему Windows. Он работает достаточно медленно, и в основном его используют при написании так называемых деловых или офисных приложений. Для игр, где требуется большая скорость работы с графикой, например трехмерных, используются специальные графические библиотеки, такие как DirectX (а точнее, его компоненты DirectDraw и Direct3D) и OpenGL, которые гораздо эффективнее работают с оборудованием и выводят графику намного быстрее. Но для небольших, несложных игр вполне сгодится и GDI.

Рассмотрим несколько ключевых понятий GDI. Первое и самое главное из них это контекст устройства (device context). В общем случае, контекст устройства представляет собой ссылку на любое устройство для вывода графики - экран, принтер, память... Но мы будем пользоваться контекстом устройства исключительно для вывода графики на экран, а точнее - в окно. Для того, чтобы рисовать в окне, нам необходим его контекст устройства. На первых порах, когда многое непонятно, достаточно запомнить только это. Контекст устройства это то, где мы рисуем.

Как же получить контекст устройства окна? Есть два способа сделать это. Наиболее распространенный, но далеко не самый лучший - это использовать пару функций BeginPaint и EndPaint. Почему именно пару, я объясню позже. Этот способ применяется в основном для обработки сообщения WM_PAINT.

Мда, на первых порах действительно приходится узнавать сразу много нового. В прошлый раз мы рассматривали, что такое сообщения Windows, но напомню вкратце еще раз. Когда мы создаем окно, оно может принимать различные сообщения, как от пользователя, так и от самой системы Windows. Например, когда мы щелкаем на окне мышью, мы посылаем одно из сообщений. Будет ли окно его обрабатывать или нет - это зависит от создателя программы. Сообщения обрабатываются с помощью оконной процедуры, которая в программах обычно называется WinProc. В ней мы и указываем, как обработать сообщение. На самом деле, даже когда мы игнорируем сообщения, они все равно обрабатываются "по умолчанию".

А теперь о сообщении WM_PAINT. Оно посылается окну в том случае, когда необходима перерисовка окна. Такое может произойти при изменении его размеров, перекрывании его части другим окном, разворачивании на весь экран... Когда система посылает окну сообщение WM_PAINT, мы обязаны его обработать, иначе оно будет посылаться бесконечное число раз. И мы делаем это, используя пару вызовов - BeginPaint и EndPaint. Функция BeginPaint как раз и предоставляет нам доступ к контексту устройства окна, возвращая его дескриптор (или описатель, хотя я люблю называть все это просто ссылкой - так понятнее), который в программах обычно сохраняется в переменной hdc, имеющей тип HDC (handle to device context). Но доступ предоставляется не ко всему окну, а только к части, которой необходима перерисовка. Согласитесь, неудобно, так же, как и дожидаться сообщения WM_PAINT.

Поэтому предпочтительней использовать для получения контекста устройства другую пару функций - GetDC и ReleaseDC. Вот как это делается.

1) Сперва объявляем переменную типа HDC, где мы и будем хранить ссылку (дескриптор) на контекст устройства.

HDC hdc;

2) Затем после создания окна, получаем контекст устройства.

hdc = GetDC(hwnd); - где hwnd это ссылка на окно, контекст устройства которого нам нужен.

3) Работаем с контекстом устройства - вызываем графические функции, рисующие в окне.

4) До того, как мы завершаем программу (или после того, когда нам уже не нужен контекст устройства), мы обязательно должны освободить его функцией ReleaseDC.

ReleaseDC(hwnd, hdc);

Для того же служит и функция EndPaint, используемая после BeginPaint. Всегда освобождайте ненужные контексты устройств!

В программе-примере я продемонстрирую, как используются обе пары функций. GetDC и ReleaseDC мы используем для наших нужд, а для обработки сообщения WM_PAINT будем использовать BeginPaint и EndPaint. Конечно, вы не обязаны делать именно так, но такой способ наиболее распространен (и на первых порах, пока не разберетесь что к чему, я советую пользоваться именно им, а не мудрить с функциями Validate- InvalidateRect).

Планируя этот выпуск, я рассчитывал дать больший объем материала, но сейчас вижу, что это нецелесообразно. Уж лучше разбираться во всем постепенно, чем пытаться сразу охватить много вопросов. Поэтому описание графических функций я оставлю на следующий выпуск, а сегодня мы рассмотрим лишь одну из них - функцию вывода текста TextOut.

Эта функция выводит строку цветного текста, начиная с указанных нами координат. Но прежде чем выводить текст, было бы неплохо задать его цвет, и цвет фона. Для этого существуют функции SetTextColor и SetBkColor. В качестве параметров им нужно передать ссылку на контекст устройства и цвет. Параметр цвета должен быть типа COLORREF (опять новый тип Windows - как видите, их сотни, если не тысячи, но это не значит, что их нужно бояться). К счастью можно для передачи цвета просто воспользоваться макросом RGB, то есть указать цвет тремя числами от 0 до 255, где первый цвет - интенсивность красного, второй - зеленого, третий - синего. Черный цвет задается RGB(0,0,0), белый - RGB(255,255,255), красный - RGB(255,0,0), т.е. красная интенсивность по максимуму, а остальные нулевые. Для тех, кто работал с графическими программами, проблем не будет, тем же, кто не работал, придется поэкспериментировать.

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

SetTextColor(hdc, RGB(0,255,0)); - разумеется, переменная hdc уже должна содержать ссылку на контекст устройства окна.

(Конечно, если хотите, можете напрямую передавать цвет в формате COLORREF, т.е. написать 0x0000ff00, но использовать макрос RGB гораздо проще).

А чтобы установить черный фон, вызываем функцию:

SetBkColor(hdc, RGB(0,0,0));

Есть еще одна функция - SetBkMode, которая нужна для установки прозрачности фона. Константа TRANSPARENT используется для прозрачного фона, а OPAQUE для непрозрачного (который, кстати, выводится быстрее). Установим прозрачный фон.

SetBkMode(hdc, TRANSPARENT);

А теперь выводим текст функцией TextOut. Параметрами являются контекст устройства, координаты начала строки (как обычно, 0,0 это верхний левый угол окна), саму строку и количество символов в строке, которое мы получаем стандартной функцией strlen.

TextOut(hdc, 200, 200, "GDI Text Demo!", strlen("GDI Text Demo!"));

Вот и все. В заключение я привожу программку из "той самой книги" Андре Ламота, снабдив ее своими комментариями. Она выводит случайным образом разноцветный текст.



#define WIN32_LEAN_AND_MEAN  // макрос для отключения MFC, что уменьшает размер exe файла.

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

// Имя оконного класса
#define WINDOW_CLASS_NAME "WINCLASS1"


// Глобальные ссылки на окно и экземпляр приложения
HWND      main_window_handle = NULL; 
HINSTANCE hinstance_app      = NULL; 



// Оконная процедура - обработчик сообщений в нашей программе
LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{

// Это "структура рисования", которая нужна для вызова BeginPaint и EndPaint.
PAINTSTRUCT ps; 

HDC hdc;  // ссылка на контекст устройства
// Будьте внимательны! Этот контекст устройства мы получаем только на момент вызовов
// BeginPaint и EndPaint, после чего он освобождается (после обработки WM_PAINT).
// Контекст устройства в WinMain освобождается только перед завершением программы.
// Обе переменные ссылаются на один и тот же контекст устройства окна и носят одинаковое
// имя, но фактически это разные контексты устройства.

// обрабатываем сообщения 
switch(msg)
 { 
 case WM_CREATE: 
        {
  return(0);
 } break;
  

 case WM_PAINT: 
 {
  hdc = BeginPaint(hwnd,&ps); 
  // Конечно, мы можем рисовать и "внутри" WM_PAINT
  // (в этом месте), используя hdc
  EndPaint(hwnd,&ps);

  return(0);
     } break;

 case WM_DESTROY: 
  {
  PostQuitMessage(0);
     return(0);
  } break;

 default:break;

    } 

// обработка сообщений "по умолчанию"
return (DefWindowProc(hwnd, msg, wparam, lparam));

} 

// WINMAIN ////////////////////////////////////////////////
int WINAPI WinMain(HINSTANCE hinstance,
     HINSTANCE hprevinstance,
     LPSTR lpcmdline,
     int ncmdshow)
{

WNDCLASSEX winclass; // оконный класс
HWND    hwnd;     // ссылка на окно
MSG    msg;      // сообщение


// Заполняем структуру оконного класса
// Ламот использует структуру WNDCLASSEX, которая немного отличается от WNDCLASS
// наличием дополнительных финтифлюшек.

winclass.cbSize         = sizeof(WNDCLASSEX);
winclass.style="CS_DBLCLKS" | CS_OWNDC | 
                          CS_HREDRAW | CS_VREDRAW;
winclass.lpfnWndProc = WindowProc;
winclass.cbClsExtra = 0;
winclass.cbWndExtra = 0;
winclass.hInstance = hinstance;
winclass.hIcon  = LoadIcon(NULL, IDI_APPLICATION);
winclass.hCursor = LoadCursor(NULL, IDC_ARROW); 
winclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
winclass.lpszMenuName = NULL;
winclass.lpszClassName = WINDOW_CLASS_NAME;
winclass.hIconSm        = LoadIcon(NULL, IDI_APPLICATION);

// Сохраняем hinstance в глобальной переменной
// (хотя сейчас вряд ли нам это пригодится)
hinstance_app = hinstance;

// Регистрируем оконный класс
// Здесь и ниже сразу же производится проверка на ошибки системы
if (!RegisterClassEx(&winclass))
 return(0);

// Создаем окно
if (!(hwnd = CreateWindowEx(NULL,                 
                            WINDOW_CLASS_NAME,    
       "GDI Text Printing Demo", 
       WS_OVERLAPPEDWINDOW | WS_VISIBLE,
       0,0,   
       400,400,  
       NULL, 
       NULL, 
       hinstance,
       NULL))) 
return(0);

// сохраняем глобальную ссылку на окно
main_window_handle = hwnd;

// А здесь мы получаем контекст устройства, который и будем использовать для
// вывода текста
HDC hdc = GetDC(hwnd);

// Обрабатываем соообщения, но используем для их получения PeekMessage()
// вместо GetMessage()
while(TRUE)
 {
    // Если есть сообщение - обрабатываем его
 if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
    { 
    // если сообщение выхода - завершаем программу
       if (msg.message == WM_QUIT)
           break;
     
    TranslateMessage(&msg);
    DispatchMessage(&msg);
    } 
    
    // Когда нет сообщений, выводим случайный текст
    // Такой прием обработки сообщений очень полезен, так как
    // позволяет добиться своевременной реакции программы на сообщения
    
    
    // Устанавливаем случайный цвет текста
    SetTextColor(hdc, RGB(rand()%256,rand()%256,rand()%256));

    // Черный фоновый цвет
    SetBkColor(hdc, RGB(0,0,0));

    // Устанавливаем прозрачный режим фона
    SetBkMode(hdc, TRANSPARENT);

    // Выводим текст в случайной позиции
    TextOut(hdc,rand()%400,rand()%400, "GDI Text Demo!", strlen("GDI Text Demo!"));

    // Задержка на 50 миллисекунд
    Sleep(50);
 
 } 

// Если мы вышли из цикла, то программа завершена

// Освобождаем контекст устройства
ReleaseDC(hwnd,hdc);

// Возвращаемся в Windows
return(msg.wParam);

}

///////////////////////////////////////////////////////////


В следующий раз мы рассмотрим другие функции вывода графики.

До встречи через неделю!

--------------------------------------------------------------

Архив рассылки вы найдете по адресам http://subscribe.ru/catalog/comp.games.gamecoder и http://www.gamecoder.nm.ru/subscribe.htm.

Евгений Казеко.
kazeko@list.ru
www.gamecoder.nm.ru
-----------------------------
Рассылка "Создание компьютерных игр", выпуск 16.
Выпускается еженедельно по пятницам.



http://subscribe.ru/
E-mail: ask@subscribe.ru
Отписаться
Убрать рекламу

В избранное