Question

In my game, I'd like to fire rockets from a rocket launcher. The player holds a rocket launcher as a child item. The rockets must be parentless. I'm trying to position the rocket so that its back lines up with the back of the rocket launcher (the player is facing north in the screenshots) and centered horizontally within it:

correct

Instead, what I'm getting is:

incorrect

The rotation is also incorrect (run the example and move the mouse cursor around to see what I mean). Where am I going wrong in my code?

#include <QtWidgets>

QPointF moveBy(const QPointF &pos, qreal rotation, float distance)
{
    return pos - QTransform().rotate(rotation).map(QPointF(0, distance));
}

float directionTo(const QPointF &source, const QPointF &target) {
    QPointF toTarget(target.x() - source.x(), target.y() - source.y());
    float facingTarget = qRadiansToDegrees(atan2(toTarget.y(), toTarget.x())) + 90.0f;
    facingTarget = fmod(facingTarget, 360.0f);
    if(facingTarget < 0)
        facingTarget += 360.0f;
    return facingTarget;
}

class Controller : public QObject
{
public:
    Controller(QGraphicsScene *scene) :
        mScene(scene)
    {
        mPlayer = scene->addRect(0, 0, 25, 25, QPen(Qt::blue));
        mPlayer->setTransformOriginPoint(mPlayer->boundingRect().width() / 2, mPlayer->boundingRect().height() / 2);

        mRocketLauncher = scene->addRect(0, 0, 16, 40, QPen(Qt::green));
        mRocketLauncher->setParentItem(mPlayer);
        mRocketLauncher->setPos(mPlayer->boundingRect().width() * 0.9 - mRocketLauncher->boundingRect().width() / 2,
            -mRocketLauncher->boundingRect().height() * 0.3);

        mRocket = scene->addRect(0, 0, 16, 20, QPen(Qt::red));
        scene->installEventFilter(this);

        QGraphicsTextItem *playerText = scene->addText("Player");
        playerText->setPos(0, 100);
        playerText->setDefaultTextColor(Qt::blue);
        QGraphicsTextItem *rocketLauncherText = scene->addText("Rocket launcher");
        rocketLauncherText->setPos(0, 120);
        rocketLauncherText->setDefaultTextColor(Qt::green);
        QGraphicsTextItem *rocketText = scene->addText("Rocket");
        rocketText->setPos(0, 140);
        rocketText->setDefaultTextColor(Qt::red);
    }

    bool eventFilter(QObject *, QEvent *event) {
        if (event->type() == QEvent::GraphicsSceneMouseMove) {
            const QGraphicsSceneMouseEvent *mouseEvent = static_cast<QGraphicsSceneMouseEvent*>(event);
            mPlayer->setRotation(directionTo(mPlayer->sceneBoundingRect().center(), mouseEvent->scenePos()));

            qreal rocketX = mRocketLauncher->sceneBoundingRect().center().x() - mRocket->boundingRect().width() / 2;
            QPointF rocketPos(rocketX, 0);
            rocketPos = moveBy(rocketPos, mPlayer->rotation(), mRocketLauncher->boundingRect().height() - mRocket->boundingRect().height());
            mRocket->setPos(rocketPos);
            mRocket->setRotation(mPlayer->rotation());

            return true;
        }
        return false;
    }
private:
    QGraphicsScene *mScene;
    QGraphicsRectItem *mPlayer;
    QGraphicsRectItem *mRocketLauncher;
    QGraphicsRectItem *mRocket;
};

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

    QGraphicsView view;
    view.setMouseTracking(true);
    QGraphicsScene *scene = new QGraphicsScene;
    view.setScene(scene);

    Controller controller(scene);

    view.resize(300, 300);
    view.show();

    return app.exec();
}
Was it helpful?

Solution

The idea is to:

  1. set rotation of both items;
  2. get positions of bottom left corner of launcher and rocket in global (scene) coordinates;
  3. shift rocket to make positinos equal.

Code:

mPlayer->setRotation(directionTo(mPlayer->sceneBoundingRect().center(), 
                                 mouseEvent->scenePos()));
mRocket->setRotation(mPlayer->rotation());
QPointF launcherPos = mRocketLauncher->mapToScene(
  mRocketLauncher->boundingRect().bottomLeft());
QPointF currentRocketPos = mRocket->mapToScene(
  mRocket->boundingRect().bottomLeft());
mRocket->setPos(mRocket->pos() - currentRocketPos + launcherPos);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top