Well so I'm creating a Tower Defense game in Qt.
So my Tower is shouting and spawning a bullet class.
In this bullet class there is a polygon around it that allows me to detect if there's an enemy touching the bullet:
QList<QGraphicsItem*> colliding_items=collidingItems();
for(int i=0,n=colliding_items.size();i<n;i++){
if(typeid(*(colliding_items[i]))==typeid(Enemy))
{
//<------------ HERE ?
scene()->removeItem(this);
delete this;
return;
}
}
I created a function in the header file Enemy.h called receiveDamage(int damage):
void Enemy::reciveDamage(int damage){
m_health -= damage;
if(m_health <= 0){
game->scene->removeItem(this);
delete this;
return;
}
}
Content of Ennemy.h:
#ifndef ENEMY_H
#define ENEMY_H
#include <QGraphicsPixmapItem>
#include <QObject>
#include <QList> // list << element
#include <QPointF>
class Enemy: public QObject, public QGraphicsPixmapItem{
Q_OBJECT
public:
Enemy(QGraphicsItem * parent=0);
void rotateToPoint(QPointF p);
void reciveDamage(int damage);
public slots:
void move_forward();
private:
QList<QPointF> points;
QPointF dest;
int point_index;
int m_speed = 2;
int m_health = 100;
};
#endif // ENEMY_H
But I don't know how to call it in my code up there, any ideas ?
Yes, just dynamic_cast or qobject_cast the QGraphicsItem into a Enemy class and call getDamage on it.
If I suppose that the amount of damage is given by a method named sendDamage, the code could look like:
QList<QGraphicsItem*> colliding_items=collidingItems();
for(int i=0, n=colliding_items.size(); i<n; i++){
if(typeid(*(colliding_items[i])) == typeid(Enemy))
{
Enemy* enemy = qobject_cast<Enemy*> (colliding_items[i]);
if (enemy)
{
enemy->getDamage(this->sendDamage());
}
scene()->removeItem(this);
delete this;
return;
}
}
Related
I'm using QCustomPlot (plot_ object) on QQuickPaintedItem (SinePlot class) so I can use it in QML. In mousePressEvent I collect initial point and in mouseMoveEvent I'm making calculations to add new points and updating cursor point:
void SinePlot::mousePressEvent(QMouseEvent* event)
{
prevPoint_ = event->globalPos();
}
void SinePlot::mouseMoveEvent(QMouseEvent* event)
{
QPointF tmp = event->globalPos();
qreal prop = (prevPoint_.x() - tmp.x()) / width();
if(prop > 0)
{
data_->shiftLeft(prop);
} else {
data_->shiftRight(prop);
}
plot_->xAxis->setRange(data_->minX, data_->maxX);
...
prevPoint_ = tmp;
update();
}
I have also trying to use pos() and localPos() but it does not make any difference, here is what I got:
As you can see mouseMoveEvent stops being called after some time(before releasing) and moving cursor does not call it.
Here is minimal reproducible example:
#ifndef SINEPLOT_H
#define SINEPLOT_H
#include "qcustomplot.h"
#include <QtQuick>
#include <QDebug>
class SinePlot : public QQuickPaintedItem
{
Q_OBJECT
public:
explicit SinePlot(QQuickItem* parent=nullptr)
{
setAcceptedMouseButtons(Qt::AllButtons);
plot_ = new QCustomPlot();
plot_->setInteractions(QCP::iRangeDrag);
plot_->addGraph();
}
virtual ~SinePlot()
{
delete plot_;
}
void paint(QPainter* painter)
{
QPicture picture;
QCPPainter qcpPainter;
qcpPainter.begin(&picture);
plot_->toPainter(&qcpPainter, width(), height());
qcpPainter.end();
picture.play(painter);
};
protected:
virtual void mousePressEvent(QMouseEvent* event) {};
virtual void mouseMoveEvent(QMouseEvent* event)
{
qDebug() << "mouse move";
};
private:
QCustomPlot* plot_;
};
#endif
In my case I got "mouse move" ~10 times.
I have a set of QImages which come to a function (after a fixed interval of 4 seconds) and the function's job is to update the QLabel to show the new image.
While doing this, I can see a very obvious delay in the image rendering.
I had also followed the suggestions on the link:
Efficient way of displaying a continuous stream of QImages
But, even with using ImageDisplay in the link above, I can see a delay in image rendering.
Can anyone please suggest the best way to do this?
Below is the code.. The images required for the code to run are located at:
https://www.dropbox.com/sh/jiqdfqoiimjs7ei/AAAXezUeeCFyZXjNNOTmWZVga?dl=0
#include <QDialog>
#include <QtGui>
#include <QtCore>
#include <QApplication>
#include <QWidget>
#include <QImage>
class imageDisplay : public QWidget
{
public:
imageDisplay(QWidget*);
~imageDisplay();
void setImage(QImage* img);
private:
QImage* m_image;
protected:
void paintEvent(QPaintEvent* evt);
};
imageDisplay::imageDisplay(QWidget* parent) : QWidget(parent)
{
m_image = 0;
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
}
imageDisplay::~imageDisplay()
{
}
void imageDisplay::setImage(QImage* img)
{
m_image = img;
repaint();
}
void imageDisplay::paintEvent(QPaintEvent*)
{
if(!m_image) return;
QPainter painter(this);
painter.drawImage(rect(), *m_image, m_image->rect());
}
////////////////////////////////////
//
int main(int arc, char ** argv)
{
QApplication theApp(arc, argv, true);
QDialog* dlg = new QDialog();
imageDisplay* wgt = new imageDisplay(dlg);
wgt->resize(600,400);
dlg->show();
for(int i = 0 ; i <= 19; ++i)
{
sleep(1);
QString fileName = "aaa" + QString::number(i) + ".png";
QImage* img = new QImage(fileName);
wgt->setImage(img);
}
return theApp.exec();
}
I want to draw a line using QGraphicsLineItem. What exactly I want is that on clicking at GraphicsView, after second click Line must be drawn. I am confused with the syntax of QGraphicsLineItem and also how to use it. I am new to Qt. Please help me out to solve this problem.
You can use this code snippet.
*h
#ifndef GRAPHICSSCENE_H
#define GRAPHICSSCENE_H
#include <QGraphicsScene>
#include <QStack>
#include <QPoint>
#include <QMouseEvent>
class GraphicsScene : public QGraphicsScene
{
Q_OBJECT
public:
explicit GraphicsScene(QObject *parent = 0);
signals:
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent);
public slots:
private:
QStack<QPoint> stack;
};
#endif // GRAPHICSSCENE_H
*.cpp
#include "graphicsscene.h"
#include <QDebug>
#include <QGraphicsSceneMouseEvent>
GraphicsScene::GraphicsScene(QObject *parent) :
QGraphicsScene(parent)
{
}
void GraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
qDebug() << "in";
if (mouseEvent->button() == Qt::LeftButton)
{
QPoint pos = mouseEvent->scenePos().toPoint();
if(stack.isEmpty())
stack.append(pos);
else if(stack.count() == 1)
{
stack.append(pos);
addLine(QLine(stack.pop(),stack.pop()),QPen(Qt::green));
}
}
}
Usage:
GraphicsScene *scene = new GraphicsScene(this);
ui->graphicsView->setScene(scene);
ui->graphicsView->show();
Edit: more beautiful solution which works as you need.
void GraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
qDebug() << "in";
if (mouseEvent->button() == Qt::LeftButton)
{
QPoint pos = mouseEvent->scenePos().toPoint();
if(stack.isEmpty())
stack.append(pos);
else
addLine(QLine(pos,stack.pop()),QPen(Qt::green));
}
}
You can derive the graphics view/scene and override the mousePressEvent
Below is example using derived QGraphicsScene and overridden mousePressEvent
Class Definition :
class MyScene : public QGraphicsScene
Data Members :
QList<QPointF> m_clickPositions;
int m_mode;
Code :
void MyScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if(false == sceneRect().contains(event->scenePos()))
{
QGraphicsScene::mousePressEvent(event);
}
else if(Qt::LeftButton == event->button() && m_mode == ConstructMode)
{
m_clickPositions.append(event->scenePos());
if(m_clickPositions.size() == 2)
{
QLineF lineF(m_clickPositions[0], m_clickPositions[1]);
QGraphicsLineItem* item = this->addLine(lineF);
m_clickPositions.clear();
m_mode = ScrollMode;
}
}
}
I had used something similar in my project and extracted the code. Hope this helps.
Please comment is this is not working.
Edit ::
ConstructMode and Scroll mode are used in the above program so that I can distinguish whether I want to Draw/Construct or just scroll the scene. You can remove them and the declaration of m_mode if not required by you.
If you want to use the modes you can define some public constants and add a method setMode(). Please see the code below.
MyScene.h or some Constant file if you have one
#define ConstructMode 100
#define ScrollMode 101
And add the following function
void MyScene::setMode(int mode)
{
m_mode = mode;
}
After this if you want to enter the construction mode you will need to call myScene->setMode(ConstructMode) everytime, as after the item is constructed the mode is reset to ScrollMode.
I'm a bit confused about how to test a QStateMachine.
I have a project well organized with source code in one side and test code on the other side.
header
class Foo
{
signals:
void sigGoToStateOne();
void sigGoToStateTwo();
void sigGoToStateThree();
private:
QStateMachine *stateMachine;
QState *state1;
QState *state2;
void initStateMachine();
}
And in the source file
Foo::initStateMachine()
{
// constructors
state1->addTransition(this,SIGNAL(sigGoToStateTwo()),this->state2);
state2->addTransition(this,SIGNAL(sigGoToStateOne()),this->state1);
}
I would like to know if there is a beautiful way to test if my stateMachine is right. In other words, how my state machine reacts if I emit sigGoToStateThree() if I'm there, etc..
Solutions i see:
1 - Get the address of stateMachine (and eventually all other states) and test it (But i don't know how)
2 - Simulate signals (sigGoToStateX()) from a test file (Again, don't know if it's possible to emit signals of my class Foo in an other class)
My unique demand is I don't want to modify the core of my source file.
Thank's in advance.
In Qt 5, signals are always public methods. To make your code compatible with Qt 4, you can make the signals explicitly public like so:
class Foo {
public:
Q_SIGNAL void sigGoToStateOne();
...
}
Alternatively, you can keep arbitrary signal visibility, and declare a friend test class:
class Foo {
friend class FooTest;
...
}
Finally, you can create a test project where you use the Qt's test framework to test the Foo class's behavior. The code below works in both Qt 4 and Qt 5.
// main.cpp
#include <QCoreApplication>
#include <QStateMachine>
#include <QEventLoop>
#include <QtTest>
#include <QTimer>
class Waiter {
QTimer m_timer;
public:
Waiter() {}
Waiter(QObject * obj, const char * signal) {
m_timer.connect(obj, signal, SIGNAL(timeout()));
}
void stop() {
m_timer.stop();
QMetaObject::invokeMethod(&m_timer, "timeout");
}
void wait(int timeout = 5000) {
QEventLoop loop;
m_timer.start(timeout);
loop.connect(&m_timer, SIGNAL(timeout()), SLOT(quit()));
loop.exec();
}
};
class SignalWaiter : public QObject, public Waiter {
Q_OBJECT
int m_count;
Q_SLOT void triggered() {
++ m_count;
stop();
}
public:
SignalWaiter(QObject * obj, const char * signal) : m_count(0) {
connect(obj, signal, SLOT(triggered()), Qt::QueuedConnection);
}
int count() const { return m_count; }
};
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
typedef QSignalSpy SignalSpy;
#else
class SignalSpy : public QSignalSpy, public Waiter {
public:
SignalSpy(QObject * obj, const char * signal) :
QSignalSpy(obj, signal), Waiter(obj, signal) {}
};
#endif
class Foo : public QObject {
Q_OBJECT
friend class FooTest;
QStateMachine m_stateMachine;
QState m_state1;
QState m_state2;
Q_SIGNAL void sigGoToStateOne();
Q_SIGNAL void sigGoToStateTwo();
public:
explicit Foo(QObject * parent = 0) :
QObject(parent),
m_state1(&m_stateMachine),
m_state2(&m_stateMachine)
{
m_stateMachine.setInitialState(&m_state1);
m_state1.addTransition(this, SIGNAL(sigGoToStateTwo()), &m_state2);
m_state2.addTransition(this, SIGNAL(sigGoToStateOne()), &m_state1);
}
Q_SLOT void start() {
m_stateMachine.start();
}
};
class FooTest : public QObject {
Q_OBJECT
void call(QObject * obj, const char * method) {
QMetaObject::invokeMethod(obj, method, Qt::QueuedConnection);
}
Q_SLOT void test1() {
// Uses QSignalSpy
Foo foo;
SignalSpy state1(&foo.m_state1, SIGNAL(entered()));
SignalSpy state2(&foo.m_state2, SIGNAL(entered()));
call(&foo, "start");
state1.wait();
QCOMPARE(state1.count(), 1);
call(&foo, "sigGoToStateTwo");
state2.wait();
QCOMPARE(state2.count(), 1);
call(&foo, "sigGoToStateOne");
state1.wait();
QCOMPARE(state1.count(), 2);
}
Q_SLOT void test2() {
// Uses SignalWaiter
Foo foo;
SignalWaiter state1(&foo.m_state1, SIGNAL(entered()));
SignalWaiter state2(&foo.m_state2, SIGNAL(entered()));
foo.start();
state1.wait();
QCOMPARE(state1.count(), 1);
emit foo.sigGoToStateTwo();
state2.wait();
QCOMPARE(state2.count(), 1);
emit foo.sigGoToStateOne();
state1.wait();
QCOMPARE(state1.count(), 2);
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
FooTest test;
QTest::qExec(&test, a.arguments());
QMetaObject::invokeMethod(&a, "quit", Qt::QueuedConnection);
return a.exec();
}
#include "main.moc"
I am forcing all signal invocations to be done from the event loop, so that the event transitions will only happen while the event loop is running. This makes the test code uniformly wait after each transition. Otherwise, the second wait would time out:
Q_SLOT void test1() {
SignalSpy state1(&m_foo.m_state1, SIGNAL(entered()));
SignalSpy state2(&m_foo.m_state2, SIGNAL(entered()));
m_foo.start();
state1.wait();
QCOMPARE(state1.count(), 1);
emit m_foo.sigGoToStateTwo(); // The state2.entered() signal is emitted here.
state2.wait(); // But we wait for it here, and this wait will time out.
QCOMPARE(state2.count(), 1); // But of course the count will match.
emit m_foo.sigGoToStateOne();
state1.wait(); // This would timeout as well.
QCOMPARE(state1.count(), 2);
}
This can be worked around without the use of explicit queued calls by the use of a signal spy class that internally uses a queued connection.
Kuba Ober gives a very good analysis of how to use the test framework & SignalSpy to do in depth testing of your state machine.
If all you're trying to do is generate a sigGoToStateX() from a test file then don't forget that you can chain signals together.
So for example given a class "Tester":
class Tester : public QObject {
Q_OBJECT
public:
Tester(Foo *fooClass) {
//Connecting signals gives you the kind of behaviour you were asking about
connect(this, SIGNAL(testTransitionToState1()), fooClass, SIGNAL(sigGoToState1()));
connect(this, SIGNAL(testTransitionToState2()), fooClass, SIGNAL(sigGoToState2()));
connect(this, SIGNAL(testTransitionToState3()), fooClass, SIGNAL(sigGoToState3()));
}
void SwitchState(int newState) {
//Now any time we emit the test signals, the foo class's signals will be emitted too!
if (newState == 1) emit testTransitionToState1();
else if (newState == 2) emit testTransitionToState1();
else if (newState == 3) emit testTransitionToState1();
}
signals:
void testTransitionToState1();
void testTransitionToState2();
void testTransitionToState3();
}
So for example calling SwitchState(1) will invoke the correct signals for switching to state 1. If this simple case is all you need for testing then that's all you really need.
If you need something more complex, go with the full SignalSpy example.
Horde3d come with 2 samples, compiled with GLFW. One of them, Knight, shows a particle emitter. I've ported the samples to Qt, with a thin layer I wrote that leave unchanged the applicative code (i.e. scene setup, rendering and events handling).
Indeed, the functionality is ok, except the particle emitter doesn't show. I can't see anything specific in GLFW initialization, and I tried some setting I found in Qt, without success. Horde3d takes care of OpenGL interface, and expose a higher level, clean C interface. Any clue?
EDIT : Most Rilevant Sources of qtKnight.pro
here (cleaned :) main.cpp
#include "glwidget.h"
#include <QApplication>
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
GLWidget glw;
glw.show();
return app.exec();
}
here glWidget.h
#ifndef GL_WIDGET_H
#define GL_WIDGET_H
#include <QtOpenGL>
#include <QTimer>
#include <QKeyEvent>
#include <Horde3D.h>
#include <Horde3DUtils.h>
#include <sstream>
#include <app.h>
class GLWidget : public QGLWidget, Application {
Q_OBJECT
public:
GLWidget();
~GLWidget();
QSize minimumSizeHint() const { return sizeHint(); }
QSize sizeHint() const { return QSize(640, 480); }
protected:
void initializeGL();
void paintGL();
void resizeGL(int width, int height);
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void keyPressEvent(QKeyEvent *e) { keyEvent(e, true); QGLWidget::keyPressEvent(e); }
void keyReleaseEvent(QKeyEvent *e) { keyEvent(e, false); QGLWidget::keyReleaseEvent(e); }
void keyEvent(QKeyEvent *, bool);
public slots:
void appLoop() { updateGL(); }
private:
QPoint lastPos;
QTimer evloop;
};
#endif
and here glWidget.cpp
#include "glwidget.h"
#include <stdexcept>
#include <QtDebug>
#include <QTextStream>
#include <QGLFormat>
GLWidget::GLWidget() :
QGLWidget(QGLFormat(QGL::AlphaChannel | QGL::SampleBuffers)),
Application("/home/carlo/horde3d/SDK_1.0.0_Beta5/Horde3D/Binaries/Content")
{
connect(&evloop, SIGNAL(timeout()), this, SLOT(appLoop()));
evloop.start(0);
}
GLWidget::~GLWidget()
{
h3dutDumpMessages();
h3dRelease();
}
void GLWidget::initializeGL()
{
if (!init())
throw std::runtime_error("Could not initialize renderer");
}
void GLWidget::paintGL()
{
keyStateHandler();
mainLoop(30);
}
void GLWidget::resizeGL(int width, int height)
{
Application::resize(width, height);
}
void GLWidget::mousePressEvent(QMouseEvent *event)
{
lastPos = event->pos();
}
void GLWidget::mouseMoveEvent(QMouseEvent *event)
{
QPoint cPos = event->pos();
float dX = cPos.x() - lastPos.x(); //event->x() - lastPos.x();
float dY = cPos.y() - lastPos.y(); //event->y() - lastPos.y();
Application::mouseMoveEvent(dX, dY);
lastPos = cPos;
}
void GLWidget::mouseReleaseEvent(QMouseEvent * /* event */)
{
}
void GLWidget::keyEvent(QKeyEvent *k, bool on_off)
{
#define setK(X,Y) case Qt::X: setKeyState(Y, on_off); break;
#define setF(X) case Qt::Key_##X: setKeyState(X, on_off); break;
#define R(X, Y) (v >= #X[0] && v <= #Y[0])
int v = k->key();
switch (v) {
case Qt::Key_F1: if (on_off) showFullScreen(); break;
case Qt::Key_F2: if (on_off) showNormal(); break;
setF(F3)
setF(F6)
setF(F7)
setF(F8)
setK(Key_Space, SP)
default:
if (R(A, Z) || R(0, 9))
setKeyState(v, on_off);
}
}
Since you say it is only the particles that aren't shown, it could have to do with alpha blending. I also saw from your code that you don't specify a format when constructing the QGLWidget, in which case the default format is used which explicitly disables the alpha channel.
Though I don't know if this has any effect (shouldn't there always be an A in RGBA?), but maybe this really requests a pixel format where there is no storage for the A channel, in which case things like alpha blending (used for the transparent particles) won't work.
So just try to request it explicitly by using an appropriate format in the QGLWidget constructor:
GLWidget::GLWidget()
: QGLWidget(QGLFormat(QGL::AlphaChannel))
You actually were on the right track with your outcommented QGLFormat argument, but it hasn't anything to do with the HasOverlay option, which you don't really need, as Horde3d does it's own overlay rendering.