![]() |
|
... В прошлый раз мы научились проектировать интерфейс приложения, используя стандартные компонентов GUI: кнопки, списки, диалоговые окна. Сегодня мы завершаем эту тему. Нам осталось рассмотреть методику обработки событий от этих компонентов, а кроме того попробуем загрузить в среду irrlicht уровень от quake3. Напоминаю: очень-очень важно при проектировании интерфейса дать всем используемым компонентам уникальные идентификаторы. В противном случае вы не сможете определить, какой именно элемент был активирован пользователем. Для знакомых с идеями delphi/cbuilder/.net методика обработки событий в irrlicht может показаться очень примитивной. При создании конкретного элемента управления мы не можем привязать к нему обработчик события, а должны внутри уже знакомой нам функции "def OnEvent (self, e)" определить, к какому классу относится произошедшее событие. Если оно относится к семейству GUI Events, то уже следует определить, что за элемент был активирован, и какое специфическое для него событие произошло. Для тех, кто когда-то писал программы под классическое winapi (без позднее появившихся MFC), есть отличная возможность вернуться в босоногое детство. Отчасти подобное ретроградство можно объяснить не слишком богатым набором компонентов, отчасти тем, что поддержка GUI в irrlicht, да и почти во всех остальных 3d-движках, никогда не была приоритетным направлением. Большей частью такая методика обработки событий не представляет никаких сложностей, разве что больший объем кода, но в определенных ситуациях возникает настоящая угроза идеям ООП: я говорю об обработке событий от всплывающих окон. В примере ниже я показал это с помощью MessageBox — окна вопроса с двумя вариантами выбора. Дело в том, что события от диалогового окна (даже если он модальный) все равно приходят и анализируются единой для всего приложения функцией обработки сообщений. Если вы будете активно использовать irrlicht gui, рекомендую предварительно потратить время на создание собственной надстройки над моделью событий. Наиболее просто будет реализовать нечто подобное картам событий в MFC. Если вы знакомы с паттернами проектирования, то сможете подобрать сами наиболее подходящий паттерн поведения. В примере ниже создается список, текстовое поле и кнопки. При нажатии на кнопку "Append" содержимое текстового поля добавляется в конец списка. При этом выполняется проверка, чтобы текстовое поле было не пустым (в случае необходимости выводится окно сообщения ошибки). При нажатии на кнопку "Choose" появляется окно выбора из двух вариантов (YES|NO). Какая бы кнопка ни была нажата, в любом случае сообщение добавляется в список "lst_log". Кнопка "File Dialog" приводит к появлению диалога выбора файла. И, наконец, кнопка "Color Dialog" приводит к появлению диалога выбора цвета (поддержка данной возможности появилась в irrlicht начиная с версии 1.2). Для отображения окна выбора файла используется функция: addFileOpenDialog(title, modal, parent, id) ![]() import java import net.sf.jirr from net.sf.jirr import dimension2di from net.sf.jirr import position2di from net.sf.jirr import recti from net.sf.jirr import SColor from net.sf.jirr import IEventReceiver # стандартно класс обработчика событий наследуется от IEventReceiver class EvtHandler (IEventReceiver): # в этих двух переменных будут находиться ссылки на открытые диалоговые окна — выбора файлов и выбора цвета cdialog = None fdialog = None # конструктор класса обработки событий def __init__ (self): IEventReceiver.__init__ (self) def OnEvent (self, e): if e.getEventType() == net.sf.jirr.EEVENT_TYPE.EET_GUI_EVENT: # определяем, что событие относится к семейству событий GUI if GUI_MESSAGEBOX_DIALOG == e.getGUIEventCaller ().getID(): # теперь проверяем: если инициатор события MsgBox — окно сообщения с двумя кнопками YES и NO, то проверяем уточняющий тип сообщения — какая именно кнопка была нажата if net.sf.jirr.EGUI_EVENT_TYPE.EGET_MESSAGEBOX_YES == e.getGUIEventType (): lst_log.addItem ("MsgBox YES") if net.sf.jirr.EGUI_EVENT_TYPE.EGET_MESSAGEBOX_NO == e.getGUIEventType (): lst_log.addItem ("MsgBox NO") if GUI_FILEOPEN_DIALOG == e.getGUIEventCaller ().getID(): # событие закрытие диалога выбора файла if net.sf.jirr.EGUI_EVENT_TYPE.EGET_FILE_SELECTED == e.getGUIEventType (): lst_log.addItem ("File: " + self.fdialog.getFilename () ) if e.getGUIEventType () == net.sf.jirr.EGUI_EVENT_TYPE.EGET_BUTTON_CLICKED: # если подтип события — это нажатие кнопки, то я проверяю, какая из четырех кнопок была активирована, и выполняю соответствующие действия if e.getGUIEventCaller ().getID() == GUI_BUTTON_ADD: if txt_fio.getText() == '': # сообщение об ошибке, если поле пустое guienv.addMessageBox ("Error", "Text Field Is Empty, cannot add") return False lst_log.addItem (txt_fio.getText()) if e.getGUIEventCaller ().getID() == GUI_BUTTON_CHOOSE: # при нажатии на кнопку Choose следует показать диалоговое окно выбора из двух вариантов, # Первый параметр — это заголовок диалогового окна, второй — текст сообщения, третий параметр отвечает за то, будет ли наше окно сообщения модальным или нет, последний параметр — это набор отображаемых кнопок окна диалога, обратите внимание на то, как именно я выполняю сборку набора — через оператор битового или "|" guienv.addMessageBox ("Question", "What Do Your Want ?", True, net.sf.jirr.EMESSAGE_BOX_FLAG.EMBF_YES.swigValue() | net.sf.jirr.EMESSAGE_BOX_FLAG.EMBF_NO.swigValue(), None, GUI_MESSAGEBOX_DIALOG) lst_log.addItem ("MsgBox Showed") if e.getGUIEventCaller ().getID() == GUI_BTN_DIALOG_FILE: self.fdialog = guienv.addFileOpenDialog ("Select File", True, None, GUI_FILEOPEN_DIALOG) if e.getGUIEventCaller ().getID() == GUI_BTN_DIALOG_COLOR: self.cdialog = guienv.addColorSelectDialog ("Select Color", True, None, GUI_COLOR_DIALOG) return False java.lang.System.loadLibrary ('irrlicht_wrap') device = net.sf.jirr.Jirr.createDevice(net.sf.jirr.E_DRIVER_TYPE.EDT_DIRECT3D9, dimension2di(800, 600), 32) driver = device.getVideoDriver() guienv = device.getGUIEnvironment() # объявляем список идентификаторов элементов управления GUI_BUTTON_ADD = 103 GUI_BUTTON_CHOOSE = 104 GUI_MESSAGEBOX_DIALOG = 105 GUI_FILEOPEN_DIALOG = 106 GUI_COLOR_DIALOG = 107 GUI_BUTTON_SAVE = 108 GUI_LIST_SOURCE = 109 GUI_BTN_DIALOG_FILE = 110 GUI_BTN_DIALOG_COLOR = 111 GUI_TEXT_SOURCE = 112 # эти идентификаторы будут служить для управления пунктами меню GUI_MNN_APPLE = 113 GUI_MNN_ORANGE = 114 GUI_MNN_ORANGE_1 = 115 GUI_MNN_ORANGE_2 = 116 # создаем кнопки btn_add = guienv.addButton(recti(120,70,190,90), None, GUI_BUTTON_ADD, "Append") btn_remove = guienv.addButton(recti(120,100,190,120), None, GUI_BUTTON_CHOOSE, "Choose") btn_move_left = guienv.addButton(recti(120,200,190,220), None, GUI_BTN_DIALOG_FILE, "File Dialog") btn_move_left = guienv.addButton(recti(120,230,190,250), None, GUI_BTN_DIALOG_COLOR, "Color Dialog") # создаем список lst_log = guienv.addListBox(recti(10,100,110,400), None, GUI_LIST_SOURCE) # создаем текстовое поле txt_fio = guienv.addEditBox("Potatoes", recti(10,70,110,90), True, None, GUI_TEXT_SOURCE) # создаем меню menus = guienv.addMenu () # добавляем к меню первый пункт — это обычный пункт без дочерних подпунктов menus.addItem ("Apple", GUI_MNN_APPLE) # добавим разделитель между пунктами меню menus.addSeparator() # а теперь при вызове addItem мы укажем больше параметров, в том числе признак того, что этот пункт меню будет содержать дочерние подпункты # предпоследний параметр отвечает за то, будет ли данный пункт меню доступным или нет (т.е. заблокированным) # последний параметр — это как раз и есть признак того, что этот подпункт раскрывается menus.addItem ("Orange", GUI_MNN_ORANGE, True, True) # теперь получаем ссылку на созданное подменю и наполняем его новыми подпунктами # следует указать как параметр порядковый номер этого пункта меню menus_orange = menus.getSubMenu (2) menus_orange.addItem ("Orange_1", GUI_MNN_ORANGE_1) menus_orange.addItem ("Orange_2", GUI_MNN_ORANGE_2) evt = EvtHandler () device.setEventReceiver (evt) # дальнейший код обычен — организуется цикл отрисовки while device.run(): driver.beginScene(1, 1, SColor(255,220,241,240)) guienv.drawAll() driver.endScene() device.drop Теперь мы переходим ко второй части нашего сегодняшнего урока. Мы попробуем загрузить карту уровня игры quake3. Если у вас под руками нет дистрибутива, то ничего страшного. В поставке irrlicht идет множество примеров, необходимые файлы (изображений, звука и других ресурсов) для которых находятся в папке media. В том числе там есть и файл map-20kdm2.pk3. Сначала я приведу пример исходного текста программы, а затем мы его проанализируем. import java import net.sf.jirr from net.sf.jirr import dimension2di from net.sf.jirr import position2di from net.sf.jirr import SColor from net.sf.jirr import vector3df from net.sf.jirr import SKeyMap java.lang.System.loadLibrary ('irrlicht_wrap') device = net.sf.jirr.Jirr.createDevice(net.sf.jirr.E_DRIVER_TYPE.EDT_DIRECT3D9, dimension2di(640, 480), 32) driver = device.getVideoDriver() smgr = device.getSceneManager() device.setWindowCaption("1.7 quake 3") # для того, чтобы irrlicht смог загрузить некоторую модель уровня quake 3, необходимо добавить ссылку на его местоположение device.getFileSystem().addZipFileArchive("E:\Program_Files_2\jirr_0.8\media\map-20kdm2.pk3", True, True) mesh = smgr.getMesh("20kdm2.bsp") node = smgr.addOctTreeSceneNode(mesh, None, -1, 128); node.setPosition(vector3df(-1300,-144,-1249)) smgr.addCameraSceneNodeFPS(None,100,500,-1,SKeyMap(),0) # прячем курсор device.getCursorControl().setVisible(False) while(device.run()): driver.beginScene(True, True, SColor(0,100,100,100)) smgr.drawAll(); driver.endScene(); device.drop Прежде всего, я должен указать местоположение файлов, образующих уровень — непосредственно модель уровня и все связанные с ним ресурсы, те же текстуры. Для этого служит вызов функции addZipFileArchive, получающей на вход первым параметром путь к архиву (файлы pk3 — это обычные zip- архивы ресурсных файлов игры). Второй параметр функции addZipFileArchive отвечает за возможность игнорировать регистр символов при загрузке уровня или связанных с ним ресурсов, третий же задает признак того, что можно использовать короткие имена файлов. Так, в архиве есть подпапки: levelshots, maps, scripts, textures. Если данный флаг не установлен (значение третьего параметра False), то при последующих загрузках ресурсов необходимо указывать путь целиком, например: device.getFileSystem().addZipFileArchive("map-20kdm2.pk3", False, False) mesh = smgr.getMesh("maps/20kdm2.bsp") В случае, если загрузка ресурса была не успешна (скажем, уровень 20kdm2.bsp не был найден), то переменная mesh будет равна специальному значению None — ничего нет. Затем мы присоединяем модель к специальному узлу. Дело в том, что все объекты, размещенные в виртуальном мире irrlicht, представляют собой node — узлы различных видов. Каждый вид узла наилучшим образом оптимизирован для представления какой-то разновидности информации. Так, есть еще узлы вида: AnimatedMeshSceneNode — для представления анимированной модели персонажа, есть BillboardSceneNode — узел для представления спрайта (плоской 2d-картинки, всегда повернутой к камере лицом), есть CameraSceneNode — узел камеры и т.д. В irrlicht-узлы организованы в некоторое подобие дерева, у многих узлов есть родительский узел, один узел имеет несколько дочерних и, в свою очередь, может принадлежать некоторому родительскому узлу. Вообще идея с представлением виртуального мира в виде деревьев узлов очень часто применяется, это позволяет реализовывать иерархические модификации персонажей. Например, есть узел Дед_Мороз, с дочерними узлами Мешок_Подарков, Посох. Если вы перемещаете или вращаете узел Дед_Мороз, то также перемещаются и вращаются все его дочерние узлы, сохраняя относительное расстояние и ориентацию относительно своего родительского узла. Проще говоря, если ДедМороз сделал шаг влево, то его Мешок_Подарков и Посох не остались висеть на старом месте. Кроме того, грамотное проектирование иерархии узлов позволяет ускорить отрисовку 3d-сцены. Дело в том, что перед непосредственно рендерингом модели происходит определение того, видим ли данный узел или нет. И если это не так, то он и все вложенные в его состав узлы не рисуются. Следующий шаг — смещение узла (сами модели не способны перемещаться в виртуальном пространстве — еще одна причина существования узлов). Дело в том, что начало координат уровня не совпадает с началом координат мира irrlicht. И если вы не хотите увидеть модельку уровня где- то очень далеко в углу, то ее нужно передвинуть поближе. Конкретные значения цифр я аккуратно переписал из примера jirr, вам же в случае использования иных моделей следует воспользоваться методом "научного тыка" или взять редактор уровней quake — например radiant, — чтобы разобраться с системой координат конкретного уровня. ![]() 3D Studio (.3ds) — этот формат использовался в старых версиях 3dsmax еще во времена dos, сейчас это фактический стандарт для создания переносимых моделей во многих других приложениях 3d-моделирования. DirectX (.x) — платформонезависимый формат с поддержкой анимации персонажей. Maya (.obj) — очень известная программа 3d-моделирования. Milkshape (.ms3d) — формат, используемый достаточно неплохой программой Milkshape — есть поддержка анимации моделей (что самое приятное, она умеет импортировать и экспортировать модели из ресурсов различных современных игр). OCT (.oct) — один из форматов, которые понимает не очень известная в нашей стране программа 3d-моделирования blender. За рубежом она более популярна, т.к., хотя и проигрывает в возможностях лидерам рынка вроде 3dsmax, maya, но зато бесплатна и удовлетворяет большинство потребностей любительского 3d-моделирования. OGRE Meshes (.mesh) — помните в главе, посвященной обзору 3d-движков, я упоминал о занимающем первое место в рейтинге devmasters движке 3d- рендеринга OGRE? Именно в формате .mesh OGRE загружает модели в сцену. Quake 2 models (.md2) — анимированные модели персонажей из quake 2. В следующий раз мы продолжим работу с 3d-функциями irrlicht — попробуем создавать собственные уровни и модели игрового окружения с помощью инструмента irrEdit и 3dsmax/MilkShape. Также на очереди рассмотрение средств работы с шейдерами. black zorro, black-zorro@tut.by © Компьютерная газета
|
|