/* OneLoneCoder - Camera2D v1.00 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A 2D world camera with various modes License (OLC-3) ~~~~~~~~~~~~~~~ Copyright 2018 - 2023 OneLoneCoder.com Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions or derivations of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions or derivative works in binary form must reproduce the above copyright notice. This list of conditions and the following disclaimer must be reproduced in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Links ~~~~~ YouTube: https://www.youtube.com/javidx9 Discord: https://discord.gg/WhwHUMV Twitter: https://www.twitter.com/javidx9 Twitch: https://www.twitch.tv/javidx9 GitHub: https://www.github.com/onelonecoder Homepage: https://www.onelonecoder.com Author ~~~~~~ David Barr, aka javidx9, �OneLoneCoder 2019, 2020, 2021, 2022 */ #pragma once #include "olcPixelGameEngine.h" namespace olc::utils { class Camera2D { public: enum class Mode : uint8_t { Simple, // No motion, just directly settable EdgeMove, // Moves as target crosses boundary LazyFollow, // Lazily follows the target FixedScreens, // Moves statically between "screens" }; public: inline Camera2D() : m_pTarget(&m_vLocalTarget) {} // Construct a camera with a viewable area size, and an optional starting position inline Camera2D(const olc::vf2d& vViewSize, const olc::vf2d& vViewPos = { 0.0f, 0.0f }) : m_pTarget(&m_vLocalTarget) { m_vViewSize = vViewSize; m_vViewPos = vViewPos; } // Set the operational mode of this camera inline void SetMode(const Mode t) { m_nMode = t; } // Get the operational mode of this camera inline Mode GetMode() const { return m_nMode; } // Get the position of the target being tracked by this camera inline const olc::vf2d& GetTarget() const { return *m_pTarget; } // Get the position of the cameras focus point inline const olc::vf2d& GetPosition() const { return m_vPosition; } // Get the top left of teh cameras visible area in world space inline const olc::vf2d& GetViewPosition() const { return m_vViewPos; } // Get the camera's visible area inline const olc::vf2d& GetViewSize() const { return m_vViewSize; } // Set tracked point via pointer inline void SetTarget(olc::vf2d& vTarget) { m_pTarget = &vTarget; } // Set tracked point via const ref - {10, 35} for example inline void SetTarget(const olc::vf2d&& vTarget) { m_vLocalTarget = vTarget; m_pTarget = &m_vLocalTarget; } // Set world boundary rectangle inline void SetWorldBoundary(const olc::vf2d& vPos, const olc::vf2d& vSize) { m_vWorldBoundaryPos = vPos; m_vWorldBoundarySize = vSize; } // Instruct camera to respect world boundaries inline void EnableWorldBoundary(const bool bEnable) { m_bWorldBoundary = bEnable; } // Are we using a world boundary? inline bool IsWorldBoundaryEnabled() const { return m_bWorldBoundary; } // Get the world boundary rectangle position inline const olc::vf2d& GetWorldBoundaryPosition() const { return m_vWorldBoundaryPos; } // Get the world boundary rectangle size inline const olc::vf2d& GetWorldBoundarySize() const { return m_vWorldBoundarySize; } // Set the velocity at which the lazy follower reaches tracked point inline void SetLazyFollowRate(const float fRate) { m_fLazyFollowRate = fRate; } // Get the velocity at which the lazy follower reaches tracked point inline float GetLazyFollowRate() const { return m_fLazyFollowRate; } // Set distance from tracked point to start nudging screen inline void SetEdgeTriggerDistance(const olc::vf2d& vEdge) { m_vEdgeTriggerDistance = vEdge; } // Return disance from tracked point that screen will nudge inline const olc::vf2d& GetEdgeTriggerDistance() const { return m_vEdgeTriggerDistance; } // Directly moves the camera to a new point, useful when transitioning areas. inline void MoveCamera(vf2d pos){ Mode prevMode=m_nMode; vf2d&prevTarget=*m_pTarget; SetMode(Mode::Simple); SetTarget(pos); Update(0); SetMode(prevMode); SetTarget(prevTarget); } // Update camera, animating if necessary, obeying world boundary rules // returns true if target is visible inline virtual bool Update(const float fElapsedTime) { switch (m_nMode) { case Mode::Simple: { m_vPosition = GetTarget(); } break; case Mode::EdgeMove: { olc::vf2d vOverlap = GetTarget() - m_vPosition; if (vOverlap.x > m_vEdgeTriggerDistance.x) m_vPosition.x += vOverlap.x - m_vEdgeTriggerDistance.x; if (vOverlap.x < -m_vEdgeTriggerDistance.x) m_vPosition.x += vOverlap.x + m_vEdgeTriggerDistance.x; if (vOverlap.y > m_vEdgeTriggerDistance.y) m_vPosition.y += vOverlap.y - m_vEdgeTriggerDistance.y; if (vOverlap.y < -m_vEdgeTriggerDistance.y) m_vPosition.y += vOverlap.y + m_vEdgeTriggerDistance.y; } break; case Mode::LazyFollow: { m_vPosition += (GetTarget() - m_vPosition) * m_fLazyFollowRate * fElapsedTime; } break; case Mode::FixedScreens: { m_vPosition = olc::vf2d(olc::vi2d(GetTarget() / m_vScreenSize) * olc::vi2d(m_vScreenSize)) + (m_vViewSize * 0.5f); } break; } // Make camera target the middle of the view m_vViewPos = m_vPosition - (m_vViewSize * 0.5f); // Clamp to World Boundary (if in place) if (m_bWorldBoundary) { m_vViewPos = m_vViewPos.max(m_vWorldBoundaryPos).min(m_vWorldBoundaryPos + m_vWorldBoundarySize - m_vViewSize); } return GetTarget().x >= m_vViewPos.x && GetTarget().x < (m_vViewPos.x + m_vViewSize.x) && GetTarget().y >= m_vViewPos.y && GetTarget().y < (m_vViewPos.y + m_vViewSize.y); } protected: // Position of camera focus point in the world olc::vf2d m_vPosition; // Rectangular size of camera viewing area olc::vf2d m_vViewSize; // Top left coordinate of camera viewing area olc::vf2d m_vViewPos; // Camera movement mode Mode m_nMode = Mode::Simple; // Target Vector2D object camera should follow (either ref or ptr) olc::vf2d* m_pTarget = nullptr; olc::vf2d m_vLocalTarget; // World Boundary bool m_bWorldBoundary = false; olc::vf2d m_vWorldBoundaryPos = { 0.0f, 0.0f }; olc::vf2d m_vWorldBoundarySize = { 256.0f, 240.0f }; // Mode specific olc::vf2d m_vEdgeTriggerDistance = { 1.0f, 1.0f }; float m_fLazyFollowRate = 4.0f; olc::vi2d m_vScreenSize = { 16,15 }; }; } using namespace olc::utils;