Simple example of game scene with QGraphicsScene

This simple example mostly shows how to deal with

  • QGraphicsScene::advance()
  • QGraphicsItem::advance()
  • QGraphicsItem::paint(…)

Overview

The example contains three classes with following responsabilities

  • The scene is a QGraphicsScene
    • Creates and deletes new items (towers and mobile units)
  • Towers
    • search for nearby units
    • shoot them if any
  • Mobile units (fighters)
    • Move
    • Explode and die

View example on Youtube [youtube.com]

Main.cpp

The main() method creates a scene and a QTimer.
The timer calls Scene::advance() every 0.01 sec.

  1. #include <QtGui>
  2. #include "scene.h"
  3. #include "simpletower.h"
  4.  
  5. int main(int argc, char **argv)
  6.  {
  7.     QApplication app(argc, argv);
  8.     Scene scene;
  9.     scene.setSceneRect(0,0,640,360);
  10.     QGraphicsView view(&scene);
  11.     QTimer timer;
  12.     QObject::connect(&timer, SIGNAL(timeout()), &scene, SLOT(advance()));
  13.     view.show();
  14.     timer.start(10);
  15.     return app.exec();
  16.  }

The Scene

  • The scene constructor creates several towers
  • Scene::advance() :
    • has a counter m_TicTacTimer
    • every 20th ticTac, a new mobile unit is created with a random position, direction and speed.
    • dead units are removed from the item list

scene.h

  1. #ifndef SCENE_H
  2. #define SCENE_H
  3.  
  4. #include <QGraphicsScene>
  5.  
  6. class Scene : public QGraphicsScene
  7. {
  8.     Q_OBJECT
  9. public:
  10.     Scene();
  11. public slots:
  12.     void advance();
  13. private:
  14.     int m_TicTacTime;
  15. };
  16.  
  17. #endif // SCENE_H

scene.cpp

  1. #include "scene.h"
  2. #include "mobileunit.h"
  3. #include "simpletower.h"
  4. #include<QDebug>
  5.  
  6. Scene::Scene()
  7.     : QGraphicsScene()
  8.     , m_TicTacTime(0)
  9. {
  10.     SimpleTower * simpleTower = new SimpleTower();
  11.     simpleTower->setPos(200.0, 100.0);
  12.     addItem(simpleTower);
  13.  
  14.     simpleTower = new SimpleTower();
  15.     simpleTower->setPos(200.0, 180.0);
  16.     addItem(simpleTower);
  17.  
  18.     simpleTower = new SimpleTower();
  19.     simpleTower->setPos(200.0, 260.0);
  20.     addItem(simpleTower);
  21.  
  22.  
  23.     simpleTower = new SimpleTower();
  24.     simpleTower->setPos(250.0, 050.0);
  25.     addItem(simpleTower);
  26.  
  27.     simpleTower = new SimpleTower();
  28.     simpleTower->setPos(250.0, 310.0);
  29.     addItem(simpleTower);
  30.  
  31.     simpleTower = new SimpleTower();
  32.     simpleTower->setPos(300.0, 110.0);
  33.     addItem(simpleTower);
  34.  
  35.     simpleTower = new SimpleTower();
  36.     simpleTower->setPos(300.0, 250.0);
  37.     addItem(simpleTower);
  38.  
  39.     simpleTower = new SimpleTower();
  40.     simpleTower->setPos(350.0, 180.0);
  41.     addItem(simpleTower);
  42.  
  43. }
  44.  
  45. void Scene::advance()
  46. {
  47.     m_TicTacTime++;
  48.  
  49.     // delete killed objects
  50.     QGraphicsItem *item=NULL;
  51.     MobileUnit * unit=NULL;
  52.     int i=0;
  53.     while (i<items().count())
  54.     {
  55.         item=items().at(i);
  56.         unit=dynamic_cast<MobileUnit* > (item);
  57.         if ( ( unit!=NULL) && (unit->isFinished()==true))
  58.         {
  59.             removeItem(item);
  60.             delete unit;
  61.         }
  62.         else ++i;
  63.     }
  64.  
  65.     // Add new units every 20 tictacs
  66.     if(m_TicTacTime % 20==0)
  67.     {
  68.         // qDebug() << "add unit";
  69.         MobileUnit * mobileUnit= new MobileUnit();
  70.         qreal h=static_cast<qreal>( qrand() % static_cast<int>(height()) );
  71.         mobileUnit->setPos(width(), h);
  72.         addItem(mobileUnit);
  73.     }
  74.  
  75.     QGraphicsScene::advance();
  76.     update();
  77. }

Towers

  • Towers search for nearby units
    • Search is performed by squared distance comparison
    • Search stops when a first item is found
    • Warning: n against n = O(n2) algorithm… Poor performances on large amount of items… but sufficient here.
  • Towers shoot the located item if any
  • after shooting, a tower must wait until its weapon is reloaded : it requires a “reload time” of 100ms
  • The laser beam is drawn with lines of different thicknesses and colors

simpletower.h

  1. #ifndef SIMPLETOWER_H
  2. #define SIMPLETOWER_H
  3.  
  4. #include <QtCore>
  5. #include <QGraphicsRectItem>
  6.  
  7. class MobileUnit;
  8.  
  9. class SimpleTower : public QGraphicsRectItem
  10. {
  11. public:
  12.     SimpleTower();
  13.     void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
  14.     void advance(int phase);
  15. private:
  16.     void searchTarget();
  17.     void shoot();
  18. private:
  19.     qreal m_DetectionDistance;
  20.     QTime m_Time;
  21.     int m_ReloadTime;
  22.     bool m_ShootIsActive;
  23.     MobileUnit * m_Target;
  24.     QImage m_TowerImage;
  25. };
  26.  
  27. #endif // SIMPLETOWER_H

simpletower.cpp

  1. #include <QPainter>
  2. #include <QGraphicsScene>
  3. #include "simpletower.h"
  4. #include "mobileunit.h"
  5. SimpleTower::SimpleTower()
  6.     , m_DetectionDistance(100.0)
  7.     , m_Time(0, 0)
  8.     , m_ReloadTime(100)
  9.     , m_ShootIsActive(false)
  10.     , m_Target(NULL)
  11.     , m_TowerImage(QImage(":/lightTower"))
  12. {
  13.     setRect(-15.0, -15.0, 30.0, 30.0);
  14.     m_Time.start();
  15. }
  16.  
  17. void SimpleTower::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
  18. {
  19.     painter->drawImage(-15,-15,m_TowerImage);
  20.     if ( (m_Target!=NULL) && (m_ShootIsActive) )
  21.     {   // laser beam
  22.         QPointF towerPoint = mapFromScene(pos());
  23.         QPointF target = mapFromScene(m_Target->pos());
  24.         painter->setPen(QPen(Qt::yellow,8.0,Qt::SolidLine));
  25.         painter->drawLine(towerPoint.x(), towerPoint.y(), target.x(), target.y());
  26.         painter->setPen(QPen(Qt::red,5.0,Qt::SolidLine));
  27.         painter->drawLine(towerPoint.x(), towerPoint.y(), target.x(), target.y());
  28.         painter->setPen(QPen(Qt::white,2.0,Qt::SolidLine));
  29.         painter->drawLine(towerPoint.x(), towerPoint.y(), target.x(), target.y());
  30.         m_ShootIsActive=false;
  31.     }
  32. }
  33.  
  34. void SimpleTower::advance(int phase)
  35. {
  36.     if (phase==0)
  37.     {
  38.         searchTarget();
  39.         if ( (m_Target!=NULL) && (m_Time.elapsed()> m_ReloadTime) )
  40.             shoot();
  41.     }
  42. }
  43.  
  44. void SimpleTower::searchTarget()
  45. {
  46.     m_Target=NULL;
  47.     QList<QGraphicsItem* > itemList = scene()->items();
  48.     int i = itemList.count()-1;
  49.     qreal dx, dy, sqrDist;
  50.     qreal sqrDetectionDist = m_DetectionDistance * m_DetectionDistance;
  51.     MobileUnit * unit=NULL;
  52.     while( (i>=0) && (NULL==m_Target) )
  53.     {
  54.         QGraphicsItem * item = itemList.at(i);
  55.         unit = dynamic_cast<MobileUnit * >(item);
  56.         if ( (unit!=NULL) && ( unit->lifePoints()>0 ) )
  57.         {
  58.             dx = unit->x()-x();
  59.             dy = unit->y()-y();
  60.             sqrDist = dx*dx+dy*dy;
  61.             if (sqrDist < sqrDetectionDist)
  62.                 m_Target=unit;
  63.         }
  64.         --i;
  65.     }
  66. }
  67.  
  68. void SimpleTower::shoot()
  69. {
  70.     m_ShootIsActive=true;
  71.     m_Target->touched(3);
  72.     m_Time.restart();
  73. }

Mobile units

Life Cycle of a mobile unit :

  • A mobile unit is created with 10 life points
  • Position changes according to predefined direction and speed
  • It loses life points if touched by a laser beam
  • The unit explodes when lifepoints are exhausted
  • The explosion has a duration.
  • The explosion is drawn by painter->drawEllipse(…) with a growing radius

mobileunit.h

  1. #ifndef MOBILEUNIT_H
  2. #define MOBILEUNIT_H
  3.  
  4. #include <QtCore>
  5. #include <QGraphicsRectItem>
  6. #include <QRadialGradient>
  7.  
  8. class MobileUnit : public QGraphicsRectItem
  9. {
  10. public:
  11.     MobileUnit();
  12.     inline int lifePoints() { return m_LifePoints; }
  13.     inline bool isFinished() const { return m_IsFinished; }
  14.     void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
  15.     void advance(int phase);
  16.     void touched (int hurtPoints);
  17. private:
  18.     int m_LifePoints;
  19.     qreal m_Alpha;
  20.     qreal m_DirX;
  21.     qreal m_DirY;
  22.     qreal m_Speed;
  23.     bool m_IsFinished;
  24.     bool m_IsExploding;
  25.     int m_ExplosionDuration;
  26.     QRadialGradient m_RedExplosion;
  27.     QTime m_Time;
  28.     QImage m_SpacecraftImage;
  29. };
  30.  
  31. #endif // MOBILEUNIT_H

mobileunit.cpp

  1. #include "mobileunit.h"
  2. #include <QPainter>
  3. #include <QGraphicsScene>
  4. #include <math.h>
  5.  
  6. MobileUnit::MobileUnit()
  7.     , m_LifePoints(10)
  8.     , m_Alpha(0)
  9.     , m_DirX(1.0)
  10.     , m_DirY(0.0)
  11.     , m_Speed(1.0)
  12.     , m_IsFinished(false)
  13.     , m_IsExploding(false)
  14.     , m_ExplosionDuration(500)
  15.     , m_RedExplosion(0.0, 0.0, 20.0, 0.0, 0.0)
  16.     , m_Time(0, 0)
  17.     , m_SpacecraftImage(QImage(":/spacecraft00") )
  18. {
  19.     m_Alpha= static_cast<qreal> (qrand()%90+60);
  20.     qreal speed= static_cast<qreal> (qrand()%10-5);
  21.     m_DirY=cos(m_Alpha/180.0*M_PI );
  22.     m_DirX=sin(m_Alpha/180.0*M_PI);
  23.     m_Alpha= -m_Alpha + 180.0 ;
  24.     m_Speed=1.0+speed*0.1;
  25.     setRect(-10.0, -10.0, 20.0, 20.0);
  26.     m_Time.start();
  27.  
  28.     m_RedExplosion.setColorAt(0.0, Qt::white);
  29.     m_RedExplosion.setColorAt(0.2, QColor(255, 255, 100, 255));
  30.     m_RedExplosion.setColorAt(0.4, QColor(255, 80, 0, 200));
  31.     m_RedExplosion.setColorAt(1.0, QColor(255, 255, 255, 0));
  32. }
  33.  
  34. void MobileUnit::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
  35. {
  36.     painter->setPen(Qt::NoPen);
  37.  
  38.     if (!m_IsExploding)
  39.     {
  40.         painter->rotate(m_Alpha);
  41.         painter->drawImage(-15,-14, m_SpacecraftImage);
  42.     }
  43.     else
  44.     {
  45.         painter->setBrush(QBrush(m_RedExplosion));
  46.         qreal explosionRadius= 8.0 + m_Time.elapsed() / 50;
  47.         painter->drawEllipse(-explosionRadius, -explosionRadius, 2.0*explosionRadius, 2.0*explosionRadius);
  48.     }
  49. }
  50.  
  51. void MobileUnit::advance(int phase)
  52. {
  53.     if (phase==0)
  54.     {
  55.         qreal xx=x(); qreal yy=y();
  56.         if ( (xx<0.0) || (xx > scene()->width() ) )
  57.         {   // rebond
  58.             m_DirX=-m_DirX;
  59.             m_Alpha=-m_Alpha;
  60.         }
  61.         if ( (yy<0.0) || (yy > scene()->height()))
  62.         {   // rebond
  63.             m_DirY=-m_DirY;
  64.             m_Alpha=180-m_Alpha;
  65.         }
  66.         if (m_IsExploding)
  67.         {
  68.             m_Speed*=0.98;  // decrease speed
  69.             if (m_Time.elapsed() > m_ExplosionDuration)
  70.                 m_IsFinished=true;  // is dead
  71.         }
  72.         setPos(x()+m_DirX*m_Speed, y()+m_DirY*m_Speed);
  73.     }
  74. }
  75.  
  76. void MobileUnit::touched (int hurtPoints)
  77. {
  78.     m_LifePoints -=hurtPoints; // decrease life
  79.     if (m_LifePoints<0) m_LifePoints=0;
  80.     if (m_LifePoints==0)
  81.     {
  82.         m_Time.start();
  83.         m_IsExploding=true;
  84.     }
  85. }

Categories: