maxif1997
maxif1997

Reputation: 25

C++ SFML How to adjust game view via mouse movements

I have a SFML window with sf::view and I need to move the view around my game map via mouse movement.
I want it to look as if the player is grabbing and moving the object.
For example: myCube.setPosition(mousePos); But I do not want to move the objects in my game world, instead I want it to move the view.

My attempt:

view.setCenter(sf::Mouse::getPosition(window).x, sf::Mouse::getPosition(window).y);

Upvotes: 0

Views: 955

Answers (2)

maxif1997
maxif1997

Reputation: 25

awesome, thanks,. perfect. maybe a bit too complicated to me, especially with auto function and usage of renderTarget but I will get into it. I add reaction only to RMB. My version:

class ViewDragger
{
private:
    sf::RenderTarget& RTwindow;
    sf::Vector2i previousMousePosition;

    bool dragging;
public:
    //get window at construct and initialize variables with initializer list
    ViewDragger(sf::RenderTarget& RTwindow)
        : RTwindow{RTwindow}, dragging{}
    {
    }
    //use after window.pollEvent(event)) to get event handler
    void handleEvent(const sf::Event event)
    {
        switch (event.type)
        {
        case sf::Event::MouseButtonPressed:
            if (event.mouseButton.button == sf::Mouse::Right)
            {
                //start dragging
                dragging = true;
            }
            break;
        case sf::Event::MouseButtonReleased:
            if (event.mouseButton.button == sf::Mouse::Right)
            {
                //stop dragging
                dragging = false;
            }
            break;
        case sf::Event::MouseMoved:
            //get new mouse position
            const sf::Vector2i mousePosition(event.mouseMove.x, event.mouseMove.y);
            if (dragging)
            {
                //if mouse is dragging, count difference between new mouse position and old mouse position 
                //example: mouse move down by x100: new(x400,.y300) - (x300,y300) = x100 and for opposite direction make it -x100
                const sf::Vector2f delta = RTwindow.mapPixelToCoords(mousePosition) - RTwindow.mapPixelToCoords(previousMousePosition);
                sf::View view = RTwindow.getView();
                view.move(-delta);
                //update view
                RTwindow.setView(view);

            }
            //save current mouse position as old mouse position for next run
            previousMousePosition = mousePosition;
            break;
        }
    
    }
};

`

Upvotes: 0

Lily
Lily

Reputation: 1397

Incase you have not read about views a good page explaining them is here: https://www.sfml-dev.org/tutorials/2.5/graphics-view.php

The way I would do it would be using the three mouse events involved with dragging (sf::Event::MouseButtonPressed, sf::Event::MouseButtonReleased and sf::Event::MouseMoved), and whilst dragging, update the view based on how far the mouse has travelled.
Events are explained here: https://www.sfml-dev.org/tutorials/2.5/window-events.php

I think you need to store whether you are dragging or not (so, a boolean), and the previous mouse position (sf::Vector2i). I will use a class that stores these and has a handleEvent() function for my example. I will also store a render target, alternatively you could store a view or pass in a render target/view into the handleEvent() function.

class ViewDragger {
public:
    /// set render target with view and initialize dragging to false
    ViewDragger(sf::RenderTarget& target) :
        target{target},
        dragging{}
    {}
    /// handle dragging related events
    void handleEvent(const sf::Event event) {
        // todo...
    }
private:
    /// the render target with the view we want to change
    sf::RenderTarget& target;
    /// the last known mouse position
    sf::Vector2i previous_mouse_position;
    /// whether we are dragging or not
    bool dragging;
};

Let us instantiate the class and call the handleEvent function. I will instantiate the class after I have created the window, and call the handleEvent function at the bottom of the event loop.

ViewDragger view_dragger{ window };
...
while (window.pollEvent(event)) {
    ...
    view_dragger.handleEvent(event);
}

Finally we will write the function that moves the view. Let's figure out if we are dragging or not first.

void handleEvent(const sf::Event event) {
    switch (event.type) {
    // if mouse button is pressed start dragging
    case sf::Event::MouseButtonPressed:
        dragging = true;
        break;
    // if mouse button is released stop draggin
    case sf::Event::MouseButtonReleased:
        dragging = false;
        break;
    }
}

Now that we know if we are dragging or not, let us create a third event underneath the two we just made that drags the view.

First we will write the code that will update the previous mouse position.

// if dragging mouse
case sf::Event::MouseMoved:
    // get mouse position
    const sf::Vector2i mouse_position{
        event.mouseMove.x, event.mouseMove.y
    };
    // if dragging, move view
    if (dragging) {
        // todo...
    }
    // update previous mouse position
    previous_mouse_position = mouse_position;
    break;

Now we are going to calculate the how far the mouse has moved in the view.

This is different to how far the mouse has moved in the window.
The mouse might move from (0, 0) to (10, 0) in the window, but that does not mean it moved 10 units in the view. If the view is already moved, then (0, 0) could be anything, and if the view is scaled, then 10 pixels across horizontally is not 10 units towards the right, and if the view is rotated it is super hard to figure out.

Luckily a function already exists for us to change from window space to view space: sf::RenderTarget::mapPixelToCoords().

We will use that with the current mouse position and the previous mouse position.

// calculate how far mouse has moved in view
const auto delta = 
    target.mapPixelToCoords(mouse_position) -
    target.mapPixelToCoords(previous_mouse_position);

Finally we need to apply it negatively to the view.

So if we moved the mouse ten units to the right, we want the view to move 10 units to the left.

// apply negatively to view
auto view = target.getView();
view.move(-delta);
target.setView(view);

That should be it!

Full code:

#include <SFML/Graphics.hpp>

class ViewDragger {
public:
    /// set render target with view and initialize dragging to false
    ViewDragger(sf::RenderTarget& target) :
        target{ target },
        dragging{}
    {}
    /// handle dragging related events
    void handleEvent(const sf::Event event) {
        switch (event.type) {
        // if mouse button is pressed start dragging
        case sf::Event::MouseButtonPressed:
            dragging = true;
            break;
        // if mouse button is released stop draggin
        case sf::Event::MouseButtonReleased:
            dragging = false;
            break;
        // if dragging mouse
        case sf::Event::MouseMoved:
            // get mouse position
            const sf::Vector2i mouse_position{
                event.mouseMove.x, event.mouseMove.y
            };
            // if dragging, move view
            if (dragging) {
                // calculate how far mouse has moved in view
                const auto delta = 
                    target.mapPixelToCoords(mouse_position) -
                    target.mapPixelToCoords(previous_mouse_position);
                // apply negatively to view
                auto view = target.getView();
                view.move(-delta);
                target.setView(view);
            }
            // update previous mouse position
            previous_mouse_position = mouse_position;
            break;
        }
    }
private:
    /// the render target with the view we want to change
    sf::RenderTarget& target;
    /// the last known mouse position
    sf::Vector2i previous_mouse_position;
    /// whether we are dragging or not
    bool dragging;
};

int main() {
    sf::RenderWindow window{ sf::VideoMode(200, 200), "View Dragging!" };
    ViewDragger view_dragger{ window };

    sf::CircleShape shape{ 100.f };
    shape.setFillColor(sf::Color::Green);

    while (window.isOpen()) {
        sf::Event event;
        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed) {
                window.close();
            }
            view_dragger.handleEvent(event);
        }

        window.clear();
        window.draw(shape);
        window.display();
    }

    return EXIT_SUCCESS;
}

Upvotes: 1

Related Questions