Пример Colliding Mice
Файлы:
Пример "Colliding Mice" показывает как использовать каркас Графического представления для реализации анимированных элементов и отслеживать столкновения между ними. Графическое представление предоставляет класс QGraphicsScene для управления и взаимодействия с большим числом изготовленных пользователем двухмерных элементов производных от класса QGraphicsItem, а также виджет QGraphicsView для их визуализации с поддержкой масштабирования и вращения. Этот пример состоит из класса элемента и главной функции: класс Mouse представляет экземпляр мыши расширяя QGraphicsItem, и функция main() предоставляет главное окно приложения. Сначала мы рассмотрим класс Mouse чтобы увидеть, как анимировать элементы и определять из столкновение, а затем мы рассмотрим функцию main() чтобы увидеть, как разместить элементы на сцене и как реализовать соответствующий вид. Определение класса MouseКласс mouse наследует QObject и QGraphicsItem. Класс QGraphicsItem является базовым классом для всех графических элементов в Каркасе Графического Представления и предоставляет удобную основу для написания своих собственных элементов. class Mouse : public QObject, public QGraphicsItem { Q_OBJECT public: Mouse(); QRectF boundingRect() const; QPainterPath shape() const; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); protected: void timerEvent(QTimerEvent *event); private: qreal angle; qreal speed; qreal mouseEyeDirection; QColor color; }; При создании собственного графического элемента необходимо реализовать две чисто виртуальные открытые функции QGraphicsItem: boundingRect(), который возвращает оценку площади, занимаемой областью рисования элемента, и paint(), которая реализует собственно отображение. Дополнительно мы переопределили функцию shape() что бы возвращать точную форму нашего элемента мыши; реализация по-умолчанию просто возвращает ограничивающий элемент прямоугольник. Обоснование наследования от QObject в дополнении к QGraphicsItem в том что бы анимировать наши элементы с помощью переопределения функции timerEvent() QObject и использовать QObject::startTimer() для генерации событий таймера. Определение класса MouseПри конструировании элемента мыши, сначала мы должны убедиться, что закрытые переменные элемента правильно инициализированы: Mouse::Mouse() : angle(0), speed(0), mouseEyeDirection(0), color(qrand() % 256, qrand() % 256, qrand() % 256) { rotate(qrand() % (360 * 16)); startTimer(1000 / 33); } Для вычисления различных компонентов цвета мыши мы используем глобальную функцию qrand() которая является потокобезопасной версией стандартной функции C++ rand(). Затем мы вызываем функцию rotate() унаследованную от QGraphicsItem. Элементы существуют в своей локальной системе координат. Эти координаты обычно привязаны к центральной точке (0, 0), и она же является центром для всех преобразований. Вызывая функцию элемента rotate() мы указываем направление в котором мышь начнёт двигаться. В конце мы вызываем функцию QObject startTimer(), вырабатывая событие таймера каждую 1000/33 миллисекунду. Это позволяет анимировать нам наших мышей используя переопределенную нами функцию timerEvent(); когда бы мышь не получила событие таймера, он вызывает timerEvent(): void Mouse::timerEvent(QTimerEvent *) { QLineF lineToCenter(QPointF(0, 0), mapFromScene(0, 0)); if (lineToCenter.length() > 150) { qreal angleToCenter = ::acos(lineToCenter.dx() / lineToCenter.length()); if (lineToCenter.dy() < 0) angleToCenter = TwoPi - angleToCenter; angleToCenter = normalizeAngle((Pi - angleToCenter) + Pi / 2); if (angleToCenter < Pi && angleToCenter > Pi / 4) { // Повернуть налево angle += (angle < -Pi / 2) ? 0.25 : -0.25; } else if (angleToCenter >= Pi && angleToCenter < (Pi + Pi / 2 + Pi / 4)) { // Повернуть вправо angle += (angle < Pi / 2) ? 0.25 : -0.25; } } else if (::sin(angle) < 0) { angle += 0.25; } else if (::sin(angle) > 0) { angle -= 0.25; } Сначала мы убеждаемся что мышь остается внутри круга радиусом 150 пикселов. Обратите внимание что функция mapFromScene() предоставляется QGraphicsItem. Эта функция преобразует позицию, заданную с координатах scene в систему координат элемента. QList<QGraphicsItem *> dangerMice = scene()->items(QPolygonF() << mapToScene(0, 0) << mapToScene(-30, -50) << mapToScene(30, -50)); foreach (QGraphicsItem *item, dangerMice) { if (item == this) continue; QLineF lineToMouse(QPointF(0, 0), mapFromItem(item, 0, 0)); qreal angleToMouse = ::acos(lineToMouse.dx() / lineToMouse.length()); if (lineToMouse.dy() < 0) angleToMouse = TwoPi - angleToMouse; angleToMouse = normalizeAngle((Pi - angleToMouse) + Pi / 2); if (angleToMouse >= 0 && angleToMouse < Pi / 2) { // Повернуть вправо angle += 0.5; } else if (angleToMouse <= TwoPi && angleToMouse > (TwoPi - Pi / 2)) { // Повернуть налево angle -= 0.5; } } if (dangerMice.size() > 1 && (qrand() % 10) == 0) { if (qrand() % 1) angle += (qrand() % 100) / 500.0; else angle -= (qrand() % 100) / 500.0; } Затем мы пытаемся избежать столкновения с другими мышами. speed += (-50 + qrand() % 100) / 100.0; qreal dx = ::sin(angle) * 10; mouseEyeDirection = (qAbs(dx / 5) < 1) ? 0 : dx / 5; rotate(dx); setPos(mapToParent(0, -(3 + sin(speed) * 3))); } Наконец, мы вычисляем скорость мыши и направление ее глаз (для использования при отрисовке мыши) и устанавливаем её новую позицию. Позиция элемента описывает ее базис (локальные координаты (0, 0)) в родительских координатах. Функция QGraphicsItem::setPos() устанавливает позицию элемента в заданную позицию в системе координат родителя. Для элементов у которых нет родителя заданная позиция интепретируется как координаты сцены. QGraphicsItem так же предоставляет функцию mapToParent() для преобразования позиции, заданной в координатах элемента, в родительскую систему координат. Если у элемента нет родителя, позиция будет преобразована в систему координат сцены. Теперь необходимо предоставить реализацию чисто виртуальных функций, унаследованных от QGraphicsItem. Давайте сначала взглянем на функцию boundingRect(): QRectF Mouse::boundingRect() const { qreal adjust = 0.5; return QRectF(-18 - adjust, -22 - adjust, 36 + adjust, 60 + adjust); } Функция boundingRect() определяет внешние границы элемента как прямоугольник. Заметьте, что каркас Графического представления использует ограничивающий прямоугольник для определения необходимости перерисовки элемента, так что всё рисование должно быть только внутри данного прямоугольника. void Mouse::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) { // Тело painter->setBrush(color); painter->drawEllipse(-10, -20, 20, 40); // Глаза painter->setBrush(Qt::white); painter->drawEllipse(-10, -17, 8, 8); painter->drawEllipse(2, -17, 8, 8); // Нос painter->setBrush(Qt::black); painter->drawEllipse(QRectF(-2, -22, 4, 4)); // Зрачки painter->drawEllipse(QRectF(-8.0 + mouseEyeDirection, -17, 4, 4)); painter->drawEllipse(QRectF(4.0 + mouseEyeDirection, -17, 4, 4)); // Уши painter->setBrush(scene()->collidingItems(this).isEmpty() ? Qt::darkYellow : Qt::red); painter->drawEllipse(-17, -12, 16, 16); painter->drawEllipse(1, -12, 16, 16); // Хвост QPainterPath path(QPointF(0, 20)); path.cubicTo(-5, 22, -5, 22, 0, 25); path.cubicTo(5, 27, 5, 32, 0, 30); path.cubicTo(-5, 32, -5, 42, 0, 35); painter->setBrush(Qt::NoBrush); painter->drawPath(path); } Каркас Графического представления вызывает функцию paint() для отрисовки содержимого элемента; функция отрисовывает элемент в локальных координатах. Обратите внимание на отрисовку ушей: при столкновении элемента с другой мышью его уши будут окрашены в красный цвет; в противном случае они будут тёмно-желтыми. Мы используем функцию QGraphicsScene::collidingItems() для проверки есть ли у нас столкнувшиеся мыши. Непосредственное определение столкновений производится каркасом Графического представления с использованием пересечения фигура-фигура. Всё что нам надо сделать это убедиться что функция QGraphicsItem::shape() возвращает правильную форму нашего элемента: QPainterPath Mouse::shape() const { QPainterPath path; path.addRect(-10, -20, 20, 40); return path; } Поскольку сложность произвольного пересечения фигуры с фигурой увеличивается на порядок когда фигуры сложные, эта операция может быть заметно занимающей время. Другой подход состоит в том что бы переопределить функцию collidesWithItem() что бы предоставить собственный алгоритм столкновения элемента и фигур. Это завершает реализацию класса Mouse, теперь он готов для использования. Давайте взглянем на функцию main() что бы увидеть как реализовать сцену для мышей и вид для отображения содержимого сцены. Функция Main()В данном примере мы решили позволить функции main() предоставить главное окно приложения, создав элементы и сцену, разместив элементы на сцене и создав соответствующий вид. int main(int argc, char **argv) { QApplication app(argc, argv); qsrand(QTime(0,0,0).secsTo(QTime::currentTime())); Сначала, мы создали объект приложения и вызвали глобальную функцию qsrand() что бы указать затравку, используемую для генерации новой последовательности псевдослучайных чисел с помощью ранее упоминавшейся функции qrand(). Теперь время создать сцену: QGraphicsScene scene; scene.setSceneRect(-300, -300, 600, 600); Класс QGraphicsScene служит как контейнер для QGraphicsItem. Он так же предоставляет функциональность, позволяющую эффективно определить положение элементов, а так же какие элементы видимы внутри произвольной области на сцене. После создания сцены рекомендуется установить прямоугольник сцены, т.е. прямоугольник, который описывает протяженность сцены. Он в основном используется QGraphicsView для определения области прокрутки вида по умолчанию и QGraphicsScene для управлением индексацией элементов. Если его не указать явно, прямоугольником сцены по умолчанию будет наибольший прямоугольник, ограничивающий все элементы на сцене с тех пор как сцена была создана (т.е. прямоугольник будет расти при добавлении или перемещении элементов на сцене, но никогда не уменьшится). scene.setItemIndexMethod(QGraphicsScene::NoIndex); Функция индексации элементов используется для ускорения обнаружения элемента. NoIndex подразумевает что поиск элементов усложняется линейно, так как ищутся все элементы на сцене. Добавление, перемещение и удаление элементов, тем не менее, совершается за постоянное время. Этот подход идеален для динамических сцен, где много элементов добавляется, перемещается или удаляется непрерывно. Альтернативой является BspTreeIndex который заставляет использовать бинарный поиск, в результате чего усложнение алгоритма поиска элементов ближе к логарифмическому. for (int i = 0; i < MouseCount; ++i) { Mouse *mouse = new Mouse; mouse->setPos(::sin((i * 6.28) / MouseCount) * 200, ::cos((i * 6.28) / MouseCount) * 200); scene.addItem(mouse); } Затем мы добавляем мышей на сцену. QGraphicsView view(&scene); view.setRenderHint(QPainter::Antialiasing); view.setBackgroundBrush(QPixmap(":/images/cheese.jpg")); Что бы просматривать сцену нам также необходимо создать виджет QGraphicsView. Класс QGraphicsView отображает содержимое сцены в прокручиваемой области. Мы также убеждаемся что содержимое отрисовывается с использованием сглаживания и мы создаём фон с сыром установив кисть фона отображения. Изображение, используемое для фона, хранится в исполняемом бинарном файле приложения с помощью системы ресурсов Qt. Конструктор QPixmap принимает как имена файлов указывающих непосредственно на файлы на диске, так и имена, указывающие на встроенные ресурсы приложения. view.setCacheMode(QGraphicsView::CacheBackground); view.setDragMode(QGraphicsView::ScrollHandDrag); Затем мы устанавливаем режим кэша; QGraphicsView может кэшировать заранее отрисованное содержимое в картинке, которая затем рисуется в области отображения. Целью такого кэширования является ускорение времени отрисовки для областей, которые медленно отрисовываются, например, текстуры, градиентные и полупрозрачные фоны. Свойство CacheMode содержит какие части отображения кэшируются и флаг CacheBackground разрешает кэширование фона отображения. Установив свойство dragMode мы определили что должно случится когда пользователь кликает по фону сцены и тянет мышку. Флаг ScrollHandDrag заставляет курсор меняться на указывающую руку и перемещение мыши будет приводить к прокрутке отображения. view.setWindowTitle(QT_TRANSLATE_NOOP(QGraphicsView, "Colliding Mice")); view.resize(400, 300); view.show(); return app.exec(); } В конце мы установили заголовок приложения и размер прежде чем мы вошли в главный цикл обработки сообщений используя функцию QApplication::exec().
|
Попытка перевода Qt документации. Если есть желание присоединиться, или если есть замечания или пожелания, то заходите на форум: Перевод Qt документации на русский язык... Люди внесшие вклад в перевод: Команда переводчиков |