Qt logo


Kapitola 11: Výstrel


Screenshot of tutoriálu č. eleven

V tomto príklade uvádzame udalosť časovača (timer event) kvôli implementácii streľby.

Prechádzka riadok po riadku

cannon.h

CannonField teraz vie strieľať.

        bool  isShooting() const { return shooting; }

Vracia TRUE ak je strela vo vzduchu.

        void  shoot();

Volanie tohto slotu spôsobí, že kanón vystrelí, ak nie je strela vo vzduchu.

    protected:
        void  timerEvent( QTimerEvent * );

Udalosť časovača (timer event) je tretí typ udalosti widgetu, ktorý stretávame. Môžete dosiahnuť, aby Qt volala obsluhu tejto udalosti v pravidelných intervaloch.

    private:
        void  stopShooting();

Táto privátna funkcia zastaví strelu vo vzduchu.

        void  paintShot( QPainter * );

Táto privátna funkcia nakreslí strelu.

        QRect shotRect() const;

Táto privátna funkcia vracia obdĺžnik obklopujúci strelu, ak je vo vzduchu, inak je vrátený obdĺžnik nedefinovaný.

        bool  shooting;

Táto privátna premenná je TRUE ak je strela vo vzduchu.

        int   timerCount;
        float shoot_ang;
        float shoot_f;
    };

Tieto privátne premenné obsahujú popis výstrelu. Premenná timerCount sleduje čas od výstrelu. Premenná shoot_ang je uhol výstrelu a shoot_f je sila výstrelu.

cannon.cpp

    #include <math.h>

Vkladáme knižnicu s matematikou kvôli funkciám sin() a cos().

    CannonField::CannonField( QWidget *parent, const char *name )
            : QWidget( parent, name )
    {
        ang           = 45;
        f             = 0;
        shooting      = FALSE;
        timerCount    = 0;
        shoot_ang     = 0;
        shoot_f       = 0;
    }

Inicializujeme naše nové privátne premenné.

    void CannonField::shoot()
    {
        if ( shooting )
            return;
        timerCount = 0;
        shoot_ang  = ang;
        shoot_f    = f;
        shooting   = TRUE;
        startTimer( 50 );
    }

Táto funkcia vystrelí strelu, pokiaľ už jedna nie je vo vzduchu. Premenná timerCount je nastavená na nulu. Premenné shoot_ang a shoot_f sú nastavené na aktuálnu hodnotu uhla a sily kanóna. Premenná shooting je nastavená na TRUE aby indikovala, že strela je vo vzduchu. Nakoniec spustíme časovač.

Volanie startTimer() spôsobí, že Qt nám bude posielať udalosti časovača v pravidelných intervaloch. Tentokrát chceme udalosť každých 50 milisekúnd.

    void CannonField::timerEvent( QTimerEvent * )
    {
        erase( shotRect() );
        timerCount++;
    
        QRect shotR = shotRect();
    
        if ( shotR.x() > width() || shotR.y() > height() ) {
            stopShooting();
            return;
        }   
        repaint( shotR, FALSE );
    }

Funkcia timerEvent() je obsluha udalosti, ktorá dostáva udalosti od Qt. V našom príklade je volaná každých 50 milisekúnd, pokiaľ je strela vo vzduchu.

Najprv vymažeme starú strelu pomocou funkcie QWidget::erase(). Všimnite si, že to nie je potrebné pri prvom volaní timerEvent() po výstrele (ale neuškodí to). Funkcia shotRect() (popísaná nižšie) vracia obdĺžnik obklopujúci strelu na aktuálnej pozícii.

Potom inkrementujeme timerCount, čo má za dôsledok pohyb strely na ďalšiu pozíciu pozdĺž jej trajektórie.

Ďalej získame nové súradnice obdĺžnika okolo strely.

Ak sa strela posunula za pravý alebo pod dolný okraj widgetu, zastavíme streľbu a ukončíme obsluhu.

Ak nie, nakreslíme novú strelu prekreslením tej časti widgetu, ktorá obsahuje strelu. Argument FALSE indikuje, že určený obdĺžnik nemá byť pred odoslaním udalosti paint event widgetu vymazaný.

    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();
    }

Táto funkcia obsluhy udalosti kreslenia je v princípe rovnaká ako v predchádzajúcej kapitole. Jediný rozdiel je, že nakreslíme aj strelu, ak je to potrebné.

    void CannonField::stopShooting()
    {
        shooting = FALSE;
        killTimers();
    }

Táto privátna funkcia zastaví strelu vo vzduchu. Najprv nastaví premennú shooting, potom zruší všetky udalosti časovača tohto widgetu. Je tiež možné zrušiť jediný časovač.

    void CannonField::paintShot( QPainter *p )
    {
        p->setBrush( black );
        p->setPen( NoPen );
        p->drawRect( shotRect() );
    }

Táto privátna funkcia kreslí strelu ako čierny vyplnený obdĺžnik.

    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;
    }

Táto privátna funkcia počíta stredný bod strely a vracia obklopujúci obdĺžnik. Používa počiatočné hodnoty sily a uhla (hodnoty pri výstrele) a premennú timerCount, ktorá rasti spolu s časom.

Použitý vzorec je klasický Newtonov vzorec pre pohyb bez odporu v gravitačnom poli. Pre jednoduchosť sme sa rozhodli nebrať ohľad na žiadne relativistické efekty.

Vypočítame centrum strely v súradnicovom systéme, kde súradnica y rastie nahor. Po jeho vypočítaní skonštruujeme QRect s rozmermi 6x6 a posunieme jeho stred na pozíciu vypočítanú vyššie. V rovnakej operácii konvertujeme bod do súradnicového systému nášho widgetu (viď suradnicový systém).

Funkcia qRound() je vložená funkcia definovaná v qglobal.h (vkladanej vo všetkých ostatných Qt hlavičkových súboroch). qRound zaokrúhli reálne číslo s dvojitou presnosťou (double) na najbližšie celé číslo.

main.cpp

    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;
    };

Pridáme iba tlačidlo výstrelu.

        shoot = new QPushButton( "Shoot", this, "shoot" );
        shoot->setGeometry( 90, 10, 75, 30 );
        shoot->setFont( QFont( "Times", 18, QFont::Bold ) );

V konštruktore vytvoríme a nastavíme tlačidlo výstrelu rovnako ako sme to urobili s tlačidlom quit. Všimnite si, že prvý argument konštruktora je text tlačidla a tretí je meno widgetu.

        connect( shoot, SIGNAL(clicked()), cannonField, SLOT(shoot()) );

Spája signál tlačidla výstrelu clicked() so slotom shoot() widgetu CannonField.

Správanie

Kanón strieľa, ale zatiaľ nemá žiadny terč.

Cvičenia

Upravte strelu tak, aby bola plný kruh (pomôcka: QPainter::drawEllipse() ).

Zmente farbu kanóna počas letu strely.

Teraz môžete ísť na kapitolu dvanásť.

[Predchádzajúci tutoriál] [Ďalší tutoriál] [Hlavná stránka tutoriálu]


Copyright © 1998 Troll TechTrademarks
Qt version 1.42