[Предыдущий: Урок 10] [Учебное пособие] [Следующий: Урок 12] Урок 11 - Попробуем выстрелить
Файлы:
В следующем примере мы познакомимся с таймером для реализации анимированной стрельбы. Разберем программу строка за строкойt11/cannonfield.hCannonField теперь содержит возможность стрелять. void shoot(); Вызов этого слота приводит к выстрелу пушки, если снаряд еще не в воздухе. private slots: void moveShot(); Этот закрытый слот используется для перемещения снаряда пока он находится в воздухе, используя QTimer. private: void paintShot(QPainter &painter); Эта закрытая функция рисует снаряд. QRect shotRect() const; Эта закрытая функция возвращает ограничивающий прямоугольник снаряда если он в воздухе; в противном случае возвращается неопределенный прямоугольник. int timerCount; QTimer *autoShootTimer; float shootAngle; float shootForce; }; Эти закрытые переменные содержат информацию, описывающую выстрел. timerCount следит за временем, прошедшим после выстрела. shootAngle - угол возвышения пушки, а shootForce - сила произведенного выстрела. t11/cannonfield.cpp#include <math.h> Мы подключили <math.h>, потому что нам необходимы функции sin() и cos(). (В качестве альтернативы можно подключить более современный заголовочный файл <cmath>. К сожалению, некоторые платформы Unix не поддерживают его на должном уровне.) CannonField::CannonField(QWidget *parent) : QWidget(parent) { currentAngle = 45; currentForce = 0; timerCount = 0; autoShootTimer = new QTimer(this); connect(autoShootTimer, SIGNAL(timeout()), this, SLOT(moveShot())); shootAngle = 0; shootForce = 0; setPalette(QPalette(QColor(250, 250, 200))); setAutoFillBackground(true); } Инициализируем наши новые закрытые переменные и соединяем сигнал QTimer::timeout() с нашим слотом moveShot(). Мы перемещаем снаряд каждый раз когда срабатывает таймер. void CannonField::shoot() { if (autoShootTimer->isActive()) return; timerCount = 0; shootAngle = currentAngle; shootForce = currentForce; autoShootTimer->start(5); } Если снаряд не в воздухе, эта функция производит выстрел. timerCount сбрасывается в ноль. Переменные shootAngle и shootForce устанавливают текущий угол возвышения и силу выстрела. В завершение мы запускаем таймер. void CannonField::moveShot() { QRegion region = shotRect(); ++timerCount; QRect shotR = shotRect(); if (shotR.x() > width() || shotR.y() > height()) { autoShootTimer->stop(); } else { region = region.unite(shotR); } update(region); } moveShot() - слот, который перемещает снаряд, - вызывается каждые 5 миллисекунд когда срабатывает QTimer. Его задачи - рассчитать новую позицию, обновить экран со снарядом в новой позиции и, если понадобится, остановить таймер. Сначала мы создали QRegion, в котором содержится старый shotRect(). QRegion имеет возможность хранить область любого сорта, и мы будем использовать это для упрощения рисования. shotRect() возвращает прямоугольник, в котором снаряд находится сейчас. Детально он будет описан позднее. Затем мы увеличиваем значение timerCount, что создает эффект движения снаряда на один шаг вдоль его траектории. Затем мы извлекаем новый прямоугольник для снаряда. Если снаряд переместился далеко за правый или нижний край виджета, то мы останавливаем таймер или добавляем новый shotRect() к QRegion. В заключение мы перерисовываем QRegion. Это вызовет отправку одного события рисования только для одного или двух прямоугольников, которые необходимо обновить. void CannonField::paintEvent(QPaintEvent * /* event */)
{
QPainter painter(this);
paintCannon(painter);
if (autoShootTimer->isActive())
paintShot(painter);
}
Функция события рисования практически совпадает с таковой из предыдущего урока. Большая часть логики переместилась в новые функции paintShot() и paintCannon(). void CannonField::paintShot(QPainter &painter) { painter.setPen(Qt::NoPen); painter.setBrush(Qt::black); painter.drawRect(shotRect()); } Эта закрытая функция рисует снаряд в виде черного прямоугольника. Мы опустили реализацию функции paintCannon(); она такая же, что и переопределенная QWidget::paintEvent() из предыдущего урока. QRect CannonField::shotRect() const { const double gravity = 4; double time = timerCount / 20.0; double velocity = shootForce; double radians = shootAngle * 3.14159265 / 180; double velx = velocity * cos(radians); double vely = velocity * sin(radians); double x0 = (barrelRect.right() + 5) * cos(radians); double y0 = (barrelRect.right() + 5) * sin(radians); double x = x0 + velx * time; double y = y0 + vely * time - 0.5 * gravity * time * time; QRect result(0, 0, 6, 6); result.moveCenter(QPoint(qRound(x), height() - 1 - qRound(y))); return result; } Эта закрытая функция вычисляет центральную точку снаряда и возвращает ограничивающий прямоугольник для снаряда. Она использует значение начальной силы выстрела и угла возвышения пушки в дополнение к timerCount, который увеличивается с течением времени. Используется стандартная формула Ньютона для движения в гравитационном поле без учета трения воздуха. Для упрощения мы предпочли не учитывать какие-либо эйнштейновские эффекты. Мы вычислили центральную точку в системе координат где координатная ось y направлена вверх. После вычисления центральной точки, мы создаем QRect с размерами 6 x 6 и перемещаем его центральную точку в заранее вычисленную точку. В этой же операции мы преобразуем точку в систему координат виджета (смотрите Систему координат). Функция qRound() - inline-функция объявленная в <QtGlobal> (включается во все остальные заголовочные файлы Qt). qRound() округляет числа с двойной точностью до ближайшего целого. t11/main.cppclass MyWidget : public QWidget { public: MyWidget(QWidget *parent = 0); }; Единственным дополнением является кнопка Shoot. QPushButton *shoot = new QPushButton(tr("&Shoot")); shoot->setFont(QFont("Times", 18, QFont::Bold)); В конструкторе мы создаем и устанавливаем кнопку Shoot точно также как делали это с кнопкой Quit. connect(shoot, SIGNAL(clicked()), cannonField, SLOT(shoot())); Соединяем сигнал clicked() кнопки Shoot со слотом shoot() виджета CannonField. Запуск приложенияПушка может стрелять, но нет целей. Домашнее заданиеСделайте снаряд в виде круга. [Подсказка: может помочь QPainter::drawEllipse().] Измените цвет пушки когда снаряд в воздухе. [Предыдущий: Урок 10] [Учебное пособие] [Следующий: Урок 12]
|
Попытка перевода Qt документации. Если есть желание присоединиться, или если есть замечания или пожелания, то заходите на форум: Перевод Qt документации на русский язык... Люди внесшие вклад в перевод: Команда переводчиков |