Главная » Статьи » Деццкий сад

Делаем игру вместе. Урок №3.
Займёмся управлением камерой. Открываем модуль 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 — запускаем. Если всё сделано правильно, то после запуска мы сможем вращать камеру, зажав правую кнопку мыши, приближать-отдалять её колёсиком и перемещать, подведя курсор к краю окна.

Категория: Деццкий сад | Добавил: ninth (13.05.2009)
Просмотров: 6458 | Комментарии: 8 | Рейтинг: 4.8/4
Всего комментариев: 8
1  
Большое спасибо, отличная серия статей

2  
не могу понять, что делает функция setH\getH в документации написано, что она не документирована =(

3  
аа все, уже понял

4  
не пойму, а зачем начиная с vDir выносить за пределы проверки условия?

5  
за пределы проверки какого из условий? условия на drag? Если да, то ведь камера продолжает двигаться не только когда мы вращаем шарниры, но и при перемещении основания и даже когда ничего не делаем - камера не сразу оказывается в j3 а постепенно туда подтягивается.
А вообще ещё раз просьба обсуждать и задавать вопросы по статьям на форуме - я просто не успею физически просматривать всю пачку статей и отвечать на вопросы - на форуме хоть видны сразу новые сообщения. В комменты лучше писать замечания и дополнения.

6  
Видимо мануал несколько устарел. Ругается на отсутствие атрибута adjustCamDist.

1
7  
Не думаю, ведь результат этого мануала до сих пор запускается. https://code.google.com/p/panda-rpg-tutorial/source/list

Скачай.

8  
Всё отлично! Всё работает - http://titov-andrei.github.io/megarpg/

Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Онлайн всего: 1
Гостей: 1
Пользователей: 0