[Предыдущий: Урок 13] [Учебное пособие] Урок 14 - Ставим стену
Файлы:
Это последний пример: готовая игра. Мы добавим "быстрые" клавиши и добавим в CannonField события мыши. Мы поместим рамку вокруг CannonField и добавим барьер (стену) чтобы сделать игру более сложной. Разберем программу строка за строкойt14/cannonfield.hCannonField теперь получает события мыши чтобы реализовать нацеливание ствола пользователем после щелчка по нему и перетаскивания его мышью. CannonField также содержит стену барьера. protected: void paintEvent(QPaintEvent *event); void mousePressEvent(QMouseEvent *event); void mouseMoveEvent(QMouseEvent *event); void mouseReleaseEvent(QMouseEvent *event); В дополнение к хорошо знакомым обработчикам событий, CannonField реализует три обработчика событий мыши. Имена которых говорят сами за себя. void paintBarrier(QPainter &painter); Это закрытая функция рисует стену барьера. QRect barrierRect() const; Эта закрытая функция возвращает ограничивающий прямоугольник барьера. bool barrelHit(const QPoint &pos) const; Эта закрытая функция проверяет, находится ли точка внутри ствола пушки. bool barrelPressed; Эта закрытая переменная равна true, если пользователь нажал мышью на ствол и не отпустил его. t14/cannonfield.cppbarrelPressed = false; Эта строчка была добавлена в конструктор. Первоначально мышью по стволу не щелкали. } else if (shotR.x() > width() || shotR.y() > height() || shotR.intersects(barrierRect())) { Теперь, поскольку имеется барьер, есть три варианта промаха. Мы также проверим третий вариант. void CannonField::mousePressEvent(QMouseEvent *event) { if (event->button() != Qt::LeftButton) return; if (barrelHit(event->pos())) barrelPressed = true; } Это обработчик событий Qt. Он вызывается, когда пользователь нажимает кнопку мыши при нахождении курсора над виджетом. Если событие было сгенерировано не левой кнопкой мыши, мы немедленно возвращаемся. В противном случае мы проверяем позицию курсора - не попадает ли он внутрь ствола пушки. Если это так, мы устанавливаем barrelPressed равным true. Обратите внимание на то, что функция QMouseEvent::pos() возвращает точку в системе координат виджета. void CannonField::mouseMoveEvent(QMouseEvent *event) { if (!barrelPressed) return; QPoint pos = event->pos(); if (pos.x() <= 0) pos.setX(1); if (pos.y() >= height()) pos.setY(height() - 1); double rad = atan(((double)rect().bottom() - pos.y()) / pos.x()); setAngle(qRound(rad * 180 / 3.14159265)); } Это другой обработчик событий Qt. Он вызывается когда пользователь уже нажал на кнопку мыши внутри виджета и затем перемещает/перетаскивает мышь. (Вы можете заставить Qt отправлять события мыши даже если ни одна из кнопок мыши не нажата. Смотрите QWidget::setMouseTracking().) Этот обработчик перенаправляет ствол пушки в соответствии с позицией курсора мыши. Прежде всего - если на ствол не нажали, мы возвращаемся. Далее, мы получаем позицию курсора мыши. Если курсор мыши находится левее или ниже виджета, то мы корректируем точку таким образом, чтобы она попала внутрь виджета. Затем мы вычисляем угол между нижним краем виджета и воображаемой линией между нижним левым углом виджета и позицией курсора. В заключение мы устанавливаем угол возвышения пушки равным новому значению, преобразованному в градусы. Припомните - setAngle() перерисовывает пушку. void CannonField::mouseReleaseEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) barrelPressed = false; } Этот обработчик события Qt вызывается всякий раз когда пользователь отпускает кнопку мыши и она была нажата внутри виджета. Если была отпущена левая кнопка, мы можем быть уверены, что ствол более не удерживается мышью. Событие рисования содержит одну дополнительную строку: paintBarrier(painter); paintBarrier() делает то же самое, что и paintShot(), paintTarget() и paintCannon(). void CannonField::paintBarrier(QPainter &painter) { painter.setPen(Qt::black); painter.setBrush(Qt::yellow); painter.drawRect(barrierRect()); } Эта закрытая функция рисует барьер в виде прямоугольника желтого цвета с черным контуром. QRect CannonField::barrierRect() const { return QRect(145, height() - 100, 15, 99); } Эта закрытая функция возвращает прямоугольник барьера. Мы закрепили нижний край барьера на нижнем краю виджета. bool CannonField::barrelHit(const QPoint &pos) const { QMatrix matrix; matrix.translate(0, height()); matrix.rotate(-currentAngle); matrix = matrix.inverted(); return barrelRect.contains(matrix.map(pos)); } Эта функция возвращает true если точка находится в стволе; в противном случае она возвращает false. Здесь мы используем класс QMatrix. QMatrix определяет отображение системы координат. Он может выполнять те же преобразования, что и QPainter. Здесь мы выполняем те же шаги преобразования, что мы делали когда рисовали ствол в функции paintCannon(). Сначала мы транслируем систему координат, а затем поворачиваем ее. Теперь нам необходимо проверить лежит ли точка pos (в координатах виджета) внутри ствола. Чтобы сделать это мы инвертируем преобразующую матрицу. Инвертированная матрица выполняет обратное преобразование к тому, которое мы использовали когда рисовали ствол. Мы отображаем точку pos, используя инвертированную матрицу, и возвращаем true, если она находится внутри исходного прямоугольника ствола. t14/gameboard.cppQFrame *cannonBox = new QFrame; cannonBox->setFrameStyle(QFrame::WinPanel | QFrame::Sunken); Мы создали, настроили QFrame и установили стиль его рамки. Результатом этого будет трехмерная рамка вокруг CannonField. (void) new QShortcut(Qt::Key_Enter, this, SLOT(fire())); (void) new QShortcut(Qt::Key_Return, this, SLOT(fire())); (void) new QShortcut(Qt::CTRL + Qt::Key_Q, this, SLOT(close())); Здесь мы создали и настроили три объекта QShortcut. Эти объекты перехватывают событий клавиатуры и вызывают слоты, если были нажаты заданные клавиши. Обратите внимание на то, что объект QShortcut является дочерним по отношению к виджету и будет уничтожен вместе с виджетом. QShortcut сам по себе не является виджетом и на своем родительском виджете никак визуально не выделен. Мы определили три "быстрых" клавиши. Мы хотим, чтобы слот fire() вызывался когда пользователь нажмет на Enter или Return. Мы также хотим, чтобы приложение завершило работу когда нажаты клавиши Ctrl+Q. Вместо того, чтобы соединять их с QCoreApplication::quit(), мы соединяем их с QWidget::close(). Поскольку GameBoard - главный виджет приложения, это имеет такой же эффект как и у quit(). Qt::CTRL, Qt::Key_Enter, Qt::Key_Return и Qt::Key_Q - это константы, объявленные в пространстве имен Qt. QVBoxLayout *cannonLayout = new QVBoxLayout; cannonLayout->addWidget(cannonField); cannonBox->setLayout(cannonLayout); QGridLayout *gridLayout = new QGridLayout; gridLayout->addWidget(quit, 0, 0); gridLayout->addLayout(topLayout, 0, 1); gridLayout->addLayout(leftLayout, 1, 0); gridLayout->addWidget(cannonBox, 1, 1, 2, 1); gridLayout->setColumnStretch(1, 10); setLayout(gridLayout); Мы передали cannonBox его собственный QVBoxLayout и добавили cannonField в эту компоновку. Неявным образом это сделало cannonField дочерним по отношению к cannonBox. Поскольку в прямоугольнике ничего нет, результатом является то, что QVBoxLayout поместит рамку вокруг CannonField. Мы поместили cannonBox, а не cannonField, в компоновку сетки. Запуск приложенияТеперь пушка стреляет когда вы нажимаете Enter. Вы также можете установить угол возвышения пушки используя мышь. Парапет делает игру несколько сложнее. Мы также имеем симпатичную рамку вокруг CannonField. Домашнее заданиеНапишите игру "Космические захватчики" (space invaders). (Первым это упражнение выполнил Игорь Рафиенко. Вы можете скачать его игру.) Новое упражнение: Напишите игру Breakout. Последнее наставление: Отправляйтесь в путь и создавайте шедевры искусства программирования! [Предыдущий: Урок 13] [Учебное пособие]
|
Попытка перевода Qt документации. Если есть желание присоединиться, или если есть замечания или пожелания, то заходите на форум: Перевод Qt документации на русский язык... Люди внесшие вклад в перевод: Команда переводчиков |