Compare commits

..

3 Commits

Author SHA1 Message Date
d93e71e716 1 2026-05-19 17:19:54 +02:00
8ff3e29374 neki 2026-05-13 08:06:15 +02:00
892d8f22eb test 2026-05-13 07:58:59 +02:00
18 changed files with 272 additions and 81 deletions

5
TODO.txt Normal file
View File

@@ -0,0 +1,5 @@
Smeti random premikanje - ok
vnos imena kot text box
neko sledenje igralcu (nasprotnikov)
zavezniki naj nekaj delajo
fullscreen

View File

@@ -27,7 +27,7 @@ GAME_ENTITY(Background)
int mPendingLevelStage = 0;
// Periodic friendly spawn settings
float mFriendlySpawnAvgInterval = 6.f; // average seconds between spawns
int mMaxAutoFriendlies = 7; // hard cap for total active friendlies
int mMaxAutoFriendlies = 2; // hard cap for total active friendlies
std::shared_ptr<Game::Renderer::Texture> mSeaTex;
std::shared_ptr<Game::Renderer::Texture> mEnemyTex;
std::shared_ptr<Game::Renderer::Texture> mTrashTex;

View File

@@ -16,5 +16,8 @@ namespace Game::AGame {
float mMoveSpeedY = 0.f;
float mDirectionChangeTimer = 0.f;
float mShoreSpawnCooldown = 0.f;
bool mFollowingPlayer = false;
static constexpr float FOLLOW_DISTANCE = 300.f;
static constexpr float FOLLOW_SPEED = 35.f;
END_GAME_ENTITY()
}

View File

@@ -14,5 +14,11 @@ namespace Game::AGame {
float mMoveSpeedX = 0.f;
float mMoveSpeedY = 0.f;
float mDirectionChangeTimer = 0.f;
bool mOnSea = false;
static constexpr float CLEANUP_RADIUS = 50.f;
static constexpr int CLEANUP_SCORE_BONUS = 5;
static constexpr float LAND_SPEED_MIN = 20.f;
static constexpr float LAND_SPEED_MAX = 50.f;
static constexpr float SEA_SPEED = 14.f;
END_GAME_ENTITY()
}

View File

@@ -58,6 +58,9 @@ namespace Game {
static void destroyEntity(T* entity);
static void processPendingEntityRemovals();
static void pushPlayerPosition(Object::Transform transform) { mPlayerTransformHistory.push_back(transform); }
static void getPlayerPositionHistory(std::vector<Object::Transform>& outHistory) { outHistory = mPlayerTransformHistory; }
private:
int mTargetUpdatesPerSecond = TARGET_UPDATE_RATE;
clock::time_point mLastUpdate;
@@ -65,6 +68,7 @@ namespace Game {
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 std::vector<Object::Transform> mPlayerTransformHistory;
static GameStateEnum mCurrentGameState;
float mLastDelta = 0.f;
};

View File

@@ -11,6 +11,8 @@ namespace Game::Renderer {
float camY;
int screenW;
int screenH;
float scaleX; // Scale from logical (1280) to actual screen width
float scaleY; // Scale from logical (720) to actual screen height
} RendererConfig;
class Renderer {

View File

@@ -35,6 +35,23 @@ namespace {
file << "Igralec: " << playerName << "\n";
file << "Točke: " << score << "\n";
file << "Datum: " << std::put_time(&localTime, "%Y-%m-%d %H:%M:%S") << "\n";
// Replay system
std::vector<Game::Object::Transform> playerHistory;
Game::GameManager::getPlayerPositionHistory(playerHistory);
std::ofstream replayFile("replay.txt", std::ios::trunc);
if (!replayFile.is_open()) {
WARN("Neuspešno odpiranje replay.txt za pisanje");
return;
}
for (const auto& transform : playerHistory) {
replayFile << transform.x << " " << transform.y << " " << transform.rotation << " " << transform.scaleX << " " << transform.scaleY << "\n";
}
LOG("Zapis končne statistike in replaya igre dokončan");
replayFile.close();
file.close();
}
}
@@ -57,7 +74,10 @@ namespace Game::AGame {
mSeaTex->setTiled(true);
}
mTiledScale = 0.5f;
SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &mW, &mH);
// Use logical world dimensions (1280×720) not actual screen size
mW = 1280;
mH = 720;
// Land boundary: left 1/3 of map in centered coordinates
// For 1280px window: -640 (left) + 426.67 (1/3) = -213.33
@@ -128,9 +148,11 @@ namespace Game::AGame {
const float viewBottom = mH / 2.f;
Object::Transform tS;
tS.x = 0.f;
tS.y = 0.f;
tS.rotation = 0.f;
tS.scaleX = 6.f;
tS.scaleY = 6.f;
tS.scaleX = 4.0f;
tS.scaleY = 4.0f;
const float halfFriendlyW = mFriendlyTex->getWidth() * tS.adjustedScaleX() / 2.f;
const float halfFriendlyH = mFriendlyTex->getHeight() * tS.adjustedScaleY() / 2.f;
@@ -194,8 +216,8 @@ namespace Game::AGame {
Object::Transform tS;
tS.rotation = 0.f;
tS.scaleX = 7.f;
tS.scaleY = 7.f;
tS.scaleX = 4.7f;
tS.scaleY = 4.7f;
const float halfEnemyW = mEnemyTex->getWidth() * tS.adjustedScaleX() / 2.f;
const float halfEnemyH = mEnemyTex->getHeight() * tS.adjustedScaleY() / 2.f;
for (int i = 0; i < enemyCount; ++i) {
@@ -268,9 +290,11 @@ namespace Game::AGame {
const bool spawnSea = sideRoll < static_cast<int>(seaProb * 100.f);
Object::Transform tS;
tS.x = 0.f;
tS.y = 0.f;
tS.rotation = 0.f;
tS.scaleX = 6.f;
tS.scaleY = 6.f;
tS.scaleX = 4.0f;
tS.scaleY = 4.0f;
const float viewLeft = -mW / 2.f;
const float viewRight = mW / 2.f;
const float viewTop = -mH / 2.f;
@@ -320,7 +344,8 @@ namespace Game::AGame {
}
void Background::onWindowResized(int newWidth, int newHeight) {
mW = newWidth;
mH = newHeight;
// Always maintain logical world dimensions (1280×720)
mW = 1280;
mH = 720;
}
}

View File

@@ -44,6 +44,22 @@ namespace Game::AGame {
const float dyv = py - ey;
mIsVisible = (dxv * dxv + dyv * dyv) <= (revealRadius * revealRadius);
// Check if player is on land (not in ship mode) and within follow distance
const float distanceToPlayer = std::sqrt(dxv * dxv + dyv * dyv);
const bool playerOnLand = !player->isShipMode();
const bool withinFollowRange = distanceToPlayer <= FOLLOW_DISTANCE;
if (playerOnLand && withinFollowRange) {
// Follow player: calculate direction and move at constant speed
mFollowingPlayer = true;
if (distanceToPlayer > 1.f) { // Avoid division by zero
mMoveSpeedX = (dxv / distanceToPlayer) * FOLLOW_SPEED;
mMoveSpeedY = (dyv / distanceToPlayer) * FOLLOW_SPEED;
}
} else {
// Revert to random movement when player is on sea or out of range
mFollowingPlayer = false;
// Semi-random movement with periodic direction changes
mDirectionChangeTimer += deltaTime;
if (mDirectionChangeTimer > 2.0f) {
@@ -53,6 +69,7 @@ namespace Game::AGame {
mMoveSpeedY = std::sin(angle) * speed;
mDirectionChangeTimer = 0.f;
}
}
// Move enemy
mTransform.x += mMoveSpeedX * deltaTime;
@@ -68,9 +85,9 @@ namespace Game::AGame {
const float halfWidth = entityWidth / 2.f;
const float halfHeight = entityHeight / 2.f;
// Get window dimensions for boundary calculations
int w = 0, h = 0;
SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &w, &h);
// Use logical world dimensions (1280×720) not actual screen size
constexpr int w = 1280;
constexpr int h = 720;
const float leftEdge = -w / 2.f + 25.f;
if (mTransform.x - halfWidth < leftEdge) {

View File

@@ -1,9 +1,12 @@
#include <game/agame/friendly.hpp>
#include <game/gamemanager.hpp>
#include <game/agame/player.hpp>
#include <game/agame/trash.hpp>
#include <object/components/boxcollider.hpp>
#include <state/gamestate.hpp>
#include <algorithm>
#include <cmath>
#include <limits>
#include <utils.hpp>
#include <window/window.hpp>
@@ -13,51 +16,131 @@ namespace Game::AGame {
addComponent<Object::Components::BoxCollider>();
LOG("Zaveznik zagnan: " << getName());
// Initialize random movement
const float landBoundaryX = GameManager::getSharedData<float>("terrainLandBoundaryX");
const float entityWidth = getTexture() ? getTexture()->getWidth() * mTransform.adjustedScaleX() : 0.f;
mOnSea = (mTransform.x + entityWidth / 2.f) > landBoundaryX;
if (!mOnSea) {
// Initialize random movement for land friendlies
const float angle = static_cast<float>(Utils::getUtils().rirng32(0, 360)) * 3.14159f / 180.f;
const float speed = 20.f + static_cast<float>(Utils::getUtils().rirng32(0, 30));
const float speed = LAND_SPEED_MIN + static_cast<float>(Utils::getUtils().rirng32(0, static_cast<int>(LAND_SPEED_MAX - LAND_SPEED_MIN)));
mMoveSpeedX = std::cos(angle) * speed;
mMoveSpeedY = std::sin(angle) * speed;
} else {
mMoveSpeedX = 0.f;
mMoveSpeedY = 0.f;
}
mDirectionChangeTimer = 0.f;
}
void Friendly::update(float deltaTime) {
(void)deltaTime;
// Auto-cleanup nearby trash
const float fw = getTexture() ? getTexture()->getWidth() * mTransform.adjustedScaleX() : 0.f;
const float fh = getTexture() ? getTexture()->getHeight() * mTransform.adjustedScaleY() : 0.f;
const float friendlyCenterX = mTransform.x + fw / 2.f;
const float friendlyCenterY = mTransform.y + fh / 2.f;
// Semi-random movement with periodic direction changes
const float landBoundaryX = GameManager::getSharedData<float>("terrainLandBoundaryX");
mOnSea = friendlyCenterX > landBoundaryX;
if (mOnSea) {
auto snapshot = State::GameState::getInstance().getEntitiesSnapshot();
Trash* nearestTrash = nullptr;
float nearestDistanceSquared = std::numeric_limits<float>::max();
for (auto* entity : snapshot) {
if (!entity) continue;
auto* trash = dynamic_cast<Trash*>(entity);
if (!trash) continue;
const float tw = trash->getTexture() ? trash->getTexture()->getWidth() * trash->getTransform()->adjustedScaleX() : 0.f;
const float th = trash->getTexture() ? trash->getTexture()->getHeight() * trash->getTransform()->adjustedScaleY() : 0.f;
const float trashCenterX = trash->getTransform()->x + tw / 2.f;
const float trashCenterY = trash->getTransform()->y + th / 2.f;
const float dx = trashCenterX - friendlyCenterX;
const float dy = trashCenterY - friendlyCenterY;
const float distanceSquared = dx * dx + dy * dy;
if (distanceSquared < nearestDistanceSquared) {
nearestDistanceSquared = distanceSquared;
nearestTrash = trash;
}
}
if (nearestTrash) {
const float tw = nearestTrash->getTexture() ? nearestTrash->getTexture()->getWidth() * nearestTrash->getTransform()->adjustedScaleX() : 0.f;
const float th = nearestTrash->getTexture() ? nearestTrash->getTexture()->getHeight() * nearestTrash->getTransform()->adjustedScaleY() : 0.f;
const float trashCenterX = nearestTrash->getTransform()->x + tw / 2.f;
const float trashCenterY = nearestTrash->getTransform()->y + th / 2.f;
const float dx = trashCenterX - friendlyCenterX;
const float dy = trashCenterY - friendlyCenterY;
const float distance = std::sqrt(dx * dx + dy * dy);
if (distance > 0.0001f) {
mMoveSpeedX = (dx / distance) * SEA_SPEED;
mMoveSpeedY = (dy / distance) * SEA_SPEED;
}
}
} else {
// Semi-random movement with periodic direction changes on land
mDirectionChangeTimer += deltaTime;
if (mDirectionChangeTimer > 2.0f) {
const float angle = static_cast<float>(Utils::getUtils().rirng32(0, 360)) * 3.14159f / 180.f;
const float speed = 20.f + static_cast<float>(Utils::getUtils().rirng32(0, 30));
const float speed = LAND_SPEED_MIN + static_cast<float>(Utils::getUtils().rirng32(0, static_cast<int>(LAND_SPEED_MAX - LAND_SPEED_MIN)));
mMoveSpeedX = std::cos(angle) * speed;
mMoveSpeedY = std::sin(angle) * speed;
mDirectionChangeTimer = 0.f;
}
}
auto snapshot = State::GameState::getInstance().getEntitiesSnapshot();
for (auto* entity : snapshot) {
if (!entity) continue;
auto* trash = dynamic_cast<Trash*>(entity);
if (!trash) continue;
// Calculate distance to trash
const float tw = trash->getTexture() ? trash->getTexture()->getWidth() * trash->getTransform()->adjustedScaleX() : 0.f;
const float th = trash->getTexture() ? trash->getTexture()->getHeight() * trash->getTransform()->adjustedScaleY() : 0.f;
const float trashCenterX = trash->getTransform()->x + tw / 2.f;
const float trashCenterY = trash->getTransform()->y + th / 2.f;
const float dx = friendlyCenterX - trashCenterX;
const float dy = friendlyCenterY - trashCenterY;
const float distanceSquared = dx * dx + dy * dy;
if (distanceSquared <= CLEANUP_RADIUS * CLEANUP_RADIUS) {
// Clean up this trash: award points and remove it
GameManager::setSharedData("gameScore", GameManager::getSharedData<int>("gameScore") + CLEANUP_SCORE_BONUS);
GameManager::setSharedData("trashActiveCount", std::max(0, GameManager::getSharedData<int>("trashActiveCount") - 1));
GameManager::destroyEntity(trash);
}
}
// Move friendly
mTransform.x += mMoveSpeedX * deltaTime;
mTransform.y += mMoveSpeedY * deltaTime;
// Clamp to land section
const float landBoundaryX = GameManager::getSharedData<float>("terrainLandBoundaryX");
const float entityWidth = getTexture() ? getTexture()->getWidth() * mTransform.adjustedScaleX() : 0.f;
const float entityHeight = getTexture() ? getTexture()->getHeight() * mTransform.adjustedScaleY() : 0.f;
const float halfWidth = entityWidth / 2.f;
const float halfHeight = entityHeight / 2.f;
const float halfWidth = fw / 2.f;
const float halfHeight = fh / 2.f;
// Get window dimensions for boundary calculations
int w = 0, h = 0;
SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &w, &h);
// Use logical world dimensions (1280×720) not actual screen size
constexpr int w = 1280;
constexpr int h = 720;
const float leftEdge = -w / 2.f + 25.f;
if (mTransform.x - halfWidth < leftEdge) {
mTransform.x = leftEdge + halfWidth;
mMoveSpeedX = std::abs(mMoveSpeedX);
}
if (mTransform.x + halfWidth > landBoundaryX - 25.f) {
if (!mOnSea && mTransform.x + halfWidth > landBoundaryX - 25.f) {
mTransform.x = landBoundaryX - 25.f - halfWidth;
mMoveSpeedX = -std::abs(mMoveSpeedX);
}
if (mOnSea && mTransform.x - halfWidth < landBoundaryX + 25.f) {
mTransform.x = landBoundaryX + 25.f + halfWidth;
mMoveSpeedX = std::abs(mMoveSpeedX);
}
if (mTransform.y - halfHeight < -h / 2.f + 25.f) {
mTransform.y = -h / 2.f + 25.f + halfHeight;
mMoveSpeedY = std::abs(mMoveSpeedY);

View File

@@ -13,9 +13,9 @@ namespace Game::AGame {
void HUDText::update(float deltaTime) {
(void)deltaTime;
int windowW = 0;
int windowH = 0;
SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &windowW, &windowH);
// Use logical world dimensions (1280×720) not actual screen size
constexpr int windowW = 1280;
constexpr int windowH = 720;
float camX = 0.f;
float camY = 0.f;
Object::Camera::getInstance().getPosition(camX, camY);

View File

@@ -26,9 +26,9 @@ namespace Game::AGame {
return;
}
int w = 0;
int h = 0;
SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &w, &h);
// Use logical world dimensions (1280×720) not actual screen size
constexpr int w = 1280;
constexpr int h = 720;
const float halfWidth = spawnTex->getWidth() * mTransform.adjustedScaleX() / 2.f;
const float halfHeight = spawnTex->getHeight() * mTransform.adjustedScaleY() / 2.f;
@@ -64,8 +64,8 @@ namespace Game::AGame {
Game::GameManager::setSharedData("gameStage", 1);
Game::GameManager::setSharedData("gameScore", 0);
mTransform.scaleX = 8.f;
mTransform.scaleY = 8.f;
mTransform.scaleX = 5.3f;
mTransform.scaleY = 5.3f;
if (!mShipTex) {
mShipTex = mTex;
@@ -74,9 +74,9 @@ namespace Game::AGame {
mGroundTex = mTex;
}
int w = 0;
int h = 0;
SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &w, &h);
// Use logical world dimensions (1280×720) not actual screen size
constexpr int w = 1280;
constexpr int h = 720;
const float halfWidth = mTex->getWidth() * mTransform.adjustedScaleX() / 2.f;
const float halfHeight = mTex->getHeight() * mTransform.adjustedScaleY() / 2.f;
@@ -134,9 +134,9 @@ namespace Game::AGame {
if (Input::isKeyPressed(SDL_SCANCODE_D)) { mTransform.x += mSpeed * deltaTime; mIsFlipped = true; }
mSpeed = Input::isKeyPressed(SDL_SCANCODE_LSHIFT) ? 400.f : 200.f;
int w = 0;
int h = 0;
SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &w, &h);
// Use logical world dimensions (1280×720) not actual screen size
constexpr int w = 1280;
constexpr int h = 720;
const float entityWidth = mTex ? mTex->getWidth() * mTransform.adjustedScaleX() : 0.f;
const float entityHeight = mTex ? mTex->getHeight() * mTransform.adjustedScaleY() : 0.f;
const float minX = -w / 2.f;
@@ -161,6 +161,9 @@ namespace Game::AGame {
} else if (!mIsShipMode && mGroundTex) {
setTexture(mGroundTex);
}
// Push replay
GameManager::pushPlayerPosition(mTransform);
}
void Player::onCollisionEnter(Object::Entity* other) {

View File

@@ -11,15 +11,20 @@ namespace Game::AGame {
void Trash::update(float deltaTime) {
(void)deltaTime;
if (mSeaOnly) {
/*if (mSeaOnly) {
const float landBoundaryX = GameManager::getSharedData<float>("terrainLandBoundaryX");
const float margin = 25.f;
const float halfWidth = getTexture() ? getTexture()->getWidth() * mTransform.adjustedScaleX() / 2.f : 0.f;
if (mTransform.x - halfWidth < landBoundaryX + margin) {
mTransform.x = landBoundaryX + margin + halfWidth;
}
}
return;
}*/
// Naključno premikanje
mTransform.x += static_cast<float>(Utils::getUtils().rirng32(-50, 50)) * deltaTime;
mTransform.y += static_cast<float>(Utils::getUtils().rirng32(-50, 50)) * deltaTime;
//return;
}
void Trash::onCollisionEnter(Object::Entity* other) {

View File

@@ -67,6 +67,7 @@ namespace Game {
std::unordered_map<std::string, int> GameManager::mSharedInts;
std::unordered_map<std::string, float> GameManager::mSharedFloats;
std::unordered_map<std::string, bool> GameManager::mSharedBools;
std::vector<Object::Transform> GameManager::mPlayerTransformHistory;
void GameManager::removeSharedData(const std::string& key, SharedDataType type) {
if (type == SharedDataType::STRING) {

View File

@@ -30,14 +30,25 @@ int main() {
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));
auto* player = dynamic_cast<AGame::Player*>(State::GameState::getInstance().addEntity(std::make_unique<AGame::Player>("Player", std::make_shared<Game::Renderer::Texture>("../resources/l3ladja.png", window.getRenderer()->getSDLRenderer()), Object::DEFAULT_TRANSFORM)));
auto* player = dynamic_cast<AGame::Player*>(
State::GameState::getInstance().addEntity(
std::make_unique<AGame::Player>(
"Player",
std::make_shared<Game::Renderer::Texture>(
"../resources/l3ladja.png",
window.getRenderer()->getSDLRenderer()
),
Object::DEFAULT_TRANSFORM
)
)
);
if (player) {
player->addComponent<Object::Components::BoxCollider>();
player->setShipTexture(std::make_shared<Game::Renderer::Texture>("../resources/l3ladja.png", window.getRenderer()->getSDLRenderer()));
player->setGroundTexture(std::make_shared<Game::Renderer::Texture>("../resources/l3player.png", window.getRenderer()->getSDLRenderer()));
}
State::GameState::getInstance().addEntity(std::make_unique<AGame::HUDText>("HUD", std::make_shared<Game::Renderer::Font>("../resources/roboto.ttf", window.getRenderer()->getSDLRenderer(), 72, "HUDFont"), Object::Transform{0.f, 0.f, 0.f, 1.f, 1.f}, 320.f, 40.f));
State::GameState::getInstance().addEntity(std::make_unique<AGame::HUDText>("HUD", std::make_shared<Game::Renderer::Font>("../resources/roboto.ttf", window.getRenderer()->getSDLRenderer(), 60, "HUDFont"), Object::Transform{0.f, 0.f, 0.f, 1.f, 1.f}, 320.f, 40.f));
window.run();

View File

@@ -46,7 +46,7 @@ namespace Game::Object {
SDL_GetTextureSize(mTex->getSDLTexture(), &w, &h);
SDL_FRect dst;
dst.w = w * mTransform.scaleX * UNIVERSAL_SCALE_COEFFICIENT; // 1.f is HUGE, so this is just a constant to make the default scale more reasonable
dst.w = w * mTransform.scaleX * UNIVERSAL_SCALE_COEFFICIENT;
dst.h = h * mTransform.scaleY * UNIVERSAL_SCALE_COEFFICIENT;
// Top-left origin; Account for camera position (center the camera on the screen)
@@ -73,7 +73,7 @@ namespace Game::Object {
SDL_GetTextureSize(mTex->getSDLTexture(), &tileW, &tileH);
SDL_FRect dst;
dst.w = tileW * mTiledScale; // Tile size is the original texture size multiplied by the universal scale coefficient (ignoring the entity's scale, since that only affects how many times the texture is tiled, not the size of each tile)
dst.w = tileW * mTiledScale;
dst.h = tileH * mTiledScale;
// Top-left origin; Account for camera position (center the camera on the screen)

View File

@@ -1,5 +1,6 @@
#include <object/ui/uitextbox.hpp>
#include <renderer/renderer.hpp>
#include <object/camera.hpp>
namespace Game::Object {
@@ -8,15 +9,17 @@ namespace Game::Object {
: Entity(name, font, transform), mX(x), mY(y), mConfig(config) { }
void UITextBox::start() {
mTransform.x = mX - mTex->getWidth() * mTransform.adjustedScaleX() / 2.f;
mTransform.y = mY - mTex->getHeight() * mTransform.adjustedScaleY() / 2.f;
mBoxWidth = static_cast<float>(mTex->getWidth()) * mTransform.adjustedScaleX();
mBoxHeight = static_cast<float>(mTex->getHeight()) * mTransform.adjustedScaleY();
// Compute visual box size first (respecting min sizes and scale), then center the box
mBoxWidth = static_cast<float>(mTex ? mTex->getWidth() : 0.f) * mTransform.adjustedScaleX();
mBoxHeight = static_cast<float>(mTex ? mTex->getHeight() : 0.f) * mTransform.adjustedScaleY();
if (mBoxWidth < mConfig.minWidth) mBoxWidth = mConfig.minWidth;
if (mBoxHeight < mConfig.minHeight) mBoxHeight = mConfig.minHeight;
// Center using the computed box dimensions rather than raw texture size so padding/min sizes are respected
mTransform.x = mX - mBoxWidth / 2.f;
mTransform.y = mY - mBoxHeight / 2.f;
refreshVisualText();
}
@@ -112,15 +115,29 @@ namespace Game::Object {
bool UITextBox::isFocused() const { return mIsFocused; }
bool UITextBox::isMouseInsideBox() const {
const float mouseX = Input::getMouseX();
const float mouseY = Input::getMouseY();
// Get screen-space mouse coordinates
const float screenMouseX = Input::getMouseX();
const float screenMouseY = Input::getMouseY();
// Get window dimensions
int windowW = 0, windowH = 0;
SDL_GetWindowSizeInPixels(SDL_GetMouseFocus(), &windowW, &windowH);
// Get camera position
float camX = 0.f, camY = 0.f;
Object::Camera::getInstance().getPosition(camX, camY);
// Convert screen coordinates to world coordinates
const float worldMouseX = screenMouseX - windowW / 2.f + camX;
const float worldMouseY = screenMouseY - windowH / 2.f + camY;
// Check bounds in world space
const float left = mTransform.x - mConfig.paddingX;
const float right = left + mBoxWidth + 2.f * mConfig.paddingX;
const float top = mTransform.y - mConfig.paddingY;
const float bottom = top + mBoxHeight + 2.f * mConfig.paddingY;
return mouseX >= left && mouseX <= right && mouseY >= top && mouseY <= bottom;
return worldMouseX >= left && worldMouseX <= right && worldMouseY >= top && worldMouseY <= bottom;
}
void UITextBox::refreshVisualText() {

View File

@@ -60,8 +60,18 @@ namespace Game::Renderer {
Object::Camera::getInstance().getPosition(camX, camY);
int screenW, screenH;
SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &screenW, &screenH);
// Pass the config to avoid wasting time recalculating it for every entity, since it's not gonna change during the frame
RendererConfig config{ camX, camY, screenW, screenH };
// Logical game world is 1280x720; scale rendering if fullscreen is at different resolution
static constexpr int LOGICAL_WIDTH = 1280;
static constexpr int LOGICAL_HEIGHT = 720;
const float scaleX = static_cast<float>(screenW) / LOGICAL_WIDTH;
const float scaleY = static_cast<float>(screenH) / LOGICAL_HEIGHT;
// Apply SDL render scale to handle fullscreen scaling
SDL_SetRenderScale(mRenderer, scaleX, scaleY);
// Pass the config WITHOUT scaling factors (SDL handles it now)
RendererConfig config{ camX, camY, LOGICAL_WIDTH, LOGICAL_HEIGHT, 1.0f, 1.0f };
try {
Game::State::GameState::getInstance().withEntitiesLocked([&](auto& entities) {
@@ -89,6 +99,9 @@ namespace Game::Renderer {
}
mPresent();
// Reset render scale for next frame
SDL_SetRenderScale(mRenderer, 1.0f, 1.0f);
}
void Renderer::mClear() {

View File

@@ -41,7 +41,7 @@ namespace Game::Window {
Audio::Audio::getInstance().init();
mWindow = SDL_CreateWindow(title.c_str(), width, height, 0);
mWindow = SDL_CreateWindow(title.c_str(), width, height, SDL_WINDOW_FULLSCREEN);
if (!mWindow) {
ERROR("Failed to create window: " << SDL_GetError());
SDL_Quit();
@@ -95,10 +95,6 @@ namespace Game::Window {
mRunning = false;
}
if (event.type == SDL_EVENT_WINDOW_ENTER_FULLSCREEN) {
SDL_SetWindowFullscreen(mWindow, false);
}
// Window resize event - update the renderer's viewport
if (event.type == SDL_EVENT_WINDOW_RESIZED) {
std::scoped_lock lock(mMutex);