dodatna logika

This commit is contained in:
2026-05-02 18:09:17 +02:00
parent 56d567b77d
commit e4389f035d
16 changed files with 239 additions and 37 deletions

View File

@@ -14,6 +14,9 @@ GAME_ENTITY(Background)
public: public:
void render(Game::Renderer::Renderer* renderer, Game::Renderer::RendererConfig config) override; void render(Game::Renderer::Renderer* renderer, Game::Renderer::RendererConfig config) override;
void onWindowResized(int newWidth, int newHeight) override; void onWindowResized(int newWidth, int newHeight) override;
// Spawn a single trash at the given transform. If seaOnly is true the trash
// will be clamped to the sea side of the land boundary.
void spawnTrashAt(const Object::Transform& tS, bool seaOnly = false);
private: private:
void spawnLevel(int stage); void spawnLevel(int stage);
void spawnFriendly(int stage, int count); void spawnFriendly(int stage, int count);
@@ -22,6 +25,9 @@ GAME_ENTITY(Background)
float mLandBoundaryX = 0.f; float mLandBoundaryX = 0.f;
bool mPendingLevelSpawn = false; bool mPendingLevelSpawn = false;
int mPendingLevelStage = 0; int mPendingLevelStage = 0;
// Periodic friendly spawn settings
float mFriendlySpawnAvgInterval = 6.f; // average seconds between spawns
int mMaxAutoFriendlies = 7; // hard cap for total active friendlies
std::shared_ptr<Game::Renderer::Texture> mSeaTex; std::shared_ptr<Game::Renderer::Texture> mSeaTex;
std::shared_ptr<Game::Renderer::Texture> mEnemyTex; std::shared_ptr<Game::Renderer::Texture> mEnemyTex;
std::shared_ptr<Game::Renderer::Texture> mTrashTex; std::shared_ptr<Game::Renderer::Texture> mTrashTex;

View File

@@ -15,5 +15,6 @@ namespace Game::AGame {
float mMoveSpeedX = 0.f; float mMoveSpeedX = 0.f;
float mMoveSpeedY = 0.f; float mMoveSpeedY = 0.f;
float mDirectionChangeTimer = 0.f; float mDirectionChangeTimer = 0.f;
float mShoreSpawnCooldown = 0.f;
END_GAME_ENTITY() END_GAME_ENTITY()
} }

View File

@@ -14,5 +14,8 @@ namespace Game::AGame {
GAME_ENTITY(Trash) GAME_ENTITY(Trash)
public: public:
void onCollisionEnter(Object::Entity* other) override; void onCollisionEnter(Object::Entity* other) override;
void setSeaOnly(bool v) { mSeaOnly = v; }
private:
bool mSeaOnly = false;
END_GAME_ENTITY() END_GAME_ENTITY()
} }

View File

@@ -21,10 +21,13 @@ namespace Game::Object::Components {
void start(Object::Entity* thisEntity) override; void start(Object::Entity* thisEntity) override;
void update(float deltaTime, Object::Entity* thisEntity) override; void update(float deltaTime, Object::Entity* thisEntity) override;
void setScale(float scale) { mScale = scale; }
float getScale() const { return mScale; }
BoxColliderBounds getBounds() const { return mBounds; } BoxColliderBounds getBounds() const { return mBounds; }
bool isColliding() const { return !mCollidingWith.empty(); } bool isColliding() const { return !mCollidingWith.empty(); }
private: private:
float mScale = 1.f;
BoxColliderBounds mBounds{0.f, 0.f, 0.f, 0.f}; BoxColliderBounds mBounds{0.f, 0.f, 0.f, 0.f};
std::unordered_set<Object::Entity*> mCollidingWith; // Track collisions per-entity so enter/stay/exit callbacks remain correct with multiple colliders std::unordered_set<Object::Entity*> mCollidingWith; // Track collisions per-entity so enter/stay/exit callbacks remain correct with multiple colliders
}; };

View File

@@ -1,10 +1,43 @@
#include <game/agame/background.hpp> #include <game/agame/background.hpp>
#include <game/agame/player.hpp> #include <game/agame/player.hpp>
#include <window/window.hpp> #include <window/window.hpp>
#include <state/gamestate.hpp>
#include <object/camera.hpp> #include <object/camera.hpp>
#include <object/components/boxcollider.hpp>
#include <algorithm> #include <algorithm>
#include <chrono>
#include <ctime>
#include <fstream>
#include <iomanip>
#include <utils.hpp> #include <utils.hpp>
namespace {
void writeFinalScoreFile(int score) {
std::ofstream file("score.txt", std::ios::trunc);
if (!file.is_open()) {
WARN("Failed to open score.txt for writing");
return;
}
const auto now = std::chrono::system_clock::now();
const std::time_t nowTime = std::chrono::system_clock::to_time_t(now);
std::tm localTime{};
#if defined(_WIN32)
localtime_s(&localTime, &nowTime);
#else
localtime_r(&nowTime, &localTime);
#endif
std::string playerName = Game::GameManager::getSharedData<std::string>("playerName");
if (playerName.empty()) playerName = "Player";
file << "Končna statistika igre:\n";
file << "Igralec: " << playerName << "\n";
file << "Točke: " << score << "\n";
file << "Datum: " << std::put_time(&localTime, "%Y-%m-%d %H:%M:%S") << "\n";
}
}
namespace Game::AGame { namespace Game::AGame {
void Background::start() { void Background::start() {
mSeaTex = std::make_shared<Game::Renderer::Texture>("../resources/l3sea.png", SDL_GetRenderer(Window::Window::getSDLWindowBackend()), "seaTex"); mSeaTex = std::make_shared<Game::Renderer::Texture>("../resources/l3sea.png", SDL_GetRenderer(Window::Window::getSDLWindowBackend()), "seaTex");
@@ -90,6 +123,7 @@ namespace Game::AGame {
void Background::spawnFriendly(int stage, int count) { void Background::spawnFriendly(int stage, int count) {
const float viewLeft = -mW / 2.f; const float viewLeft = -mW / 2.f;
const float viewRight = mW / 2.f;
const float viewTop = -mH / 2.f; const float viewTop = -mH / 2.f;
const float viewBottom = mH / 2.f; const float viewBottom = mH / 2.f;
@@ -100,10 +134,38 @@ namespace Game::AGame {
const float halfFriendlyW = mFriendlyTex->getWidth() * tS.adjustedScaleX() / 2.f; const float halfFriendlyW = mFriendlyTex->getWidth() * tS.adjustedScaleX() / 2.f;
const float halfFriendlyH = mFriendlyTex->getHeight() * tS.adjustedScaleY() / 2.f; const float halfFriendlyH = mFriendlyTex->getHeight() * tS.adjustedScaleY() / 2.f;
for (int i = 0; i < count; ++i) {
// Split friendlies: most on land, a smaller number may appear on sea
// Decide how many friendlies appear on the sea.
// For stage 1 keep them on land; for later stages allow at least one on sea
int seaCount = 0;
if (stage > 1) {
seaCount = std::max(1, count / 3); // roughly one third on sea for later stages
}
int landCount = count - seaCount;
// Spawn land friendlies (left side)
for (int i = 0; i < landCount; ++i) {
tS.x = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(viewLeft + halfFriendlyW + 25.f), static_cast<int>(mLandBoundaryX - halfFriendlyW - 25.f))); tS.x = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(viewLeft + halfFriendlyW + 25.f), static_cast<int>(mLandBoundaryX - halfFriendlyW - 25.f)));
tS.y = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(viewTop + halfFriendlyH + 100.f), static_cast<int>(viewBottom - halfFriendlyH - 100.f))); tS.y = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(viewTop + halfFriendlyH + 100.f), static_cast<int>(viewBottom - halfFriendlyH - 100.f)));
GameManager::instantiateEntity(std::make_unique<AGame::Friendly>("Friendly" + std::to_string(stage) + "_" + std::to_string(i + 1), mFriendlyTex, tS)); auto* friendly = State::GameState::getInstance().addEntity(std::make_unique<AGame::Friendly>("Friendly" + std::to_string(stage) + "_L" + std::to_string(i + 1), mFriendlyTex, tS));
if (friendly) {
if (auto* collider = friendly->getComponent<Object::Components::BoxCollider>()) {
collider->setScale(0.75f);
}
}
}
// Spawn a smaller number of friendlies on the sea (right side)
for (int i = 0; i < seaCount; ++i) {
tS.x = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(mLandBoundaryX + halfFriendlyW + 25.f), static_cast<int>(viewRight - halfFriendlyW - 25.f)));
tS.y = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(viewTop + halfFriendlyH + 100.f), static_cast<int>(viewBottom - halfFriendlyH - 100.f)));
auto* friendly = State::GameState::getInstance().addEntity(std::make_unique<AGame::Friendly>("Friendly" + std::to_string(stage) + "_S" + std::to_string(i + 1), mFriendlyTex, tS));
if (friendly) {
if (auto* collider = friendly->getComponent<Object::Components::BoxCollider>()) {
collider->setScale(0.75f);
}
}
} }
} }
@@ -155,6 +217,34 @@ namespace Game::AGame {
spawnFriendly(stage, friendlyCount); spawnFriendly(stage, friendlyCount);
} }
void Background::spawnTrashAt(const Object::Transform& tS, bool seaOnly) {
// Prepare transform and ensure sea-only pop is placed on the sea side
Object::Transform t = tS;
t.rotation = 0.f;
t.scaleX = 5.5f;
t.scaleY = 5.5f;
const float halfTrashW = mTrashTex->getWidth() * t.adjustedScaleX() / 2.f;
if (seaOnly) {
const float minSeaX = mLandBoundaryX + halfTrashW + 25.f;
if (t.x < minSeaX) t.x = minSeaX;
}
// Create a unique-ish name for the auto-spawned trash
const int id = Utils::getUtils().rirng32(0, 1000000);
const std::string name = "Trash_Auto_" + std::to_string(id);
GameManager::instantiateEntity(std::make_unique<AGame::Trash>(name, mTrashTex, t));
// If requested, mark the spawned trash as sea-only
if (seaOnly) {
auto* tr = GameManager::getEntityByName<AGame::Trash>(name);
if (tr) tr->setSeaOnly(true);
}
GameManager::setSharedData("trashActiveCount", GameManager::getSharedData<int>("trashActiveCount") + 1);
}
void Background::update(float deltaTime) { void Background::update(float deltaTime) {
(void)deltaTime; (void)deltaTime;
@@ -162,6 +252,52 @@ namespace Game::AGame {
const int trashCount = GameManager::getSharedData<int>("trashActiveCount"); const int trashCount = GameManager::getSharedData<int>("trashActiveCount");
const int stage = GameManager::getSharedData<int>("gameStage"); const int stage = GameManager::getSharedData<int>("gameStage");
// Periodically spawn a friendly on land or sea with a small probability
// evaluated each update using deltaTime so the average interval is respected.
const int activeFriendlies = GameManager::getSharedData<int>("friendlyActiveCount");
if (activeFriendlies < mMaxAutoFriendlies) {
// Compute chance = deltaTime / avgInterval
const float chance = deltaTime / std::max(0.0001f, mFriendlySpawnAvgInterval);
const int thresh = static_cast<int>(chance * 10000.f);
if (thresh > 0) {
const int roll = Utils::getUtils().rirng32(0, 9999);
if (roll < thresh) {
// Decide side: sea probability increases with stage
float seaProb = (stage > 1) ? 0.3f : 0.1f;
const int sideRoll = Utils::getUtils().rirng32(0, 99);
const bool spawnSea = sideRoll < static_cast<int>(seaProb * 100.f);
Object::Transform tS;
tS.rotation = 0.f;
tS.scaleX = 6.f;
tS.scaleY = 6.f;
const float viewLeft = -mW / 2.f;
const float viewRight = mW / 2.f;
const float viewTop = -mH / 2.f;
const float viewBottom = mH / 2.f;
const float halfFriendlyW = mFriendlyTex->getWidth() * tS.adjustedScaleX() / 2.f;
const float halfFriendlyH = mFriendlyTex->getHeight() * tS.adjustedScaleY() / 2.f;
if (!spawnSea) {
tS.x = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(viewLeft + halfFriendlyW + 25.f), static_cast<int>(mLandBoundaryX - halfFriendlyW - 25.f)));
} else {
tS.x = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(mLandBoundaryX + halfFriendlyW + 25.f), static_cast<int>(viewRight - halfFriendlyW - 25.f)));
}
tS.y = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(viewTop + halfFriendlyH + 100.f), static_cast<int>(viewBottom - halfFriendlyH - 100.f)));
const int id = Utils::getUtils().rirng32(0, 1000000);
const std::string name = std::string("Friendly_Auto_") + std::to_string(id);
auto* friendly = State::GameState::getInstance().addEntity(std::make_unique<AGame::Friendly>(name, mFriendlyTex, tS));
if (friendly) {
if (auto* collider = friendly->getComponent<Object::Components::BoxCollider>()) {
collider->setScale(0.75f);
}
GameManager::setSharedData("friendlyActiveCount", GameManager::getSharedData<int>("friendlyActiveCount") + 1);
}
}
}
}
if (mPendingLevelSpawn) { if (mPendingLevelSpawn) {
if (enemyCount <= 0 && trashCount <= 0) { if (enemyCount <= 0 && trashCount <= 0) {
GameManager::processPendingEntityRemovals(); GameManager::processPendingEntityRemovals();
@@ -176,6 +312,7 @@ namespace Game::AGame {
mPendingLevelSpawn = true; mPendingLevelSpawn = true;
mPendingLevelStage = stage + 1; mPendingLevelStage = stage + 1;
} else if (!GameManager::getSharedData<bool>("gameWon")) { } else if (!GameManager::getSharedData<bool>("gameWon")) {
writeFinalScoreFile(GameManager::getSharedData<int>("gameScore"));
GameManager::setSharedData("gameWon", true); GameManager::setSharedData("gameWon", true);
LOG("All levels cleared"); LOG("All levels cleared");
} }

View File

@@ -1,4 +1,5 @@
#include <game/agame/enemy.hpp> #include <game/agame/enemy.hpp>
#include <game/agame/background.hpp>
#include <object/components/boxcollider.hpp> #include <object/components/boxcollider.hpp>
#include <game/agame/player.hpp> #include <game/agame/player.hpp>
#include <state/gamestate.hpp> #include <state/gamestate.hpp>
@@ -31,8 +32,17 @@ namespace Game::AGame {
return; return;
} }
// Enemies are always visible // Enemies are visible only within a reveal radius around the player
mIsVisible = true; const float revealRadius = GameManager::getSharedData<float>("enemyRevealRadius");
const float px = player->getTransform()->x + (player->getTexture() ? player->getTexture()->getWidth() * player->getTransform()->adjustedScaleX() / 2.f : 0.f);
const float py = player->getTransform()->y + (player->getTexture() ? player->getTexture()->getHeight() * player->getTransform()->adjustedScaleY() / 2.f : 0.f);
const float ew = getTexture() ? getTexture()->getWidth() * mTransform.adjustedScaleX() : 0.f;
const float eh = getTexture() ? getTexture()->getHeight() * mTransform.adjustedScaleY() : 0.f;
const float ex = mTransform.x + ew / 2.f;
const float ey = mTransform.y + eh / 2.f;
const float dxv = px - ex;
const float dyv = py - ey;
mIsVisible = (dxv * dxv + dyv * dyv) <= (revealRadius * revealRadius);
// Semi-random movement with periodic direction changes // Semi-random movement with periodic direction changes
mDirectionChangeTimer += deltaTime; mDirectionChangeTimer += deltaTime;
@@ -47,6 +57,9 @@ namespace Game::AGame {
// Move enemy // Move enemy
mTransform.x += mMoveSpeedX * deltaTime; mTransform.x += mMoveSpeedX * deltaTime;
mTransform.y += mMoveSpeedY * deltaTime; mTransform.y += mMoveSpeedY * deltaTime;
// Decrease shoreline-spawn cooldown
if (mShoreSpawnCooldown > 0.f) mShoreSpawnCooldown = std::max(0.f, mShoreSpawnCooldown - deltaTime);
// Clamp to land section // Clamp to land section
const float landBoundaryX = GameManager::getSharedData<float>("terrainLandBoundaryX"); const float landBoundaryX = GameManager::getSharedData<float>("terrainLandBoundaryX");
@@ -67,6 +80,22 @@ namespace Game::AGame {
if (mTransform.x + halfWidth > landBoundaryX - 25.f) { if (mTransform.x + halfWidth > landBoundaryX - 25.f) {
mTransform.x = landBoundaryX - 25.f - halfWidth; mTransform.x = landBoundaryX - 25.f - halfWidth;
mMoveSpeedX = -std::abs(mMoveSpeedX); mMoveSpeedX = -std::abs(mMoveSpeedX);
// Enemy hit the shoreline on the right side; spawn a trash on the sea side
if (mShoreSpawnCooldown <= 0.f) {
auto* bg = GameManager::getEntityByName<AGame::Background>("BG");
if (bg) {
Object::Transform t;
t.rotation = 0.f;
t.scaleX = 5.5f;
t.scaleY = 5.5f;
// Place trash on sea side at same Y
t.y = mTransform.y;
t.x = landBoundaryX + 10.f; // will be adjusted inside spawnTrashAt
bg->spawnTrashAt(t, true);
}
mShoreSpawnCooldown = 3.0f; // 3 second cooldown per enemy
}
} }
if (mTransform.y - halfHeight < -h / 2.f + 25.f) { if (mTransform.y - halfHeight < -h / 2.f + 25.f) {
mTransform.y = -h / 2.f + 25.f + halfHeight; mTransform.y = -h / 2.f + 25.f + halfHeight;

View File

@@ -33,13 +33,13 @@ namespace Game::AGame {
}; };
if (GameManager::getSharedData<bool>("gameLost")) { if (GameManager::getSharedData<bool>("gameLost")) {
setText("You Died!"); setText("Umrl si!");
anchorTopRight(); anchorTopRight();
return; return;
} }
if (GameManager::getSharedData<bool>("gameWon")) { if (GameManager::getSharedData<bool>("gameWon")) {
setText("You Won!"); setText("Zmagal si!");
anchorTopRight(); anchorTopRight();
return; return;
} }

View File

@@ -126,6 +126,21 @@ namespace Game::AGame {
if (Input::isKeyPressed(SDL_SCANCODE_D)) { mTransform.x += mSpeed * deltaTime; mIsFlipped = true; } if (Input::isKeyPressed(SDL_SCANCODE_D)) { mTransform.x += mSpeed * deltaTime; mIsFlipped = true; }
mSpeed = Input::isKeyPressed(SDL_SCANCODE_LSHIFT) ? 400.f : 200.f; mSpeed = Input::isKeyPressed(SDL_SCANCODE_LSHIFT) ? 400.f : 200.f;
int w = 0;
int h = 0;
SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &w, &h);
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;
const float maxX = w / 2.f - entityWidth;
const float minY = -h / 2.f;
const float maxY = h / 2.f - entityHeight;
if (mTransform.x < minX) mTransform.x = minX;
if (mTransform.x > maxX) mTransform.x = maxX;
if (mTransform.y < minY) mTransform.y = minY;
if (mTransform.y > maxY) mTransform.y = maxY;
if (mIsShipMode && (mTransform.x + halfWidth) < landBoundaryX + mShoreMargin) { if (mIsShipMode && (mTransform.x + halfWidth) < landBoundaryX + mShoreMargin) {
mTransform.x = landBoundaryX + mShoreMargin - halfWidth; mTransform.x = landBoundaryX + mShoreMargin - halfWidth;
} }

View File

@@ -11,6 +11,14 @@ namespace Game::AGame {
void Trash::update(float deltaTime) { void Trash::update(float deltaTime) {
(void)deltaTime; (void)deltaTime;
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; return;
} }

View File

@@ -6,6 +6,7 @@
#include <game/agame/player.hpp> #include <game/agame/player.hpp>
#include <game/agame/background.hpp> #include <game/agame/background.hpp>
#include <game/agame/hudtext.hpp> #include <game/agame/hudtext.hpp>
#include <game/gamemanager.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>
@@ -17,6 +18,12 @@ using namespace Game;
int main() { int main() {
PLNIMP("Letnik3Zadnja - Licenca: LGPLv2.1-only, CC BY-SA 4.0"); PLNIMP("Letnik3Zadnja - Licenca: LGPLv2.1-only, CC BY-SA 4.0");
// Prompt for player name before initializing the window/engine
std::string playerName;
std::cout << "Vnesi ime igralca (pritisni Enter za 'Igralec'): ";
std::getline(std::cin, playerName);
if (playerName.empty()) playerName = "Igralec";
Game::GameManager::setSharedData<std::string>("playerName", playerName);
Window::Window window = Window::Window(); Window::Window window = Window::Window();
window.init(1280, 720, "Game Window"); window.init(1280, 720, "Game Window");

View File

@@ -50,11 +50,11 @@ namespace Game::Object::Components {
float width = 1.f; float width = 1.f;
float height = 1.f; float height = 1.f;
if (const auto tex = thisEntity->getTexture()) { if (const auto tex = thisEntity->getTexture()) {
width = tex->getWidth() * transform->scaleX * UNIVERSAL_SCALE_COEFFICIENT; width = tex->getWidth() * transform->scaleX * UNIVERSAL_SCALE_COEFFICIENT * mScale;
height = tex->getHeight() * transform->scaleY * UNIVERSAL_SCALE_COEFFICIENT; height = tex->getHeight() * transform->scaleY * UNIVERSAL_SCALE_COEFFICIENT * mScale;
} else { } else {
width = transform->scaleX * UNIVERSAL_SCALE_COEFFICIENT; width = transform->scaleX * UNIVERSAL_SCALE_COEFFICIENT * mScale;
height = transform->scaleY * UNIVERSAL_SCALE_COEFFICIENT; height = transform->scaleY * UNIVERSAL_SCALE_COEFFICIENT * mScale;
} }
width = std::max(1.f, width); width = std::max(1.f, width);
height = std::max(1.f, height); height = std::max(1.f, height);

View File

@@ -5,9 +5,9 @@ namespace Game::Object {
: Entity(name, texture, transform), mClickFunction(clickFunction), mX(x), mY(y) { } : Entity(name, texture, transform), mClickFunction(clickFunction), mX(x), mY(y) { }
void UIButton::start() { void UIButton::start() {
// Center the button on the position // Center the button on the requested position
mTransform.x -= mTex->getWidth() * mTransform.adjustedScaleX() / 2.f; mTransform.x = mX - mTex->getWidth() * mTransform.adjustedScaleX() / 2.f;
mTransform.y -= mTex->getHeight() * mTransform.adjustedScaleY() / 2.f; mTransform.y = mY - mTex->getHeight() * mTransform.adjustedScaleY() / 2.f;
} }
void UIButton::update(float deltaTime) { void UIButton::update(float deltaTime) {

View File

@@ -5,9 +5,9 @@ namespace Game::Object {
: Entity(name, font, transform), mX(x), mY(y) { } : Entity(name, font, transform), mX(x), mY(y) { }
void UIText::start() { void UIText::start() {
// Center the text on the position // Center the text on the requested position
mTransform.x -= mTex->getWidth() * mTransform.adjustedScaleX() / 2.f; mTransform.x = mX - mTex->getWidth() * mTransform.adjustedScaleX() / 2.f;
mTransform.y -= mTex->getHeight() * mTransform.adjustedScaleY() / 2.f; mTransform.y = mY - mTex->getHeight() * mTransform.adjustedScaleY() / 2.f;
} }
void UIText::update(float deltaTime) {} /* { void UIText::update(float deltaTime) {} /* {

View File

@@ -8,8 +8,8 @@ namespace Game::Object {
: Entity(name, font, transform), mX(x), mY(y), mConfig(config) { } : Entity(name, font, transform), mX(x), mY(y), mConfig(config) { }
void UITextBox::start() { void UITextBox::start() {
mTransform.x -= mTex->getWidth() * mTransform.adjustedScaleX() / 2.f; mTransform.x = mX - mTex->getWidth() * mTransform.adjustedScaleX() / 2.f;
mTransform.y -= mTex->getHeight() * mTransform.adjustedScaleY() / 2.f; mTransform.y = mY - mTex->getHeight() * mTransform.adjustedScaleY() / 2.f;
mBoxWidth = static_cast<float>(mTex->getWidth()) * mTransform.adjustedScaleX(); mBoxWidth = static_cast<float>(mTex->getWidth()) * mTransform.adjustedScaleX();
mBoxHeight = static_cast<float>(mTex->getHeight()) * mTransform.adjustedScaleY(); mBoxHeight = static_cast<float>(mTex->getHeight()) * mTransform.adjustedScaleY();

View File

@@ -49,6 +49,11 @@ namespace Game::Renderer {
ERROR("SDL_CreateTextureFromSurface Error: " << SDL_GetError() << " (This object may be unusuable)"); ERROR("SDL_CreateTextureFromSurface Error: " << SDL_GetError() << " (This object may be unusuable)");
return; return;
} }
// Fonts look better with linear filtering than with nearest-neighbor scaling.
if (!SDL_SetTextureScaleMode(mTex, SDL_SCALEMODE_LINEAR)) {
WARN("Failed to set texture scale mode to LINEAR for font '" << mId << "': " << SDL_GetError());
}
} }

View File

@@ -40,7 +40,7 @@ namespace Game::Window {
Audio::Audio::getInstance().init(); Audio::Audio::getInstance().init();
mWindow = SDL_CreateWindow(title.c_str(), width, height, SDL_WINDOW_RESIZABLE); mWindow = SDL_CreateWindow(title.c_str(), width, height, 0);
if (!mWindow) { if (!mWindow) {
ERROR("Failed to create window: " << SDL_GetError()); ERROR("Failed to create window: " << SDL_GetError());
SDL_Quit(); SDL_Quit();
@@ -94,36 +94,24 @@ namespace Game::Window {
mRunning = false; mRunning = false;
} }
if (event.type == SDL_EVENT_WINDOW_ENTER_FULLSCREEN) {
SDL_SetWindowFullscreen(mWindow, false);
}
// Window resize event - update the renderer's viewport // Window resize event - update the renderer's viewport
if (event.type == SDL_EVENT_WINDOW_RESIZED) { if (event.type == SDL_EVENT_WINDOW_RESIZED) {
std::scoped_lock lock(mMutex); std::scoped_lock lock(mMutex);
SDL_SetWindowSize(mWindow, mLastWindowWidth, mLastWindowHeight);
SDL_SetRenderViewport(mRenderer.getSDLRenderer(), nullptr); SDL_SetRenderViewport(mRenderer.getSDLRenderer(), nullptr);
int newWidth, newHeight;
SDL_GetWindowSizeInPixels(mWindow, &newWidth, &newHeight);
const int oldWidth = mLastWindowWidth;
const int oldHeight = mLastWindowHeight;
const bool canScale = oldWidth > 0 && oldHeight > 0;
const float scaleX = canScale ? static_cast<float>(newWidth) / static_cast<float>(oldWidth) : 1.f;
const float scaleY = canScale ? static_cast<float>(newHeight) / static_cast<float>(oldHeight) : 1.f;
State::GameState::getInstance().withEntitiesLocked([&](auto& entities) { State::GameState::getInstance().withEntitiesLocked([&](auto& entities) {
for (auto& [name, entity] : entities) { for (auto& [name, entity] : entities) {
(void)name; (void)name;
if (entity) { if (entity) {
if (canScale) { entity->onWindowResized(mLastWindowWidth, mLastWindowHeight);
Object::Transform* transform = entity->getTransform();
transform->x *= scaleX;
transform->y *= scaleY;
}
entity->onWindowResized(newWidth, newHeight);
} }
} }
}); });
mLastWindowWidth = newWidth;
mLastWindowHeight = newHeight;
} }
} }