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

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


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

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

Выпуск 14. (от 5 сентября 2003 года)
Работа с простейшей графикой.

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

Приветствую вас, уважаемые читатели рассылки.

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

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

Мы уже рассматривали, как в контекст устройства окна выводить отдельные точки. Конечно же, в Windows есть и другие функции, которые позволяют выводить прямоугольники, эллипсы и другие фигуры.

Я очень надеюсь, что новички уже более-менее комфортно ориентируются в коде Windows, что их не смущает множество типов данных и функций. Сегодня мы рассмотрим еще несколько, и поговорим о таких понятиях, как перья и кисти. Для начала, как всегда, на пальцах. Перья служат для рисования контуров фигур, а кисти для их заливки. Сперва мы создаем перо или кисть, затем делаем его текущим (выбираем его), а в конце работы удаляем его. Все очень просто. И конечно же, для перьев и кистей в Windows имеются собственные типы данных. А теперь перейдем к программе, которая выводит на экран игровое поле, для наглядности расчерченное на квадраты, и фигуру.


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



#define WIN_WIDTH 500   // Ширина окна
#define WIN_HEIGHT 500   // Высота окна
#define CAPTIONBAR_TEXT "Test"  // Текст заголовка окна

// Это тот самый двумерный массив, с помощью которого мы будем работать с фигурами
// Здесь мы определим его размеры

#define MATRIX_X 10  // Количество элементов матрицы (стакана)
#define MATRIX_Y 22  // (последние элементы не считаются, т.е. от 0 до 21)


#define GRID_START_X 150  // Координаты, откуда рисовать сетку
#define GRID_START_Y 10
#define GRID_END_X 350   
#define GRID_END_Y 410

#define GRID_STEP 20   // Шаг сетки



// Глобальные переменные ---
HDC hdc; // Это наш глобальный контекст устройства (для рисования)


// Наши глобальные перья
// Как видите, для них есть свой собственный тип
HPEN block_pen;


// Наши глобальные кисти
HBRUSH block_brush1;
HBRUSH background_brush;


// Инициализируем наш глобальный массив игрового поля, делая стакан пустым
int matrix[MATRIX_X][MATRIX_Y] = {0};


// Далее идут определения (прототипы) функций


LRESULT CALLBACK WinProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam);

void ClearScreen();  // Функция очистки экрана
void DrawFigure();  // Функция рисования фигуры
void DrawBackground();  // Функция рисования фона
void DrawGrid();  // Функция рисования сетки
void UpdateGameScreen(); // Обновление экрана



// Думаю, что с WinMain все уже хорошо знакомы

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hprev, PSTR cmdline, int ishow)
{
    char class_name[] = "Windowclass";
    
    HWND hwnd;
    MSG msg;
    WNDCLASS wndclass = {0};
 
    wndclass.style="CS_HREDRAW" | CS_VREDRAW;
    wndclass.lpfnWndProc = WinProc;
    wndclass.hInstance = hinstance;
    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndclass.lpszClassName = class_name;

    RegisterClass(&wndclass); // Регистрируем окно
  
    hwnd = CreateWindow(class_name,
   CAPTIONBAR_TEXT,
   WS_SYSMENU,
   CW_USEDEFAULT,   
   CW_USEDEFAULT,   
   WIN_WIDTH,
   WIN_HEIGHT,
   NULL,
   NULL,
   hinstance,
   NULL);


    if(!hwnd)
 return EXIT_FAILURE; 



    hdc = GetDC(hwnd); // Получаем контекст устройства окна
  
    // Проверка на ошибку
    if(!hdc)
 return EXIT_FAILURE; // Не смогли получить hdc окна


    // А теперь мы создаем одно перо и две кисти, используя для этого
    // функции CreatePen и CreateSolidBrush


    block_pen = CreatePen(PS_SOLID, 1, RGB(255, 255, 0));
    block_brush1 = CreateSolidBrush(RGB(255,0,255)); 
    background_brush = CreateSolidBrush(RGB(100,100,100));

    // Выбираем кисть и перо (делаем их текущими).
    // Теперь функции рисования будут использовать именно их.

    SelectObject(hdc,block_pen);
    SelectObject(hdc,background_brush);


 
    ShowWindow(hwnd, ishow);
    UpdateWindow(hwnd);

 


   
    while(1)
    {
    // Получаем сообщения, если они есть
 if(PeekMessage(&msg,NULL,0,0,PM_REMOVE))
 {
  if(msg.message == WM_QUIT)
   break;
   
  TranslateMessage(&msg);
  DispatchMessage(&msg);
 }
 else
 {
 }

     }



 // Освобождаем HPEN и HBRUSH
 // Нужно обязательно делать это

 DeleteObject(block_pen);

 DeleteObject(block_brush1);
 DeleteObject(background_brush);

 // Освобождаем глобальный HDC
 ReleaseDC(hwnd,hdc);

 UnregisterClass(class_name,hinstance);


 return msg.wParam;
}







// Оконная процедура

LRESULT CALLBACK WinProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{
     
    PAINTSTRUCT ps;

    // В зависимости от сообщения выполняем различные действия
    switch(message)
    {
 
 // При нажатии ESC выходим из программы
 case WM_KEYDOWN:

  switch(wparam) // wparam хранит виртуальный код нажимаемой клавиши
  {      
   case VK_ESCAPE:
     
   PostQuitMessage(0);
   break;
  
   
  }
   
  return 0;

    
        // При необходимости перерисовки экрана, делаем это
 case WM_PAINT:
          
 BeginPaint(hwnd,&ps);

 UpdateGameScreen(); // Вызываем нашу функцию обновления экрана

 EndPaint(hwnd,&ps);
 return 0;


        case WM_DESTROY:
 case WM_CLOSE:
            
 PostQuitMessage(0);
        return 0;

    } // конец switch(message)

    return DefWindowProc(hwnd, message, wparam, lparam);
}




// ********** Очистка экрана **********

// Эта функция создает прямоугольник размером с окно и закрашивает его
// белым цветом

void ClearScreen()
{

 RECT rect={0, 0, WIN_WIDTH, WIN_HEIGHT};

 FillRect(hdc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
}




// ********** Фон для стакана **********
// Создает прямоугольник заданных нами размеров и закрашивает
// его установленной нами кистью

void DrawBackground()
{
  
 RECT rect = {GRID_START_X, GRID_START_Y, GRID_END_X, GRID_END_Y};
 
 FillRect(hdc, &rect, background_brush);
}




// ********** Сетка **********

void DrawGrid()
{
 int i;

 LPPOINT lp_point = NULL; /* Это указатель long pointer на точку. Единственная
     причина объявлять его - это то, что мы должны
     передать LPPOINT в MoveToEx(). Эта функция
     заполнит его "предыдущей текущей позицией"
    */

 SelectObject(hdc,block_pen);

 // Этот цикл рисует горизонтальные линии
 for (i = GRID_START_X; i <= GRID_END_X; i += GRID_STEP)
 {
  // Сперва устанавливаем начальную точку рисования линии
  MoveToEx(hdc, i, GRID_START_Y, lp_point);
  // Затем рисуем из установленной точки линию в указанную точку
  LineTo(hdc, i, GRID_END_Y + 1);
 }

 // Этот цикл рисует вертикальные линии
 for (i = GRID_START_Y; i <= GRID_END_Y; i += GRID_STEP)
 {
  MoveToEx(hdc, GRID_START_X, i, lp_point);
  LineTo(hdc, GRID_END_X, i);
 }

}

/* А вот теперь начинается самое интересное. Функция вывода фигуры.
   Именно здесь мы будем использовать наш двумерный массив для определения
   наличия или отсутствия элемента фигуры в заданном месте.
*/

void DrawFigure()
{ 
 int i,j;
 int SquareX, SquareY;

 // Чтобы понять, как это работает, начертите на бумаге массив размером 10 на 22.
 // В массиве нумерация элементов начинается с нуля, а последние элементы мы
 // не используем, поэтому первый элемент будет "нулевым", а последний - 
 // двадцать первым.

 matrix[3][0] = 1;
 matrix[4][0] = 1;
 matrix[5][0] = 1;
 matrix[4][1] = 1;

 // Эти вложенные циклы "пробегаются" по всему массиву построчно.
 // При этом мы ищем ненулевые элементы, и если находим, рисуем
 // в нужных ячейках сетки квадратики.

 for (i = 0; i < MATRIX_X; ++i)
  for (j = 0; j < MATRIX_Y; ++j)
  {
   SquareX = GRID_START_X + i * GRID_STEP;
   SquareY = GRID_START_Y + j * GRID_STEP;

   if (matrix[i][j])
   {  
   
    SelectObject(hdc,block_brush1);

    // Рисуем прямоугольник текущей кистью и пером, указав при этом его
    // начальные и конечные координаты
    Rectangle(hdc, SquareX, SquareY , SquareX + GRID_STEP + 1, SquareY + GRID_STEP + 1);
   }
    
  }
 
}


// Функция, вызывающая по порядку все функции рисования.

void UpdateGameScreen()
{
 ClearScreen();
 DrawBackground();
 DrawGrid();
 DrawFigure();
}



Как видите, в программе довольно много циклов. Но именно с помощью циклов проще всего обработать все элементы массива один за другим. Сразу отмечу, что мы уже создали "графический движок" нашего будущего тетриса, единственным отличием вывода графики в реальном тетрисе будет отсутствие сетки и функции DrawGrid. А это совсем не мало.

До следующего выпуска!

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

Что почитать:

Программирование игр для Windows. Советы профессионала (+ CD-ROM)
Автор: Андре Ламот

В первых главах этой замечательной книги, о которой я уже говорил, вы найдете все необходимое, чтобы научиться работать с GDI, в том числе и с упомянутыми в этом выпуске функциями. Кстати, я был очень удивлен, когда увидел, что примером, выводящим в окне точки, является то самое звездное небо, которое мы создавали в позапрошлом выпуске (конечно же, программа отличается от нашей). А еще больше я удивился, когда, поиграв недавно в Diablo 2 в очередной раз, я увидел, что такое звездное небо является фоном в Ancient Sanctuary. Это лишний раз говорит о том, что простые эффекты, которые мы создаем в выпусках рассылки, используются даже в больших профессиональных играх.

Программирование графики для Windows
Автор: Фень Юань

Книга посвящена графическому программированию для Windows с использованием Win32 GDI API. Кроме того, в ней приведены начальные сведения о DirectDraw и краткое введение в непосредственный режим Direct3D. Рассматриваются стандартные возможности, поддерживаемые на всех платформах Win32, 32-разрядные возможности, реализованные только в Windows NT/2000, и новейшие расширения GDI, появившиеся только в Windows 2000 и Windows 98. В книге приведено множество фрагментов кода, подходящих для практического применения. Помимо простейших тестовых и демонстрационных программ, вы найдете в ней множество функций, классов C++, драйверов, утилит и нетривиальных программ, вполне подходящих для использования в коммерческих проектах. На компакт-диске находятся полные исходные тексты, файлы рабочих областей Microsoft Visual C++, заранее откомпилированные двоичные файлы (в отладочных и окончательных версиях) и файлы в формате JPEG для глав, посвященных графическим алгоритмам.

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

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

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



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

В избранное