Займёмся управлением камерой. Открываем модуль control.py он у нас пока пустой, как обычно, указываем кодировку: # -*- coding: utf_8 -*- Для работы нам понадобится два модуля DirectObject и Vec3. Импортируем их. Code from direct.showbase.DirectObject import DirectObject from pandac.PandaModules import Vec3 Создадим новый класс cameraHandler и пропишем процедуру его инициализации Code class cameraHandler(DirectObject): def __init__(self): base.disableMouse() self.mx,self.my=0,0 self.dragging=False self.j1 = render.attachNewNode('cam_j1') self.j2 = self.j1.attachNewNode('cam_j2') self.j2.setZ(5) self.j3 = self.j2.attachNewNode('cam_j3') self.j3.setY(-40) self.accept("mouse3",self.drag,[True]) self.accept("mouse3-up",self.drag,[False]) self.accept("wheel_up", self.adjustCamDist,[0.9]) self.accept("wheel_down", self.adjustCamDist,[1.1]) taskMgr.add(self.dragTask,'dragTask')
Обратите внимание, наш класс наследуется от DirectObject — это
необходимо для того чтоб мы могли назначить ему события нажатия кнопок.
Смотрим в процедуру инициализации: Первой строкой мы отключаем дефолтное управление, которое мы получаем при импорте DirectStart.
Следующие две строки — переменные для запоминания положения курсора
мыши на экране и флаг состояния — dragging — этот флаг мы будем ставить
в True при нажатии правой кнопки мыши и в False — при отпускании. Дальше — мы создаём из пустышек () что-то наподобие системы шарниров j1 – основание системы, которое мы будем двигать в горизонтальной плоскости и вращать вокруг вертикальной оси. j2 – крепится к основанию на небольшой высоте над ним, будет вращаться на оси проходящей слева-направо - «в ухо».
j3 – закрепляем на j2 и отодвигаем назад. Сам по себе j3 двигаться
никуда не будет, но, благодаря предыдущим двум «шарнирам» он имеет три
степени свободы — вращение вокруг двух осей и перемещение. Вот на этот
самый j3 мы и повесим камеру, точнее не совсем повесим — мы не будем её
жёстко закреплять, а будем подтягивать к j3, одновременно направляя на
j2, это даст нам плавность управления камерой. Естественно, ограничение на вращение — условное и реализовано будет только нашим кодом.
Следующие 4 строки — назначение событий реакции на мышь —
соответственно — нажатие третьей (правой) кнопки, отпускание правой
кнопки, колесо прокрутки вверх, колесо вниз. В процедуре accept первым
параметром идёт условное обозначение события — в статьях есть материал
по этому поводу, вторым — процедура или функция, назначенная событию,
третьим — список аргументов процедуры. Последняя строка добавляет в менеджер задач процедуру с помощью которой мы будем крутить нашу систему «шарниров». Процедуры drag, adjustCamDist и dragTask у нас пока отсутствуют — будем это исправлять.
Процедура drag состоит всего из одной строчки — присваивание флагу
dragging переданной переменной. Это потому что в события accept можно
назначать только процедуру. Можно было бы использовать такую штуку как
lambda, но так, ИМХО, нагляднее. Code def drag(self,bool): self.dragging=bool
adjustCamDist – снова одна строка, на этот раз посложнее, но не сильно
— мы меняем расстояние на котором наодится j3 от j2 путём умножения
текущего расстояния на некоторую цифру, переданную в виде переменной в
функцию. Таким образом, если цифра больше единицы — расстояние
увеличивается, если меньше — уменьшается, чем больше расстояние, тем
быстрее оно изменяется. Code def adjustCamDist(self,aspect): self.j3.setY(self.j3.getY()*aspect) Прежде чем перейдём к dragTask, вынесем операции по вращению нашей системы в отдельную функцию turnCamera Code def turnCamera(self,tx,ty): self.j1.setH(self.j1.getH()+tx) self.j2.setP(self.j2.getP()-ty) if self.j2.getP()<-80: self.j2.setP(-80) if self.j2.getP()>-10: self.j2.setP(-10)
В нашу функцию помимо self, мы передаём две переменных — вращение по
двум осям и в соответствии с этим поворачиваем j1 по одной оси и j2 –
по другой. После поворота проверяем j2 - не слишком-ли сильно
повернули. Я ввёл ограничение поворотом от -10 до -80 градусов. Почему
с минусом? - потому что изначально поворот j2 — 0 градусов, а j3
отодвинут назад по оси Y, соответственно, чтобы поднять j3 нужно
повернуть j2 против часовой стрелки. Ну и, собственоо dragTask Code def dragTask(self,task): if base.mouseWatcherNode.hasMouse(): mpos = base.mouseWatcherNode.getMouse() if self.dragging: self.turnCamera((self.mx-mpos.getX())*100,(self.my-mpos.getY())*100) else: if self.my>0.8: aspect=-(1-self.my-0.2)*5 self.j1.setY(self.j1,aspect) if self.my<-0.8: aspect=(1+self.my-0.2)*5 self.j1.setY(self.j1,aspect) if self.mx>0.8: aspect=-(1-self.mx-0.2)*5 self.j1.setX(self.j1,aspect) if self.mx<-0.8: aspect=(1+self.mx-0.2)*5 self.j1.setX(self.j1,aspect) self.mx=mpos.getX() self.my=mpos.getY() vDir=Vec3(self.j3.getPos(render))-Vec3(base.camera.getPos(render)) vDir=vDir*0.2 base.camera.setPos(Vec3(base.camera.getPos())+vDir) base.camera.lookAt(self.j2.getPos(render)) return task.cont
Сначала проверяем находится ли курсор мыши в пределах окна нашей
программы (1 строка). Если находится, то получаем его координаты (2с).
Если флаг dragging активен (3с), т. е. У нас нажата правая кнопка мыши,
то вращаем нашу систему с помощью написанной перед этим функции (4с).
На сколько вращать определяем вычитая из запомненной ранее позиции
курсора текущую позицию и умножая результат на 100 Если кнопка
отпущена (5с), то проверяем не находится ли курсор у края окна и если
находится, то смещаем основание (6с — 17с). т. е. Реализуем привычное
еемещение камеры при подведении курсора к краю. Пока не будем
ограничивать курсор окном, чтобы можно было спокойно выводить его за
пределы. Для тех кто не знает или забыл — в панде координаты экрана
обычно задаются следующим образом — 0:0 — центр экрана, -1:1 — верхний
левый угол, 1:-1 нижний правый вне зависимости от реального разрешения
и отношения сторон, хотя, можно получить и более привычные пиксельные
координаты. Запоминаем текущие координаты, чтоб в следующем цикле можно было посчитать смещение курсора (18с — 19с) Рассчитываем вектор от текущей позиции камеры к позиции j3 — всё это в координатах корневого узла — renderer (20с) Масштабируем полученный вектор (21с) — это чтоб камера не сразу оказалась в позиции j3, а плавно к ней приближалась.
Небольшое отступление по векторам — для тех кто не понял что такое
произошло у меня в строках 20-21 — срочно бежать повторять курс
школьной геометрии, а ещё лучше - читать учебник по аналитической
геометрии. Матрицы и вектора — это основа трёхмерной графики, сейчас мы
работаем с высокоуровневым движком, поэтому вектора требуются не так
часто, матрицы ещё реже, а если брать чистые графические API, то сильно
дальше чистого экрана без этого не уйдёшь. Просто приведу пару примеров
применения — любая точка пространства может быть представлена вектором,
начало которого совпадает с началом координат, исходя из этого мы можем
найти направление от одной точки к другой и расстояние с помощью
вычитания векторов и нахождения длины результирующего. А, например,
умножение векторов даст нам вектор, перпендикулярный обоим умножаемым
векторам, т. е. нормаль плоскости к которой принадлежат эти вектора
или, например третью ось объекта, если известны две. Вернёмся к коду: Смещаем камеру по полученному вектору (22c) Направляем камеру на j2. Можно и на j1 кому как больше нравится ) (23с) Возвращаем task.cont — этим сообщаем менеджеру задач, что нашу функцию нужно снова запустить в следующем шаге. С control пока закончили, открываем location, импортируем наш класс from modules.control import cameraHandler В __init__ дописываем camera=cameraHandler()
Идём в main — запускаем. Если всё сделано правильно, то после запуска
мы сможем вращать камеру, зажав правую кнопку мыши, приближать-отдалять
её колёсиком и перемещать, подведя курсор к краю окна.
|