Removed some agame things; Added VSYNC
This commit is contained in:
@@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
namespace Game::AGame {
|
namespace Game::AGame {
|
||||||
GAME_ENTITY(Background)
|
GAME_ENTITY(Background)
|
||||||
|
public:
|
||||||
|
void onWindowResized(int newWidth, int newHeight) override;
|
||||||
private:
|
private:
|
||||||
Object::Sound mSound;
|
Object::Sound mSound;
|
||||||
END_GAME_ENTITY()
|
END_GAME_ENTITY()
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <object/entity.hpp>
|
|
||||||
#include <renderer/texture.hpp>
|
|
||||||
#include <renderer/font.hpp>
|
|
||||||
#include <object/sound.hpp>
|
|
||||||
|
|
||||||
namespace Game::AGame {
|
|
||||||
GAME_ENTITY(CamController)
|
|
||||||
public:
|
|
||||||
void onWindowResized(int newWidth, int newHeight) override {
|
|
||||||
mScreenW = newWidth;
|
|
||||||
mScreenH = newHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
int mScreenW, mScreenH;
|
|
||||||
END_GAME_ENTITY()
|
|
||||||
}
|
|
||||||
24
include/game/agame/sampletextbox.hpp
Normal file
24
include/game/agame/sampletextbox.hpp
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <object/ui/uitextbox.hpp>
|
||||||
|
|
||||||
|
namespace Game::AGame {
|
||||||
|
class SampleTextBox : public Object::UITextBox {
|
||||||
|
using Object::UITextBox::UITextBox; // Inherit constructors
|
||||||
|
|
||||||
|
public:
|
||||||
|
~SampleTextBox() override = default;
|
||||||
|
|
||||||
|
void start() override {
|
||||||
|
// Call the base class start to initialize the text box
|
||||||
|
mZIndex = 1000; // Ensure it renders on top of most other entities
|
||||||
|
Object::UITextBox::start();
|
||||||
|
setText("Hello, World!");
|
||||||
|
|
||||||
|
mIsActive = false;
|
||||||
|
}
|
||||||
|
void update(float deltaTime) override {
|
||||||
|
// Call the base class update to handle input and text refreshing
|
||||||
|
Object::UITextBox::update(deltaTime);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -11,10 +11,24 @@
|
|||||||
#include <game/input.hpp>
|
#include <game/input.hpp>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
namespace Game {
|
namespace Game {
|
||||||
using clock = std::chrono::steady_clock;
|
using clock = std::chrono::steady_clock;
|
||||||
|
|
||||||
|
enum class GameStateEnum {
|
||||||
|
RUNNING,
|
||||||
|
PAUSED,
|
||||||
|
STOPPED
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class SharedDataType {
|
||||||
|
STRING,
|
||||||
|
INT,
|
||||||
|
FLOAT,
|
||||||
|
BOOL
|
||||||
|
};
|
||||||
|
|
||||||
class GameManager {
|
class GameManager {
|
||||||
public:
|
public:
|
||||||
GameManager() { LOG("Created GameManager"); };
|
GameManager() { LOG("Created GameManager"); };
|
||||||
@@ -27,14 +41,77 @@ namespace Game {
|
|||||||
int getTargetUpdatesPerSecond() { return mTargetUpdatesPerSecond; }
|
int getTargetUpdatesPerSecond() { return mTargetUpdatesPerSecond; }
|
||||||
float getLastDelta() { return mLastDelta; }
|
float getLastDelta() { return mLastDelta; }
|
||||||
|
|
||||||
static void setSharedData(const std::string& key, std::string data);
|
template<typename T>
|
||||||
static std::string getSharedData(const std::string& key);
|
static void setSharedData(const std::string& key, T data);
|
||||||
static void removeSharedData(const std::string& key);
|
template<typename T>
|
||||||
|
static T getSharedData(const std::string& key);
|
||||||
|
static void removeSharedData(const std::string& key, SharedDataType type);
|
||||||
|
|
||||||
|
static GameStateEnum getCurrentGameState() { return mCurrentGameState; }
|
||||||
|
static void setCurrentGameState(GameStateEnum newState) { mCurrentGameState = newState; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int mTargetUpdatesPerSecond = TARGET_UPDATE_RATE;
|
int mTargetUpdatesPerSecond = TARGET_UPDATE_RATE;
|
||||||
clock::time_point mLastUpdate;
|
clock::time_point mLastUpdate;
|
||||||
static std::unordered_map<std::string, std::string> mSharedStrings;
|
static std::unordered_map<std::string, std::string> mSharedStrings;
|
||||||
|
static std::unordered_map<std::string, int> mSharedInts;
|
||||||
|
static std::unordered_map<std::string, float> mSharedFloats;
|
||||||
|
static std::unordered_map<std::string, bool> mSharedBools;
|
||||||
|
static GameStateEnum mCurrentGameState;
|
||||||
float mLastDelta = 0.f;
|
float mLastDelta = 0.f;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void GameManager::setSharedData(const std::string& key, T data) {
|
||||||
|
if constexpr (std::is_same_v<T, std::string>) {
|
||||||
|
mSharedStrings[key] = data;
|
||||||
|
} else if constexpr (std::is_same_v<T, int>) {
|
||||||
|
mSharedInts[key] = data;
|
||||||
|
} else if constexpr (std::is_same_v<T, float>) {
|
||||||
|
mSharedFloats[key] = data;
|
||||||
|
} else if constexpr (std::is_same_v<T, bool>) {
|
||||||
|
mSharedBools[key] = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T GameManager::getSharedData(const std::string& key) {
|
||||||
|
if constexpr (std::is_same_v<T, std::string>) {
|
||||||
|
auto it = mSharedStrings.find(key);
|
||||||
|
if (it != mSharedStrings.end()) {
|
||||||
|
return it->second;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
} else if constexpr (std::is_same_v<T, int>) {
|
||||||
|
auto it = mSharedInts.find(key);
|
||||||
|
if (it != mSharedInts.end()) {
|
||||||
|
return it->second;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else if constexpr (std::is_same_v<T, float>) {
|
||||||
|
auto it = mSharedFloats.find(key);
|
||||||
|
if (it != mSharedFloats.end()) {
|
||||||
|
return it->second;
|
||||||
|
} else {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
} else if constexpr (std::is_same_v<T, bool>) {
|
||||||
|
auto it = mSharedBools.find(key);
|
||||||
|
if (it != mSharedBools.end()) {
|
||||||
|
return it->second;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assert(
|
||||||
|
std::is_same_v<T, std::string> || std::is_same_v<T, int> ||
|
||||||
|
std::is_same_v<T, float> || std::is_same_v<T, bool>,
|
||||||
|
"Unsupported type for shared data"
|
||||||
|
);
|
||||||
|
|
||||||
|
return T{};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
37
include/object/ui/uitextbox.hpp
Normal file
37
include/object/ui/uitextbox.hpp
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <object/entity.hpp>
|
||||||
|
#include <renderer/font.hpp>
|
||||||
|
#include <renderer/texture.hpp>
|
||||||
|
#include <utility>
|
||||||
|
#include <game/input.hpp>
|
||||||
|
|
||||||
|
namespace Game::Object {
|
||||||
|
class UITextBox : public Entity {
|
||||||
|
public:
|
||||||
|
UITextBox(const std::string& name, std::shared_ptr<Renderer::Font> font, const Transform& transform, float x = 0.f, float y = 0.f);
|
||||||
|
~UITextBox() override = default;
|
||||||
|
|
||||||
|
void start() override;
|
||||||
|
void update(float deltaTime) override;
|
||||||
|
|
||||||
|
void setText(const std::string& text);
|
||||||
|
std::string getText() const;
|
||||||
|
std::string getValue() const;
|
||||||
|
bool isFocused() const;
|
||||||
|
|
||||||
|
void setPosition(float x, float y) { mX = x; mY = y; }
|
||||||
|
std::pair<float, float> getPosition() const { return {mX, mY}; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool isMouseInsideBox() const;
|
||||||
|
void refreshVisualText();
|
||||||
|
|
||||||
|
float mX, mY;
|
||||||
|
std::string mText;
|
||||||
|
bool mIsFocused = false;
|
||||||
|
float mBoxWidth = 0.f;
|
||||||
|
float mBoxHeight = 0.f;
|
||||||
|
bool mNeedsTextRefresh = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -24,11 +24,13 @@ namespace Game::Renderer {
|
|||||||
void destroy();
|
void destroy();
|
||||||
|
|
||||||
SDL_Renderer* getSDLRenderer() { return mRenderer; }
|
SDL_Renderer* getSDLRenderer() { return mRenderer; }
|
||||||
|
bool isVSyncEnabled() const { return mVSyncEnabled; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void mClear();
|
void mClear();
|
||||||
void mPresent();
|
void mPresent();
|
||||||
|
|
||||||
SDL_Renderer* mRenderer;
|
SDL_Renderer* mRenderer;
|
||||||
|
bool mVSyncEnabled = false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -44,4 +44,6 @@
|
|||||||
#define PI 3.14159265358979323846f
|
#define PI 3.14159265358979323846f
|
||||||
#define UNIVERSAL_SCALE_COEFFICIENT 0.25f
|
#define UNIVERSAL_SCALE_COEFFICIENT 0.25f
|
||||||
#define TARGET_FPS 60
|
#define TARGET_FPS 60
|
||||||
#define TARGET_UPDATE_RATE 60
|
#define TARGET_UPDATE_RATE 120
|
||||||
|
#define ENABLE_LOW_LATENCY_VSYNC 1
|
||||||
|
#define VSYNC_FPS_OFFSET 2
|
||||||
@@ -40,7 +40,11 @@ namespace Game::Window {
|
|||||||
std::jthread mGameThread;
|
std::jthread mGameThread;
|
||||||
bool mRunning;
|
bool mRunning;
|
||||||
int mTargetFPS = TARGET_FPS;
|
int mTargetFPS = TARGET_FPS;
|
||||||
size_t mFrameCount = 0;
|
int mEffectiveFrameCap = TARGET_FPS;
|
||||||
std::chrono::steady_clock::time_point mLastFPSTime;
|
#if DEBUG
|
||||||
|
size_t mTelemetryFrameCount = 0;
|
||||||
|
double mTelemetryFrameTimeMsTotal = 0.0;
|
||||||
|
#endif
|
||||||
|
std::chrono::steady_clock::time_point mTelemetryStart = std::chrono::steady_clock::now();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -17,6 +17,11 @@ namespace Game::AGame {
|
|||||||
|
|
||||||
LOG("W: " << w << " H: " << h);
|
LOG("W: " << w << " H: " << h);
|
||||||
|
|
||||||
|
mTransform.x = w / 2.f - (w / 3.f);
|
||||||
|
mTransform.y = 0.f;
|
||||||
|
|
||||||
|
mTransform.scaleX = 1.f;
|
||||||
|
mTransform.scaleY = 1.f;
|
||||||
mSound.~Sound();
|
mSound.~Sound();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,4 +43,10 @@ namespace Game::AGame {
|
|||||||
|
|
||||||
//Object::Camera::getInstance().move(1.f, 0.f);
|
//Object::Camera::getInstance().move(1.f, 0.f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Background::onWindowResized(int newWidth, int newHeight) {
|
||||||
|
// Re-center the background on window resize
|
||||||
|
mTransform.x = newWidth / 2.f - (newWidth / 3.f);
|
||||||
|
mTransform.y = 0.f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
#include <game/agame/camcontroller.hpp>
|
|
||||||
#include <window/window.hpp>
|
|
||||||
#include <cmath>
|
|
||||||
#include <object/camera.hpp>
|
|
||||||
#include <state/gamestate.hpp>
|
|
||||||
#include <game/input.hpp>
|
|
||||||
|
|
||||||
namespace Game::AGame {
|
|
||||||
void CamController::start() {
|
|
||||||
mTex = nullptr; // No texture
|
|
||||||
SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &mScreenW, &mScreenH);
|
|
||||||
|
|
||||||
mTransform.x = 0.f;
|
|
||||||
mTransform.y = 0.f;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CamController::update(float deltaTime) {
|
|
||||||
if (!mIsActive) return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,13 +2,15 @@
|
|||||||
#include <window/window.hpp>
|
#include <window/window.hpp>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <game/input.hpp>
|
#include <game/input.hpp>
|
||||||
|
#include <game/gamemanager.hpp>
|
||||||
|
|
||||||
namespace Game::AGame {
|
namespace Game::AGame {
|
||||||
void Player::start() {
|
void Player::start() {
|
||||||
//mSound = Object::Sound("../resources/example.wav", Object::Format::WAV);
|
//mSound = Object::Sound("../resources/example.wav", Object::Format::WAV);
|
||||||
//mSound.play();
|
//mSound.play();
|
||||||
|
|
||||||
mZIndex = 100;
|
mZIndex = 100;
|
||||||
|
Game::GameManager::setSharedData("gameStage", 1);
|
||||||
|
Game::GameManager::setSharedData("gameScore", 0);
|
||||||
|
|
||||||
int w, h;
|
int w, h;
|
||||||
SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &w, &h);
|
SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &w, &h);
|
||||||
@@ -17,7 +19,6 @@ namespace Game::AGame {
|
|||||||
mTransform.y -= mTex->getHeight() * mTransform.adjustedScaleY() / 2.f;
|
mTransform.y -= mTex->getHeight() * mTransform.adjustedScaleY() / 2.f;
|
||||||
|
|
||||||
LOG("W: " << w << " H: " << h);
|
LOG("W: " << w << " H: " << h);
|
||||||
|
|
||||||
//mSound.~Sound();
|
//mSound.~Sound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
namespace Game {
|
namespace Game {
|
||||||
|
GameStateEnum GameManager::mCurrentGameState = GameStateEnum::RUNNING;
|
||||||
|
|
||||||
void GameManager::run(std::stop_token stopToken) {
|
void GameManager::run(std::stop_token stopToken) {
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
LOG("GameManager slave thread started");
|
LOG("GameManager slave thread started");
|
||||||
@@ -10,6 +12,11 @@ namespace Game {
|
|||||||
mLastUpdate = clock::now(); // Get the update
|
mLastUpdate = clock::now(); // Get the update
|
||||||
|
|
||||||
while (!stopToken.stop_requested()) {
|
while (!stopToken.stop_requested()) {
|
||||||
|
if (mCurrentGameState == GameStateEnum::PAUSED) {
|
||||||
|
std::this_thread::sleep_for(100ms);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
clock::time_point now = clock::now();
|
clock::time_point now = clock::now();
|
||||||
std::chrono::duration<float> elapsedDt = now - mLastUpdate;
|
std::chrono::duration<float> elapsedDt = now - mLastUpdate;
|
||||||
float seconds = elapsedDt.count();
|
float seconds = elapsedDt.count();
|
||||||
@@ -31,9 +38,12 @@ namespace Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mLastDelta = seconds;
|
mLastDelta = seconds;
|
||||||
|
|
||||||
mLastUpdate = now;
|
mLastUpdate = now;
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
GameManager::setSharedData("lastDelta", mLastDelta);
|
||||||
|
#endif
|
||||||
|
|
||||||
const auto elapsed = std::chrono::steady_clock::now() - frameStart;
|
const auto elapsed = std::chrono::steady_clock::now() - frameStart;
|
||||||
const auto remaining = frameDuration - elapsed;
|
const auto remaining = frameDuration - elapsed;
|
||||||
if (remaining > 0s) {
|
if (remaining > 0s) {
|
||||||
@@ -44,20 +54,19 @@ namespace Game {
|
|||||||
|
|
||||||
// Statics
|
// Statics
|
||||||
std::unordered_map<std::string, std::string> GameManager::mSharedStrings;
|
std::unordered_map<std::string, std::string> GameManager::mSharedStrings;
|
||||||
|
std::unordered_map<std::string, int> GameManager::mSharedInts;
|
||||||
|
std::unordered_map<std::string, float> GameManager::mSharedFloats;
|
||||||
|
std::unordered_map<std::string, bool> GameManager::mSharedBools;
|
||||||
|
|
||||||
void GameManager::setSharedData(const std::string& key, std::string data) {
|
void GameManager::removeSharedData(const std::string& key, SharedDataType type) {
|
||||||
mSharedStrings[key] = std::move(data);
|
if (type == SharedDataType::STRING) {
|
||||||
}
|
|
||||||
|
|
||||||
std::string GameManager::getSharedData(const std::string& key) {
|
|
||||||
auto it = mSharedStrings.find(key);
|
|
||||||
if (it != mSharedStrings.end()) {
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
return ""; // Key not found
|
|
||||||
}
|
|
||||||
|
|
||||||
void GameManager::removeSharedData(const std::string& key) {
|
|
||||||
mSharedStrings.erase(key);
|
mSharedStrings.erase(key);
|
||||||
|
} else if (type == SharedDataType::INT) {
|
||||||
|
mSharedInts.erase(key);
|
||||||
|
} else if (type == SharedDataType::FLOAT) {
|
||||||
|
mSharedFloats.erase(key);
|
||||||
|
} else if (type == SharedDataType::BOOL) {
|
||||||
|
mSharedBools.erase(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,10 +5,10 @@
|
|||||||
#include <object/transform.hpp>
|
#include <object/transform.hpp>
|
||||||
#include <game/agame/player.hpp>
|
#include <game/agame/player.hpp>
|
||||||
#include <game/agame/background.hpp>
|
#include <game/agame/background.hpp>
|
||||||
#include <game/agame/camcontroller.hpp>
|
|
||||||
#include <renderer/renderer.hpp>
|
#include <renderer/renderer.hpp>
|
||||||
#include <renderer/texture.hpp>
|
#include <renderer/texture.hpp>
|
||||||
#include <renderer/font.hpp>
|
#include <renderer/font.hpp>
|
||||||
|
#include <game/agame/sampletextbox.hpp>
|
||||||
|
|
||||||
using namespace Game;
|
using namespace Game;
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ int main() {
|
|||||||
Window::Window window = Window::Window();
|
Window::Window window = Window::Window();
|
||||||
window.init(1280, 720, "Game Window");
|
window.init(1280, 720, "Game Window");
|
||||||
|
|
||||||
State::GameState::getInstance().addEntity(std::make_unique<AGame::CamController>("Camera Controller", nullptr, Object::DEFAULT_TRANSFORM));
|
State::GameState::getInstance().addEntity(std::make_unique<AGame::SampleTextBox>("Sample Text Box", std::make_shared<Game::Renderer::Font>("../resources/roboto.ttf", window.getRenderer()->getSDLRenderer(), 48, "Roboto"), Object::DEFAULT_TRANSFORM, 640.f, 360.f));
|
||||||
|
|
||||||
//Object::Transform t1{100.f, 100.f, 0.f, 1.f, 1.f};
|
//Object::Transform t1{100.f, 100.f, 0.f, 1.f, 1.f};
|
||||||
State::GameState::getInstance().addEntity(std::make_unique<AGame::Background>("BG", std::make_shared<Game::Renderer::Texture>("../resources/bgtest.png", window.getRenderer()->getSDLRenderer()), Object::DEFAULT_TRANSFORM));
|
State::GameState::getInstance().addEntity(std::make_unique<AGame::Background>("BG", std::make_shared<Game::Renderer::Texture>("../resources/bgtest.png", window.getRenderer()->getSDLRenderer()), Object::DEFAULT_TRANSFORM));
|
||||||
|
|||||||
105
src/object/ui/uitextbox.cpp
Normal file
105
src/object/ui/uitextbox.cpp
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
#include <object/ui/uitextbox.hpp>
|
||||||
|
|
||||||
|
namespace Game::Object {
|
||||||
|
UITextBox::UITextBox(const std::string& name, std::shared_ptr<Renderer::Font> font, const Transform& transform, float x, float y)
|
||||||
|
: Entity(name, font, transform), mX(x), mY(y) { }
|
||||||
|
|
||||||
|
void UITextBox::start() {
|
||||||
|
// Center the text box on the position
|
||||||
|
mTransform.x -= mTex->getWidth() * mTransform.adjustedScaleX() / 2.f;
|
||||||
|
mTransform.y -= mTex->getHeight() * mTransform.adjustedScaleY() / 2.f;
|
||||||
|
|
||||||
|
// Keep a stable interaction area even when current text is empty.
|
||||||
|
mBoxWidth = static_cast<float>(mTex->getWidth()) * mTransform.adjustedScaleX();
|
||||||
|
mBoxHeight = static_cast<float>(mTex->getHeight()) * mTransform.adjustedScaleY();
|
||||||
|
if (mBoxWidth <= 0.f) mBoxWidth = 180.f;
|
||||||
|
if (mBoxHeight <= 0.f) mBoxHeight = 36.f;
|
||||||
|
|
||||||
|
refreshVisualText();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UITextBox::update(float deltaTime) {
|
||||||
|
if (!mIsActive) return;
|
||||||
|
|
||||||
|
if (Input::isMouseButtonJustPressed(SDL_BUTTON_LEFT)) {
|
||||||
|
mIsFocused = isMouseInsideBox();
|
||||||
|
mNeedsTextRefresh = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mIsFocused) {
|
||||||
|
if (mNeedsTextRefresh) {
|
||||||
|
refreshVisualText();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Input::isKeyJustPressed(SDL_SCANCODE_BACKSPACE) && !mText.empty()) {
|
||||||
|
mText.pop_back();
|
||||||
|
mNeedsTextRefresh = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Input::isKeyJustPressed(SDL_SCANCODE_RETURN) || Input::isKeyJustPressed(SDL_SCANCODE_KP_ENTER)) {
|
||||||
|
mIsFocused = false;
|
||||||
|
mNeedsTextRefresh = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int key = 0; key < SDL_SCANCODE_COUNT; ++key) {
|
||||||
|
SDL_Scancode scancode = static_cast<SDL_Scancode>(key);
|
||||||
|
if (!Input::isKeyJustPressed(scancode)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Keycode keycode = SDL_GetKeyFromScancode(scancode, SDL_GetModState(), true);
|
||||||
|
if (keycode >= 32 && keycode <= 126) {
|
||||||
|
mText.push_back(static_cast<char>(keycode));
|
||||||
|
mNeedsTextRefresh = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mNeedsTextRefresh) {
|
||||||
|
refreshVisualText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UITextBox::setText(const std::string& text) {
|
||||||
|
mText = text;
|
||||||
|
mNeedsTextRefresh = true;
|
||||||
|
refreshVisualText();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string UITextBox::getText() const {
|
||||||
|
return mText;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string UITextBox::getValue() const {
|
||||||
|
return mText;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UITextBox::isFocused() const {
|
||||||
|
return mIsFocused;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UITextBox::isMouseInsideBox() const {
|
||||||
|
const float mouseX = Input::getMouseX();
|
||||||
|
const float mouseY = Input::getMouseY();
|
||||||
|
|
||||||
|
const float left = mTransform.x;
|
||||||
|
const float right = mTransform.x + mBoxWidth;
|
||||||
|
const float top = mTransform.y;
|
||||||
|
const float bottom = mTransform.y + mBoxHeight;
|
||||||
|
|
||||||
|
return mouseX >= left && mouseX <= right && mouseY >= top && mouseY <= bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UITextBox::refreshVisualText() {
|
||||||
|
SDL_Color color = mIsFocused ? SDL_Color{255, 240, 180, 255} : SDL_Color{255, 255, 255, 255};
|
||||||
|
std::string rendered = mText.empty() ? " " : mText;
|
||||||
|
if (mIsFocused) {
|
||||||
|
rendered += "_";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::dynamic_pointer_cast<Renderer::Font>(mTex)->build(color, rendered);
|
||||||
|
mNeedsTextRefresh = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -21,6 +21,9 @@ namespace Game::Renderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Renderer::init(SDL_Window* window) {
|
bool Renderer::init(SDL_Window* window) {
|
||||||
|
// Request VSync before/at renderer setup; some backends honor this hint.
|
||||||
|
SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1");
|
||||||
|
|
||||||
mRenderer = SDL_CreateRenderer(window, nullptr);
|
mRenderer = SDL_CreateRenderer(window, nullptr);
|
||||||
if (!mRenderer) {
|
if (!mRenderer) {
|
||||||
std::string errorMsg = "Failed to create renderer: " + std::string(SDL_GetError());
|
std::string errorMsg = "Failed to create renderer: " + std::string(SDL_GetError());
|
||||||
@@ -28,6 +31,11 @@ namespace Game::Renderer {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mVSyncEnabled = SDL_SetRenderVSync(mRenderer, 1);
|
||||||
|
if (!mVSyncEnabled) {
|
||||||
|
WARN("VSync could not be enabled, using software frame pacing fallback: " << SDL_GetError());
|
||||||
|
}
|
||||||
|
|
||||||
if (!SDL_SetRenderDrawColor(mRenderer, 0, 0, 255, 255)) {
|
if (!SDL_SetRenderDrawColor(mRenderer, 0, 0, 255, 255)) {
|
||||||
ERROR("Failed to set renderer draw color: " << SDL_GetError());
|
ERROR("Failed to set renderer draw color: " << SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#include <window/window.hpp>
|
#include <window/window.hpp>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
namespace Game::Window {
|
namespace Game::Window {
|
||||||
std::mutex Window::sMutex;
|
std::mutex Window::sMutex;
|
||||||
|
|
||||||
@@ -56,6 +58,22 @@ namespace Game::Window {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if ENABLE_LOW_LATENCY_VSYNC
|
||||||
|
if (mRenderer.isVSyncEnabled()) {
|
||||||
|
const int vsyncCap = std::max(1, mTargetFPS - VSYNC_FPS_OFFSET);
|
||||||
|
mEffectiveFrameCap = vsyncCap;
|
||||||
|
LOG("Low-latency VSync mode enabled. Target FPS: " << mTargetFPS << ", cap: " << mEffectiveFrameCap);
|
||||||
|
} else {
|
||||||
|
mEffectiveFrameCap = std::max(1, mTargetFPS);
|
||||||
|
LOG("VSync unavailable, using software cap: " << mEffectiveFrameCap);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
mEffectiveFrameCap = std::max(1, mTargetFPS);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
mGameManager.setTargetUpdatesPerSecond(TARGET_UPDATE_RATE);
|
||||||
|
LOG("Target updates per second: " << mGameManager.getTargetUpdatesPerSecond());
|
||||||
|
|
||||||
mGameThread = std::jthread(std::bind_front(&Game::GameManager::run, &mGameManager));
|
mGameThread = std::jthread(std::bind_front(&Game::GameManager::run, &mGameManager));
|
||||||
|
|
||||||
mRunning = true;
|
mRunning = true;
|
||||||
@@ -66,6 +84,8 @@ namespace Game::Window {
|
|||||||
void Window::run() {
|
void Window::run() {
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
while (mRunning) {
|
while (mRunning) {
|
||||||
|
const auto frameStart = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
SDL_PumpEvents();
|
SDL_PumpEvents();
|
||||||
while (SDL_PollEvent(&event)) {
|
while (SDL_PollEvent(&event)) {
|
||||||
if (event.type == SDL_EVENT_QUIT) {
|
if (event.type == SDL_EVENT_QUIT) {
|
||||||
@@ -90,19 +110,33 @@ namespace Game::Window {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mRenderer.renderFrame();
|
mRenderer.renderFrame();
|
||||||
SDL_Delay(1000 / mTargetFPS); // Delay to cap the frame rate to the target FPS
|
|
||||||
|
|
||||||
// Set the window title to show the current FPS for testing
|
// Keep rendering slightly below refresh in low-latency mode to reduce queueing/input delay.
|
||||||
mFrameCount++;
|
const int capFps = std::max(1, mEffectiveFrameCap);
|
||||||
auto now = std::chrono::steady_clock::now();
|
const auto targetFrameDuration = std::chrono::duration<double>(1.0 / static_cast<double>(capFps));
|
||||||
auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - mLastFPSTime).count();
|
const auto elapsed = std::chrono::steady_clock::now() - frameStart;
|
||||||
if (elapsed >= 1) {
|
const auto remaining = targetFrameDuration - elapsed;
|
||||||
int fps = static_cast<int>(mFrameCount / elapsed);
|
if (remaining > std::chrono::duration<double>::zero()) {
|
||||||
std::string title = "Game Window - FPS: " + std::to_string(fps) + " : Update Time: " + std::to_string(mGameManager.getLastDelta());
|
std::this_thread::sleep_for(remaining);
|
||||||
SDL_SetWindowTitle(mWindow, title.c_str());
|
}
|
||||||
mFrameCount = 0;
|
|
||||||
mLastFPSTime = now;
|
#ifdef DEBUG
|
||||||
}
|
const auto frameEnd = std::chrono::steady_clock::now();
|
||||||
|
const double frameMs = std::chrono::duration<double, std::milli>(frameEnd - frameStart).count();
|
||||||
|
mTelemetryFrameTimeMsTotal += frameMs;
|
||||||
|
mTelemetryFrameCount++;
|
||||||
|
|
||||||
|
const auto telemetryElapsed = std::chrono::duration_cast<std::chrono::seconds>(frameEnd - mTelemetryStart).count();
|
||||||
|
if (telemetryElapsed >= 2 && mTelemetryFrameCount > 0) {
|
||||||
|
const double avgFrameMs = mTelemetryFrameTimeMsTotal / static_cast<double>(mTelemetryFrameCount);
|
||||||
|
const double avgFps = 1000.0 / std::max(0.001, avgFrameMs);
|
||||||
|
LOG("Frame telemetry: avg ms=" << avgFrameMs << ", avg fps=" << avgFps << ", cap=" << mEffectiveFrameCap << ", vsync=" << (mRenderer.isVSyncEnabled() ? "on" : "off"));
|
||||||
|
|
||||||
|
mTelemetryFrameTimeMsTotal = 0.0;
|
||||||
|
mTelemetryFrameCount = 0;
|
||||||
|
mTelemetryStart = frameEnd;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user