W tym końcowym przykładzie - ostateczna wersja gry.
Dodamy obsługe klawiatury, wprowadzimy wydzarzenia myszy w CannonField. Dodamy ramkę wokół CannonField i dodamy przeszkodę (ścianę), aby gra była bardziej wyzywająca.
protected: void paintEvent( QPaintEvent * ); void mousePressEvent( QMouseEvent * ); void mouseMoveEvent( QMouseEvent * ); void mouseReleaseEvent( QMouseEvent * );Dodatkowo do znanych uchwytów zdarzeń, CannonField implementuje uchwyty zdarzeń myszy. Nazywy mówią za siebie.
void paintBarrier( QPainter * );Ta prywatna funkcja rysuje ścianę.
QRect barrierRect() const;Ta prywatna funcja zwraca prostokąt zawierający barierę.
bool barrelHit( const QPoint & ) const;Ta prywatna funkcja sprawdza czy punkt znajduje się na dziale.
bool barrelPressed;Ta prywatna zmienna ma wartość TRUE jeżeli użytkownik przycisnął klawisz myszy na dziale i nie puścił go.
barrelPressed = FALSE;Ta linia zostastała dodana do konstruktora. Na początku mysz nie znajduje się nad działem.
} else if ( shotR.x() > width() || shotR.y() > height() ||Zauważ, że po wprowadzeniu bariery mamy trzy sposoby nietrafienia. Testujemy także i trzeci.
void CannonField::mousePressEvent( QMouseEvent *e ) { if ( e->button() != LeftButton ) return; if ( barrelHit( e->pos() ) ) barrelPressed = TRUE; }To jest uchwyt wydzarzenia w Qt. Jest wywoływany gdy użytkownik wciśnie przycisk myszy oraz gdy wskaźnik myszy jest nad widgetem.
Jeżeli wydzarzenie nie zostało wygenerowane przez lewy klawisz myszy, natychmiast wracamy. W przyciwnym przypadku, sprawdzamy czy pozycja kursorta myszy jest w dziale. Jeśli jest, ustawiamy barrelPressed na TRUE.
Zauważmy, że funkcja pos() zwraca punkt w systemie koordynatów widgetu.
void CannonField::mouseMoveEvent( QMouseEvent *e ) { if ( !barrelPressed ) return; QPoint pnt = e->pos(); if ( pnt.x() <= 0 ) pnt.setX( 1 ); if ( pnt.y() >= height() ) pnt.setY( height() - 1 ); double rad = atan(((double)rect().bottom()-pnt.y())/pnt.x()); setAngle( qRound ( rad*180/3.14159265 ) ); }Jest to kolejny uchwyt wydarzenia w QT. Jest wywoływany gdy użytkownik nacisął przycisk myszy w obrębie widgetu i/lub przesuwa mysz. (Można takze zmusić Qt do wysyłania wydzarzeń myszy bez wciśniętych przycisków, patrz QWidget::setMouseTracking().)
Ten uchwyt przesuwa działo ze względu na przesunięcie myszy.
Najpierw gdy działo nie jest naciśnięte, wracamy. Następnie pobieramy pozycję kursora myszy.
Pamiętaj, że setAngle() przerysowuje działo.
void CannonField::mouseReleaseEvent( QMouseEvent *e ) { if ( e->button() == LeftButton ) barrelPressed = FALSE; }Ten uchwyt wydarzenia Qt jest wywoływany gdy użytkownik uwolni przycisk myszy, a który był wciśnięty w tym widgecie.
Jeżeli uwolniony zostanie lewy przycisk, możemy być pewni, że działo nie jest już dłużej wybierane.
Wydarzenie rysowania ma dwie dodatkowe linie:
if ( updateR.intersects( barrierRect() ) ) paintBarrier( &p );paintBarrier() robi to samo co paintShot(), paintTarget() i paintCannon().
void CannonField::paintBarrier( QPainter *p ) { p->setBrush( yellow ); p->setPen( black ); p->drawRect( barrierRect() ); }Ta prywatna funkcja rysuje żółtą przeszkodę z czarną obwódką.
QRect CannonField::barrierRect() const { return QRect( 145, height() - 100, 15, 100 ); }Ta prywatna funkcja zwraca prostokąt wokół przeszkody.
bool CannonField::barrelHit( const QPoint &p ) const { QWMatrix mtx; mtx.translate( 0, height() - 1 ); mtx.rotate( -ang ); mtx = mtx.invert(); return barrelRect.contains( mtx.map(p) ); }Ta funkcja zwraca TRUE jeśli punkt leży w dziale, inaczej FALSE.
Używamy tu klasy QWMatrix. Jest zdefinowana przez qwmatrix.h, zawartą przez qpainter.h.
QWMatrix definjuje mapowanie systemu koordynatów. Może przeprowaedzić takie same transformacje jak QPainter.
Przeprowadzamy tu takie same kroki transforacyjne jak przy rysowaniu działa w funkcji paintCannon(). Najpierw tłumaczymy system koordynatów, potem obracamy nim.
Teraz musimy sprawdzić czy punkt p (w koordynatach widgetu) leży w dziale. Aby to sprawdzić, odwracamy macierz. Odwracani macierzy daje te same kroki jakie zastosowaliśmy przy transformacji systemu koordynatów. Mapujemy punkt p dzięki odwróconej macierzy i ustawiamy na TRUE jeżeli znajduje się w dziale.
#include <qaccel.h>Zawieramy definicję klasy QAccel.
QVBox *box = new QVBox( this, "cannonFrame" ); box->setFrameStyle( QFrame::WinPanel | QFrame::Sunken ); cannonField = new CannonField( box, "cannonField" );Teraz tworzymy i ustawiamy QVBox, ustalamy styl ramki, a potem tworzymy cannonField jako jego dziecko. Ponieważ nic więcej nie ma w pudełku, w efekcie QVBox doda ramkę wokół CannonField.
QAccel *accel = new QAccel( this ); accel->connectItem( accel->insertItem( Key_Enter ), this, SLOT(fire()) ); accel->connectItem( accel->insertItem( Key_Return ), this, SLOT(fire()) );Tutaj ustawiamy klawiaturę, oraz sloty do klawiszy. To tak jak z klawiszami skrótu. QAccel nie jest widgetem i nie ma widzialnego efektu na swojego rodzica.
Definjujemy dwa klawisze. Chcemy by slotfire() był pod klawiszem Enter, a wyjście przez Control-Q.
accel->connectItem( accel->insertItem( CTRL+Key_Q ), qApp, SLOT(quit()) );CTRL, Key_Enter, Key_Return and Key_Q są stałymi dostarczanymi przez Qt. Tak naprawde to jest to Qt::Key_Enter itd., ale wszytkie klasy Qt dziedziczą nazwy.
QGridLayout *grid = new QGridLayout( this, 2, 2, 10 ); grid->addWidget( quit, 0, 0 ); grid->addWidget( box, 1, 1 ); grid->setColStretch( 1, 10 );
(Pierwszy taką grę napisał Igor Rafienko. Możesz sprawdzić tę grę.)
Nowym ćwiczeniem jest:
Napisz grę typu Breakout i oddaj ją projektowi KDE.
Możesz teraz pisać własne aplikacje Qt.
[Poprzedni tutorial] [Pierwszy tutorial] [Głowna strona tutoriala]
Copyright (c) 2000 Troll Tech | Znaki towarowe |
Wersja Qt 2.1.0
|