In this example, we introduce timer events to implement shooting.
The CannonField now has a shooting capabilities.
bool isShooting() const { return shooting; }
Returns TRUE if a shot is underway.
void shoot();
Calling this slot will make the cannon shoot if a shot is not in the air.
protected: void timerEvent( QTimerEvent * );
The timer event is the third type of widget event we encounter. You can make Qt call this event handler at regular intervals.
private: void stopShooting();
This private function stops a shot in mid-air.
void paintShot( QPainter * );
This private function paints the shot.
QRect shotRect() const;
This private function returns the shot's enclosing rectangle if one is in the air, otherwise the returned rectangle is undefined.
bool shooting;
This private variable is TRUE when there's a shot in the air.
int timerCount; float shoot_ang; float shoot_f; };
These private variables contain information that describes the shot. The
timerCount
keeps track of the time passed since the shot was fired.
The shoot_ang
is the cannon angle and shoot_f
is the cannon force
when the shot was fired.
#include <math.h>
We include the math library because we need the sin() and cos() functions.
CannonField::CannonField( QWidget *parent, const char *name ) : QWidget( parent, name ) { ang = 45; f = 0; shooting = FALSE; timerCount = 0; shoot_ang = 0; shoot_f = 0; }
We initialize our new private variables.
void CannonField::shoot() { if ( shooting ) return; timerCount = 0; shoot_ang = ang; shoot_f = f; shooting = TRUE; startTimer( 50 ); }
This function shoots a shot unless a shot is in the air. The timerCount
is reset to zero. The shoot_ang
and shoot_f
are set to the current
cannon angle and force. shooting
is set to TRUE to indicate that a shot
is in the air. Finally we start timer events.
Calling startTimer() will make Qt send us timer events at regular intervals. In this case, we want timer events every 50 milliseconds.
void CannonField::timerEvent( QTimerEvent * ) { erase( shotRect() ); timerCount++; QRect shotR = shotRect(); if ( shotR.x() > width() || shotR.y() > height() ) { stopShooting(); return; } repaint( shotR, FALSE ); }
The timerEvent() function is an event handler that receives timer events from Qt. In our case, it is called every 50 milliseconds when a shot is in the air.
First we erase the old shot using the QWidget::erase() function. Note that this is not strictly
necessary the first time the timerEvent() is called after a shot has been
fired (but it does no harm). The shotRect()
function (explained below)
returns the enclosing rectangle of the shot at the current position.
Then we increment the timerCount,
which has the effect of moving the
shot one step along its trajectory.
Next, we fetch the new shot rectangle.
If the shot has moved beyond the right or bottom edge of the widget, we stop shooting and return.
If not, we paint the new shot by repainting the portion of the widget that contains the shot. The FALSE argument indicates that the specified rectangle should not be erased before a paint event is sent to the widget.
void CannonField::paintEvent( QPaintEvent *e ) { QRect updateR = e->rect(); QPainter p; p.begin( this ); if ( updateR.intersects( cannonRect() ) ) paintCannon( &p ); if ( shooting && updateR.intersects( shotRect() ) ) paintShot( &p ); p.end(); }
This paint event function is basically the same as the one in the previous chapter. The only difference is that we also paint the shot if necessary.
void CannonField::stopShooting() { shooting = FALSE; killTimers(); }
This private function stops a shot in mid-air. First it resets the shooting
variable, then it kills all timer
events for this widget. It is also possible to kill a single timer.
void CannonField::paintShot( QPainter *p ) { p->setBrush( black ); p->setPen( NoPen ); p->drawRect( shotRect() ); }
This private function paints the shot by drawing a black filled rectangle.
QRect CannonField::shotRect() const { const double gravity = 4; double time = timerCount / 4.0; double velocity = shoot_f; double radians = shoot_ang*3.14159265/180; double velx = velocity*cos( radians ); double vely = velocity*sin( radians ); double x0 = ( barrel_rect.right() + 5 )*cos(radians); double y0 = ( barrel_rect.right() + 5 )*sin(radians); double x = x0 + velx*time; double y = y0 + vely*time - 0.5*gravity*time*time; QRect r = QRect( 0, 0, 6, 6 ); r.moveCenter( QPoint( qRound(x), height() - 1 - qRound(y) ) ); return r; }
This private function calculates the center point of the shot and returns
the enclosing rectangle of the shot. It uses the initial cannon force and
angle in addition to timerCount,
which increases as time passes.
The formula used is the classical Newtonian formula for frictionless movement in a gravity field. For simplicity, we've chosen to disregard any Einsteinian effects.
We calculate the center point in a coordinate system where y coordinates increase upwards. After we have calculated the center point, we construct a QRect with size 6x6 and move its center point to the point calculated above. In the same operation, we convert the point into the widget's coodinate system (see The Coordinate System).
The qRound() function is an inline function defined in qglobal.h (included by all other Qt header files). qRound() rounds a double to the closest integer.
class MyWidget : public QWidget { public: MyWidget( QWidget *parent=0, const char *name=0 ); protected: void resizeEvent( QResizeEvent * ); private: QPushButton *quit; QPushButton *shoot; LCDRange *angle; LCDRange *force; CannonField *cannonField; };
The only addition is the shoot button.
shoot = new QPushButton( "Shoot", this, "shoot" ); shoot->setGeometry( 90, 10, 75, 30 ); shoot->setFont( QFont( "Times", 18, QFont::Bold ) );
In the constructor we create and set up the shoot button exactly like we did with the quit button. Note that the first argument to the constructor is the button text and the third is the widget's name.
connect( shoot, SIGNAL(clicked()), cannonField, SLOT(shoot()) );
Connects the clicked() signal of the shoot button to the shoot() slot of the CannonField.
The cannon can shoot, but there's nothing to shoot at.
Make the shot a filled circle (hint: QPainter::drawEllipse() ).
Change the color of the cannon when a shot is in the air.
You may now go on to chapter twelve.
[Previous tutorial] [Next tutorial] [Main tutorial page]
Copyright © 1998 Troll Tech | Trademarks | Qt version 1.42
|