Qt logo


Kapitola 14: Proti múru


Screenshot of tutoriálu č. fourteen

Toto je záverečný príklad, kompletná hra.

Pridali sme klávesové skratky, uvádzame udalosti myši. Pridali sme rám okolo CannonFieldu a pridali bariéru (múr), aby bola hra zaujímavejšia.

Prechádzka riadok po riadku

cannon.h

CannonField môže teraz prijímať udalosti myši, takže užívateľ môže mieriť kliknutím a ťahaním hlavne. Obsahuje zároveň bariérový múr.

    protected:
        void  timerEvent( QTimerEvent * );
        void  paintEvent( QPaintEvent * );
        void  mousePressEvent( QMouseEvent * );
        void  mouseMoveEvent( QMouseEvent * );
        void  mouseReleaseEvent( QMouseEvent * );

Na prídavok k starým známym rutinám obsluhy udalostí, implementujeme tri rutiny obsluhy udalostí myši. Ich mená hovoria sami za seba (tým čo vedia aspoň trochu anglicky :).

        void  paintBarrier( QPainter * );

Táto privátna funkcia kreslí bariérový múr.

        QRect barrierRect() const;

Táto privátna funkcia vracia obdĎžnik obklopujúci múr.

        bool  barrelHit( const QPoint & ) const;

Táto privátna funkcia kontroluje, či bod je v hlavni kanóna.

        bool barrelPressed;

Privátna premenná, ktorá je TRUE ako užívateľ stlačil tlačidlo myši nad hlavňou kanóna a ešte ho nepustil.

cannon.cpp

        barrelPressed = FALSE;

Tento riadok sme pridali do konštruktora. Na začiatku nie je tlačidlo myši stlačené nad hlavňou.

        if ( (shotR.x() > width() || shotR.y() > height()) ||
             shotR.intersects(barrierRect()) ) {
            stopShooting();
            emit missed();
            return;
        }   

Vo funkcii timerEvent() teraz musíme testovať či strela nezasiahla bariéru, okrem obvyklých testov na prekročenie pravého alebo dolného okraja widgetu.

        if ( updateR.intersects( barrierRect() ) )
            paintBarrier( &p );

V obsluhe udalosti paint event sme pridali kreslenie bariéry.

    void CannonField::mousePressEvent( QMouseEvent *e )
    {
        if ( e->button() != LeftButton )
            return;
        if ( barrelHit( e->pos() ) )
            barrelPressed = TRUE;
    }

Toto je Qt obsluha udalosti. Je volaná keď užívateľ stlačí tlačidlo myši keď je kurzor nad widgetom.

Ak udalosť nevygenerovalo ľavé tlačidlo myši, ukončíme obsluhu. Inak skontrolujeme či je kurzor myši nad hlavňou kanóna. Ak áno, nastavíme premennú barrelPressed na TRUE.

Všimnite si, že funkcia pos() vracia bod podľa súradníc 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 ) );
    }

Ďalšia Qt obsluha udalosti. Je volaná keď užívateľ má stlačené tlačidlo myši vo vnútri tohto widgetu a zároveň pohne myšou. Môžete nastaviť, aby Qt vysielala udalosť pohnutia myši aj keď nie je stlačené tlačidlo, viď QWidget::setMouseTracking().

Táto obslužná rutina nastaví hlaveň kanóna podľa pozície kurzora myši.

Ak nie je kurzor nad hlavňou, nerobíme nič. Inak zistíme jeho pozíciu. Ak je kurzor pod alebo vľavo od widgetu, upravíme bod tak, aby bol v jeho vnútri.

Potom počítame uhol medzi dolným okrajom widgetu a imaginárnou priamkou z ľavého dolného rohu widgetu do bodu pozície kurzora. Nakoniec nastavíme uhol kanóna na novú hodnotu (konvertovanú na stupne).

Pamätajte, že setAngle() prekresľuje kanón.

    void CannonField::mouseReleaseEvent( QMouseEvent *e )
    {
        if ( e->button() == LeftButton )
            barrelPressed = FALSE;
    }

Táto Qt obslužná rutina je volaná vždy keď užívateľ pustí tlačidlo myši po tom, čo bolo stlačené vo vnútri widgetu.

Ak je pustené ľavé tlačidlo, môžeme si byť istí, že hlaveň už nie je stlačená (presúvaná).

    void CannonField::paintBarrier( QPainter *p )
    {
        p->setBrush( yellow );
        p->setPen( black );
        p->drawRect( barrierRect() );
    }

Táto privátna funkcia kreslí bariérový múr ako žltý obdĺžnik s čiernym okrajom.

    QRect CannonField::barrierRect() const
    {
        return QRect( 145, height() - 100, 15, 100 );
    }

Táto privátna funkcia vracia obdĺžnik okolo bariéry. Upevníme dolný okraj bariéry na dolný okraj widgetu.

    bool CannonField::barrelHit( const QPoint &p ) const
    {
        QWMatrix mtx;
        mtx.translate( 0, height() - 1 );
        mtx.rotate( -ang );
        mtx = mtx.invert();
        return barrel_rect.contains( mtx.map(p) );
    }

Táto funkcia vráti TRUE ak je bod vo vnútri hlavne, inak vráti FALSE.

Používame tu triedu QWMatrix. Je definovaná v hlavičkovom súbore qwmatrix.h, ktorý je vložený cez qpainter.h.

QWMatrix definuje mapovanie systému súradníc. Dokáže uskutočniť rovnaké transformácie ako QPainter.

Tu robíme rovnaké transformačné kroky ako keď kreslíme hlaveň vo funkcii paintCannon(). Najprv posunieme súradnicový systém, potom ho otočíme.

Teraz potrebujeme zistiť, či bod p (v súradniciach widgetu) leží bo vnútri obdĺžnika hlavne. Aby sme to zistili, invertujeme transformačnú maticu. Invertovaná matica urobí inverznú transformáciu k tej, ktorú sme robili pri kreslení hlavne. Namapujeme bod p pomocou inverznej matice a ak je vo vnútri pôvodného obdĺžnika hlavne, vrátime TRUE.

gamebrd.h

Jediná zmena je pridaný rám.

    class QFrame;

Deklarujeme meno QFrame...

        QFrame      *frame;

a pridáme QFrame do widgetu.

gamebrd.cpp

    #include <qaccel.h>

Vložíme definíciu triedy QAccel.

        frame = new QFrame( this, "cannonFrame" );
        frame->setFrameStyle( QFrame::WinPanel | QFrame::Sunken );

Vytvoríme a nastavíme rám. Chceme tento rám okolo widgetu CannonField. Pretože widgety nemôžu byť priesvitné, umiestnime rám za CannonField. Vytvoríme rám pred vytvorením CannonFieldu. Qt zaručuje, že posledný vytvorený widget bude vždy na povrchu ostatných.

Štýl rámu je nastavený na vpadnutý WinPanel. Viac informácií viď dokumentáciu QFrame.

        QAccel *accel = new QAccel( this );
        accel->connectItem( accel->insertItem( Key_Space), this, SLOT(fire()) );
        accel->connectItem( accel->insertItem( Key_Q), qApp, SLOT(quit()) );

Tu vytvárame akcelerátor. Akcelerátor je objekt, ktorý zachytáva udalosti z klávesnice a volá určené sloty ak sú stlačené určité klávesy. Tento mechanizmus sa nazýva aj klávesové skratky. Všimnite si, že akcelerátor môže byť dcérsky objekt a je zničený pri zničení rodiča. QAccel nie je widget a nemá žiadny viditeľný vplyv na svojho rodiča.

Definujeme dve klávesové skratky. Chceme, aby bol slot fire() volaný keď užívateľ stlačí medzeru, a chceme ukončiť aplikáciu, keď stlačí kláves "Q". Konštanty Key_Space a Key_Q sú definované v hlavičkovom súbore qkeycode.h (vložený cez qaccel.h). Všimnite si tiež, že Key_Q znamená kláves Q na klávesnici. Slot bude volaný po stlačení "q" aj "Q".

        frame->move( angle->x() + angle->width() + 10, angle->y() );
        cannonField->move( frame->x() + 2, frame->y() + 2 );

Nastavíme počiatočnú pozíciu rámu na rovnakú pozíciu akú sme predtým použili pre CannonField. Potom nastavíme pozíciu CannonFieldu relatívne k rámu.

    void GameBoard::resizeEvent( QResizeEvent * )
    {
        frame->resize( width()  - frame->x() - 10,
                       height() - frame->y() - 10 );
        cannonField->resize( frame->width() - 4, frame->height() - 4 );
    }

Musíme zapracovať widget rámu do našej obsluhy zmeny veľkosti. Najprv zmeníme veľkosť rámu rovnako ako sme predtým menili veľkosť CannonFieldu. Potom jednoducho zmeníme veľkosť CannonFieldu relatívne k rámu.

Správanie

Kanón teraz vystrelí keď stlačíte medzeru. Môžete tiež meniť uhol kanóna pomocou myši. Bariéra robí hru o niečo menej jednoduchou. A máme aj celkom pekný rám okolo CannonFieldu.

Cvičenia

Napíšte hru "Space invaders" (votrelci) a pripojte ju ku KDE projektu.

Teraz môžete ísť písať vaše vlastné Qt aplikácie.

[Predchádzajúci tutoriál] [Prvý tutoriál] [Hlavná stránka tutoriálu]


Copyright © 1998 Troll TechTrademarks
Qt version 1.42