QT: cannot set position of an object in mainwindow even after static_cast

321 views Asked by At

I am making a little game, and I want my enemy in the game to move and spawn bullet automatically at the same time.

So far, my enemy can move and spawn bullet automatically, but the problem is that

my bullet cannot follow the enemy's movement and spawn at the different start point of the enemy.

To be more specific,

I want to catch the very position of the enemy every time the enemy spawn bullet. However, I cannot setPos my bullet dynamically.

Now, I design enemy-moving function(enemyMove()) and spawning-bullet function(MakeBall()) separately. And I set a different timer for each function in my mainwindow constructor in order to evoke the function automatically every time window.

My attempts and thoughts so far:

  • My enemy is an object of the class Enemy, but I have static casted this object to QGraphicsPixmapItem in main window constructor, so I thought it should be able to use enemy as QGraphicsPixmapItem. However, as I try to do things like enemy->x() in my MakeBall() function, the compiler ended forcefully.
  • I send the enemy position as argument to MakeBall function so that the bullet can setPos at the very position of the enemy. However, the compiler says it is an invalid initialization of non-const reference of type 'int&' from an rvalue of type 'int'.
  • I send QGraphicsPixmapItem *enemy as argument to MakeBall function and use enemy->x() to catch its position, However, it end up not even spawning any bullet, which means the MakeBall function never called successfully.

Any sharing or advice will be beyond thankful. Thank you very very much.

main window.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"


MainWindow::MainWindow(QWidget *parent):
    QMainWindow(parent),
    ui(new Ui::MainWindow),
    enemyMoveTimer(new QTimer),
    balldrop(new QTimer),
    makeballtimer(new QTimer)
{ 
    ui->setupUi(this);
    //scene
    scene = new QGraphicsScene(0, 0, 1050, 600);
    //view
    view = new QGraphicsView;
    view ->setScene(scene);
    setCentralWidget(view);

    //enemy
    Enemy * enemy = new Enemy();
    enemy->setPixmap(QPixmap(QPixmap(":/img/ghost.gif").scaled(100,100)));
    enemy->setPos(0,0);
    scene->addItem(static_cast<QGraphicsPixmapItem*>(enemy));

    enemyMoveTimer->start(100);
    enemy->connect(enemyMoveTimer, SIGNAL(timeout()), enemy, SLOT(enemyMove()));

    //enemy's bullet
    makeballtimer->start(1000);
    //int & x=enemy->x();
    //connect(makeballtimer, SIGNAL(timeout()), this, SLOT(MakeBall(int & x)));
    connect(makeballtimer, SIGNAL(timeout()), this, SLOT(MakeBall(QGraphicsPixmapItem * enemy)));

}


void MainWindow::MakeBall(QGraphicsPixmapItem * enemy)
{
    EnemyBullet * ball = new EnemyBullet();
    ball->setPixmap(QPixmap(":/img/yellowblankball.png").scaled(50, 30));

    ball->setPos(enemy->x()+50, 100);//the problem is here

    balldrop->start(100);
    ball->connect(balldrop, SIGNAL(timeout()), ball, SLOT(fall()));
    scene->addItem(static_cast<QGraphicsPixmapItem*>(ball));
}

MainWindow::~MainWindow()
{
    delete ui;
}

main window.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsPixmapItem>
#include <QTimer>
#include <QKeyEvent>
#include <QtGui>

#include "enemy.h"
#include "ball.h"
#include "enemybullet.h"

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

public slots:
    //virtual void MakeBall(int &x);
    virtual void MakeBall( QGraphicsPixmapItem * enemy);
    //virtual void MakeBall();

private:
    Ui::MainWindow *ui;
    QGraphicsScene *scene;
    QTimer *enemyMoveTimer;
    QTimer *balldrop;
    QTimer *makeballtimer;
    QGraphicsItem *enemy;

    QGraphicsView * view;

};

#endif // MAINWINDOW_H

enemybullet.cpp

#include "enemybullet.h"
#include <QTimer>
#include <QGraphicsScene>
#include <QList>

EnemyBullet::EnemyBullet()
{

}

void EnemyBullet::fall()
{
    setPos(x(), y() +10 );
}

enemybullet.h

#ifndef ENEMYBULLET_H
#define ENEMYBULLET_H

#include <QObject>
#include <QGraphicsPixmapItem>
#include <QGraphicsScene>

class EnemyBullet: public QObject, public QGraphicsPixmapItem
{
    Q_OBJECT

public:
    EnemyBullet();

public slots:
    void fall();
};


#endif// ENEMYBULLET_H

enemy.cpp (trimmed to the essential. it can actually move in the straight line left to right and right to left repeatedly. So we cannot count the specific time to spawn bullet, because every position will be revisited again and again. )

#include "enemy.h"
#include "enemybullet.h"
#include <QDebug>

Enemy::Enemy()
{

}

void Enemy::enemyMove()
{
      setPos(x() + 10,y());
}

enemy.h

#ifndef ENEMY_H
#define ENEMY_H

#include <QObject>
#include <QGraphicsPixmapItem>
#include <QTimer>


class Enemy:  public QObject,public QGraphicsPixmapItem
{
    Q_OBJECT

public:
    Enemy();

public slots:
    void enemyMove();
};

#endif // ENEMY_H
1

There are 1 answers

6
shrpq On BEST ANSWER

Generally you want to avoid mixing UI code with data structures, it makes it much easier to scale so in your case I would really suggest creating backing data classes that will hold just data and then UI.

Your problem is however that you are mixing signal and slot - you are connecting QTimer::timeout() signal to your slot that takes parameter of type QGraphicsPixmapItem *. While this compiles I think at least in Debug mode you will get message saying something like signal and slots are not compatible. It's much better to use modern connect style like this:

 connect(makeballtimer, &QTimer::timeout, this, &MainWindow::MakeBall);

Since this notation will fail already in compile time.

My suggestion to you will be to do following: create model classes (or structures, it's really up to you) which will hold just the data associated with each element; for example:

struct Enemy {
    QPoint position;
};

struct BulletModel {
    std::shared_ptr<Enemy> enemy;
    QPoint position;
};

Your list of enemies will then be stored as vector of std::shared_ptr to make sure their lifetimes are managed:

std::vector<std::shared_ptr<Enemy>> m_enemies;

Then you can create enemy and bullet:

auto enemy = std::make_shared<EnemyModel>();
auto bullet = std::make_shared<BulletModel>();
bullet->enemy = enemy;

Then you can create widgets that will be using the models to draw the elements into their positions.