Журнал LinuxFormat - перейти на главную

LXF93:Blender

Материал из Linuxformat
Перейти к: навигация, поиск


Содержание

Blender: Чат будет почат

С помощью Python и Blender Бен Харлинг набросает среду для обмена сообщениями, открывая дорогу ко всестороннему взаимодействию.

Есть отличный мультик, под названием Noob make MMORPG, сделанный парнем с Украины, который часто появляется на форумах Ogre 3D. Его можно найти вот здесь http://snipurl.com/1eff4 Это история про начинающих разработчиков игр, которые, в эйфории от изобилия открытых решений для работы с графикой, хватаются за дело и сляпывают собственное жалкое подобие World of Warcraft. История способна сильно охладить энтузиазм новичков; и все-таки создать 3D-среду взаимодействия за очень короткий промежуток времени возможно – используя Blender совместно c Python.

На данном уроке мы разработаем простое приложение обмена сообщениями с 3D- интерфейсом на Blender, управляемое скриптом на Python и пригодное для обслуживания множества пользователей. Приложение предоставит большие возможности для расширения или включения его в ваш собственный проект Blender, а также может быть легко встроено в игру или же работать отдельно. Примечательно, что с этим приложением я сумел установить единую сессию обмена сообщениями между Windows, Linux, OS X и даже Playstation 3 с помощью одного и того же файла Blender, произвольно обрабатываемого с любой платформы – а это явно вызовет некоторое изумление.

Чтобы добиться такого, нужны всего-навсего последние версии Python и Blender для вашей системы. Вам также потребуется нарисовать интерфейс пользователя для окна чата. Я использовал Inkscape, чтобы создать макет простого графического интерфейса с окном для текста и с полем ввода. В данном проекте понадобятся растровые шрифты для отображения текста в Blender, поэтому я предусмотрел их подборку на диске. Шрифты можно создать инструментом FTBlender, но на время написания этого руководства были доступны версии только под FreeBSD и Windows.

Создаем клиента и интерфейс

Запустите Blender и удалите куб или любой другой примитив, который по умолчанию появится в окне. В качестве окна мы собираемся использовать простую плоскость, поэтому создайте такую, развернув лицом к себе; выровняйте камеру и увеличьте изображение, пока оно не займет все окно. Затем разделите окно просмотра и измените одну из панелей на UV-редактор изображения (UV Image Editor). Вернитесь на вид с 3D и нажмите F для перехода к выбору граней UV.

Теперь войдите в редактор изображений, нажмите А, чтобы выбрать все вершины, затем нажмите Image > Open и загрузите свой интерфейс. Вызовите кнопки редактирования нажатием F9, и увидите панель Texture Face, которая активна, потому что мы все еще в режиме UV Face Select. В этой панели активируйте кнопки Tex, Light, TwoSide и Alpha. Кнопка Alpha полезна при использовании изображений с альфа-каналом прозрачности.

Рис. 1

Начало окна чата. Кнопки UV диктуют, как объект с текстурой будет выглядеть в движке игры.

Перейдите обратно к 3D-виду и проверьте, что кнопка Viewport Shading установлена в режим Texture. Вы должны увидеть свой интерфейс отраженным на многоугольник. Если он развернут неправильно, перейдите обратно к UV Face Select, выберите снова все вершины UV, затем нажмите R для поворота. Чтобы повернуть на нужный угол, наберите значение в градусах (скажем, 90) и нажмите Enter. Выровняв текстуру, создайте новую небольшую плоскость, перекрывающую фон – проверьте, что она немного ближе к камере, чем большая плоскость. Поместите ее внизу интерфейса (она станет первой строкой текста) и еще раз с выбранной плоскостью установите 3D-окно в режим UV Face Select (нажмите F в окне 3D), затем загрузите растровый шрифт (например arailbd.tga с компакт-диска) в качестве текстуры для плоскости. На этот раз вам понадобится уменьшить масштаб вершин в редакторе изображений, чтобы они комфортно размещались рядом с символом @, который появляется на изображении шрифта (показан на панели справа, выше). Как и до этого, активируйте кнопки Texture Face, а также нажмите на кнопку Text в том же окне, чтобы Blender знал, как использовать объект для отображения текста. Переименуйте объект (не сетку!) во что-нибудь вроде txtLine.


Теперь нажмите F4 для активации панели Logic. Добавьте строковое свойство с именем Text к плоскости и задайте ему некое тестовое значение, например, This is some text! или что-нибудь столь же оригинальное. Перейдите к 3D-виду, нажмите клавишу P, и если все пойдет хорошо, вы увидите ваш текст поверх фона. Если текст имеет большие межбуквенные расстояния, уменьшите объект-плоскость в режиме Edit Mode' (нажмите Tab в 3D-виде), потом сделайте предпросмотр, и так пока не будет достигнуто нужное расстояние; затем увеличьте масштаб в режиме Object Mode, чтобы восстановить размер шрифта.

Рис. 2

Нажмите P, переведя курсор на окно 3D-вида, для запуска игрового движка и проверки своей работы. Заметьте, что здесь выбран знак @.

Доведя до ума первую строку, скопируйте ее несколько раз, размещая строки друг над другом, чтобы заполнить главное графическое окно. Это гарантирует нам, что текст будет правильно отображаться при каждом новом сообщении. В моем примере у меня получилось 16 строк, но вы вольны сделать их сколько угодно; надо, однако, записать, сколько именно (не считая строки ввода), так как некоторые скрипты учитывают количество отображаемых строк.

Скопируйте текстовую плоскость и поместите ее над полем ввода вашего интерфейса. Переименуйте его как txtInput. Вам понадобится добавить свойство sendmessage под свойством Text для этой панели, показывающее, что это сообщение готовится к отправке. Наконец, создайте пустой объект в любом месте и назовите его controller.

Начинаем расширять

С пустым выбранным объектом переходим на панель Logic, добавляем сенсор Always и отключаем обе кнопки (pulse mode). Добавьте контроллер Python и свяжите сенсор с его вводом. Измените один из видов на текстовый редактор и добавьте новый скрипт client_connect. На свежесозданном контроллере Python есть кнопка Script. Нажмите ее и введите client_connect.

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

Наш контроллер, естественно, должен настроить приложение и попытаться подсоединиться к узлу, чтобы начать разговор с другими участниками. Сначала нам нужно импортировать игровой движок Blender (далее называемый ИД Blender) и стандартную библиотеку Python socket c помощью

  import GameLogic as g
  import socket
  scn = g.getCurrentScene()

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

Первое свойство, которое надо определить – это список плоскостей, созданных нами для отображения текста. Создадим его поиском в результатах вызова функции getObjectList() по ссылке на сцену: скрипт будет добавлять плоскости в порядке их создания, и если вы создавали их последовательно снизу вверх, то в списке mDisplay они окажутся в нужном порядке.


  # загрузить объекты для отображения сообщений
  if not hasattr(g, “mDisplay”):
              g.mDisplay = []
  for obj in scn.getObjectList():
              if ‘line’ in obj.name:
                             g.mDisplay.append(obj)

Далее мы попробуем подсоединиться к узлу и создадим свойство GameLogic, если соединение прошло удачно. Снова используем здесь hasattr – для гарантии, что мы не замещаем предыдущее соединение. Начнем с простого объекта socket и попробуем установить соединение. Хотя это не лучшее решение, мы обрамим все в конструкцию try…except, которая позволит функции «упасть» красивее. После установки соединения, немедленно пошлем серверу наше имя для записи в журнал. Наконец, установим флаг на GameLogic, сигнализируя, что соединение было успешно установлено, и остальные скрипты могут начать работу.

Контроллеру требуется установить еще один сенсор Always, чтобы он издавал сигнал True каждые пять тиков или около того. Он управляет частотой обновления клиента с сервера, и сильно влияет на ширину канала, которую пытается занять клиент. Данный проект не использует балансировку нагрузки, поэтому потребуется более подробная модель взаимодействия с сервером, если вы захотите использовать этот код в реальности. Снова, создайте новый Python-контроллер и ассоциируйте с ним новый текст скрипта. Назовите его client и добавьте следующие строки:

 import GameLogic as g
 import pickle
 import socket
 cont = g.getCurrentController()
 own = cont.getOwner()
 scn = g.getCurrentScene()
 if not hasattr(g, ‘message’):
     g.message = “”
 if hasattr(g, “connected”):
     toSend = pickle.dumps(g.message, pos)
     g.conn.send(toSend)
     g.message = “”
     data = g.conn.recv(1024)
     if data:
         messages = pickle.loads(data)
         # display the messages list
         for n in range(len(g.mDisplay)):
            g.mDisplay[n].Text = messages[n]

Мы импортируем игровой движок и модуль socket, как и раньше, а также модуль Python pickle, для подготовки наших данных к передаче по сети. Далее, мы получаем ссылку на объект, к которому подсоединен скрипт, посредством ссылки на логический блок, представляющий данный Python-контроллер. Мы также добавляем еще одно свойство к GameLogic, которое будет содержать любой текст для отправки.

Остальная часть скрипта зависит от того, удачно ли произошло соединение с сервером. По каждому сигналу сервера клиент собирает все локальные данные – в нашем случае просто свойство Text входного объекта – и «закатывает» его в файлоподобный объект, пригодный для передачи. Затем данные пересылаются на сервер, а GameLogic.message сбрасывается. Клиент ожидает ответ от сервера, который, будучи полученным, «разворачивается» в список строк. Затем мы перебираем глобальный список текстовых панелей, определенный ранее, и назначаем каждой из них соответствующую строку.

Обработаем ввод

Далее нам необходимо присоединить новый скрипт к объекту ввода. Он будет обрабатывать ввод с клавиатуры и преобразовывать его в символы для отображения в нашем окне. Объекту ввода требуется сенсор приема клавиши, подсоединенный к новому Python-контроллеру и новому скрипту. ИД Blender включает модуль GameKeys, который можно использовать для преобразования кодов клавиш в полезные символы. Модуль GameKeys – это, попросту говоря, справочник имен и номеров кодов клавиш, поиск по которому осуществляется сравнительно легко. На самом деле, вы можете и не прибегать к скриптам, просто используя логические блоки, слушающие определенные клавиши и добавляющие символы в свойство Text. Но, скажем честно, вы сохраните кучу времени, используя Python в качестве решения, как и во многих других вещах.

Со скриптом ввода клавиши все ясно, а вызов printdir(GameKeys) быстро выведет все коды клавиш, если у вас настроен консольный вывод. Я присоединил сенсоры, слушающие определенные клавиши, к тому же скрипту, чтобы продемонстрировать некоторые альтернативные установки. Скрипт получает ссылки на эти сенсоры и автоматически вызывается при нажатии определенной клавиши.

Скрипт ввода также связан и имеет ссылку на объект Message Actuator, именуемый send. При нажатии клавиши Enter скрипт устанавливает у актуатора свойство Body равным свойству ввода Text. Затем он изменяет свойство sendmessage на True, сигнализируя, что сообщение готово к отправке. Другой сенсор, на сей раз сенсор свойства, присоединен, чтобы слушать наличие True у sendmessage. Если это так, то запускается актуатор сообщения, и оно посылается контроллеру Empty. Мы добавим к контроллеру еще один сенсор, слушающий сообщение. Тут вызывается новый скрипт, просто вставляющий свойство GameLogic.message в тело получаемого сообщения. Сообщениеm уже автоматически отослано и очищается скриптом client каждые пять тиков, так что по части клиента у нас все в порядке.

Рис. 3

Логические блоки настроить непросто, зато потом можно обойтись без кода.


Обслуживание клиентов

Наш сервер выполняет совершенно другую Python-программу, поэтому запустите вашу любимую IDE для работы с Python и создайте новый файл chat_server.py. Собственная версия этого файла опять-таки есть на диске, поэтому за полным кодом обращайтесь к нему. Наш сервер использует преимущества потоков для одновременного обслуживания множества клиентов, но помните, что приведенный пример примитивен, и его можно значительно улучшить.

После импорта требуемых модулей приступим к определению клиентского класса на стороне сервера. Он будет дублироваться при каждом новом соединении и работать одновременно вместе со своими собратьями:


 # Наш класс потока:
  class ClientThread ( threading.Thread ):
     # Создаем этого клиента и его поток
     def __init__ ( self, channel, details ):
       self.channel = channel
       self.details = details
       self.position = [0,0,0]
       self.message = “”
       self.newPosition = [0,0,0]
       self.newMessage = “”
       # слушаем имя клиента, которое нам передается
       self.name = channel.recv(1024)
      print self.name, ‘connected’
      # вызываем конструктор суперкласса
      threading.Thread.__init__ ( self )
    def run ( self ):
      # пока клиент не подсоединен
      while True:
         try:
            # слушаем данные от клиента
            data = self.channel.recv(1024)
            if data:
                # распаковываем данные
                self.newMessage = pickle.loads(data)
                # если получено сообщение, добавляем его в список
                if not self.newMessage == “”:
                    txtList.append(self.name + ‘:’ + self.newMessage)
            # выбираем последние 16 сообщений, которые отошлем обратно
            txtToSend = txtList[-16:]
            # упакуем их и отошлем
            toSend = pickle.dumps(txtToSend)
            self.channel.send(toSend)
         except:
            # нет, мы потеряли соединение
            self.channel.close()
            print ‘Connection to’, self.name, ‘lost’
      return False

Клиентский поток постоянно работает в цикле в каждой сессии Blender, обмениваясь данными «туда-обратно» (регулируется сенсором, установленным в Blender). Сначала поток ожидает сигнала от клиента (помните, в нашем клиентском скрипте Blender первым действием является отсылка данных), и если получены какие-то данные, они распаковываются и проверяются. Пустые строки мы игнорируем: если этого не сделать, GUI быстро переполнится ими. Если у нас есть какой-то текст, предваряем его именем клиента и добавляем текст в стек. Затем мы обрезаем массив ([-16:]), беря последние 16 записей, упаковываем результат и, наконец, посылаем его клиенту, где он будет выведен на экран.

Последние строки скрипта сервера определяют бесконечный цикл, в котором сервер ожидает на определенном сокете и создает потоки клиентов при поступлении соединения.

Командир, запускай!

Программу обмена сообщениями легко протестировать локально; рекомендуем так и сделать, поскольку скрипт сервера выставит ваш компьютер напоказ в сети. Чтобы протестировать программу в оффлайне, установите свойство узла в localhost и используйте одинаковый номер порта для сервера и клиента. Может также оказаться, что сетевой экран блокирует соединения с локальной машиной, и потребуется разрешить входящие соединения для Python.

Сохраните вашу работу, затем откройте терминал, перейдите в каталог с проектом (cd) и наберите python chatserver.py для запуска. Когда он запустился, перейдите в Blender и нажмите P в 3D-виде. Если все в порядке, то тестовые значения, установленные на вашем дисплее будут заменены на пустые, загруженные с сервера. Напечатайте какойнибудь текст, нажмите Enter, и он должен появиться в окне выше. Теперь запустите еще одну сессию Blender, нажмите P в этом окне, и магическим образом появится текст другой сессии!

Скрипт сервера можно запустить с любого сервера, на котором есть Python – просто установите адрес узла на ваших клиентах, и вы в игре. Уж не знаю, сколько клиентов выдержит такой сервер, но он вполне подходит для небольших переговоров. LXF

Рис. 4

Итог проекта: несколько разных платформ общаются через Blender.

Персональные инструменты
купить
подписаться
Яндекс.Метрика