Manipulating an image both in scale and position for an SFML GUI game
up vote
0
down vote
favorite
I am building a GUI game with SFML that will have two screens (possibly a third) that display a map to the player and the player may need or want to zoom in/out or drag the map so they can see areas that were previously off screen. Because I will have multiple map classes that need the same functionality I wrote a base class for the maps and inherit from it.
I keep my global constants in a single separate header for the entire project. These are almost exclusively used for removing magic numbers. It is also useful with GUI positioning when some elements are positioned relative to other elements. This way if the parent moves so does the child. Here are the ones that are relevant to this question:
constexpr unsigned screen_width = 1200U;
constexpr unsigned screen_height = 800U;
constexpr float screen_width_float = static_cast<float>(screen_width);
constexpr float screen_height_float = static_cast<float>(screen_height);
constexpr float side_panel_width = 15.F;
constexpr float drop_button_height = 80.F;
constexpr float game_button_height = 100.F;
constexpr float map_width = screen_width_float - (side_panel_width * 2.F);
constexpr float map_height = screen_height_float - (drop_button_height + game_button_height);
constexpr float map_x = side_panel_width;
constexpr float map_y = drop_button_height;
constexpr float map_button_side = 30.F;
constexpr float map_button_y = 650.F;
constexpr float zoom_in_x = 60.F;
constexpr float center_x = 100.F;
constexpr float zoom_out_x = 140.F;
constexpr unsigned minimum_scale = 1;
constexpr unsigned maximum_scale = 10;
A little about the base class. It will hold the map sprite as well as the functionality for scaling and dragging the image. I want to quickly note that I used a static analysis tool that told me to lose the protected
access specifier in lieu of private
but the amount of abstraction that would require seems unnecessary to me. Should I declare my base class member variables and functions private?
The grab()
and releaseGrip()
functions simply abstract the mouse press/release to the appropriate layer to handle them. It is worth noting that the mouse not only needs to be within the bounds of the image to grab but that it also will interact with any other UI elements first and forego "grabbing". I may also in the future require there to be situations where the map is non-responsive.
The zoom buttons simply increment/decrement the unsigned mapScale
variable. Said variable is constrained to values of 1 through 10.
Lastly the main purpose of the class is in the resizeMap()
and moveMap()
functions. constrainMapCoordinates()
is used internally by both the other two functions and is simply for deduplication.
Map.h
#ifndef BRUGLESCO_FLEETCOMMAND_MAP_H
#define BRUGLESCO_FLEETCOMMAND_MAP_H
#include "GameEvent.h"
#include "Expressions.h"
#include <Graphics.hpp>
namespace bruglesco
{
class Map
{
public:
Map();
virtual ~Map() = default;
void releaseGrip() { grabbed = false; }
virtual GameEvent input(const sf::Vector2f& mousePos) = 0;
virtual void update(const sf::Vector2f& mousePos) = 0;
virtual void draw(sf::RenderWindow& window) = 0;
protected:
sf::Texture worldMap;
sf::RectangleShape map{ sf::Vector2f(map_width, map_height) };
sf::RectangleShape zoomIn{ sf::Vector2f(map_button_side, map_button_side) };
sf::RectangleShape center{ sf::Vector2f(map_button_side, map_button_side) };
sf::RectangleShape zoomOut{ sf::Vector2f(map_button_side, map_button_side) };
unsigned mapScale{ minimum_scale };
sf::Vector2f mouseWasAt{ 0.f, 0.f };
bool grabbed{ false };
void grab(const sf::Vector2f& mousePos);
void resizeMap();
void moveMap(const sf::Vector2f& newPos);
void constrainMapCoordinates(float& x, float& y);
void mouseOverButtons(const sf::Vector2f& mousePos);
};
}
#endif // !BRUGLESCO_FLEETCOMMAND_MAP_H
Map.cpp
#include "Map.h"
namespace bruglesco
{
Map::Map()
{
worldMap.loadFromFile("Assets/WorldMap.png");
map.setTexture(&worldMap);
map.setPosition(map_x, map_y);
zoomIn.setPosition(zoom_in_x, map_button_y);
center.setPosition(center_x, map_button_y);
zoomOut.setPosition(zoom_out_x, map_button_y);
}
void Map::grab(const sf::Vector2f& mousePos)
{
grabbed = true;
mouseWasAt = mousePos;
}
void Map::resizeMap()
{
float scale = 0.8F + (static_cast<float>(mapScale) * 0.2F);
float map_x_offset = ((map.getSize().x * map.getScale().x) - (map_width * scale)) / 2.F;
float map_y_offset = ((map.getSize().y * map.getScale().y) - (map_height * scale)) / 2.F;
float x = map.getPosition().x + map_x_offset;
float y = map.getPosition().y + map_y_offset;
map.setScale(scale, scale);
constrainMapCoordinates(x, y);
map.setPosition(x, y);
}
void Map::moveMap(const sf::Vector2f& newPos)
{
if (grabbed)
{
sf::Vector2f moved = newPos - mouseWasAt;
float x = map.getPosition().x + moved.x;
float y = map.getPosition().y + moved.y;
constrainMapCoordinates(x, y);
map.setPosition(x, y);
mouseWasAt = newPos;
}
}
void Map::constrainMapCoordinates(float& x, float& y)
{
if (x > map_x)
{
x = map_x;
}
else if (x < -(map_width * map.getScale().x - map_width) + map_x)
{
x = -(map_width * map.getScale().x - map_width) + map_x;
}
if (y > map_y)
{
y = map_y;
}
else if (y < -(map_height * map.getScale().y - map_height) + map_y)
{
y = -(map_height * map.getScale().y - map_height) + map_y;
}
}
void Map::mouseOverButtons(const sf::Vector2f& mousePos)
{
if (zoomIn.getGlobalBounds().contains(mousePos))
{
if (mapScale < maximum_scale)
{
zoomIn.setFillColor(sf::Color::Green);
}
else
{
zoomIn.setFillColor(sf::Color::Red);
}
}
else
{
zoomIn.setFillColor(sf::Color::White);
}
if (center.getGlobalBounds().contains(mousePos))
{
center.setFillColor(sf::Color::Green);
}
else
{
center.setFillColor(sf::Color::White);
}
if (zoomOut.getGlobalBounds().contains(mousePos))
{
if (mapScale > minimum_scale)
{
zoomOut.setFillColor(sf::Color::Green);
}
else
{
zoomOut.setFillColor(sf::Color::Red);
}
}
else
{
zoomOut.setFillColor(sf::Color::White);
}
}
}
And one of the derived classes that use the base class. Here it mostly just overrides input()
, update()
and draw()
. Note that input()
is only called on frames when the mouse is pressed. update()
and draw()
are called every frame.
WorldMap.h
#ifndef BRUGLESCO_FLEETCOMMAND_WORLDMAP_H
#define BRUGLESCO_FLEETCOMMAND_WORLDMAP_H
#include "Map.h"
#include <vector>
namespace bruglesco
{
class WorldMap : public Map
{
public:
WorldMap();
GameEvent input(const sf::Vector2f& mousePos) override;
void update(const sf::Vector2f& mousePos) override;
void draw(sf::RenderWindow& window) override;
private:
std::vector<sf::RectangleShape> cities{};
};
}
#endif // !BRUGLESCO_FLEETCOMMAND_WORLDMAP_H
WorldMap.cpp
#include "WorldMap.h"
namespace bruglesco
{
WorldMap::WorldMap()
{
// set cities
}
GameEvent WorldMap::input(const sf::Vector2f& mousePos)
{
cityCounter = 0;
for (auto&& city : cities)
{
if (city.getGlobalBounds().contains(mousePos))
{
return GameEvent::OpenCity;
}
++cityCounter;
}
if (zoomIn.getGlobalBounds().contains(mousePos) && mapScale < maximum_scale)
{
++mapScale;
resizeMap();
return GameEvent::ActionComplete;
}
else if (center.getGlobalBounds().contains(mousePos))
{
// focus on selected city
// if no city selected focus on 0,0 center point.
// do not exceed constraints. map edges must never exist within the bounds of the frame.
}
else if (zoomOut.getGlobalBounds().contains(mousePos) && mapScale > minimum_scale)
{
--mapScale;
resizeMap();
return GameEvent::ActionComplete;
}
else if (map.getGlobalBounds().contains(mousePos))
{
grab(mousePos);
}
return GameEvent::None;
}
void WorldMap::update(const sf::Vector2f& mousePos)
{
mouseOverButtons(mousePos);
moveMap(mousePos);
}
void WorldMap::draw(sf::RenderWindow& window)
{
window.draw(map);
window.draw(zoomIn);
window.draw(center);
window.draw(zoomOut);
}
}
And a few screenshots of what it looks like:
This is the full, zoomed out map.
And this is zoomed in and moved slightly to the right.
c++ object-oriented game inheritance sfml
add a comment |
up vote
0
down vote
favorite
I am building a GUI game with SFML that will have two screens (possibly a third) that display a map to the player and the player may need or want to zoom in/out or drag the map so they can see areas that were previously off screen. Because I will have multiple map classes that need the same functionality I wrote a base class for the maps and inherit from it.
I keep my global constants in a single separate header for the entire project. These are almost exclusively used for removing magic numbers. It is also useful with GUI positioning when some elements are positioned relative to other elements. This way if the parent moves so does the child. Here are the ones that are relevant to this question:
constexpr unsigned screen_width = 1200U;
constexpr unsigned screen_height = 800U;
constexpr float screen_width_float = static_cast<float>(screen_width);
constexpr float screen_height_float = static_cast<float>(screen_height);
constexpr float side_panel_width = 15.F;
constexpr float drop_button_height = 80.F;
constexpr float game_button_height = 100.F;
constexpr float map_width = screen_width_float - (side_panel_width * 2.F);
constexpr float map_height = screen_height_float - (drop_button_height + game_button_height);
constexpr float map_x = side_panel_width;
constexpr float map_y = drop_button_height;
constexpr float map_button_side = 30.F;
constexpr float map_button_y = 650.F;
constexpr float zoom_in_x = 60.F;
constexpr float center_x = 100.F;
constexpr float zoom_out_x = 140.F;
constexpr unsigned minimum_scale = 1;
constexpr unsigned maximum_scale = 10;
A little about the base class. It will hold the map sprite as well as the functionality for scaling and dragging the image. I want to quickly note that I used a static analysis tool that told me to lose the protected
access specifier in lieu of private
but the amount of abstraction that would require seems unnecessary to me. Should I declare my base class member variables and functions private?
The grab()
and releaseGrip()
functions simply abstract the mouse press/release to the appropriate layer to handle them. It is worth noting that the mouse not only needs to be within the bounds of the image to grab but that it also will interact with any other UI elements first and forego "grabbing". I may also in the future require there to be situations where the map is non-responsive.
The zoom buttons simply increment/decrement the unsigned mapScale
variable. Said variable is constrained to values of 1 through 10.
Lastly the main purpose of the class is in the resizeMap()
and moveMap()
functions. constrainMapCoordinates()
is used internally by both the other two functions and is simply for deduplication.
Map.h
#ifndef BRUGLESCO_FLEETCOMMAND_MAP_H
#define BRUGLESCO_FLEETCOMMAND_MAP_H
#include "GameEvent.h"
#include "Expressions.h"
#include <Graphics.hpp>
namespace bruglesco
{
class Map
{
public:
Map();
virtual ~Map() = default;
void releaseGrip() { grabbed = false; }
virtual GameEvent input(const sf::Vector2f& mousePos) = 0;
virtual void update(const sf::Vector2f& mousePos) = 0;
virtual void draw(sf::RenderWindow& window) = 0;
protected:
sf::Texture worldMap;
sf::RectangleShape map{ sf::Vector2f(map_width, map_height) };
sf::RectangleShape zoomIn{ sf::Vector2f(map_button_side, map_button_side) };
sf::RectangleShape center{ sf::Vector2f(map_button_side, map_button_side) };
sf::RectangleShape zoomOut{ sf::Vector2f(map_button_side, map_button_side) };
unsigned mapScale{ minimum_scale };
sf::Vector2f mouseWasAt{ 0.f, 0.f };
bool grabbed{ false };
void grab(const sf::Vector2f& mousePos);
void resizeMap();
void moveMap(const sf::Vector2f& newPos);
void constrainMapCoordinates(float& x, float& y);
void mouseOverButtons(const sf::Vector2f& mousePos);
};
}
#endif // !BRUGLESCO_FLEETCOMMAND_MAP_H
Map.cpp
#include "Map.h"
namespace bruglesco
{
Map::Map()
{
worldMap.loadFromFile("Assets/WorldMap.png");
map.setTexture(&worldMap);
map.setPosition(map_x, map_y);
zoomIn.setPosition(zoom_in_x, map_button_y);
center.setPosition(center_x, map_button_y);
zoomOut.setPosition(zoom_out_x, map_button_y);
}
void Map::grab(const sf::Vector2f& mousePos)
{
grabbed = true;
mouseWasAt = mousePos;
}
void Map::resizeMap()
{
float scale = 0.8F + (static_cast<float>(mapScale) * 0.2F);
float map_x_offset = ((map.getSize().x * map.getScale().x) - (map_width * scale)) / 2.F;
float map_y_offset = ((map.getSize().y * map.getScale().y) - (map_height * scale)) / 2.F;
float x = map.getPosition().x + map_x_offset;
float y = map.getPosition().y + map_y_offset;
map.setScale(scale, scale);
constrainMapCoordinates(x, y);
map.setPosition(x, y);
}
void Map::moveMap(const sf::Vector2f& newPos)
{
if (grabbed)
{
sf::Vector2f moved = newPos - mouseWasAt;
float x = map.getPosition().x + moved.x;
float y = map.getPosition().y + moved.y;
constrainMapCoordinates(x, y);
map.setPosition(x, y);
mouseWasAt = newPos;
}
}
void Map::constrainMapCoordinates(float& x, float& y)
{
if (x > map_x)
{
x = map_x;
}
else if (x < -(map_width * map.getScale().x - map_width) + map_x)
{
x = -(map_width * map.getScale().x - map_width) + map_x;
}
if (y > map_y)
{
y = map_y;
}
else if (y < -(map_height * map.getScale().y - map_height) + map_y)
{
y = -(map_height * map.getScale().y - map_height) + map_y;
}
}
void Map::mouseOverButtons(const sf::Vector2f& mousePos)
{
if (zoomIn.getGlobalBounds().contains(mousePos))
{
if (mapScale < maximum_scale)
{
zoomIn.setFillColor(sf::Color::Green);
}
else
{
zoomIn.setFillColor(sf::Color::Red);
}
}
else
{
zoomIn.setFillColor(sf::Color::White);
}
if (center.getGlobalBounds().contains(mousePos))
{
center.setFillColor(sf::Color::Green);
}
else
{
center.setFillColor(sf::Color::White);
}
if (zoomOut.getGlobalBounds().contains(mousePos))
{
if (mapScale > minimum_scale)
{
zoomOut.setFillColor(sf::Color::Green);
}
else
{
zoomOut.setFillColor(sf::Color::Red);
}
}
else
{
zoomOut.setFillColor(sf::Color::White);
}
}
}
And one of the derived classes that use the base class. Here it mostly just overrides input()
, update()
and draw()
. Note that input()
is only called on frames when the mouse is pressed. update()
and draw()
are called every frame.
WorldMap.h
#ifndef BRUGLESCO_FLEETCOMMAND_WORLDMAP_H
#define BRUGLESCO_FLEETCOMMAND_WORLDMAP_H
#include "Map.h"
#include <vector>
namespace bruglesco
{
class WorldMap : public Map
{
public:
WorldMap();
GameEvent input(const sf::Vector2f& mousePos) override;
void update(const sf::Vector2f& mousePos) override;
void draw(sf::RenderWindow& window) override;
private:
std::vector<sf::RectangleShape> cities{};
};
}
#endif // !BRUGLESCO_FLEETCOMMAND_WORLDMAP_H
WorldMap.cpp
#include "WorldMap.h"
namespace bruglesco
{
WorldMap::WorldMap()
{
// set cities
}
GameEvent WorldMap::input(const sf::Vector2f& mousePos)
{
cityCounter = 0;
for (auto&& city : cities)
{
if (city.getGlobalBounds().contains(mousePos))
{
return GameEvent::OpenCity;
}
++cityCounter;
}
if (zoomIn.getGlobalBounds().contains(mousePos) && mapScale < maximum_scale)
{
++mapScale;
resizeMap();
return GameEvent::ActionComplete;
}
else if (center.getGlobalBounds().contains(mousePos))
{
// focus on selected city
// if no city selected focus on 0,0 center point.
// do not exceed constraints. map edges must never exist within the bounds of the frame.
}
else if (zoomOut.getGlobalBounds().contains(mousePos) && mapScale > minimum_scale)
{
--mapScale;
resizeMap();
return GameEvent::ActionComplete;
}
else if (map.getGlobalBounds().contains(mousePos))
{
grab(mousePos);
}
return GameEvent::None;
}
void WorldMap::update(const sf::Vector2f& mousePos)
{
mouseOverButtons(mousePos);
moveMap(mousePos);
}
void WorldMap::draw(sf::RenderWindow& window)
{
window.draw(map);
window.draw(zoomIn);
window.draw(center);
window.draw(zoomOut);
}
}
And a few screenshots of what it looks like:
This is the full, zoomed out map.
And this is zoomed in and moved slightly to the right.
c++ object-oriented game inheritance sfml
add a comment |
up vote
0
down vote
favorite
up vote
0
down vote
favorite
I am building a GUI game with SFML that will have two screens (possibly a third) that display a map to the player and the player may need or want to zoom in/out or drag the map so they can see areas that were previously off screen. Because I will have multiple map classes that need the same functionality I wrote a base class for the maps and inherit from it.
I keep my global constants in a single separate header for the entire project. These are almost exclusively used for removing magic numbers. It is also useful with GUI positioning when some elements are positioned relative to other elements. This way if the parent moves so does the child. Here are the ones that are relevant to this question:
constexpr unsigned screen_width = 1200U;
constexpr unsigned screen_height = 800U;
constexpr float screen_width_float = static_cast<float>(screen_width);
constexpr float screen_height_float = static_cast<float>(screen_height);
constexpr float side_panel_width = 15.F;
constexpr float drop_button_height = 80.F;
constexpr float game_button_height = 100.F;
constexpr float map_width = screen_width_float - (side_panel_width * 2.F);
constexpr float map_height = screen_height_float - (drop_button_height + game_button_height);
constexpr float map_x = side_panel_width;
constexpr float map_y = drop_button_height;
constexpr float map_button_side = 30.F;
constexpr float map_button_y = 650.F;
constexpr float zoom_in_x = 60.F;
constexpr float center_x = 100.F;
constexpr float zoom_out_x = 140.F;
constexpr unsigned minimum_scale = 1;
constexpr unsigned maximum_scale = 10;
A little about the base class. It will hold the map sprite as well as the functionality for scaling and dragging the image. I want to quickly note that I used a static analysis tool that told me to lose the protected
access specifier in lieu of private
but the amount of abstraction that would require seems unnecessary to me. Should I declare my base class member variables and functions private?
The grab()
and releaseGrip()
functions simply abstract the mouse press/release to the appropriate layer to handle them. It is worth noting that the mouse not only needs to be within the bounds of the image to grab but that it also will interact with any other UI elements first and forego "grabbing". I may also in the future require there to be situations where the map is non-responsive.
The zoom buttons simply increment/decrement the unsigned mapScale
variable. Said variable is constrained to values of 1 through 10.
Lastly the main purpose of the class is in the resizeMap()
and moveMap()
functions. constrainMapCoordinates()
is used internally by both the other two functions and is simply for deduplication.
Map.h
#ifndef BRUGLESCO_FLEETCOMMAND_MAP_H
#define BRUGLESCO_FLEETCOMMAND_MAP_H
#include "GameEvent.h"
#include "Expressions.h"
#include <Graphics.hpp>
namespace bruglesco
{
class Map
{
public:
Map();
virtual ~Map() = default;
void releaseGrip() { grabbed = false; }
virtual GameEvent input(const sf::Vector2f& mousePos) = 0;
virtual void update(const sf::Vector2f& mousePos) = 0;
virtual void draw(sf::RenderWindow& window) = 0;
protected:
sf::Texture worldMap;
sf::RectangleShape map{ sf::Vector2f(map_width, map_height) };
sf::RectangleShape zoomIn{ sf::Vector2f(map_button_side, map_button_side) };
sf::RectangleShape center{ sf::Vector2f(map_button_side, map_button_side) };
sf::RectangleShape zoomOut{ sf::Vector2f(map_button_side, map_button_side) };
unsigned mapScale{ minimum_scale };
sf::Vector2f mouseWasAt{ 0.f, 0.f };
bool grabbed{ false };
void grab(const sf::Vector2f& mousePos);
void resizeMap();
void moveMap(const sf::Vector2f& newPos);
void constrainMapCoordinates(float& x, float& y);
void mouseOverButtons(const sf::Vector2f& mousePos);
};
}
#endif // !BRUGLESCO_FLEETCOMMAND_MAP_H
Map.cpp
#include "Map.h"
namespace bruglesco
{
Map::Map()
{
worldMap.loadFromFile("Assets/WorldMap.png");
map.setTexture(&worldMap);
map.setPosition(map_x, map_y);
zoomIn.setPosition(zoom_in_x, map_button_y);
center.setPosition(center_x, map_button_y);
zoomOut.setPosition(zoom_out_x, map_button_y);
}
void Map::grab(const sf::Vector2f& mousePos)
{
grabbed = true;
mouseWasAt = mousePos;
}
void Map::resizeMap()
{
float scale = 0.8F + (static_cast<float>(mapScale) * 0.2F);
float map_x_offset = ((map.getSize().x * map.getScale().x) - (map_width * scale)) / 2.F;
float map_y_offset = ((map.getSize().y * map.getScale().y) - (map_height * scale)) / 2.F;
float x = map.getPosition().x + map_x_offset;
float y = map.getPosition().y + map_y_offset;
map.setScale(scale, scale);
constrainMapCoordinates(x, y);
map.setPosition(x, y);
}
void Map::moveMap(const sf::Vector2f& newPos)
{
if (grabbed)
{
sf::Vector2f moved = newPos - mouseWasAt;
float x = map.getPosition().x + moved.x;
float y = map.getPosition().y + moved.y;
constrainMapCoordinates(x, y);
map.setPosition(x, y);
mouseWasAt = newPos;
}
}
void Map::constrainMapCoordinates(float& x, float& y)
{
if (x > map_x)
{
x = map_x;
}
else if (x < -(map_width * map.getScale().x - map_width) + map_x)
{
x = -(map_width * map.getScale().x - map_width) + map_x;
}
if (y > map_y)
{
y = map_y;
}
else if (y < -(map_height * map.getScale().y - map_height) + map_y)
{
y = -(map_height * map.getScale().y - map_height) + map_y;
}
}
void Map::mouseOverButtons(const sf::Vector2f& mousePos)
{
if (zoomIn.getGlobalBounds().contains(mousePos))
{
if (mapScale < maximum_scale)
{
zoomIn.setFillColor(sf::Color::Green);
}
else
{
zoomIn.setFillColor(sf::Color::Red);
}
}
else
{
zoomIn.setFillColor(sf::Color::White);
}
if (center.getGlobalBounds().contains(mousePos))
{
center.setFillColor(sf::Color::Green);
}
else
{
center.setFillColor(sf::Color::White);
}
if (zoomOut.getGlobalBounds().contains(mousePos))
{
if (mapScale > minimum_scale)
{
zoomOut.setFillColor(sf::Color::Green);
}
else
{
zoomOut.setFillColor(sf::Color::Red);
}
}
else
{
zoomOut.setFillColor(sf::Color::White);
}
}
}
And one of the derived classes that use the base class. Here it mostly just overrides input()
, update()
and draw()
. Note that input()
is only called on frames when the mouse is pressed. update()
and draw()
are called every frame.
WorldMap.h
#ifndef BRUGLESCO_FLEETCOMMAND_WORLDMAP_H
#define BRUGLESCO_FLEETCOMMAND_WORLDMAP_H
#include "Map.h"
#include <vector>
namespace bruglesco
{
class WorldMap : public Map
{
public:
WorldMap();
GameEvent input(const sf::Vector2f& mousePos) override;
void update(const sf::Vector2f& mousePos) override;
void draw(sf::RenderWindow& window) override;
private:
std::vector<sf::RectangleShape> cities{};
};
}
#endif // !BRUGLESCO_FLEETCOMMAND_WORLDMAP_H
WorldMap.cpp
#include "WorldMap.h"
namespace bruglesco
{
WorldMap::WorldMap()
{
// set cities
}
GameEvent WorldMap::input(const sf::Vector2f& mousePos)
{
cityCounter = 0;
for (auto&& city : cities)
{
if (city.getGlobalBounds().contains(mousePos))
{
return GameEvent::OpenCity;
}
++cityCounter;
}
if (zoomIn.getGlobalBounds().contains(mousePos) && mapScale < maximum_scale)
{
++mapScale;
resizeMap();
return GameEvent::ActionComplete;
}
else if (center.getGlobalBounds().contains(mousePos))
{
// focus on selected city
// if no city selected focus on 0,0 center point.
// do not exceed constraints. map edges must never exist within the bounds of the frame.
}
else if (zoomOut.getGlobalBounds().contains(mousePos) && mapScale > minimum_scale)
{
--mapScale;
resizeMap();
return GameEvent::ActionComplete;
}
else if (map.getGlobalBounds().contains(mousePos))
{
grab(mousePos);
}
return GameEvent::None;
}
void WorldMap::update(const sf::Vector2f& mousePos)
{
mouseOverButtons(mousePos);
moveMap(mousePos);
}
void WorldMap::draw(sf::RenderWindow& window)
{
window.draw(map);
window.draw(zoomIn);
window.draw(center);
window.draw(zoomOut);
}
}
And a few screenshots of what it looks like:
This is the full, zoomed out map.
And this is zoomed in and moved slightly to the right.
c++ object-oriented game inheritance sfml
I am building a GUI game with SFML that will have two screens (possibly a third) that display a map to the player and the player may need or want to zoom in/out or drag the map so they can see areas that were previously off screen. Because I will have multiple map classes that need the same functionality I wrote a base class for the maps and inherit from it.
I keep my global constants in a single separate header for the entire project. These are almost exclusively used for removing magic numbers. It is also useful with GUI positioning when some elements are positioned relative to other elements. This way if the parent moves so does the child. Here are the ones that are relevant to this question:
constexpr unsigned screen_width = 1200U;
constexpr unsigned screen_height = 800U;
constexpr float screen_width_float = static_cast<float>(screen_width);
constexpr float screen_height_float = static_cast<float>(screen_height);
constexpr float side_panel_width = 15.F;
constexpr float drop_button_height = 80.F;
constexpr float game_button_height = 100.F;
constexpr float map_width = screen_width_float - (side_panel_width * 2.F);
constexpr float map_height = screen_height_float - (drop_button_height + game_button_height);
constexpr float map_x = side_panel_width;
constexpr float map_y = drop_button_height;
constexpr float map_button_side = 30.F;
constexpr float map_button_y = 650.F;
constexpr float zoom_in_x = 60.F;
constexpr float center_x = 100.F;
constexpr float zoom_out_x = 140.F;
constexpr unsigned minimum_scale = 1;
constexpr unsigned maximum_scale = 10;
A little about the base class. It will hold the map sprite as well as the functionality for scaling and dragging the image. I want to quickly note that I used a static analysis tool that told me to lose the protected
access specifier in lieu of private
but the amount of abstraction that would require seems unnecessary to me. Should I declare my base class member variables and functions private?
The grab()
and releaseGrip()
functions simply abstract the mouse press/release to the appropriate layer to handle them. It is worth noting that the mouse not only needs to be within the bounds of the image to grab but that it also will interact with any other UI elements first and forego "grabbing". I may also in the future require there to be situations where the map is non-responsive.
The zoom buttons simply increment/decrement the unsigned mapScale
variable. Said variable is constrained to values of 1 through 10.
Lastly the main purpose of the class is in the resizeMap()
and moveMap()
functions. constrainMapCoordinates()
is used internally by both the other two functions and is simply for deduplication.
Map.h
#ifndef BRUGLESCO_FLEETCOMMAND_MAP_H
#define BRUGLESCO_FLEETCOMMAND_MAP_H
#include "GameEvent.h"
#include "Expressions.h"
#include <Graphics.hpp>
namespace bruglesco
{
class Map
{
public:
Map();
virtual ~Map() = default;
void releaseGrip() { grabbed = false; }
virtual GameEvent input(const sf::Vector2f& mousePos) = 0;
virtual void update(const sf::Vector2f& mousePos) = 0;
virtual void draw(sf::RenderWindow& window) = 0;
protected:
sf::Texture worldMap;
sf::RectangleShape map{ sf::Vector2f(map_width, map_height) };
sf::RectangleShape zoomIn{ sf::Vector2f(map_button_side, map_button_side) };
sf::RectangleShape center{ sf::Vector2f(map_button_side, map_button_side) };
sf::RectangleShape zoomOut{ sf::Vector2f(map_button_side, map_button_side) };
unsigned mapScale{ minimum_scale };
sf::Vector2f mouseWasAt{ 0.f, 0.f };
bool grabbed{ false };
void grab(const sf::Vector2f& mousePos);
void resizeMap();
void moveMap(const sf::Vector2f& newPos);
void constrainMapCoordinates(float& x, float& y);
void mouseOverButtons(const sf::Vector2f& mousePos);
};
}
#endif // !BRUGLESCO_FLEETCOMMAND_MAP_H
Map.cpp
#include "Map.h"
namespace bruglesco
{
Map::Map()
{
worldMap.loadFromFile("Assets/WorldMap.png");
map.setTexture(&worldMap);
map.setPosition(map_x, map_y);
zoomIn.setPosition(zoom_in_x, map_button_y);
center.setPosition(center_x, map_button_y);
zoomOut.setPosition(zoom_out_x, map_button_y);
}
void Map::grab(const sf::Vector2f& mousePos)
{
grabbed = true;
mouseWasAt = mousePos;
}
void Map::resizeMap()
{
float scale = 0.8F + (static_cast<float>(mapScale) * 0.2F);
float map_x_offset = ((map.getSize().x * map.getScale().x) - (map_width * scale)) / 2.F;
float map_y_offset = ((map.getSize().y * map.getScale().y) - (map_height * scale)) / 2.F;
float x = map.getPosition().x + map_x_offset;
float y = map.getPosition().y + map_y_offset;
map.setScale(scale, scale);
constrainMapCoordinates(x, y);
map.setPosition(x, y);
}
void Map::moveMap(const sf::Vector2f& newPos)
{
if (grabbed)
{
sf::Vector2f moved = newPos - mouseWasAt;
float x = map.getPosition().x + moved.x;
float y = map.getPosition().y + moved.y;
constrainMapCoordinates(x, y);
map.setPosition(x, y);
mouseWasAt = newPos;
}
}
void Map::constrainMapCoordinates(float& x, float& y)
{
if (x > map_x)
{
x = map_x;
}
else if (x < -(map_width * map.getScale().x - map_width) + map_x)
{
x = -(map_width * map.getScale().x - map_width) + map_x;
}
if (y > map_y)
{
y = map_y;
}
else if (y < -(map_height * map.getScale().y - map_height) + map_y)
{
y = -(map_height * map.getScale().y - map_height) + map_y;
}
}
void Map::mouseOverButtons(const sf::Vector2f& mousePos)
{
if (zoomIn.getGlobalBounds().contains(mousePos))
{
if (mapScale < maximum_scale)
{
zoomIn.setFillColor(sf::Color::Green);
}
else
{
zoomIn.setFillColor(sf::Color::Red);
}
}
else
{
zoomIn.setFillColor(sf::Color::White);
}
if (center.getGlobalBounds().contains(mousePos))
{
center.setFillColor(sf::Color::Green);
}
else
{
center.setFillColor(sf::Color::White);
}
if (zoomOut.getGlobalBounds().contains(mousePos))
{
if (mapScale > minimum_scale)
{
zoomOut.setFillColor(sf::Color::Green);
}
else
{
zoomOut.setFillColor(sf::Color::Red);
}
}
else
{
zoomOut.setFillColor(sf::Color::White);
}
}
}
And one of the derived classes that use the base class. Here it mostly just overrides input()
, update()
and draw()
. Note that input()
is only called on frames when the mouse is pressed. update()
and draw()
are called every frame.
WorldMap.h
#ifndef BRUGLESCO_FLEETCOMMAND_WORLDMAP_H
#define BRUGLESCO_FLEETCOMMAND_WORLDMAP_H
#include "Map.h"
#include <vector>
namespace bruglesco
{
class WorldMap : public Map
{
public:
WorldMap();
GameEvent input(const sf::Vector2f& mousePos) override;
void update(const sf::Vector2f& mousePos) override;
void draw(sf::RenderWindow& window) override;
private:
std::vector<sf::RectangleShape> cities{};
};
}
#endif // !BRUGLESCO_FLEETCOMMAND_WORLDMAP_H
WorldMap.cpp
#include "WorldMap.h"
namespace bruglesco
{
WorldMap::WorldMap()
{
// set cities
}
GameEvent WorldMap::input(const sf::Vector2f& mousePos)
{
cityCounter = 0;
for (auto&& city : cities)
{
if (city.getGlobalBounds().contains(mousePos))
{
return GameEvent::OpenCity;
}
++cityCounter;
}
if (zoomIn.getGlobalBounds().contains(mousePos) && mapScale < maximum_scale)
{
++mapScale;
resizeMap();
return GameEvent::ActionComplete;
}
else if (center.getGlobalBounds().contains(mousePos))
{
// focus on selected city
// if no city selected focus on 0,0 center point.
// do not exceed constraints. map edges must never exist within the bounds of the frame.
}
else if (zoomOut.getGlobalBounds().contains(mousePos) && mapScale > minimum_scale)
{
--mapScale;
resizeMap();
return GameEvent::ActionComplete;
}
else if (map.getGlobalBounds().contains(mousePos))
{
grab(mousePos);
}
return GameEvent::None;
}
void WorldMap::update(const sf::Vector2f& mousePos)
{
mouseOverButtons(mousePos);
moveMap(mousePos);
}
void WorldMap::draw(sf::RenderWindow& window)
{
window.draw(map);
window.draw(zoomIn);
window.draw(center);
window.draw(zoomOut);
}
}
And a few screenshots of what it looks like:
This is the full, zoomed out map.
And this is zoomed in and moved slightly to the right.
c++ object-oriented game inheritance sfml
c++ object-oriented game inheritance sfml
asked 1 min ago
bruglesco
1,2642520
1,2642520
add a comment |
add a comment |
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f208805%2fmanipulating-an-image-both-in-scale-and-position-for-an-sfml-gui-game%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown