Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0790d76236 | ||
|
|
991bccb021 | ||
|
|
cac2457654 | ||
|
|
df9595d19a |
219
examples/TEST_Camera2D.cpp
Normal file
219
examples/TEST_Camera2D.cpp
Normal file
@ -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;
|
||||
}
|
||||
@ -3,7 +3,7 @@
|
||||
olcPixelGameEngine.h
|
||||
|
||||
+-------------------------------------------------------------+
|
||||
| OneLoneCoder Pixel Game Engine v2.20 |
|
||||
| OneLoneCoder Pixel Game Engine v2.21 |
|
||||
| "What do you need? Pixels... Lots of Pixels..." - javidx9 |
|
||||
+-------------------------------------------------------------+
|
||||
|
||||
@ -197,7 +197,7 @@
|
||||
|
||||
Author
|
||||
~~~~~~
|
||||
David Barr, aka javidx9, ©OneLoneCoder 2018, 2019, 2020, 2021, 2022
|
||||
David Barr, aka javidx9, <EFBFBD>OneLoneCoder 2018, 2019, 2020, 2021, 2022
|
||||
*/
|
||||
#pragma endregion
|
||||
|
||||
@ -303,6 +303,7 @@
|
||||
2.20: +DrawRectDecal() - Keeps OneSketchyGuy quiet
|
||||
+GetScreenSize()
|
||||
+olc::Sprite::Size() - returns size of sprite in vector format
|
||||
2.21: Emscripten Overhaul - Thanks Moros!
|
||||
|
||||
!! Apple Platforms will not see these updates immediately - Sorry, I dont have a mac to test... !!
|
||||
!! Volunteers willing to help appreciated, though PRs are manually integrated with credit !!
|
||||
@ -382,7 +383,7 @@ int main()
|
||||
#include <cstring>
|
||||
#pragma endregion
|
||||
|
||||
#define PGE_VER 220
|
||||
#define PGE_VER 221
|
||||
|
||||
// O------------------------------------------------------------------------------O
|
||||
// | COMPILER CONFIGURATION ODDITIES |
|
||||
@ -5817,118 +5818,100 @@ namespace olc
|
||||
// the giant web baby.
|
||||
|
||||
|
||||
// Fullscreen and Resize Observers
|
||||
EM_ASM({
|
||||
|
||||
// cache for reuse
|
||||
Module._olc_EmscriptenShellCss = "width: 100%; height: 70vh; margin-left: auto; margin-right: auto;";
|
||||
// olc_ApsectRatio
|
||||
//
|
||||
// Used by olc_ResizeHandler to calculate the viewport from the
|
||||
// dimensions of the canvas container's element.
|
||||
Module.olc_AspectRatio = $0 / $1;
|
||||
|
||||
// width / height = aspect ratio
|
||||
Module._olc_WindowAspectRatio = $0 / $1;
|
||||
Module.canvas.parentNode.addEventListener("resize", function(e) {
|
||||
|
||||
if (e.defaultPrevented) { e.stopPropagation(); return; }
|
||||
var viewWidth = e.detail.width;
|
||||
var viewHeight = e.detail.width / Module._olc_WindowAspectRatio;
|
||||
if (viewHeight > e.detail.height)
|
||||
{
|
||||
viewHeight = e.detail.height;
|
||||
viewWidth = e.detail.height * Module._olc_WindowAspectRatio;
|
||||
}
|
||||
|
||||
if (Module.canvas.parentNode.className == 'emscripten_border')
|
||||
Module.canvas.parentNode.style.cssText = Module._olc_EmscriptenShellCss + " width: " + viewWidth.toString() + "px; height: " + viewHeight.toString() + "px;";
|
||||
// HACK ALERT!
|
||||
//
|
||||
// Here we assume any html shell that uses 3 or more instance of the class "emscripten"
|
||||
// is using one of the default or minimal emscripten page layouts
|
||||
Module.olc_AssumeDefaultShells = (document.querySelectorAll('.emscripten').length >= 3) ? true : false;
|
||||
|
||||
Module.canvas.setAttribute("width", viewWidth);
|
||||
Module.canvas.setAttribute("height", viewHeight);
|
||||
|
||||
if (document.fullscreenElement != null)
|
||||
{
|
||||
var top = (e.detail.height - viewHeight) / 2;
|
||||
var left = (e.detail.width - viewWidth) / 2;
|
||||
Module.canvas.style.position = "fixed";
|
||||
Module.canvas.style.top = top.toString() + "px";
|
||||
Module.canvas.style.left = left.toString() + "px";
|
||||
Module.canvas.style.width = "";
|
||||
Module.canvas.style.height = "";
|
||||
}
|
||||
|
||||
// trigger PGE update
|
||||
Module._olc_PGE_UpdateWindowSize(viewWidth, viewHeight);
|
||||
// this is really only needed when enter/exiting fullscreen
|
||||
Module.canvas.focus();
|
||||
// prevent this event from ever affecting the document beyond this element
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
// helper function to prevent repeating the same code everywhere
|
||||
Module._olc_ResizeCanvas = function()
|
||||
// olc_ResizeHandler
|
||||
//
|
||||
// Used by olc_Init, and is called when a resize observer and fullscreenchange event is triggered.
|
||||
var olc_ResizeHandler = function()
|
||||
{
|
||||
// yes, we still have to wait, sigh..
|
||||
// are we in fullscreen mode?
|
||||
let isFullscreen = (document.fullscreenElement != null);
|
||||
|
||||
// get the width of the containing element
|
||||
let width = (isFullscreen || !Module.olc_AssumeDefaultShells) ? window.innerWidth : Module.canvas.parentNode.clientWidth;
|
||||
let height = (isFullscreen || !Module.olc_AssumeDefaultShells) ? window.innerHeight : Module.canvas.parentNode.clientHeight;
|
||||
|
||||
// calculate the expected viewport size
|
||||
let viewWidth = width;
|
||||
let viewHeight = width / Module.olc_AspectRatio;
|
||||
|
||||
// if we're taller than the containing element, recalculate based on height
|
||||
if(viewHeight > height)
|
||||
{
|
||||
viewWidth = height * Module.olc_AspectRatio;
|
||||
viewHeight = height;
|
||||
}
|
||||
|
||||
// ensure resulting viewport is in integer space
|
||||
viewWidth = parseInt(viewWidth);
|
||||
viewHeight = parseInt(viewHeight);
|
||||
|
||||
setTimeout(function()
|
||||
{
|
||||
// if default template, stretch width as well
|
||||
if (Module.canvas.parentNode.className == 'emscripten_border')
|
||||
Module.canvas.parentNode.style.cssText = Module._olc_EmscriptenShellCss;
|
||||
|
||||
// override it's styling so we can get it's stretched size
|
||||
Module.canvas.style.cssText = "width: 100%; height: 100%; outline: none;";
|
||||
|
||||
// setup custom resize event
|
||||
var resizeEvent = new CustomEvent('resize',
|
||||
{
|
||||
detail: {
|
||||
width: Module.canvas.clientWidth,
|
||||
height : Module.canvas.clientHeight
|
||||
},
|
||||
bubbles : true,
|
||||
cancelable : true
|
||||
});
|
||||
|
||||
// trigger custom resize event on canvas element
|
||||
Module.canvas.dispatchEvent(resizeEvent);
|
||||
}, 50);
|
||||
// if default shells, apply default styles
|
||||
if(Module.olc_AssumeDefaultShells)
|
||||
Module.canvas.parentNode.setAttribute('style', 'width: 100%; height: 70vh; margin-left: auto; margin-right: auto;');
|
||||
|
||||
// apply viewport dimensions to teh canvas
|
||||
Module.canvas.setAttribute('width', viewWidth);
|
||||
Module.canvas.setAttribute('height', viewHeight);
|
||||
Module.canvas.setAttribute('style', `width: ${viewWidth}px; height: ${viewHeight}px;`);
|
||||
|
||||
// update the PGE window size
|
||||
Module._olc_PGE_UpdateWindowSize(viewWidth, viewHeight);
|
||||
|
||||
// force focus on our PGE canvas
|
||||
Module.canvas.focus();
|
||||
}, 200);
|
||||
};
|
||||
|
||||
|
||||
// Disable Refresh Gesture on mobile
|
||||
document.body.style.cssText += " overscroll-behavior-y: contain;";
|
||||
|
||||
if (Module.canvas.parentNode.className == 'emscripten_border')
|
||||
|
||||
// olc_Init
|
||||
//
|
||||
// set up resize observer and fullscreenchange event handler
|
||||
var olc_Init = function()
|
||||
{
|
||||
// force body to have no margin in emscripten's minimal shell
|
||||
document.body.style.margin = "0";
|
||||
Module.canvas.parentNode.style.cssText = Module._olc_EmscriptenShellCss;
|
||||
}
|
||||
|
||||
Module._olc_ResizeCanvas();
|
||||
|
||||
// observe and react to resizing of the container element
|
||||
var resizeObserver = new ResizeObserver(function(entries) {Module._olc_ResizeCanvas();}).observe(Module.canvas.parentNode);
|
||||
|
||||
// observe and react to changes that occur when entering/exiting fullscreen
|
||||
var mutationObserver = new MutationObserver(function(mutationsList, observer)
|
||||
{
|
||||
// a change has occurred, let's check them out!
|
||||
for (var i = 0; i < mutationsList.length; i++)
|
||||
if(Module.olc_AspectRatio === undefined)
|
||||
{
|
||||
// cycle through all of the newly added elements
|
||||
for (var j = 0; j < mutationsList[i].addedNodes.length; j++)
|
||||
{
|
||||
// if this element is a our canvas, trigger resize
|
||||
if (mutationsList[i].addedNodes[j].id == 'canvas')
|
||||
Module._olc_ResizeCanvas();
|
||||
}
|
||||
setTimeout(function() { Module.olc_Init(); }, 50);
|
||||
return;
|
||||
}
|
||||
}).observe(Module.canvas.parentNode,
|
||||
{
|
||||
attributes: false,
|
||||
childList : true,
|
||||
subtree : false
|
||||
});
|
||||
|
||||
let resizeObserver = new ResizeObserver(function(entries)
|
||||
{
|
||||
Module.olc_ResizeHandler();
|
||||
}).observe(Module.canvas.parentNode);
|
||||
|
||||
// add resize listener on window
|
||||
window.addEventListener("resize", function(e) { Module._olc_ResizeCanvas(); });
|
||||
let mutationObserver = new MutationObserver(function(mutationsList, observer)
|
||||
{
|
||||
setTimeout(function() { Module.olc_ResizeHandler(); }, 200);
|
||||
}).observe(Module.canvas.parentNode, { attributes: false, childList: true, subtree: false });
|
||||
|
||||
window.addEventListener('fullscreenchange', function(e)
|
||||
{
|
||||
setTimeout(function() { Module.olc_ResizeHandler();}, 200);
|
||||
});
|
||||
};
|
||||
|
||||
// set up hooks
|
||||
Module.olc_ResizeHandler = (Module.olc_ResizeHandler != undefined) ? Module.olc_ResizeHandler : olc_ResizeHandler;
|
||||
Module.olc_Init = (Module.olc_Init != undefined) ? Module.olc_Init : olc_Init;
|
||||
|
||||
// run everything!
|
||||
Module.olc_Init();
|
||||
|
||||
}, vWindowSize.x, vWindowSize.y); // Fullscreen and Resize Observers
|
||||
#pragma warning restore format
|
||||
|
||||
258
utilities/olcUTIL_Camera2D.h
Normal file
258
utilities/olcUTIL_Camera2D.h
Normal file
@ -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…
x
Reference in New Issue
Block a user