1

I'm quite new to Qt, but I got into it to start experimenting with making 2D games. I've got a extremely rough and simple game started, but I have an issue. Whenever the health gets to 0, the game doesn't end. I just want to know how to end the game and where to put this exit command before making a "Game Over" screen. My code is below, and from what I can grasp, I assume the QApplication::quit() goes in the Game.cpp file. Doing this by taking the health integer from Health.cpp and Health.h and putting it in Game.cpp. Any help is appreciated. Here is the code I feel the answers lie in, if more info is needed, ask.

Game.h

#ifndef GAME_H
#define GAME_H

#include <QGraphicsView>
#include <QWidget>
#include <QGraphicsScene>
#include "Player.h"
#include "Score.h"
#include "Health.h"
#include "Level.h"
#include "Main.h"

class Game: public QGraphicsView{
public:
    Game(QWidget * parent=0);

    QGraphicsScene * scene;
    Player * player;
    Score * score;
    Health * health;
    Level * level;
    Main * close;
    int end();
};

#endif // GAME_H

Game.cpp

#include "Game.h" 
#include <QTimer>
#include <QGraphicsTextItem>
#include <QFont>
#include "Enemy.h"
#include <QMediaPlayer>
#include <QBrush>
#include <QImage>
#include <QApplication>

Game::Game(QWidget *parent){
    // create the scene
    scene = new QGraphicsScene();
    scene->setSceneRect(0,0,800,600); // make the scene 800x600        instead of infinity by infinity (default)
    setBackgroundBrush(QBrush(QImage(":/images/bg.png")));

    // make the newly created scene the scene to visualize (since Game is a QGraphicsView Widget,
    // it can be used to visualize scenes)
    setScene(scene);
    setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    setFixedSize(800,600);

    // create the player
    player = new Player();
    player->setPos(400,500); // TODO generalize to always be in the     middle bottom of screen
    // make the player focusable and set it to be the current focus
    player->setFlag(QGraphicsItem::ItemIsFocusable);
    player->setFocus();
    // add the player to the scene
    scene->addItem(player);

    // create the score/health
    score = new Score();
    scene->addItem(score);
    health = new Health();
    health->setPos(health->x(),health->y()+25);
    scene->addItem(health);
    level = new Level();
    scene->addItem(level);Bull
    level->setPos(level->x(),level->y()+50);

    // spawn enemies
    QTimer * timer = new QTimer();
    QObject::connect(timer,SIGNAL(timeout()),player,SLOT(spawn()));
    timer->start(2000);

    // play background music
    QMediaPlayer * music = new QMediaPlayer();
    music->setMedia(QUrl("qrc:/sounds/bgsound.mp3"));
    music->play();

    show();
}

int Game::end(){
    if (health == 0){
        QApplication::quit();
    }
    return 0;
}

Health.h

#ifndef HEALTH_H
#define HEALTH_H

#include <QGraphicsTextItem>

class Health: public QGraphicsTextItem{
public:
    Health(QGraphicsItem * parent=0);
    void decrease();
    int getHealth();
private:
    int health;
};

#endif // HEALTH_H

Health.cpp

#include "Health.h"
#include <QFont>
#include <QApplication>

Health::Health(QGraphicsItem *parent): QGraphicsTextItem(parent){
    // initialize the score to 0
    health = 3;

    // draw the text
    setPlainText(QString("Health: ") + QString::number(health)); //   Health: 3
    setDefaultTextColor(Qt::red);
    setFont(QFont("times",16));
}

void Health::decrease(){
    health--;
    setPlainText(QString("Health: ") + QString::number(health)); //     Health: 2
}

int Health::getHealth(){
    return health;
}

main.cpp

#include <QApplication>
#include "Game.h"
#include "Main.h"

Game * game;

int main(int argc, char *argv[]){
    QApplication a(argc, argv);

    game = new Game();
    game->show();

    return a.exec();
}
Jarrod A.
  • 93
  • 1
  • 9

2 Answers2

2

Your end() function is never called.

The best way to achieve what you want to is to use Qt's signal/slot mechanism. It amkes it easy to connect an event (signal) to an action (slot):

  • Add Q_OBJECT macro to Health and Game classes and make sure your compilation environment moc's the two header files
  • Declare in Health a signal named dead()
  • Emit the signal from Health::decrease()
  • Make Game::end() be a slot and be void
  • Connect Health::dead() to Game::end()

Then, Game::end() will be called as soon as Health reaches zero.

class Health: public QGraphicsTextItem 
{
    Q_OBJECT
public:
    Health(QGraphicsItem * parent=0);
    void decrease();
    int getHealth();
signals:
    void dead();
private:
    int health;
};

...

class Game: public QGraphicsView{
    Q_OBJECT
public:
    ...

public slots:
    void end();
};

...

void Health::decrease(){
    health--;
    setPlainText(QString("Health: ") + QString::number(health));
    if ( health == 0 )
        emit dead();
}

...

Game::Game(QWidget *parent){

    ...

    connect( health, SIGNAL(dead()), this, SLOT(end()) );

}

...

void Game::end(){
    // no need to test health anymore, as signal is emited when health is zero
    // do some extra stuff before exiting
    QApplication::quit();
}

If end() only calls QApplication::quit(), you can remove it and diectly connect the signal to QApplication::quit(), like that:

connect( health, SIGNAL(dead()), qApp, SLOT(quit()) );

Also note that you were testing health == 0 in Game::end(), but health is a pointer, and, looking at your code, it will never be 0 (you maybe meant to write if ( health->getHealth() == 0 ).

jpo38
  • 20,821
  • 10
  • 70
  • 151
  • Did everything you said, but when I compile I get the error "undefined reference to Health::dead()" and points to the lines `if (health==0)` `emit dead();`. What's wrong now? – Jarrod A. Feb 09 '16 at 18:07
  • Did you moc Health.h? – jpo38 Feb 09 '16 at 18:12
  • I did not, but when I added Q_OBJECT to Health.h I now get MORE errors while compiling, adding "undefined reference to vtable for Health" in the function Health::decrease and Health.cpp, while still having the "undefined reference" error. Frustration is setting in >.> – Jarrod A. Feb 09 '16 at 18:20
  • If you're writting code based on Qt, you'll have to deal with that moc stuff. You need to have any header file with a Q_OBJECT macro be moced (it's a compilation step specific to Qt, see http://doc.qt.io/qt-5/moc.html). QtCreator does it for you and there could be a plugin for your IDE if you're not using QtCreator (there's a Visual Studio plugin and also some CMake macros if using CMake). If you can't make Q_OBJECT work, you can't use signa/slots, then you can't use Qt... – jpo38 Feb 09 '16 at 19:49
  • Please read this: http://stackoverflow.com/questions/2555816/qt-linker-error-undefined-reference-to-vtable. – jpo38 Feb 09 '16 at 20:19
0

In Health::decrease emit a signal or put the logic for it in where the "player is shot" area. But to do that, you to Health or the class with the logic to be a QObject so that it gets the signals and slots in the header. EDIT QGraphicsTextItem is already a QObject. See comments.

Connect the signal to the view's close() right after the Health class or Player class is instantiated.

http://doc.qt.io/qt-5/signalsandslots.html

Hope that helps.

phyatt
  • 18,472
  • 5
  • 61
  • 80
  • `Health` is already a `QObject` (derives from `QGraphicsTextItem`) – jpo38 Feb 08 '16 at 19:07
  • You are right; thanks for keeping me honest. I hadn't looked at the docs for it lately. http://doc.qt.io/qt-5/qgraphicsobject.html However, most stock QGraphicsItems are not `QGraphicsObjects`. Only `QGraphicsSvgItem, QGraphicsTextItem, QGraphicsVideoItem,' and 'QGraphicsWidget` are QObjects. – phyatt Feb 08 '16 at 19:27
  • Also this seems a great place to use a `Q_PROPERTY`. You can get all your setters and getters and signals and slots and everything very quickly if you right click and use `Refactor...` in Qt Creator. – phyatt Feb 08 '16 at 19:28