parent
f467e7dce2
commit
cac2457654
@ -0,0 +1,219 @@ |
||||
/*
|
||||
Example file for olcUTIL_Camera2D.h |
||||
|
||||
License (OLC-3) |
||||
~~~~~~~~~~~~~~~ |
||||
|
||||
Copyright 2018 - 2022 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 |
||||
|
||||
*/ |
||||
|
||||
#define OLC_PGE_APPLICATION |
||||
#include "olcPixelGameEngine.h" |
||||
|
||||
#define OLC_PGEX_TRANSFORMEDVIEW |
||||
#include "extensions/olcPGEX_TransformedView.h" |
||||
|
||||
#include "utilities/olcUTIL_Camera2D.h" |
||||
|
||||
/*
|
||||
To demonstrate the camera, we need a world. In this case its a simle tile |
||||
world of 80x75 tiles, and each tile is 32x32 screen pixels. |
||||
|
||||
A transformed view is used to navigate the world manually via the middle |
||||
mouse button in "free roam" mode, or controlled by the camera.
|
||||
|
||||
Specifically a Tile Transformed View is used, which means all units for |
||||
drawing and for the camera are specified in tile space, i.e. 1 tile is
|
||||
1x1 units (regardless of pixel size) |
||||
|
||||
No assets are used for this application, so the world is constructed |
||||
out of coloured squares so you can see you are moving through it. |
||||
|
||||
Pressing "TAB" key will swap between "free roam" and "play" modes. In |
||||
free roam mode, you can use middle mouse button to pan and zoom around
|
||||
the world. The camera's visible area to the player is highlighted in red. |
||||
In play mode, the camera behaves as it would in a 2D game, depending upon
|
||||
the selected mode. |
||||
*/ |
||||
|
||||
class TEST_Camera2D : public olc::PixelGameEngine |
||||
{ |
||||
public: |
||||
TEST_Camera2D() |
||||
{ |
||||
sAppName = "Camera2D Utility Test"; |
||||
} |
||||
|
||||
// Transformed view object to make world offsetting simple
|
||||
olc::TileTransformedView tv; |
||||
|
||||
// Conveninet constants to define tile map world
|
||||
olc::vi2d m_vWorldSize = { 80, 75 }; |
||||
olc::vi2d m_vTileSize = { 32, 32 }; |
||||
|
||||
// The camera!
|
||||
olc::utils::Camera2D camera; |
||||
|
||||
// The point that represents the player, it is "tracked"
|
||||
// by the camera
|
||||
olc::vf2d vTrackedPoint; |
||||
|
||||
// Flag whether we are in "free roam" or "play" mode
|
||||
bool bFreeRoam = false; |
||||
|
||||
// The world map, stored as a 1D array
|
||||
std::vector<uint8_t> vWorldMap; |
||||
|
||||
public: |
||||
bool OnUserCreate() override |
||||
{ |
||||
// Construct transform view
|
||||
tv = olc::TileTransformedView(GetScreenSize(), m_vTileSize); |
||||
|
||||
// Construct Camera
|
||||
vTrackedPoint = { 20.0f, 20.0f };
|
||||
camera = olc::utils::Camera2D(GetScreenSize() / m_vTileSize, vTrackedPoint); |
||||
|
||||
// Configure Camera
|
||||
camera.SetTarget(vTrackedPoint); |
||||
camera.SetMode(olc::utils::Camera2D::Mode::Simple); |
||||
camera.SetWorldBoundary({ 0.0f, 0.0f }, m_vWorldSize); |
||||
camera.EnableWorldBoundary(true); |
||||
|
||||
// Create "tile map" world with just two tiles
|
||||
vWorldMap.resize(m_vWorldSize.x * m_vWorldSize.y); |
||||
for (int i = 0; i < vWorldMap.size(); i++) |
||||
vWorldMap[i] = ((rand() % 20) == 1) ? 1 : 0;
|
||||
|
||||
// Set background colour
|
||||
Clear(olc::CYAN); |
||||
return true; |
||||
} |
||||
|
||||
bool OnUserUpdate(float fElapsedTime) override |
||||
{ |
||||
// In free roam, middle mouse button pans & zooms
|
||||
if (bFreeRoam) |
||||
tv.HandlePanAndZoom(); |
||||
|
||||
// Handle player "physics" in response to key presses
|
||||
olc::vf2d vVel = { 0.0f, 0.0f }; |
||||
if (GetKey(olc::Key::W).bHeld) vVel += {0, -1}; |
||||
if (GetKey(olc::Key::S).bHeld) vVel += {0, +1}; |
||||
if (GetKey(olc::Key::A).bHeld) vVel += {-1, 0}; |
||||
if (GetKey(olc::Key::D).bHeld) vVel += {+1, 0}; |
||||
vTrackedPoint += vVel * 8.0f * fElapsedTime; |
||||
|
||||
// Switch between "free roam" and "play" mode with TAB key
|
||||
if (GetKey(olc::Key::TAB).bPressed) |
||||
{ |
||||
// Always setup camera to play mode when tab key pressed
|
||||
tv.SetWorldOffset(camera.GetViewPosition()); |
||||
tv.SetWorldScale(m_vTileSize); |
||||
bFreeRoam = !bFreeRoam; |
||||
} |
||||
|
||||
// Switch camera mode in operation
|
||||
if (GetKey(olc::Key::K1).bPressed)
|
||||
camera.SetMode(olc::utils::Camera2D::Mode::Simple); |
||||
if (GetKey(olc::Key::K2).bPressed)
|
||||
camera.SetMode(olc::utils::Camera2D::Mode::EdgeMove); |
||||
if (GetKey(olc::Key::K3).bPressed)
|
||||
camera.SetMode(olc::utils::Camera2D::Mode::LazyFollow); |
||||
if (GetKey(olc::Key::K4).bPressed)
|
||||
camera.SetMode(olc::utils::Camera2D::Mode::FixedScreens); |
||||
|
||||
// Update the camera, if teh tracked object remains visible,
|
||||
// true is returned
|
||||
bool bOnScreen = camera.Update(fElapsedTime); |
||||
|
||||
// In "play" mode, set the transformed view to that required by
|
||||
// the camera
|
||||
if (!bFreeRoam) |
||||
tv.SetWorldOffset(camera.GetViewPosition()); |
||||
|
||||
// Render "tile map", by getting visible tiles
|
||||
olc::vi2d vTileTL = tv.GetTopLeftTile().max({ 0,0 }); |
||||
olc::vi2d vTileBR = tv.GetBottomRightTile().min(m_vWorldSize); |
||||
olc::vi2d vTile; |
||||
// Then looping through them and drawing them
|
||||
for (vTile.y = vTileTL.y; vTile.y < vTileBR.y; vTile.y++) |
||||
for (vTile.x = vTileTL.x; vTile.x < vTileBR.x; vTile.x++) |
||||
{ |
||||
int idx = vTile.y * m_vWorldSize.x + vTile.x; |
||||
|
||||
if (vWorldMap[idx] == 0) |
||||
tv.FillRectDecal(vTile, { 1.0f, 1.0f }, olc::Pixel(40, 40, 40)); |
||||
|
||||
if (vWorldMap[idx] == 1) |
||||
tv.FillRectDecal(vTile, { 1.0f, 1.0f }, olc::Pixel(60, 60, 60));
|
||||
} |
||||
|
||||
// Draw the "player" as a 1x1 cell
|
||||
tv.FillRectDecal(vTrackedPoint - olc::vf2d(0.5f, 0.5f), {1.0f, 1.0f}, olc::BLUE); |
||||
|
||||
// Overlay with information
|
||||
if (bFreeRoam) |
||||
{ |
||||
tv.FillRectDecal(camera.GetViewPosition(), camera.GetViewSize(), olc::PixelF(1.0f, 0.0f, 0.0f, 0.5f)); |
||||
DrawStringPropDecal({ 2, 2 }, "TAB: Free Mode, M-Btn to Pan & Zoom", olc::YELLOW); |
||||
} |
||||
else |
||||
DrawStringPropDecal({ 2,2 }, "TAB: Play Mode", olc::YELLOW); |
||||
|
||||
DrawStringPropDecal({ 2,12 }, "WASD : Move", olc::YELLOW); |
||||
DrawStringPropDecal({ 2,22 }, "CAMERA: 1) Simple 2) EdgeMove 3) LazyFollow 4) Screens", olc::YELLOW);
|
||||
DrawStringPropDecal({ 2,42 }, vTrackedPoint.str(), olc::YELLOW); |
||||
return true; |
||||
} |
||||
}; |
||||
|
||||
int main() |
||||
{ |
||||
TEST_Camera2D demo; |
||||
if (demo.Construct(512, 480, 2, 2)) |
||||
demo.Start(); |
||||
return 0; |
||||
} |
@ -0,0 +1,258 @@ |
||||
/*
|
||||
OneLoneCoder - Camera2D v1.00 |
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
||||
A 2D world camera with various modes |
||||
|
||||
|
||||
License (OLC-3) |
||||
~~~~~~~~~~~~~~~ |
||||
|
||||
Copyright 2018 - 2022 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; |
||||
} |
||||
|
||||
// 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 }; |
||||
}; |
||||
} |
Loading…
Reference in new issue