netlib.narod.ru | < Назад | Оглавление | Далее > |
Хватит уже теории! Как насчет какого-нибудь кода, который покажет вам как создать свой собственный редактор карт? Загрузите проект D3D_MapViewer и следуйте вперед.
Программа D3D_MapViewer создает случайную карту, которую вы можете прокручивать в различных направлениях. Вы не сможете редактировать эту карту, но проект покажет вам основы навигации на блочной карте. После того, как вы разберетесь с реализацией прокручиваемой в разные стороны блочной карты, я покажу вам как осуществлять редактирование карты.
Запустите программу просмотра карт, и вы увидите окно, изображенное на рис. 10.3.
Рис. 10.3. Окно программы просмотра карты
На рис. 10.3 вы видите выглядящий знакомым набор блоков, отображенный в окне несколько большего размера. Отличие этой блочной карты от примеров из главы 5 заключается в том, что вы можете передвигать карту с помощью клавиш управления курсором. Стрелки вверх и вниз вызывают перемещение вдоль оси Y, а стрелки влево и вправо — вдоль оси X. Запустите программу и проверьте это самостоятельно.
Вы можете обратить внимание на отладочную информацию, отображаемую в левом верхнем углу окна. Здесь показаны координаты блока, который в данный момент отображается в левом верхнем углу окна. Когда вы перемещаетесь по карте, эти координаты изменяются, отражая вашу глобальную позицию. Помните, что значение координат по любой из осей не может быть меньше нуля.
В заголовочном файле main.h проекта находится несколько исключительно важных типов данных, используемых для просмотра карты. Вот их краткий список:
int g_iTileSize = 32; int g_iTilesWide = 20; int g_iTilesHigh = 15; int g_iMapWidth = 100; int g_iMapHeight = 100; int g_iXPos = 0; int g_iYPos = 0; int g_iTileMap[10000];
Первая переменная, g_iTileSize, сообщает программе просмотра карты сколько точек в ширину и в высоту занимают используемые блоки. Я присваиваю ей значение 32, следовательно ширина и высота моих блоков будут равны 32 точкам.
Вторая переменная, g_iTilesWide, сообшает программе сколько блоков должно помещаться в окне просмотра по горизонтали. Поскольку ширина используемого мной окна равна 640 точкам, а ширина блока равна 32 точкам, я присваиваю этой переменной значение 20, чтобы карта занимала все окно.
Третья переменная, g_iTilesHigh, работает точно так же, как g_iTilesWide, за исключением того, что задает количество блоков в окне по вертикали. Высота области просмотра равна 480 точкам, так что 15 блоков замечательно заполнят ее.
Четвертая переменная, g_iMapWidth, сообщает программе сколько блоков в карте по оси X. Поскольку программа просмотра может прокручивать карту, последняя может быть больше, чем область просмотра. Я задаю здесь значение 100, чего должно быть вполне достаточно для демонстрации прокрутки.
Пятая переменная, g_iMapHeight, работает точно так же как и предыдущее поле, за исключением того, что задает количество блоков в карте по оси Y. Ей я также присваиваю значение 100, чтобы карта была квадратной.
Шестая переменная, g_iXPos, сообщает программе просмотра в каком месте по оси X расположена область просмотра. Поскольку карта по размерам больше чем окно просмотра, программа должна отслеживать положение окна просмотра на карте. Это число не может быть отрицательным, поскольку отрицательные координаты располагаются за пределами карты.
Седьмая переменная, g_iYPos, задает вторую координату местоположения окна просмотра на карте.
Восьмая переменная, g_iTileMap, представляет собой массив целых чисел, описывающий блочную карту. В нем хранятся номера всех блоков, отображаемых на карте. Поскольку ширина и высота нашей карты равны 100 блокам, я создаю массив размером 10 000 элементов.
Назначение этих переменных и их значения представлены на рис. 10.4.
Рис. 10.4. Глобальные переменные для просмотра карты
На рис. 10.4 видно как ширина и высота карты задают ее общий размер. Также видно как размер окна просмотра задается в блоках. Кроме того, можно обратить внимание как координаты области просмотра задают ее положение на карте.
Ход выполнения программы очень похож на работу остальных примеров в этой книге. Сперва выполняется инициализация составляющих программу систем. После ее завершения программа начинает обработку сообщений и вводимых пользователем данных. Это продолжается до тех пор, пока пользователь не завершит работу приложения. Все это и кое-что еще показано на рис. 10.5.
Рис. 10.5. Ход выполнения программы просмотра карт
На рис. 10.5 появилась только одна новая функция — vInitMap().
Функция vInitMap() отвечает за создание случайной карты. Взгляните как выглядит код, выполняющий эти действия:
void vInitMap(void) { int i; // Заполнение карты случайными блоками for(i = 0; i < g_iMapWidth * g_iMapHeight; i++) { g_iTileMap[i] = rand()%3; } }
Возможно вы думаете «Что мне может дать случайное заполнение карты?». Гораздо больше, чем вы могли предположить. Хотя вы будуте редактировать почти каждый блок на игровом поле, случайный набор блоков является хорошей отправной точкой, благодаря которой карта будет выглядеть естественно. Скорее всего, вы не захотите вручную размещать каждый камень, куст или ягоду на нескольких десятках карт. Сначала это может быть забавно, но очень быстро вы устанете.
В коде видно, как я просматриваю весь буфер карты и присваиваю каждому блоку случайное значение в диапазоне от 0 до 2. В результате карта будет похожа на мешанину. Но есть несколько вещей, которые вы можете реализовать здесь. Например, вы можете задать параметры распределения случайных блоков. Пусть 10 процентов карты беспорядочно заполняются камнями, а 60 процентов — водой. Тпкие игры, как SimCity 4 и Civilization используют подобный метод при создании собственных карт. Позднее я подробнее рассмотрю автоматическую генерацию карт, так что пока прекратим вешать лапшу на уши.
На блок-схеме программы, изображенной на рис. 10.5, присутствует вызов функции vCheckInput(). Навигация по карте осуществляется путем нажатия на клавиши, так что это очень важная функция. Следуйте далее и взгляните на приведенный ниже код:
void vCheckInput(void) { // Чтение из буфера клавиатуры int iResult = iReadKeyboard(); // Проверяем, сколько нажатий на клавиши возвращено if(iResult) { // Перебираем в цикле полученные данные for(int i = 0; i < iResult; i++) { // Выход из программы, если нажата клавиша ESC if(diks[DIK_ESCAPE][i]) { PostQuitMessage(0); } // Вверх if(diks[DIK_UP][i]) { g_iYPos--; } // Вниз if(diks[DIK_DOWN][i]) { g_iYPos++; } // Влево if(diks[DIK_LEFT][i]) { g_iXPos--; } // Вправо if(diks[DIK_RIGHT][i]) { g_iXPos++; } // Проверяем, не вышли ли за границы if(g_iYPos < 0) g_iYPos = 0; else if (g_iYPos >= (g_iMapHeight - g_iTilesHigh)) g_iYPos = (g_iMapHeight - g_iTilesHigh); if(g_iXPos < 0) g_iXPos = 0; else if (g_iXPos >= (g_iMapWidth - g_iTilesWide)) g_iXPos = (g_iMapWidth - g_iTilesWide); } } }
Иллюстрации всегда хорошо дополняют слова, так что взгляните на рис. 10.6, показывающий работу кода.
Рис. 10.6. Ход выполнения функции проверки входных данных
На рис. 10.6 показан ход выполнения функции проверки входных данных. В первой части кода я проверяю буфер клавиатуры, чтобы убедиться, что пользователь нажимал на какие-нибудь клавиши. Если да, код проверяет какие именно клавиши нажаты. Если нажата клавиша Esc, программа завершает работу. Если нажата какая-нибудь клавиша управления курсором в коде соответствующим образом меняются значения координат области просмотра g_iXPos и g_iYPos. После того, как выполнена проверка нажатия клавиш управления курсором, код выполняет проверку, чтобы убедиться, что координаты находятся в допустимом диапазоне. Благодаря этому в окне просмотра не отображаются области, лежащие за границами карты.
Вы уже посмотрели, как программа осуществляет навигацию на карте, но что насчет отображения графики? Программа бесполезна без визуальной обратной связи. Старая добрая функция vInitInterfaceObjects() заботится о загрузке используемых в программе изображений блоков. Вот фрагмент кода, выполняющий этот трюк:
for(int i = 0; i < 3; i++) { // Установка имени sprintf(szTileName, "tile%d.bmp", i); // Загрузка if(FAILED(D3DXCreateTextureFromFile( g_pd3dDevice, szTileName, &g_pTexture[i]))) { return; } }
Загрузка блоков довольно проста. Я просто запускаю цикл, перебирающий доступные блоки и загружающий файлы с именами от tile0.bmp до tile2.bmp. Поскольку загружаются только три блока, процесс выполняется очень быстро. Для последующего использования загруженные блоки сохраняются в массиве g_pTexture.
Функция vRender() занимается отображением блочной карты. В ней я в цикле перебираю блоки карты и отображаю текстуры, соответствующие номерам блоков. Вот код цикла визуализации:
// Сверху вниз for(iY = 0; iY < g_iTilesHigh; iY++) { // Справа налево for(iX = 0; iX < g_iTilesWide; iX++) { // Вычисляем смещение в буфере iBufferPos = iX + g_iXPos + ((iY + g_iYPos) * g_iMapWidth); // Получаем требуемый блок iCurTile = g_iTileMap[iBufferPos]; // отображаем блок vDrawInterfaceObject((iX * g_iTileSize), (iY * g_iTileSize), (float)g_iTileSize, (float)g_iTileSize, iCurTile); } }
В функции визуализации присутствуют два цикла. Первый цикл осуществляет перебор блоков вдоль оси Y. Внутренний цикл перебирает блоки вдоль оси X. Таким образом я отображаю все необходимые блоки. Это тот же самый метод, который я описывал в главе 5.
netlib.narod.ru | < Назад | Оглавление | Далее > |