diff --git a/CMakeLists.txt b/CMakeLists.txt index 3276e26..55088a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,7 +55,7 @@ set(SOURCES src/utilities/magic_enum.hpp src/game/player/player_spawner.cpp src/game/player/player_spawner.hpp - src/game/camera/tracking_area.h) + src/game/camera/tracking_area.h src/game/camera/tracking_view_options.hpp) set(PHYSICS_00_SOURCES src/prototypes/physics_00.cpp) diff --git a/src/config.h b/src/config.h index 6d20682..a25cc37 100644 --- a/src/config.h +++ b/src/config.h @@ -20,7 +20,13 @@ #define ISOMETRIC_SKEW 0.3f #define WORLD_TO_ISO_SCALE 10.0f -#define VIEW_DYNAMIC_FOLLOW_SPEED 2.f +// Tracking view defaults +#define DEF_TV_FREE_MOVE_THRESHOLD 0.f +#define DEF_TV_SOFT_FOLLOW_SPEED 2.5f +#define DEF_TV_SOFT_RESIZE_SPEED 5.f +#define DEF_TV_MIN_VIEW_SIZE sf::Vector2f(300, 300) +#define DEF_TV_MAX_VIEW_SIZE sf::Vector2f(0, 0) +#define DEF_TV_VIEW_SIZE_PADDING sf::Vector2f(0.5f, 0.5f) // Inputs #define JOYSTICK_DEADZONE 0.1f diff --git a/src/game/camera/tracking_area.h b/src/game/camera/tracking_area.h index 6ae8948..db5b566 100644 --- a/src/game/camera/tracking_area.h +++ b/src/game/camera/tracking_area.h @@ -12,6 +12,11 @@ struct TrackingArea { return topLeft + (bottomRight - topLeft) / 2.f; } + + sf::Vector2f getSize() const + { + return bottomRight - topLeft; + } }; diff --git a/src/game/camera/tracking_view.cpp b/src/game/camera/tracking_view.cpp index 2f99905..c172fca 100644 --- a/src/game/camera/tracking_view.cpp +++ b/src/game/camera/tracking_view.cpp @@ -1,10 +1,9 @@ #include "tracking_view.h" #include "../../utilities/vector_utils.hpp" -TrackingView::TrackingView(float freeMoveRadius, float dynamicFollowRadius) : freeMoveRadius(freeMoveRadius), - dynamicFollowRadius(dynamicFollowRadius), - view(nullptr), - hasViewChanged(false) +TrackingView::TrackingView(TrackingViewOptions options) : options(options), + view(nullptr), + hasViewChanged(false) { trackables = std::vector(); marker = new CircleObject(2, sf::Color::Yellow); @@ -27,13 +26,10 @@ void TrackingView::lateUpdate() processTrackableStates(); - // Update size - // TODO: Update size based on length of tracked objects - setSize(Game::getInstance()->window->getSize()); - if (!trackables.empty()) { - followTarget(); + followTrackables(); + adjustSizeToTrackables(); } // Update window if necessary @@ -51,18 +47,22 @@ void TrackingView::initializeView() hasViewChanged = true; } -void TrackingView::setSize(sf::Vector2u windowSize) +void TrackingView::setSize(sf::Vector2f newSize) { - // TODO: Maybe listen to resize events instead of checking every frame? // Is different? - if (view->getSize().x == windowSize.x && view->getSize().y == windowSize.y) + auto size = getSize(); + if (size.x == newSize.x && size.y == newSize.y) { // Nothing to do return; } - sf::Vector2f viewSize = sf::Vector2f(windowSize.x, windowSize.y); - view->setSize(viewSize); + if (options.softResizeSpeed != 0) { + // Smooth out transition to new size + newSize = size + (newSize - size) * options.softResizeSpeed * FRAME_TIME.asSeconds(); + } + + view->setSize(newSize); hasViewChanged = true; } @@ -76,7 +76,7 @@ sf::Vector2f TrackingView::getCenter() const return view->getCenter(); } -void TrackingView::followTarget() +void TrackingView::followTrackables() { auto trackingPoint = getTrackingArea().getCenter(); if (DEVELOPER_MODE) @@ -87,32 +87,33 @@ void TrackingView::followTarget() // Calculate distance to target to check how to handle it auto currentCenter = view->getCenter(); auto vectorToTarget = trackingPoint - currentCenter; - float distanceToTarget = length(vectorToTarget); + auto distanceToTarget = length(vectorToTarget); - if (distanceToTarget <= freeMoveRadius) + if (distanceToTarget <= getRadius(options.freeMoveThreshold)) { // Nothing to do return; } - // Move view to place tracking-point at edge of area - auto deltaToDesiredView = normalize(vectorToTarget) * FRAME_TIME.asSeconds(); - if (distanceToTarget <= dynamicFollowRadius) - { - // Reduce delta to make it less jaring - deltaToDesiredView *= VIEW_DYNAMIC_FOLLOW_SPEED * (distanceToTarget - freeMoveRadius); - } else - { - // Hard follow - deltaToDesiredView *= dynamicFollowRadius; - } + + // Targets are outside of free move radius, follow them + auto deltaToDesiredView = normalize(vectorToTarget); + + // Reduce delta to edge of free-move area to make it less jaring + deltaToDesiredView *= distanceToTarget - getRadius(options.freeMoveThreshold); moveCenter(deltaToDesiredView); } void TrackingView::moveCenter(sf::Vector2 delta) { + if (options.softFollowSpeed != 0) + { + // Soft + delta *= options.softFollowSpeed * FRAME_TIME.asSeconds(); + } + view->move(delta); hasViewChanged = true; } @@ -141,45 +142,118 @@ void TrackingView::processTrackableStates() }); } -void TrackingView::setCenter(sf::Vector2 newCenter) -{ - view->setCenter(newCenter); - hasViewChanged = true; -} - TrackingArea TrackingView::getTrackingArea() const { - sf::Vector2f initialPoints = trackables[0]->getTrackablePosition(); - TrackingArea area = {initialPoints, initialPoints}; + auto initialTrackable = trackables[0]; + TrackingArea area = { + initialTrackable->getTrackablePosition() - initialTrackable->getTrackableSize() / 2.f, + initialTrackable->getTrackablePosition() + initialTrackable->getTrackableSize() / 2.f + }; - // Find min and max point coordinates for x and y axis over all trackables + // Find min and max point coordinates for x- and y-axis over all trackables for (auto trackable: trackables) { auto trackableCoordinates = trackable->getTrackablePosition(); auto trackableSize = trackable->getTrackableSize(); - auto minPointX = trackableCoordinates.x - trackableSize.x / 2.f; - auto maxPointX = trackableCoordinates.x + trackableSize.x / 2.f; - auto minPointY = trackableCoordinates.y - trackableSize.y / 2.f; - auto maxPointY = trackableCoordinates.y + trackableSize.y / 2.f; + auto topLeft = trackableCoordinates - trackableSize / 2.f; + auto bottomRight = trackableCoordinates + trackableSize / 2.f; - if (minPointX < area.topLeft.x) + if (topLeft.x < area.topLeft.x) { - area.topLeft.x = minPointX; + area.topLeft.x = topLeft.x; } - if (maxPointX > area.bottomRight.x) + if (bottomRight.x > area.bottomRight.x) { - area.bottomRight.x = maxPointX; + area.bottomRight.x = bottomRight.x; } - if (minPointY < area.topLeft.y) + if (topLeft.y < area.topLeft.y) { - area.topLeft.y = minPointY; + area.topLeft.y = topLeft.y; } - if (maxPointY > area.bottomRight.y) + if (bottomRight.y > area.bottomRight.y) { - area.bottomRight.y = maxPointY; + area.bottomRight.y = bottomRight.y; } } return area; } + +void TrackingView::adjustSizeToTrackables() +{ + // Calculate new view size + auto newViewSize = getTrackingArea().getSize(); + + newViewSize = applyPadding(newViewSize); + newViewSize = restrainToBounds(newViewSize); + + // Extend view to match aspect ratio + auto currentViewSize = getSize(); + auto aspectRatio = currentViewSize.x / currentViewSize.y; + if (newViewSize.x / newViewSize.y > aspectRatio) + { + // Extend x-axis + newViewSize.y = newViewSize.x / aspectRatio; + } else + { + // Extend y-axis + newViewSize.x = newViewSize.y * aspectRatio; + } + + setSize(newViewSize); +} + +sf::Vector2f TrackingView::restrainToBounds(sf::Vector2f viewSize) const +{ + // x-axis + if (options.minViewSize.x > 0 && viewSize.x < options.minViewSize.x) + { + viewSize.x = options.minViewSize.x; + } else if (options.maxViewSize.x > 0 && viewSize.x > options.maxViewSize.x) + { + viewSize.x = options.maxViewSize.x; + } + // y-axis + if (options.minViewSize.y > 0 && viewSize.y < options.minViewSize.y) + { + viewSize.y = options.minViewSize.y; + } else if (options.maxViewSize.y > 0 && viewSize.y > options.maxViewSize.y) + { + viewSize.y = options.maxViewSize.y; + } + return viewSize; +} + +sf::Vector2f TrackingView::applyPadding(sf::Vector2f viewSize) const +{ + auto padding = options.viewSizePadding; + if (padding.x <= 1) + { + padding.x *= viewSize.x; + } + if (padding.y <= 1) + { + padding.y *= viewSize.y; + } + return viewSize + padding; +} + +float TrackingView::getRadius(float threshold) const +{ + if (threshold <= 0) + { + return 0; + } + + if (threshold > 1) + { + return threshold; + } + + auto viewSize = getSize(); + float smallestSide = std::min(viewSize.x, viewSize.y); + + // Only half of the side, since we are calculating radius + return smallestSide / 2.f * threshold; +} diff --git a/src/game/camera/tracking_view.h b/src/game/camera/tracking_view.h index 4601fb9..5abe30b 100644 --- a/src/game/camera/tracking_view.h +++ b/src/game/camera/tracking_view.h @@ -6,14 +6,14 @@ #include "ITrackable.h" #include "../../primitives/circle_object.h" #include "tracking_area.h" +#include "tracking_view_options.hpp" class CircleObject; class TrackingView : public GameObject { public: - explicit TrackingView(float freeMoveRadius = 0, - float dynamicFollowRadius = 300); + explicit TrackingView(TrackingViewOptions options = TrackingViewOptions()); ~TrackingView(); @@ -31,17 +31,14 @@ public: private: sf::View *view{}; bool hasViewChanged{}; - float freeMoveRadius; - float dynamicFollowRadius; + TrackingViewOptions options; std::vector trackables; CircleObject *marker; void initializeView(); - void setSize(sf::Vector2u windowSize); - - void followTarget(); + void followTrackables(); void moveCenter(sf::Vector2 delta); @@ -49,7 +46,15 @@ private: void processTrackableStates(); - void setCenter(sf::Vector2 newCenter); + void adjustSizeToTrackables(); + + void setSize(sf::Vector2f newSize); + + sf::Vector2f applyPadding(sf::Vector2f viewSize) const; + + sf::Vector2f restrainToBounds(sf::Vector2f viewSize) const; + + float getRadius(float threshold) const; }; diff --git a/src/game/camera/tracking_view_options.hpp b/src/game/camera/tracking_view_options.hpp new file mode 100644 index 0000000..49d2bce --- /dev/null +++ b/src/game/camera/tracking_view_options.hpp @@ -0,0 +1,42 @@ +#ifndef HOLESOME_TRACKING_VIEW_OPTIONS_HPP +#define HOLESOME_TRACKING_VIEW_OPTIONS_HPP + +#include +#include "../../config.h" + +struct TrackingViewOptions +{ + /** + * Value >1 to set pixel radius. + * Value between 0 and 1 to set relative radius based on smallest half-axis-size. + */ + float freeMoveThreshold = DEF_TV_FREE_MOVE_THRESHOLD; + + /** + * 0 for instant follow. + */ + float softFollowSpeed = DEF_TV_SOFT_FOLLOW_SPEED; + /** + * 0 for instant resize. + */ + float softResizeSpeed = DEF_TV_SOFT_RESIZE_SPEED; + + /** + * If set to 0, view will not be limited. + */ + sf::Vector2f minViewSize = DEF_TV_MIN_VIEW_SIZE; + + /** + * If set to 0, view will not be limited. + */ + sf::Vector2f maxViewSize = DEF_TV_MAX_VIEW_SIZE; + + /** + * Will be added to tracked area size twice, as padding for each side. + * Value >1 to set pixel padding. + * Value between 0 and 1 to set relative padding. + */ + sf::Vector2f viewSizePadding = DEF_TV_VIEW_SIZE_PADDING; +}; + +#endif //HOLESOME_TRACKING_VIEW_OPTIONS_HPP diff --git a/src/game/player/player.cpp b/src/game/player/player.cpp index c7504b6..6e695a1 100644 --- a/src/game/player/player.cpp +++ b/src/game/player/player.cpp @@ -10,12 +10,13 @@ sf::Vector2f Player::getTrackablePosition() const sf::Vector2f Player::getTrackableSize() const { // TODO: Proper implementation - return {static_cast(circle->getRadius() * 2), static_cast(circle->getRadius() * 2)}; + return {circle->getRadius() * 2.f, circle->getRadius() * 2.f}; } void Player::update() { - if (!input->isActive) { + if (!input->isActive) + { isActive = false; return; }