netlib.narod.ru | < Назад | Оглавление | Далее > |
К данному моменту у вас появилась лишь возможность интерактивного просмотра карты. Как насчет того, чтобы действительно поредактировать ее? Заучит заманчиво, а? В этом разделе я покажу вам как написать редактор карт, который позволит вам самим размещать блоки на карте. Ушли те дни, когда вы задавали карты в виде набора значений в коде программы! Взгляните на рис. 10.7, где изображено окно редактора карт, о котором я буду рассказывать.
Рис. 10.7. Окно программы D3D_MapEditorLite
На рис. 10.7 изображено окно программы D3D_MapEditorLite. Оно выглядит очень похоже на окно программы просмотра карт, о которой вы уже читали. Главным отличием данной программы является добавление области выбора блоков и возможность редактировать карту в реальном времени.
Теперь загрузите проект D3D_MapEditorLite и следуйте дальше вместе со мной.
В заголовочном файле проекта main.h появилось несколько новых членов данных, необходмых для редактирования. Вот они в порядке их появления:
int g_iCurTile = 0; int g_iCurTileSet = 0; int g_iMaxTileSet = 3; int g_iTotalTiles = 18;
Первая переменная, g_iCurTile, сообщает редактору какой именно блок выбран пользователем для рисования в данный момент. Когда пользователь редактирует карту, на нее будет помещаться именно этот блок.
Следующая переменная, g_iCurTileSet, сообщает редактору какая страница набора блоков отображается на панели инструментов. Данная переменная необходима для навигации по страницам набора блоков, поскольку у вас может быть больше блоков, чем одновременно можно отобразить на панели инструментов. В рассматриваемом редакторе карт есть только одна страница блоков, но в нем реализована поддержка нескольких страниц на тот случай, если вы захотите увеличить количество блоков.
Далее идет переменная g_iMaxTileSet. Она сообщает системе сколько страниц может быть в наборе блоков. Фактически вы можете указать здесь сколь угодно большое число. Я использую его лишь для того, чтобы уберечь пользователя от погони за горизонтом.
Последний элемент, g_iTotalTiles, сообщает программе сколько блоков загружено в память. Это очень важная информация, поскольку она позволяет предотвратить выбор пользователем отсутствующих блоков, что может привести к краху программы. Я загружаю 18 блоков, но вы можете увеличить количество загружаемых блоков в соответствии с вашими потребностями, увеличив значение переменной.
Ход выполнения программы редактирования карты практически не отличается от работы программы просмотра карты. Также как и в программе просмотра карт, здесь сперва инициализируются клавиатура, система визуализации, текстуры и карта. Нововведением является инициализация панели инструментов. Панель инструментов содержит область выбора блоков, позволяющую указать тот блок, который будет использоваться для редактирования. После того, как все компоненты инициализированы, программа ожидает ввода данных пользователем и отображает карту. Ход выполнения программы показан на рис. 10.8.
Рис. 10.8. Ход выполнения программы редактирования карт
Панель инструментов с областью выбора блоков является очень важной частью редактора карт. Без нее динамическое редактирование блочной карты было бы очень трудным. Код использует стандартные приемы программирования для Windows, чтобы создать дочернее окно главного окна программы и добавить к нему несколько элементов управления. Отображение блоков на панели инструментов выполняется посредством обращения к DirectX. Вот как выглядит код, создающий окно панели инструментов и кнопки навигации по страницам набора блоков:
void vCreateToolbar(HWND hwnd, HINSTANCE hinst) { WNDCLASSEX wcToolBar; // Инициализация и регистрация класса окна панели инструментов wcToolBar.cbSize = sizeof(wcToolBar); wcToolBar.style = CS_HREDRAW | CS_VREDRAW; wcToolBar.lpfnWndProc = fnMessageProcessor; wcToolBar.cbClsExtra = 0; wcToolBar.cbWndExtra = 0; wcToolBar.hInstance = hinst; wcToolBar.hIcon = LoadIcon(NULL, IDI_APPLICATION); wcToolBar.hCursor = LoadCursor(NULL, IDC_ARROW); wcToolBar.hbrBackground= (HBRUSH) GetStockObject (COLOR_BACKGROUND); wcToolBar.lpszMenuName = NULL; wcToolBar.lpszClassName= "ToolBar"; wcToolBar.hIconSm = LoadIcon(NULL, IDI_APPLICATION); RegisterClassEx(&wcToolBar); // Создание окна панели инструментов hWndToolBar = CreateWindowEx( WS_EX_LEFT|WS_EX_TOPMOST|WS_EX_TOOLWINDOW, "ToolBar", "ToolBar", WS_BORDER | WS_VISIBLE | WS_CAPTION | WS_MINIMIZEBOX, g_iWindowWidth - 100, g_iYOffset, 100, g_iWindowHeight - 20, hwnd, NULL, hinst, NULL); // Кнопка выбора предыдущего блока hBUTTON_PREVTILE = CreateWindow( "BUTTON", "<", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 10, 405, 20, 20, hWndToolBar, (HMENU)ID_BUTTON_PREVTILE, hinst, NULL); // Кнопка выбора следующего блока hBUTTON_NEXTTILE = CreateWindow( "BUTTON", ">", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 65, 405, 20, 20, hWndToolBar, (HMENU)ID_BUTTON_NEXTTILE, hinst, NULL); // Активация области редактирования SetActiveWindow(g_hWnd); // Отображение набора блоков на панели инструментов vRenderTileSet(); }
Переменная wcToolBar хранит информацию класса окна панели инструментов. Значения для класса не представляют собой ничего такого, что следовало бы описать, поскольку следуют стандартным правилам программирования для Windows.
Функция CreateWindowEx() выполняет фактическое создание панели инструментов. Она создает окно с именем ToolBar без кнопок закрытия и свертывания. Это гарантирует, что пользователь случайно не закроет область выбора блоков. Кроме того, в функции создания окна я задаю местоположение панели инструментов таким образом, чтобы она находилась внутри границ главного окна.
После того, как окно панели инструментов создано, я создаю пару кнопок для навигации по набору блоков. Эти кнопки называются hBUTTON_PREVTILE и hBUTTON_NEXTTILE. Когда их нажимают, программа переходит к предыдущей или следующей странице странице набора блоков.
Панель инструментов сама по себе выглядит великолепно, но окончательный блеск ей придают отображаемые на ней блоки. В области выбора блоков одновременно может отображаться до 21 блока. Если у вас больше 21 блока, для доступа к дополнительным блокам используются кнопки навигации. Чтобы отобразить 21 блок я визуализирую их в пустом буфере, а затем копирую полученный результат на панель инструментов. Код для этого содержится в следующей функции:
void vRenderTileSet(void) { RECT rectDest; RECT rectSrc; int iX; int iY; int iTile; // Включаем рассеянное освещение g_pd3dDevice->SetRenderState(D3DRS_AMBIENT, 0x00606060); // Очищаем вторичный буфер и z-буфер g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0f, 0); // Начинаем визуализацию g_pd3dDevice->BeginScene(); // Задаем состояние альфа-смешивания // Это необходимо для реализации прозрачности/полупрозрачности g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); // Отображение активных блоков for(iY = 0; iY < 7; iY++) { for(iX = 0; iX < 3; iX++) { // Вычисляем отображаемый блок iTile = (g_iCurTileSet * 21) + (iX + (iY * 3)); // Отображаем, если это существующий блок if(iTile < g_iTotalTiles) { vDrawInterfaceObject( iX * g_iTileSize, iY * g_iTileSize, (float)g_iTileSize, (float)g_iTileSize, iTile); } // Рисуем рамку поверх текущего блока if(iTile == g_iCurTile) { vDrawInterfaceObject( iX * g_iTileSize, iY * g_iTileSize, (float)g_iTileSize, (float)g_iTileSize, 18); } } } // Отображаем текущий блок vDrawInterfaceObject( 32, 32 * 7, (float)g_iTileSize, (float)g_iTileSize, g_iCurTile); // Завершаем визуализацию g_pd3dDevice->EndScene(); // Исходный прямоугольник rectSrc.top = 0; rectSrc.bottom = g_iTileSize * 8; rectSrc.left = 0; rectSrc.right = g_iTileSize * 3; // Целевой прямоугольник rectDest.top = 2; rectDest.bottom = (g_iTileSize * 8) + 2; rectDest.left = 0; rectDest.right = (g_iTileSize * 3); g_pd3dDevice->Present(&rectSrc, &rectDest, hWndToolBar, NULL); }
Первая часть логики визуализации содержит код, который очищает буфер, включает рассеянное освещение и активизирует альфа-смешивание. Главное удовольствие начинается в идущих следом циклах визуализации. В основном цикле программа перебирает в цикле ряды из трех блоков и визуализирует каждый блок в экранном буфере. Код продолжает работать таким образом, пока не будут отображены все семь рядов блоков.
Чтобы помочь пользователю понять, какой именно блок активен в данный момент времени, код изображает вокруг выбранного блока красный квадрат. В цикле визуализации для каждого блока проверяется не является ли он активным в данный момент, и, если да, то к его изображению добавляется красный квадрат.
После того, как визуализация набора блоков завершена, копия выбранного в данный момент блока отображается в нижней части экрана. Это еще один полезный индикатор текущего блока. Взгляните на рис. 10.9, чтобы увидеть структуру панели инструментов.
Рис. 10.9. Структура панели инструментов
На рис. 10.9 видно как отображение блоков начинается в левом верхнем углу и продолжается сверху вниз. На иллюстрации текущим блоком является блок с номером 10 и поэтому именно вокруг него нарисован красный квадрат. Тот же самый блок скопирован и в нижней части области просмотра блоков, что так же указывает какой именно блок выбран. Еще ниже на панели инструментов выводятся кнопки для навигации по набору блоков.
Теперь, когда выполнена визуализация всех блоков, надо поместить их на панель инструментов. Это выполняется путем задания исходной и целевой областей и вызова функции Present(). Как видите, в качестве параметров этой функции передаются исходная прямоугольная область и прямоугольная область места назначения. Сама функция сообщает системе визуализации, что необходимо взять изображение из одной области и скопировать его в другую. В этом случае вы можете визуализировать блоки в буфере трехмерной графики, а затем скопировать их на панель инструментов для показа. Посмотрите на код, и вы увидите как я копирую исходную область и перемещаю ее на панель инструментов.
Панель инструментов теперь видима, но как же вычислить, какой блок на ней выбран? В этом случае мышь — лучший друг человека, и именно она будет использоваться для выбора блоков. Процесс может показаться легким, если не учесть тот факт, что панель инструментов может свободно перемещаться. Поскольку вы не можете гарантировать, что панель инструментов всякий раз будет находиться в одном и том же месте, необходимо принимать во внимание ее текущее местоположение и уже основываясь на этой информации проверять координаты указателя мыши.
В программе D3D_MapEditorLite есть два режима работы мыши. В режиме редактирования вы шелкаете левой кнопкой мыши по карте в области просмотра и редактируете выбранный блок. В режиме выбора блока вы щелкаете по изображениям блоков на панели инструментов, чтобы выбрать новый блок для использования при редактировании. Чтобы выяснить в каком режиме работает пользователь, я проверяю координаты указателя мыши и смотрю, находятся ли они внутри области панели инструментов или внутри области просмотра карты. Если координаты находятся внутри области панели инструментов, я перехожу к логике для панели инструментов, чтобы определить, какой блок находится под указателем мыши. Если координаты находятся внутри области просмотра карты, в игру вступает логика редактирования карты, вычисляющая какой именно блок карты редактируется. Вот как выглядит код, выполняющий эти задачи:
void vCheckMouse(void) { RECT rcWindow; POINT Point; int iMouseX; int iMouseY; int iTileX; int iTileY; // Получаем координаты указателя мыши GetCursorPos(&Point); iMouseX = Point.x; iMouseY = Point.y; // Определяем местонахождение рабочей области панели инструментов GetWindowRect(hWndToolBar, &rcWindow); // Проверяем, находится ли указатель мыши внутри окна панели инструментов if(iMouseX > rcWindow.left && iMouseX < rcWindow.right && iMouseY > rcWindow.top && iMouseY < rcWindow.bottom) { // Преобразуем координаты указателя мыши // в локальные координаты панели инструментов iMouseX -= rcWindow.right; iMouseY -= rcWindow.top; // Вычисляем координаты блока iTileX = iMouseX / g_iTileSize; iTileY = iMouseY / g_iTileSize; // Вычисляем, какой блок выбран g_iCurTile = (g_iCurTileSet * 21) + (iTileX + (iTileY * 3)) - 1; // Проверяем, что выбран существующий блок if(g_iCurTile < 0|| g_iCurTile >= g_iTotalTiles) { g_iCurTile = 0; } vRenderTileSet(); } // Проверяем, находится ли указатель мыши в окне редактирования else { GetWindowRect(g_hWnd, &rcWindow); if(iMouseX > rcWindow.left && iMouseX < rcWindow.right && iMouseY > rcWindow.top && iMouseY < rcWindow.bottom) { // Преобразуем координаты указателя мыши // в локальные координаты окна редактирования iMouseX -= rcWindow.left + g_iXOffset; iMouseY -= rcWindow.top + g_iYOffset; // Вычисляем координаты блока iTileX = iMouseX / g_iTileSize; iTileY = iMouseY / g_iTileSize; g_iTileMap[iTileX + g_iXPos + ((iTileY + g_iYPos) * g_iMapWidth)] = g_iCurTile; } } }
Чтобы определить местоположение панели инструментов и окна редактирования я воспользовался функцией GetWindowRect(). После того, как я узнал где они расположены, достаточно простой проверки, чтобы определить находится ли указатель мыши в заданной области.
Если указатель мыши находится внутри панели инструментов, я беру координаты указателя мыши и делю их на размеры блока, чтобы увидеть, какой именно блок выбирает пользователь. Как только я узнал, какой блок выбран, я заношу в переменную g_iCurTile новое значение. Затем я вызываю функцию vRenderTileSet(), чтобы переместить красный квадрат, отмечающий выбранный блок на новое место.
Если указатель мыши находится в окне редактирования, я корректирую координаты указателя мыши с учетом местоположения клиентской области окна. Затем я делю координаты указателя мыши на размеры блока, чтобы вычислить какой блок карты выбран для редактирования. Последний шаг к Валгалле — помещение значения из переменной g_iCurTile в соответствующий элемент массива g_iTileMap.
netlib.narod.ru | < Назад | Оглавление | Далее > |