netlib.narod.ru< Назад | Оглавление | Далее >

Основы работы с блочной графикой

Время пришло. Вы готовы создать движок блочной графики (не библиотеку рисования блоков). Хотя этот механизм блочной графики почти так же стар, как компьютерные игры, он все еще остается наиболее используемой техникой двухмерной графики. Фактически, почти все игры, созданные для портативной игровой консоли Nintendo Gameboy Advance используют представленные ниже технологии блочной графики. Эта игровая система использует исключительно блочную графику в той или иной форме, чтобы предоставить вам совершенную графику на портативном игровом устройстве (я ведь уже говорил это?).

Теперь у вас есть шанс повторить несколько старых техник, которые могут помочь в ваших проектах.

Рисование основной карты

Рисование основной блочной карты — это быстрый и безболезненный процесс; вам необходимо в цикле перебирать столбцы и строки, рисуя те блоки которые вы проходите. Общее количество рисуемых блоков зависит от их размера и разрешения экрана. Например, если размеры игровой области на экране 384 × 384 пикселя, и используются блоки размером 64 × 64 пикселя, то у вас на экране поместится 6 рядов по 6 блоков в каждом, то есть всего 36 блоков.

Сейчас вернемся к рис. 7.1, где изображены четыре блока, карта и результат визуализации карты. Чтобы представить карту в виде массива, используя номера блоков, указываемые при их рисовании, сделайте следующее:

char Map[6][6] = { // Map[y][x]
    { 3, 3, 3, 3, 1, 1 },
    { 3, 3, 3, 3, 3, 3 },
    { 2, 3, 3, 2, 3, 3 },
    { 3, 3, 3, 3, 3, 3 },
    { 3, 3, 3, 3, 3, 1 },
    { 4, 4, 1, 1, 1, 1 }
};

Чтобы нарисовать показанную выше карту, вы должны перебрать все элементы массива и для каждого нарисовать соответствующий блок:

// Tile = ранее инициализированный и загруженный объект cTiles
for(short row = 0; row < 6; row++) {
    for(short column = 0; column < 6; column++) {

        // Получаем номер рисуемого блока
        char TileNum = Map[row][column];

        // Рисуем блок (размером 64 x 64), соответствующий
        // TileNum из первой загруженной текстуры
        Tile.Draw(0, TileNum, column*64, row*64);
    }
}

Если у вас есть набор из трех блоков и показанная выше карта (и функция рисования), вы получите на экране визуализированную карту, аналогичную той, что была показана раньше на рис. 7.1.

Использование нескольких слоев

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


Рис. 7.3. Чтобы создать имитацию трехмерного окружения, вы располагаете слои карты один над другим

Рис. 7.3. Чтобы создать имитацию трехмерного окружения, вы располагаете слои карты один над другим


Чтобы использовать несколько слоев вы просто объявляете еще один массив карты (один массив для каждого слоя) и заполняете его информацией о блоках. Начните с первого слоя и нарисуйте все содержащиеся в нем блоки. Когда вы нарисуете последний блок слоя, перейдите к следующему слою и рисуйте его блоки. Продолжайте, пока не нарисуете все слои.

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

Теперь вы создаете пять массивов карт и заполняете каждый массив данными о блоках, которые будут рисоваться. Войдя в функцию визуализации карты мы проходите по каждому массиву слоя карты и рисуете его на экране. Это продолжается пока не нарисованы все слои. Код для рисования многослойной карты может выглядеть так:

// Tile = ранее инициализированный и загруженный объект cTiles 
char Map[5][10][10]; // Данные карты, подразумеваем,
                     // что они уже загружены

// Перебираем в цикле все слои
for(short Layer = 0;Layer < 5; Layer++) {

    // Перебираем строки и столбцы слоя
    for(short row = 0; row < 10; row++) {
        for(short column=0; column < 10; column++) {

            // Получаем номер рисуемого блока
            char TileNum = Map[Layer][row][column];

            // Рисуем блок (размером 32 x 32)
            // соответствующий TileNum из первой
            // загруженной текстуры
            Tile.Draw(0, TileNum, column*32, row*32);
        }
    }
}

Добавление объектов

Недавно я упомянул, что персонажи рисуются как слой карты; такое решение не совсем верно, поскольку персонажи могут свободно перемещаться по всему миру и не соответствуют лежащей в основе многослойных карт теории. Вместо этого персонажи и другие перемещаемые объекты нужно рисовать как свободно позиционируемые блоки; для них не требуется использовать массив карты. Если говорить более подробно, вы отслеживаете все персонажи и объекты по их относительным координатам в мире; затем, когда они появляются в поле зрения, вы преобразуете эти относительные координаты в координаты, пригодные для рисования на экране.

Чтобы сейчас не усложнять вещи, давайте создадим структуру, хранящую координаты объекта и единственный номер рисуемого блока (который представляет объект):

typedef struct sObject {
    long XPos, YPos; // Координаты объекта
    char Tile;       // Рисуемый блок
} sObject;

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

#define MAX_OBJECTS 1024
sObject Objects[MAX_OBJECTS]; // Список для 1024 объектов

Для каждого кадра отслежвайте количество находящихся в нем объектов, которые будут нарисованы, используя переменную:

long NumObjectsToDraw = 0;

В каждом кадре вашей игры NumObjectToDraw сбрасывается в 0, а затем, когда объект добавляется к списку, счетчик увеличивается. Например, для добавления объекта вы можете использовать следующую функцию:

void AddObject(long XPos, long YPos, char Tile)
{
    if(NumObjectsToDraw < MAX_OBJECTS) {
        Objects[NumObjectsToDraw].XPos = XPos;
        Objects[NumObjectsToDraw].YPos = XPos;
        Objects[NumObjectsToDraw].Tile = Tile;

        NumObjectsToDraw++;
    }
}

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

// Tiles = ранее инициализированный и загруженный объект cTiles
// MapXPos, MapYPos = координаты карты
for(i = 0; i < NumObjectsToDraw; i++)
    Tiles.Draw(0, Objects[i].Tile,
               Objects[i].XPos - MapXPos * TileWidth,
               Objects[i].YPos - MapYPos * TileHeight);

 

ПРИМЕЧАНИЕ
Вы обратили внимание, что во фрагменте кода рисования я умножал координаты карты на ширину и высоту блока (определенные как переменные TileWidth и TileHeigth). Вы спрашиваете зачем? Да потому что объекты не привязаны к сетчатой структуре карты! Объекты используют точные координаты (fine coordinate), о которых вы прочтете в последующих разделах.

Плавная прокрутка

Поэкспериментировав с блочными картами вы обнаружите, что для больших карт необходима возможность прокрутки, чтобы игрок мог осматривать всю карту. В текущем воплощении механизм работы с блоками производит похожий на рывок сдвиг всякий раз, когда вы меняете координаты рисования карты. Чтобы улучшить визуальное впечатление надо сделать это движение более плавным, используя технику, известную как плавная прокрутка (smooth scrolling).

Чтобы визуально представить плавную прокрутку, вообразите блочную карту в виде большого растрового изображения. У каждого пикселя растра есть собственная пара координат, называемая точные координаты (fine coordinates) карты. У каждой группы пикселей, представляющей блок, есть собственный набор координат на карте. Например, если размер блока 16 × 16 пикселей, а карта представляет собой массив 10 × 10, размеры визуализированного изображения карты будут 160 × 160 пикселей (значит у карты разрешение точных координат 160 × 160 пикселей). Такой пример показан на рис. 7.4.


Рис. 7.4. Карта, размером 2 х 2 блока использует блоки размером 16 х 16 пикселей, так что разрешение точных координат будет 32 х 32

Рис. 7.4. Карта, размером 2 × 2 блока использует блоки размером 16 × 16 пикселей, так что разрешение точных координат будет 32 × 32. Обратите внимание, что у каждого пикселя есть собственная пара координат


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

Я не буду переусложнять предмет обсуждения; для использования плавной прокрутки достаточно добавить несколько строчек кода. Когда вы рисуете блоки карты надо всего лишь слегка сместить их, чтобы выровнять по точным координатам карты.

Чтобы вычислить смещение при рисовании блоков, вы просто берете точные координаты карты, с которых хотите начать рисование (точные координаты карты относятся к первому рисуемому пикселю, находящемуся в верхнем левом углу экрана) и вычисляете несколько переменных, как показано ниже:

// Подразумеваем, что FineX и FineY - это
// используемые точные координаты
// TileWidth и TileHeight - это размеры блока
// Вычисляем действительные координаты блока карты
long MapX, MapY; // Координаты в массиве карты

MapX = FineX / TileWidth;  // Получаем координату карты X
MapY = FineY / TileHeight; // Получаем координату карты Y

// Вычисляем смещение блока в пикселях
long XOff, YOff;           // Смещение в пикселях
XOff = FineX % TileWidth;  // Смещение по X
YOff = FineY % TileHeight; // Смещение по Y

Обратите внимание, что я вычисляю четыре переменных. Первую пару координат, MapX и MapY, вы используете когда обращаетесь к массиву карты. Например, если вы указываете точные координаты карты 8, 8 (и размер блока 16 × 16), действительные координаты в массиве карты будут 0, 0 (поскольку пиксель с координатами 8, 8 находится внутри верхнего левого блока карты).

Вторая пара координат, XOff и YOff, являются смещениями рисуемых блоков. Для вычисления XOff и YOff вы берете модуль (остаток) от деления точных координат на размеры блока. Потом, всякий раз, когда вы хотите нарисовать блок карты (или любой блок, который использует точные координаты карты), вычтите значение XOff из координаты X блока, и значение YOff из координаты Y блока.

Вот как смещения плавной прокрутки работают в цикле визуализации:

// Tile = ранее инициализированный и загруженный объект cTiles
for(short row = 0; row < 11; row++) {
    for(short column = 0; column < 11; column++) {
        // Получаем номер рисуемого блока
        char TileNum = Map[row][column];

        // Рисуем блок (размером 32x32), связанный с TileNum
        // из первой загруженной текстуры.
        Tile.Draw(0, TileNum, column*32-XOff, row*32-YOff);
    }
}

 

ПРИМЕЧАНИЕ
Когда вы добавляете к своей графической библиотеке возможность плавной прокрутки, то в цикле визуализации должны рисовать дополнительную строку и столбец блоков, как в приведенном выше примере. Сцена может содержать карту 10 × 10, но поскольку ее можно плавно прокрутить в любом направлении, необходимо заполнять левый и нижний края, для чего и рисуется фрагмент 11 × 11 блоков.)

Карта и мышь

Даже если вы очень долго не играли в компьютерные игры, мимо вашего внимания не мог проскользнуть тот факт, что большинство игр активно используют мышь. Управление в стиле «укажи и щелкни» является действительно интуитивно понятным. Игрок просто щелкает по какому-то месту на экране, и его персонаж идет, куда было указано; или можно щелкнуть по предмету, чтобы взять его. А если вы захотите использовать такие возможности в вашей игре — как определить по чему щелкнул игрок?

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

Для примера предположим, что вы используете плавную прокрутку карты с блоками размером 32 × 32 пикселя. Координаты карты (в точных координатах) 48, 102, а координаты мыши — 45, 80. Сначала вычислим значения смещений для плавной прокрутки:

XOff = 48 % 32;  // FineX / TileWidth
YOff = 102 % 32; // FineY / TileHeight

Затем вычтем значения смещений из координат мыши и прибавим точные координаты карты, с которых она рисуется:

// Подразумеваем, что MouseX, MouseY - координаты мыши
long MouseFineX, MouseFineY; // Точные координаты щелчка
MouseFineX = MouseX - XOff + 48;
MouseFineY = MouseY - YOff + 102;

Теперь у вас есть точные координаты карты для того пикселя, по которому щелкнул пользователь. Осталось разделить MouseFineX и MouseFineY на размеры блока, чтобы получить координаты элемента массива:

long MouseMapX, MouseMapY; // Координаты щелчка по карте
MouseMapX = MouseFineX / 32;
MouseMapY = MouseFineY / 32;

Хотя приведенного выше фрагмента кода в большинстве случаев достаточно, иногда он не подходит, например при работе со свободно перемещающимися объектами, такими как игровые персонажи. Предположим, игрок щелкает по персонажу, который пересекает экран. Поскольку персонажи не относятся к массиву данных карты, вы используете метод сканирования рисуемых блоков и сравниваете их координаты с координатами указателя мыши. Это простой вопрос проверки границ (bounds checking).

Предположим, что размер блока 64 × 64 пикселя, и он рисуется в точке экрана с координатами 48, 100. Если указатель мыши находится в точке экрана с координатами 60, 102, то он попадает в блок, поскольку блок занимает область от 48, 100 до 112, 164. Вы можете написать небольшую функцию, которая получает координаты блока (в экранном пространстве), размеры блока и координаты указателя мыши и возвращает TRUE, если указатель находится внутри блока и FALSE в ином случае.

BOOL IsMouseTouchingTile(
          long TileX, long TileY,          // Координаты блока
          long TileWidth, long TileHeight, // Размеры блока
          long MouseX, long MouseY)        // Координаты мыши
{
    // Проверяем выход за левую строну
    if(MouseX < TileX) return FALSE;

    // Проверяем выход за правую сторону
    if(MouseX >= TileX + TileWidth) return FALSE;

    // Проверяем выход за верхнюю сторону
    if(MouseY < TileY) return FALSE;

    // Проверяем выход за нижнюю сторону
    if(MouseY >= TileY + TileHeight) return FALSE;

    // Указатель находится внутри блока
    return TRUE; // Возвращаем успех
}

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

if(IsMouseTouchingTile(48,100,64,64,60,102) == TRUE)

    // Мышь касается блока!

else

    // Мышь не касается блока

Создание класса карты

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

#define MAX_OBJECTS 1024

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

typedef struct {
    long XPos, YPos;
    char Tile;
} sObject;

Далее идет объявление класса карты. Он содержит массив структур sObject и массив слоев карты. Размеры карты хранятся в двух переменных, m_Width и m_Heigth, которые устанавливаются при инициализации класса карты вызовом Create.

Каждый экземпляр класса карты может хранить огромное количество слоев (около миллиона). Вы храните данные каждого слоя блоков в отдельном массиве m_Map. Поскольку размеры карты фиксируются при ее создании, вы получаете доступ к данным каждого слоя блоков вычисляя текущее смещение в массиве m_Map и осуществляя чтение или запись этого слоя через указатель. Доступ к сданным слоев демонстрируют функции класса SetMapData и Render.

Что касается используемой графики, класс карты ограничен одним набором блоков (для хранения изображений блоков используется только одна текстура). Вы информируете класс карты какой объект класса блоков будет использоваться для рисования карты через функцию UseTiles, которую вы увидите в объявлении класса.

Поговорив об объявлении класса карты давайте посмотрим на него:

class cMap
{
  private:
    long m_Width, m_Height;     // Ширина и высота карты
    long m_NumLayers;           // Количество слоев
    char *m_Map;                // Массив для данных блоков

    cTiles *m_Tiles;            // Класс cTile для работы с блоками

    long m_NumObjectsToDraw;        // Количество рисуемых объектов
    sObject m_Objects[MAX_OBJECTS]; // Список объектов

  public:
    cMap(); // Конструктор
    ~cMap(); // Деструктор

    // Функции создания и освобождения класса карты
    BOOL Create(long NumLayers, long Width, long Height);
    BOOL Free();

    // Функция для установки данных слоя карты
    BOOL SetMapData(long Layer, char *Data);

    // Функции очистки и добавления объекта в список
    void ClearObjectList();
    BOOL AddObject(long XPos, long YPos, char Tile);

    char *GetPtr(long Layer); // Получение указателя на массив карты
    long GetWidth(); // Получение ширины карты
    long GetHeight(); // Получение высоты карты

    // Назначаем объект класса cTile, который будет
    // использоваться для рисования блоков карты
    BOOL UseTiles(cTiles *Tiles);

    // Визуализируем карту, используя заданные координаты
    // верхнего левого угла карты, количество рисуемых строк
    // и столбцов и слой, используемый для рисования объектов
    BOOL Render(long XPos, long YPos,
                long NumRows, long NumColumns,
                long ObjectLayer);
};

Большинство функций прокомментированы и самодокументируемы, так что давайте пойдем дальше и посмотрим на полный код класса cMap. Код начинается с конструктора и деструктора, обеспечивающих установку данных класса в известное состояние и освобождение всех ресурсов при уничтожении объекта класса:

cMap::cMap()
{
    m_Map              = NULL;
    m_Tiles            = NULL;
    m_NumObjectsToDraw = 0;
    m_Width = m_Height = 0;
}

cMap::~cMap()
{
    Free();
}

Чтобы использовать класс карты вы должны сначала объявить экземпляр класса и вызвать функцию Create. Функция Create получает количество слоев в карте, а также ширину и высоту карты:

BOOL cMap::Create(long NumLayers, long Width, long Height)
{
    // Освобождаем предыдущую карту
    Free();

    // Сохраняем количество слоев, ширину и высоту
    m_NumLayers = NumLayers;
    m_Width     = Width;
    m_Height    = Height;

    // Выделяем память для данных карты
    if((m_Map = new char[m_NumLayers*m_Width*m_Height]) == NULL)
         return FALSE;

    // Очищаем карту
    ZeroMemory(m_Map, m_NumLayers*m_Width*m_Height);

    // Сбрасываем количество рисуемых объектов
    m_NumObjectsToDraw = 0;

    return TRUE;
}

Говоря кратко и по существу, функция Create выделяет память для массива значений char, в котором будет храниться информация. Размер массива определяется шириной, высотой и количеством используемых столбцов; перемножьте эти три значения в вы получите итоговый размер массива.

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

BOOL cMap::Free()
{
    // Освобождаем массив карты
    delete [] m_Map;
    m_Map = NULL;
    m_Width = m_Height = 0;
    m_NumLayers = 0;

    return TRUE;
}

Чтобы заполнить массив слоев карты полезной информацией о блоках вы сперва создаете представляющий слой массив значений char, как было показано раньше в разделе «Рисование основной карты». Используя этот массив в качестве аргумента вы обращаетесь к функции SetMapData, указав также номер слоя карты, в который вы хотите скопировать данные из массива:

BOOL cMap::SetMapData(long Layer, char *Data)
{
    // Проверка ошибок
    if(Layer >= m_NumLayers)
        return FALSE;

    // Копирование данных
    memcpy(&m_Map[Layer*m_Width*m_Height],Data,m_Width*m_Height);

    return TRUE;
}

Для каждого визуализируемого кадра (или как минимум, каждый раз когда вы рисуете карту), вы конструируете список объектов спрайтов, которые должны быть нарисованы. Сперва вызовом ClearObjectList вы подготавливаете класс карты к началу приема информации о спрайтах, которая будет использоваться при визуализации:

void cMap::ClearObjectList()
{
    m_NumObjectsToDraw = 0;
}

Чтобы добавить спрайт к списку рисуемых в кадре, вызовите AddObject, предоставив экранные координаты, где будет нарисован указанный блок:

BOOL cMap::AddObject(long XPos, long YPos, char Tile)
{
    if(m_NumObjectsToDraw < MAX_OBJECTS) {
        m_Objects[m_NumObjectsToDraw].XPos = XPos;
        m_Objects[m_NumObjectsToDraw].YPos = XPos;
        m_Objects[m_NumObjectsToDraw].Tile = Tile;
        m_NumObjectsToDraw++;

        return TRUE;
    }
    return FALSE;
}

Если вы хотите напрямую менять данные слоев карты, можете вызвать методы GetPtr, GetWidth и GetHeight класса карты, возвращающие указатель на массив данных слоев карты, ширину карты и высоту карты, соответственно:

char *cMap::GetPtr(long Layer)
{
    if(Layer >= m_NumLayers)
        return NULL;

    return &m_Map[Layer*m_Width*m_Height];
}

long cMap::GetWidth()
{
    return m_Width;
}

long cMap::GetHeight()
{
    return m_Height;
}

Раньше я говорил, что для рисования карты вы можете использовать только один набор блоков. Чтобы сообщить классу карты, какой объект класса блоков использовать, передайте указатель на экземпляр класса в функцию класса карты UseTiles:

BOOL cMap::UseTiles(cTiles *Tiles)
{
    if((m_Tiles = Tiles) == NULL)
        return FALSE;

    return TRUE;
}

Наконец вы добрались до функции Render, которая визуализирует карту на экране, используя предоставленные вами точные координаты карты, а также количество рисуемых строк и столбцов. Если вы используете спрайты, то должны также указать, после какого слоя их рисовать, установив аргумент ObjectLayer:

BOOL cMap::Render(long XPos, long YPos,
                  long NumRows, long NumColumns,
                  long ObjectLayer)
{
    long MapX, MapY;
    long XOff, YOff;
    long Layer, Row, Column, i;

    char TileNum;
    char *MapPtr;

    // Проверка ошибок
    if(m_Map == NULL || m_Tiles == NULL)
        return FALSE;

    // Вычисление переменных для плавной прокрутки
    MapX = XPos / m_Tiles->GetWidth(0);
    MapY = YPos / m_Tiles->GetHeight(0);
    XOff = XPos % m_Tiles->GetWidth(0);
    YOff = YPos % m_Tiles->GetHeight(0);

    // Цикл перебора слоев
    for(Layer = 0; Layer < m_NumLayers; Layer++) {

        // Получаем указатель на данные карты
        MapPtr = &m_Map[Layer*m_Width*m_Height];

        // Цикл по строкам и столбцам
        for(Row = 0; Row < NumRows+1; Row++) {
            for(Column = 0; Column < NumColumns+1; Column++) {

                // Получаем номер рисуемого блока (и рисуем его)
                TileNum = MapPtr[(Row+MapY) * m_Width+Column+MapX];
                m_Tiles->Draw(0, TileNum,
                              Column * m_Tiles->GetWidth(0) - XOff,
                              Row * m_Tiles->GetHeight(0) - YOff);
            }
        }

        // Если это слой объектов - рисуем объекты
        if(Layer == ObjectLayer) {
            for(i = 0; i < m_NumObjectsToDraw; i++)
                m_Tiles->Draw(0, m_Objects[i].Tile,
                              m_Objects[i].XPos - XOff,
                              m_Objects[i].YPos - YOff);
        }
    }
    return TRUE;
}

Каждая функция в cMap прямолинейна и, по сути, повторяет информацию, изложенную ранее в разделе «Основы использования блочной графики». Я предлагаю вам посмотреть примеры программ к этой главе, чтобы увидеть класс cMap в действии. Вы найдете их на прилагаемом к книге CD-ROM (загляните в папку BookCode\Chap07\).


netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz