0

I'm creating a simple game with qt 5.0.1. It's something like Warblade. I have problem with creating waves of enemies.

int k;
int pos = 100;
for (k = 0; k < 5; k++)
{
    pos = 100;
    for (int i = 0; i < 9; i++)
    {
        player->spawn_in_pos(pos);
        pos += 100;
    }
    //QThread::sleep(2);
}

When i use sleep() function, my game just can't run. It's waiting for loop finish and then it shows.

I'm also dealing with second option:

QTimer * timer = new QTimer();
QObject::connect( timer, SIGNAL(timeout()), player, SLOT(spawn_in_pos(pos)) );
timer->start(450);

But it looks like SLOT can't get the position.

Edit: I just did what @ddriver said, and that helped me a lot. Now I'm getting some 'laggy' style enemies movement.

enter image description here

Edit2:

I'm moving my enemies down like this:

setPos(x(),y()+1);

with that timer:

// connect
QTimer * timer = new QTimer(this);
connect(timer,SIGNAL(timeout()),this,SLOT(move()));
// start the timer
timer->start(10);

It looks like very smooth movement but probably +1 pixel down and a 10 timer is to less:((

sovas
  • 1,508
  • 11
  • 23
  • 3
    You can't pass values to slots inside connect method. I propose you to read about signals and slots: http://doc.qt.io/qt-5/signalsandslots.html – Dmitry Sazonov Oct 29 '15 at 13:20
  • 1
    Are you hoping to make an entire game by asking a question about each and every step? It looks like you are getting ahead of yourself. I recommend you pick up a book on game programming and study the subject before you rush into making games. Your approach is very naive, and fundamentally wrong. One size does not fit all, just because the timer was an OK solution for spawning enemy waves doesn't mean you should use a dedicated timer for each and every game object. This is not how games work. Also, stick to one question at a time, this question was about spawning enemies, not enemy movement. – dtech Oct 29 '15 at 21:27

3 Answers3

1

I'm not sure what you are trying to achieve, but in your second option, you cannot get the position, because the timeout doesn't send it. The signal is timeout(void) and your slot expects an parameter. I guess you lack some basic understanding of the signal/slot mechanism. The QT Documentation is pretty neat: http://doc.qt.io/qt-5/signalsandslots.html

And if you just want to create a game out of nothing, here you can find a little tutorial, how to write games in QT: https://www.youtube.com/watch?v=8ntEQpg7gck

0rko
  • 168
  • 6
0

Calling sleep is going to stop the thread from processing anything, which is not what you want to do.

Using C++ 11, you can use the QTimer with a lambda function like this: -

int pos = 100;
int nextWaveTime = 2000; // 2 seconds per wave
for (k = 0; k < 5; k++) // 5 waves of enemies
{       
    for (int i = 0; i < 9; i++) // 9 enemies per wave
    {
        QTimer * timer = new QTimer(); 
        timer->setSingleShot(true);

        pos = pos + (100*i); // set the pos, which is captured by value, in the lambda function

        QObject::connect( timer, QTimer::timeout, [=](){
           player->spawn_in_pos(pos); 
           timer->deleteLater(); // must cleanup the timer
        });

        timer->start(450 + (k*nextWaveTime));
     } 
 }
Community
  • 1
  • 1
TheDarkKnight
  • 27,181
  • 6
  • 55
  • 85
0

In order to pass parameters with signals and slots in Qt, the signal parameters must match the parameters of the slot (or function since Qt 5).

One way to solve the issue is to use a lambda as in TheDarkKnight's answer.

What I would suggest is to use encapsulation - you could create a Spawner object, dedicated to spawning enemies and keep the position internal to it. This way the spawner will manage the position, and you can have something like Spawner::createWave() slot with no parameters, since the position is internal. Then setup the timer and connect it to createWave() and you are set.

Also it is a very bad idea to hardcode stuff like that, you really need more flexibility, the option to change enemy and wave count, the wave time as well as the screen width, so that your game can change those things as it gets harder.

class Spawner : public QObject {
    Q_OBJECT
public:
    Spawner(int wCount = 5, int eCount = 9, int time = 2000, int sWidth = 1000)
        : waveCount(wCount), enemyCount(eCount), currentWave(0), screenWidth(sWidth) {
        timer.setInterval(time);
        connect(&timer, SIGNAL(timeout()), this, SLOT(createWave()));
    }
    void set(int wCount, int eCount, int time) {
        timer.setInterval(time);
        waveCount = wCount;
        enemyCount = eCount;
    }
    void changeWidth(int w) { screenWidth = w; }
public slots:
    void start() { timer.start(); }
    void stop() {
        timer.stop();
        currentWave = 0;
    }    
private slots:
    void createWave() {
        int pos = screenWidth / (enemyCount + 1);
        int step = pos;
        for (int i = 0; i < enemyCount; ++i) {
            Game::spawnEnemyAt(pos);
            pos += step;
        }
        if (++currentWave >= waveCount) stop();
    }
private:
    QTimer timer;
    int waveCount, enemyCount, currentWave, screenWidth;
};

Create a Spawner object and connect the game new level to start() - it will span the given number waves of enemies evenly across the game screen, when you finish the waves off, you adjust the spawner settings and start a new level.

That encapsulation will come in handy later on as your game becomes less of a test and more like a real game - with increasing difficulty, changing spawning and attack patterns and so on. So it is a good idea to implement it right from the start and build upon a good and flexible design rather than going back and changing stuff around, which may break other code. You really don't want to start without a good design and make design changes later. Thus the need to encapsulate functionality and responsibility and just connect the pieces rather than building on a pile of spaghetti code. In this line of thought, I noticed you are using player->spawn_in_pos(pos); - which is an example of bad design, as spawning should be a responsibility of the Game class, not the Player class. A good design is not only flexible, but also clean. The Spawner object is only responsible for spawning waves of enemies, and its visible interface is limited to start(), stop() and set().

Edit:

class Game : public QObject {
    Q_OBJECT
public:
    Game() {
        if (!scene) scene = new QGraphicsScene(this);
        connect(this, SIGNAL(newLevel()), &spawner, SLOT(start()));
    }
    static void spawnEnemyAt(int x = 0) {
        scene->addItem(new Enemy(x, 0));
        qDebug() << "enemy created";
    }
public slots:
    void newGame() {
        // initialize game
        emit newLevel(); // begin spawning
    }
    void onLevelEnd() {
        // spawner.set(new level settings); 
        emit newLevel();
    }
    void onGameEnded() {
        // ...
    }
signals:
    void newLevel();
private:
    Spawner spawner;
    static QGraphicsScene * scene;
};

// in game.cpp
QGraphicsScene * Game::scene = nullptr;

If you don't want to use static members, you can make spawnEnemyAt() and scene instance members, but then you will have to pass the Game instance to the Spawner in the constructor so that you have a reference to the game the spawner operates on and use game->spawnEnemyAt() instead. This way you can create multiple games with their own dedicated scenes. Or parent the spawner to the game and cast the spawner's parent() to a Game * to access the game instance which is a little hacky, but saves on the extra member by reusing the parent.

dtech
  • 47,916
  • 17
  • 112
  • 190