Compare commits
8 Commits
master
...
d93e71e716
| Author | SHA1 | Date | |
|---|---|---|---|
| d93e71e716 | |||
| 8ff3e29374 | |||
| 892d8f22eb | |||
| d9769bdbbb | |||
| c46443e2f4 | |||
| fcc598adb1 | |||
| e4389f035d | |||
| 56d567b77d |
5
TODO.txt
Normal file
5
TODO.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
Smeti random premikanje - ok
|
||||||
|
vnos imena kot text box
|
||||||
|
neko sledenje igralcu (nasprotnikov)
|
||||||
|
zavezniki naj nekaj delajo
|
||||||
|
fullscreen
|
||||||
@@ -7,16 +7,30 @@
|
|||||||
#include <game/gamemanager.hpp>
|
#include <game/gamemanager.hpp>
|
||||||
#include <game/agame/enemy.hpp>
|
#include <game/agame/enemy.hpp>
|
||||||
#include <game/agame/trash.hpp>
|
#include <game/agame/trash.hpp>
|
||||||
|
#include <game/agame/friendly.hpp>
|
||||||
|
|
||||||
namespace Game::AGame {
|
namespace Game::AGame {
|
||||||
GAME_ENTITY(Background)
|
GAME_ENTITY(Background)
|
||||||
public:
|
public:
|
||||||
|
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:
|
||||||
float mEnemySpawnTimer = 0.f;
|
void spawnLevel(int stage);
|
||||||
float mTimeToSpawn = 5.f;
|
void spawnFriendly(int stage, int count);
|
||||||
int mW, mH;
|
int mW, mH;
|
||||||
|
int mMaxLevels = 2;
|
||||||
|
float mLandBoundaryX = 0.f;
|
||||||
|
bool mPendingLevelSpawn = false;
|
||||||
|
int mPendingLevelStage = 0;
|
||||||
|
// Periodic friendly spawn settings
|
||||||
|
float mFriendlySpawnAvgInterval = 6.f; // average seconds between spawns
|
||||||
|
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> mEnemyTex;
|
||||||
std::shared_ptr<Game::Renderer::Texture> mTrashTex;
|
std::shared_ptr<Game::Renderer::Texture> mTrashTex;
|
||||||
|
std::shared_ptr<Game::Renderer::Texture> mFriendlyTex;
|
||||||
END_GAME_ENTITY()
|
END_GAME_ENTITY()
|
||||||
}
|
}
|
||||||
@@ -10,5 +10,14 @@ namespace Game::AGame {
|
|||||||
GAME_ENTITY(Enemy)
|
GAME_ENTITY(Enemy)
|
||||||
public:
|
public:
|
||||||
void onCollisionEnter(Object::Entity* other) override;
|
void onCollisionEnter(Object::Entity* other) override;
|
||||||
|
bool hasAdjacentEnemy();
|
||||||
|
private:
|
||||||
|
float mMoveSpeedX = 0.f;
|
||||||
|
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()
|
END_GAME_ENTITY()
|
||||||
}
|
}
|
||||||
24
include/game/agame/friendly.hpp
Normal file
24
include/game/agame/friendly.hpp
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <game/gamemanager.hpp>
|
||||||
|
#include <object/entity.hpp>
|
||||||
|
#include <renderer/texture.hpp>
|
||||||
|
#include <renderer/font.hpp>
|
||||||
|
#include <object/sound.hpp>
|
||||||
|
|
||||||
|
namespace Game::AGame {
|
||||||
|
GAME_ENTITY(Friendly)
|
||||||
|
public:
|
||||||
|
void onCollisionEnter(Object::Entity* other) override;
|
||||||
|
private:
|
||||||
|
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()
|
||||||
|
}
|
||||||
14
include/game/agame/hudtext.hpp
Normal file
14
include/game/agame/hudtext.hpp
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <object/ui/uitext.hpp>
|
||||||
|
|
||||||
|
namespace Game::AGame {
|
||||||
|
class HUDText : public Object::UIText {
|
||||||
|
using Object::UIText::UIText;
|
||||||
|
|
||||||
|
public:
|
||||||
|
~HUDText() override = default;
|
||||||
|
void start() override;
|
||||||
|
void update(float deltaTime) override;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -7,9 +7,21 @@
|
|||||||
|
|
||||||
namespace Game::AGame {
|
namespace Game::AGame {
|
||||||
GAME_ENTITY(Player)
|
GAME_ENTITY(Player)
|
||||||
private:
|
public:
|
||||||
Object::Sound mSound;
|
void setShipTexture(std::shared_ptr<Game::Renderer::Texture> tex);
|
||||||
float mSpeed = 200.f; // Pixels per second
|
void setGroundTexture(std::shared_ptr<Game::Renderer::Texture> tex);
|
||||||
[[maybe_unused]] float mHealth = 100.f;
|
void respawnRandomSea(float landBoundaryX);
|
||||||
|
bool isShipMode() const { return mIsShipMode; }
|
||||||
|
void onCollisionEnter(Object::Entity* other) override;
|
||||||
|
private:
|
||||||
|
Object::Sound mSound;
|
||||||
|
float mSpeed = 200.f; // Pixels per second
|
||||||
|
[[maybe_unused]] float mHealth = 100.f;
|
||||||
|
std::shared_ptr<Game::Renderer::Texture> mShipTex;
|
||||||
|
std::shared_ptr<Game::Renderer::Texture> mGroundTex;
|
||||||
|
bool mIsShipMode = true;
|
||||||
|
float mShoreMargin = 40.f;
|
||||||
|
float mStateTransitionCooldown = 1.f;
|
||||||
|
float mStateTransitionCooldownTimer = 0.f;
|
||||||
END_GAME_ENTITY()
|
END_GAME_ENTITY()
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,16 @@
|
|||||||
#include <renderer/font.hpp>
|
#include <renderer/font.hpp>
|
||||||
#include <object/sound.hpp>
|
#include <object/sound.hpp>
|
||||||
|
|
||||||
|
namespace Game::AGame {
|
||||||
|
class Player;
|
||||||
|
}
|
||||||
|
|
||||||
namespace Game::AGame {
|
namespace Game::AGame {
|
||||||
GAME_ENTITY(Trash)
|
GAME_ENTITY(Trash)
|
||||||
|
public:
|
||||||
|
void onCollisionEnter(Object::Entity* other) override;
|
||||||
|
void setSeaOnly(bool v) { mSeaOnly = v; }
|
||||||
|
private:
|
||||||
|
bool mSeaOnly = false;
|
||||||
END_GAME_ENTITY()
|
END_GAME_ENTITY()
|
||||||
}
|
}
|
||||||
@@ -58,6 +58,9 @@ namespace Game {
|
|||||||
static void destroyEntity(T* entity);
|
static void destroyEntity(T* entity);
|
||||||
static void processPendingEntityRemovals();
|
static void processPendingEntityRemovals();
|
||||||
|
|
||||||
|
static void pushPlayerPosition(Object::Transform transform) { mPlayerTransformHistory.push_back(transform); }
|
||||||
|
static void getPlayerPositionHistory(std::vector<Object::Transform>& outHistory) { outHistory = mPlayerTransformHistory; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int mTargetUpdatesPerSecond = TARGET_UPDATE_RATE;
|
int mTargetUpdatesPerSecond = TARGET_UPDATE_RATE;
|
||||||
clock::time_point mLastUpdate;
|
clock::time_point mLastUpdate;
|
||||||
@@ -65,6 +68,7 @@ namespace Game {
|
|||||||
static std::unordered_map<std::string, int> mSharedInts;
|
static std::unordered_map<std::string, int> mSharedInts;
|
||||||
static std::unordered_map<std::string, float> mSharedFloats;
|
static std::unordered_map<std::string, float> mSharedFloats;
|
||||||
static std::unordered_map<std::string, bool> mSharedBools;
|
static std::unordered_map<std::string, bool> mSharedBools;
|
||||||
|
static std::vector<Object::Transform> mPlayerTransformHistory;
|
||||||
static GameStateEnum mCurrentGameState;
|
static GameStateEnum mCurrentGameState;
|
||||||
float mLastDelta = 0.f;
|
float mLastDelta = 0.f;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,11 +17,16 @@ namespace Game::Renderer {
|
|||||||
|
|
||||||
// Build the texture for the font; Call getSDLTexture() afterwards
|
// Build the texture for the font; Call getSDLTexture() afterwards
|
||||||
void build(SDL_Color color, std::string text);
|
void build(SDL_Color color, std::string text);
|
||||||
|
// Rebuild GPU-backed texture after a renderer/device reset
|
||||||
|
bool reload(SDL_Renderer* renderer);
|
||||||
|
|
||||||
SDL_Texture* getSDLTexture();
|
SDL_Texture* getSDLTexture();
|
||||||
std::string getId();
|
std::string getId();
|
||||||
private:
|
private:
|
||||||
TTF_Font* mFont;
|
TTF_Font* mFont;
|
||||||
SDL_Renderer* mRenderer;
|
SDL_Renderer* mRenderer;
|
||||||
|
// Remember last build params so we can rebuild fonts on device reset
|
||||||
|
std::string mLastText;
|
||||||
|
SDL_Color mLastColor;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,8 @@ namespace Game::Renderer {
|
|||||||
float camY;
|
float camY;
|
||||||
int screenW;
|
int screenW;
|
||||||
int screenH;
|
int screenH;
|
||||||
|
float scaleX; // Scale from logical (1280) to actual screen width
|
||||||
|
float scaleY; // Scale from logical (720) to actual screen height
|
||||||
} RendererConfig;
|
} RendererConfig;
|
||||||
|
|
||||||
class Renderer {
|
class Renderer {
|
||||||
|
|||||||
@@ -21,10 +21,15 @@ namespace Game::Renderer {
|
|||||||
float getHeight();
|
float getHeight();
|
||||||
bool isTiled() { return mIsTiled; }
|
bool isTiled() { return mIsTiled; }
|
||||||
void setTiled(bool tiled) { mIsTiled = tiled; }
|
void setTiled(bool tiled) { mIsTiled = tiled; }
|
||||||
|
// Reload GPU-backed texture using a new renderer after device reset
|
||||||
|
virtual bool reload(SDL_Renderer* renderer);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
SDL_Texture* mTex;
|
SDL_Texture* mTex;
|
||||||
std::string mId;
|
std::string mId;
|
||||||
|
// For textures created from disk, store the path so we can reload on device reset
|
||||||
|
std::string mPath;
|
||||||
|
bool mIsFromFile = false;
|
||||||
private:
|
private:
|
||||||
bool mIsTiled = false; // Whether the texture is a tileset that should be rendered as a single tile or not
|
bool mIsTiled = false; // Whether the texture is a tileset that should be rendered as a single tile or not
|
||||||
};
|
};
|
||||||
|
|||||||
BIN
resources/l3friendly.png
Normal file
BIN
resources/l3friendly.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 393 B |
BIN
resources/l3player.png
Normal file
BIN
resources/l3player.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 393 B |
BIN
resources/l3sea.png
Normal file
BIN
resources/l3sea.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 179 B |
@@ -1,18 +1,89 @@
|
|||||||
#include <game/agame/background.hpp>
|
#include <game/agame/background.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>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void writeFinalScoreFile(int score) {
|
||||||
|
std::ofstream file("score.txt", std::ios::trunc);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
WARN("Neuspešno odpiranje score.txt za pisanje");
|
||||||
|
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";
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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");
|
||||||
mEnemyTex = std::make_shared<Game::Renderer::Texture>("../resources/l3enemy.png", SDL_GetRenderer(Window::Window::getSDLWindowBackend()), "enemyTex");
|
mEnemyTex = std::make_shared<Game::Renderer::Texture>("../resources/l3enemy.png", SDL_GetRenderer(Window::Window::getSDLWindowBackend()), "enemyTex");
|
||||||
mTrashTex = std::make_shared<Game::Renderer::Texture>("../resources/l3trash.png", SDL_GetRenderer(Window::Window::getSDLWindowBackend()), "trashTex");
|
mTrashTex = std::make_shared<Game::Renderer::Texture>("../resources/l3trash.png", SDL_GetRenderer(Window::Window::getSDLWindowBackend()), "trashTex");
|
||||||
|
mFriendlyTex = std::make_shared<Game::Renderer::Texture>("../resources/l3friendly.png", SDL_GetRenderer(Window::Window::getSDLWindowBackend()), "friendlyTex");
|
||||||
GameManager::setSharedData("enemyActiveCount", 0);
|
GameManager::setSharedData("enemyActiveCount", 0);
|
||||||
|
GameManager::setSharedData("trashActiveCount", 0);
|
||||||
|
GameManager::setSharedData("friendlyActiveCount", 0);
|
||||||
|
GameManager::setSharedData("gameStage", 1);
|
||||||
|
GameManager::setSharedData("gameWon", false);
|
||||||
|
GameManager::setSharedData("gameLost", false);
|
||||||
|
|
||||||
mZIndex = -1; // Ensure background renders behind other entities
|
mZIndex = -1; // Ensure background renders behind other entities
|
||||||
mTex->setTiled(true); // Set the background texture to be tiled
|
mTex->setTiled(true); // Set the background texture to be tiled
|
||||||
|
if (mSeaTex) {
|
||||||
|
mSeaTex->setTiled(true);
|
||||||
|
}
|
||||||
mTiledScale = 0.5f;
|
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
|
||||||
|
mLandBoundaryX = -static_cast<float>(mW) / 2.f + static_cast<float>(mW) / 3.f;
|
||||||
|
GameManager::setSharedData("terrainLandBoundaryX", mLandBoundaryX);
|
||||||
|
GameManager::setSharedData("enemyRevealRadius", 260.f);
|
||||||
|
|
||||||
mTransform.scaleX *= 10.f;
|
mTransform.scaleX *= 10.f;
|
||||||
mTransform.scaleY *= 10.f;
|
mTransform.scaleY *= 10.f;
|
||||||
@@ -21,76 +92,260 @@ namespace Game::AGame {
|
|||||||
mTransform.y -= mTex->getHeight() * mTransform.adjustedScaleY() / 2.f;
|
mTransform.y -= mTex->getHeight() * mTransform.adjustedScaleY() / 2.f;
|
||||||
|
|
||||||
LOG("W: " << mW << " H: " << mH);
|
LOG("W: " << mW << " H: " << mH);
|
||||||
|
spawnLevel(1);
|
||||||
|
}
|
||||||
|
|
||||||
mTransform.x = mW / 2.f - (mW / 3.f);
|
void Background::render(Game::Renderer::Renderer* renderer, Game::Renderer::RendererConfig config) {
|
||||||
mTransform.y = -mH;
|
if (!renderer || !mTex || !mSeaTex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float worldLeft = -static_cast<float>(mW) / 2.f;
|
||||||
|
const float worldRight = static_cast<float>(mW) / 2.f;
|
||||||
|
const float worldTop = -static_cast<float>(mH) / 2.f;
|
||||||
|
const float worldBottom = static_cast<float>(mH) / 2.f;
|
||||||
|
|
||||||
|
const float landLeft = worldLeft;
|
||||||
|
const float landRight = mLandBoundaryX;
|
||||||
|
const float seaLeft = mLandBoundaryX;
|
||||||
|
const float seaRight = worldRight;
|
||||||
|
|
||||||
|
auto drawSection = [&](const std::shared_ptr<Game::Renderer::Texture>& tex, float startX, float endX) {
|
||||||
|
if (!tex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float tileW = 0.f;
|
||||||
|
float tileH = 0.f;
|
||||||
|
SDL_GetTextureSize(tex->getSDLTexture(), &tileW, &tileH);
|
||||||
|
tileW *= mTiledScale;
|
||||||
|
tileH *= mTiledScale;
|
||||||
|
if (tileW <= 0.f || tileH <= 0.f) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float screenStartX = startX - config.camX + config.screenW / 2.f;
|
||||||
|
const float screenEndX = endX - config.camX + config.screenW / 2.f;
|
||||||
|
const float screenStartY = worldTop - config.camY + config.screenH / 2.f;
|
||||||
|
const float screenEndY = worldBottom - config.camY + config.screenH / 2.f;
|
||||||
|
|
||||||
|
for (float x = screenStartX; x < screenEndX; x += tileW) {
|
||||||
|
for (float y = screenStartY; y < screenEndY; y += tileH) {
|
||||||
|
SDL_FRect dst{ x, y, tileW, tileH };
|
||||||
|
SDL_RenderTexture(renderer->getSDLRenderer(), tex->getSDLTexture(), nullptr, &dst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
drawSection(mTex, landLeft, landRight);
|
||||||
|
drawSection(mSeaTex, seaLeft, seaRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Background::spawnFriendly(int stage, int count) {
|
||||||
|
const float viewLeft = -mW / 2.f;
|
||||||
|
const float viewRight = mW / 2.f;
|
||||||
|
const float viewTop = -mH / 2.f;
|
||||||
|
const float viewBottom = mH / 2.f;
|
||||||
|
|
||||||
|
Object::Transform tS;
|
||||||
|
tS.x = 0.f;
|
||||||
|
tS.y = 0.f;
|
||||||
|
tS.rotation = 0.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;
|
||||||
|
|
||||||
|
// 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.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) + "_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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Background::spawnLevel(int stage) {
|
||||||
|
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 int enemyCount = 2 + stage * 2;
|
||||||
|
const int trashCount = 4 + stage * 3;
|
||||||
|
const int friendlyCount = 1 + (stage - 1);
|
||||||
|
|
||||||
|
GameManager::setSharedData("gameStage", stage);
|
||||||
|
GameManager::setSharedData("enemyActiveCount", enemyCount);
|
||||||
|
GameManager::setSharedData("trashActiveCount", trashCount);
|
||||||
|
GameManager::setSharedData("friendlyActiveCount", friendlyCount);
|
||||||
|
|
||||||
|
if (stage > 1) {
|
||||||
|
auto* player = GameManager::getEntityByName<Player>("Player");
|
||||||
|
if (player) {
|
||||||
|
player->respawnRandomSea(mLandBoundaryX);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object::Transform tS;
|
||||||
|
tS.rotation = 0.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) {
|
||||||
|
tS.x = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(viewLeft + halfEnemyW + 25.f), static_cast<int>(mLandBoundaryX - halfEnemyW - 25.f)));
|
||||||
|
tS.y = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(viewTop + halfEnemyH + 100.f), static_cast<int>(viewBottom - halfEnemyH - 100.f)));
|
||||||
|
GameManager::instantiateEntity(std::make_unique<AGame::Enemy>("Enemy" + std::to_string(stage) + "_" + std::to_string(i + 1), mEnemyTex, tS));
|
||||||
|
}
|
||||||
|
|
||||||
|
tS.scaleX = 5.5f;
|
||||||
|
tS.scaleY = 5.5f;
|
||||||
|
const float halfTrashW = mTrashTex->getWidth() * tS.adjustedScaleX() / 2.f;
|
||||||
|
const float halfTrashH = mTrashTex->getHeight() * tS.adjustedScaleY() / 2.f;
|
||||||
|
for (int i = 0; i < trashCount; ++i) {
|
||||||
|
tS.x = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(mLandBoundaryX + halfTrashW + 25.f), static_cast<int>(viewRight - halfTrashW - 25.f)));
|
||||||
|
tS.y = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(viewTop + halfTrashH + 100.f), static_cast<int>(viewBottom - halfTrashH - 100.f)));
|
||||||
|
GameManager::instantiateEntity(std::make_unique<AGame::Trash>("Trash" + std::to_string(stage) + "_" + std::to_string(i + 1), mTrashTex, tS));
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
mEnemySpawnTimer += deltaTime;
|
(void)deltaTime;
|
||||||
|
|
||||||
int cnt = GameManager::getSharedData<int>("enemyActiveCount");
|
const int enemyCount = GameManager::getSharedData<int>("enemyActiveCount");
|
||||||
if (mEnemySpawnTimer >= mTimeToSpawn && cnt < 5) {
|
const int trashCount = GameManager::getSharedData<int>("trashActiveCount");
|
||||||
mEnemySpawnTimer = 0.f; // RESET
|
const int stage = GameManager::getSharedData<int>("gameStage");
|
||||||
GameManager::setSharedData("enemyActiveCount", cnt + 1);
|
|
||||||
// Spawn Enemy on grass
|
|
||||||
Object::Transform tS;
|
|
||||||
tS.scaleY = 7.f;
|
|
||||||
tS.scaleX = 7.f;
|
|
||||||
tS.rotation = 0.f;
|
|
||||||
|
|
||||||
float camX, camY;
|
// Periodically spawn a friendly on land or sea with a small probability
|
||||||
Object::Camera::getInstance().getPosition(camX, camY);
|
// 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);
|
||||||
|
|
||||||
const float halfEnemyW = mEnemyTex->getWidth() * tS.adjustedScaleX() / 2.f;
|
Object::Transform tS;
|
||||||
const float halfEnemyH = mEnemyTex->getHeight() * tS.adjustedScaleY() / 2.f;
|
tS.x = 0.f;
|
||||||
|
tS.y = 0.f;
|
||||||
|
tS.rotation = 0.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;
|
||||||
|
const float viewBottom = mH / 2.f;
|
||||||
|
const float halfFriendlyW = mFriendlyTex->getWidth() * tS.adjustedScaleX() / 2.f;
|
||||||
|
const float halfFriendlyH = mFriendlyTex->getHeight() * tS.adjustedScaleY() / 2.f;
|
||||||
|
|
||||||
const float viewLeft = camX - (mW / 2.f);
|
if (!spawnSea) {
|
||||||
const float viewRight = camX + (mW / 2.f);
|
tS.x = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(viewLeft + halfFriendlyW + 25.f), static_cast<int>(mLandBoundaryX - halfFriendlyW - 25.f)));
|
||||||
const float viewTop = camY - (mH / 2.f);
|
} else {
|
||||||
const float viewBottom = camY + (mH / 2.f);
|
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)));
|
||||||
|
|
||||||
// Right 1/3 of the currently visible screen, in world coordinates.
|
const int id = Utils::getUtils().rirng32(0, 1000000);
|
||||||
int spawnMinX = static_cast<int>(viewLeft + (2.f * mW / 3.f) + halfEnemyW);
|
const std::string name = std::string("Friendly_Auto_") + std::to_string(id);
|
||||||
int spawnMaxX = static_cast<int>(viewRight - halfEnemyW - 25.f);
|
auto* friendly = State::GameState::getInstance().addEntity(std::make_unique<AGame::Friendly>(name, mFriendlyTex, tS));
|
||||||
int spawnMinY = static_cast<int>(viewTop + halfEnemyH + 100.f);
|
if (friendly) {
|
||||||
int spawnMaxY = static_cast<int>(viewBottom - halfEnemyH - 100.f);
|
if (auto* collider = friendly->getComponent<Object::Components::BoxCollider>()) {
|
||||||
|
collider->setScale(0.75f);
|
||||||
// Safety for tiny windows / huge sprites.
|
}
|
||||||
if (spawnMinX > spawnMaxX) spawnMinX = spawnMaxX = static_cast<int>(camX);
|
GameManager::setSharedData("friendlyActiveCount", GameManager::getSharedData<int>("friendlyActiveCount") + 1);
|
||||||
if (spawnMinY > spawnMaxY) spawnMinY = spawnMaxY = static_cast<int>(camY);
|
}
|
||||||
|
}
|
||||||
tS.x = static_cast<float>(Utils::getUtils().rirng32(spawnMinX, spawnMaxX));
|
}
|
||||||
tS.y = static_cast<float>(Utils::getUtils().rirng32(spawnMinY, spawnMaxY));
|
|
||||||
GameManager::instantiateEntity(std::make_unique<AGame::Enemy>("Enemy" + std::to_string(cnt + 1), mEnemyTex, tS));
|
|
||||||
|
|
||||||
// Spawn Trash at shoreline
|
|
||||||
tS.scaleX = 5.5f;
|
|
||||||
tS.scaleY = 5.5f;
|
|
||||||
tS.rotation = 0.f;
|
|
||||||
tS.x = mTransform.x - 75.f;
|
|
||||||
tS.y = static_cast<float>(Utils::getUtils().rirng32(spawnMinY, spawnMaxY));
|
|
||||||
GameManager::instantiateEntity(std::make_unique<AGame::Trash>("Trash" + std::to_string(cnt + 1), mTrashTex, tS));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*const bool* state = SDL_GetKeyboardState(nullptr);
|
if (mPendingLevelSpawn) {
|
||||||
if (state[SDL_SCANCODE_P]) {
|
if (enemyCount <= 0 && trashCount <= 0) {
|
||||||
mTransform.scaleX *= 2.f;
|
GameManager::processPendingEntityRemovals();
|
||||||
mTransform.scaleY *= 2.f;
|
mPendingLevelSpawn = false;
|
||||||
|
spawnLevel(mPendingLevelStage);
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (state[SDL_SCANCODE_L]) {
|
|
||||||
mTransform.scaleX *= 0.5f;
|
|
||||||
mTransform.scaleY *= 0.5f;
|
|
||||||
}*/
|
|
||||||
//mTransform.rotation += 1.f; // Rotate clockwise for testing
|
|
||||||
//mTransform.scaleX = 1.f + 1.f * std::sin(RUNNING_TIME() / 0.5f); // Pulsate scale for testing
|
|
||||||
//mTransform.scaleY = 1.f + 0.5f * std::cos(RUNNING_TIME() / 0.5f); // Pulsate scale for testing
|
|
||||||
|
|
||||||
//Object::Camera::getInstance().move(1.f, 0.f);
|
if (enemyCount <= 0 && trashCount <= 0) {
|
||||||
|
if (stage < mMaxLevels) {
|
||||||
|
mPendingLevelSpawn = true;
|
||||||
|
mPendingLevelStage = stage + 1;
|
||||||
|
} else if (!GameManager::getSharedData<bool>("gameWon")) {
|
||||||
|
writeFinalScoreFile(GameManager::getSharedData<int>("gameScore"));
|
||||||
|
GameManager::setSharedData("gameWon", true);
|
||||||
|
LOG("Vsi nivoji so zaključeni");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Background::onWindowResized(int newWidth, int newHeight) {
|
void Background::onWindowResized(int newWidth, int newHeight) {
|
||||||
mW = newWidth;
|
// Always maintain logical world dimensions (1280×720)
|
||||||
mH = newHeight;
|
mW = 1280;
|
||||||
|
mH = 720;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,22 +1,175 @@
|
|||||||
#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 <state/gamestate.hpp>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <utils.hpp>
|
||||||
|
#include <game/gamemanager.hpp>
|
||||||
|
#include <window/window.hpp>
|
||||||
|
|
||||||
namespace Game::AGame {
|
namespace Game::AGame {
|
||||||
void Enemy::start() {
|
void Enemy::start() {
|
||||||
mZIndex = 20;
|
mZIndex = 20;
|
||||||
addComponent<Object::Components::BoxCollider>();
|
addComponent<Object::Components::BoxCollider>();
|
||||||
LOG("Enemy started: " << getName());
|
LOG("Sovražnik zagnan: " << getName());
|
||||||
|
|
||||||
|
// Initialize random movement
|
||||||
|
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));
|
||||||
|
mMoveSpeedX = std::cos(angle) * speed;
|
||||||
|
mMoveSpeedY = std::sin(angle) * speed;
|
||||||
|
mDirectionChangeTimer = 0.f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Enemy::update(float deltaTime) {
|
void Enemy::update(float deltaTime) {
|
||||||
return;
|
(void)deltaTime;
|
||||||
|
|
||||||
|
auto* player = GameManager::getEntityByName<Player>("Player");
|
||||||
|
if (!player) {
|
||||||
|
mIsVisible = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enemies are visible only within a reveal radius around the player
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
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));
|
||||||
|
mMoveSpeedX = std::cos(angle) * speed;
|
||||||
|
mMoveSpeedY = std::sin(angle) * speed;
|
||||||
|
mDirectionChangeTimer = 0.f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move enemy
|
||||||
|
mTransform.x += mMoveSpeedX * deltaTime;
|
||||||
|
mTransform.y += mMoveSpeedY * deltaTime;
|
||||||
|
|
||||||
|
// Decrease shoreline-spawn cooldown
|
||||||
|
if (mShoreSpawnCooldown > 0.f) mShoreSpawnCooldown = std::max(0.f, mShoreSpawnCooldown - 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;
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
mTransform.x = landBoundaryX - 25.f - halfWidth;
|
||||||
|
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) {
|
||||||
|
mTransform.y = -h / 2.f + 25.f + halfHeight;
|
||||||
|
mMoveSpeedY = std::abs(mMoveSpeedY);
|
||||||
|
}
|
||||||
|
if (mTransform.y + halfHeight > h / 2.f - 25.f) {
|
||||||
|
mTransform.y = h / 2.f - 25.f - halfHeight;
|
||||||
|
mMoveSpeedY = -std::abs(mMoveSpeedY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Enemy::hasAdjacentEnemy() {
|
||||||
|
if (!getTexture()) return false;
|
||||||
|
|
||||||
|
const float detectionRadius = 40.f;
|
||||||
|
const float enemyWidth = getTexture()->getWidth() * mTransform.adjustedScaleX();
|
||||||
|
const float enemyHeight = getTexture()->getHeight() * mTransform.adjustedScaleY();
|
||||||
|
const float centerX = mTransform.x + enemyWidth / 2.f;
|
||||||
|
const float centerY = mTransform.y + enemyHeight / 2.f;
|
||||||
|
|
||||||
|
auto entities = GameManager::getEntityByName<Object::Entity>("Dummy");
|
||||||
|
if (!entities) {
|
||||||
|
auto snapshot = State::GameState::getInstance().getEntitiesSnapshot();
|
||||||
|
for (auto* other : snapshot) {
|
||||||
|
if (!other || other == this || !dynamic_cast<Enemy*>(other)) continue;
|
||||||
|
auto* otherEnemy = dynamic_cast<Enemy*>(other);
|
||||||
|
if (!otherEnemy || !otherEnemy->getTexture()) continue;
|
||||||
|
const float otherWidth = otherEnemy->getTexture()->getWidth() * otherEnemy->getTransform()->adjustedScaleX();
|
||||||
|
const float otherHeight = otherEnemy->getTexture()->getHeight() * otherEnemy->getTransform()->adjustedScaleY();
|
||||||
|
const float otherCenterX = otherEnemy->getTransform()->x + otherWidth / 2.f;
|
||||||
|
const float otherCenterY = otherEnemy->getTransform()->y + otherHeight / 2.f;
|
||||||
|
const float dx = centerX - otherCenterX;
|
||||||
|
const float dy = centerY - otherCenterY;
|
||||||
|
if (dx * dx + dy * dy <= detectionRadius * detectionRadius) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Enemy::onCollisionEnter(Object::Entity* other) {
|
void Enemy::onCollisionEnter(Object::Entity* other) {
|
||||||
LOG("Enemy '" << getName() << "' collided with '" << other->getName() << "' (onCollisionEnter); Killing myself now!");
|
auto* player = dynamic_cast<Player*>(other);
|
||||||
GameManager::setSharedData("enemyActiveCount", GameManager::getSharedData<int>("enemyActiveCount") - 1);
|
if (!player || !mIsVisible) {
|
||||||
|
return;
|
||||||
// Find in state
|
}
|
||||||
|
|
||||||
|
if (hasAdjacentEnemy()) {
|
||||||
|
LOG("Igralec je trčil v močno skupino onesnaževalcev; konec igre!");
|
||||||
|
GameManager::setSharedData("gameLost", true);
|
||||||
|
GameManager::destroyEntity(player);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG("Sovražnik '" << getName() << "' je trčil v igralca; odstranjujem onesnaževalca in dodeljujem točke");
|
||||||
|
GameManager::setSharedData("enemyActiveCount", std::max(0, GameManager::getSharedData<int>("enemyActiveCount") - 1));
|
||||||
|
GameManager::setSharedData("gameScore", GameManager::getSharedData<int>("gameScore") + 100);
|
||||||
GameManager::destroyEntity(this);
|
GameManager::destroyEntity(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
164
src/game/agame/friendly.cpp
Normal file
164
src/game/agame/friendly.cpp
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
#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>
|
||||||
|
|
||||||
|
namespace Game::AGame {
|
||||||
|
void Friendly::start() {
|
||||||
|
mZIndex = 20;
|
||||||
|
addComponent<Object::Components::BoxCollider>();
|
||||||
|
LOG("Zaveznik zagnan: " << getName());
|
||||||
|
|
||||||
|
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 = 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) {
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
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 = 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 halfWidth = fw / 2.f;
|
||||||
|
const float halfHeight = fh / 2.f;
|
||||||
|
|
||||||
|
// 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 (!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);
|
||||||
|
}
|
||||||
|
if (mTransform.y + halfHeight > h / 2.f - 25.f) {
|
||||||
|
mTransform.y = h / 2.f - 25.f - halfHeight;
|
||||||
|
mMoveSpeedY = -std::abs(mMoveSpeedY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Friendly::onCollisionEnter(Object::Entity* other) {
|
||||||
|
auto* player = dynamic_cast<Player*>(other);
|
||||||
|
if (!player) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameManager::setSharedData("friendlyActiveCount", std::max(0, GameManager::getSharedData<int>("friendlyActiveCount") - 1));
|
||||||
|
GameManager::setSharedData("gameScore", GameManager::getSharedData<int>("gameScore") - 50);
|
||||||
|
GameManager::destroyEntity(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
59
src/game/agame/hudtext.cpp
Normal file
59
src/game/agame/hudtext.cpp
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#include <game/agame/hudtext.hpp>
|
||||||
|
#include <game/gamemanager.hpp>
|
||||||
|
#include <window/window.hpp>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace Game::AGame {
|
||||||
|
void HUDText::start() {
|
||||||
|
mZIndex = 1000;
|
||||||
|
Object::UIText::start();
|
||||||
|
setText("Level 1 | Score 0 | Trash 0 | Polluters 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
void HUDText::update(float deltaTime) {
|
||||||
|
(void)deltaTime;
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
auto anchorTopRight = [&]() {
|
||||||
|
if (!mTex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float marginX = 24.f;
|
||||||
|
const float marginY = 24.f;
|
||||||
|
const float textWidth = mTex->getWidth() * mTransform.adjustedScaleX();
|
||||||
|
mTransform.x = camX + windowW / 2.f - marginX - textWidth;
|
||||||
|
mTransform.y = camY - windowH / 2.f + marginY;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (GameManager::getSharedData<bool>("gameLost")) {
|
||||||
|
setText("Umrl si!");
|
||||||
|
anchorTopRight();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GameManager::getSharedData<bool>("gameWon")) {
|
||||||
|
setText("Zmagal si!");
|
||||||
|
anchorTopRight();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string playerName = GameManager::getSharedData<std::string>("playerName");
|
||||||
|
|
||||||
|
std::stringstream stream;
|
||||||
|
stream << "Igralec: " << (playerName.empty() ? std::string("Anonimni") : playerName)
|
||||||
|
<< " | Level " << GameManager::getSharedData<int>("gameStage")
|
||||||
|
<< " | Točke " << GameManager::getSharedData<int>("gameScore")
|
||||||
|
<< " | Smeti " << GameManager::getSharedData<int>("trashActiveCount")
|
||||||
|
<< " | Sovražniki " << GameManager::getSharedData<int>("enemyActiveCount");
|
||||||
|
|
||||||
|
setText(stream.str());
|
||||||
|
anchorTopRight();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,8 +3,60 @@
|
|||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <game/input.hpp>
|
#include <game/input.hpp>
|
||||||
#include <game/gamemanager.hpp>
|
#include <game/gamemanager.hpp>
|
||||||
|
#include <utils.hpp>
|
||||||
|
|
||||||
namespace Game::AGame {
|
namespace Game::AGame {
|
||||||
|
void Player::setShipTexture(std::shared_ptr<Game::Renderer::Texture> tex) {
|
||||||
|
mShipTex = std::move(tex);
|
||||||
|
if (mIsShipMode && mShipTex) {
|
||||||
|
setTexture(mShipTex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::setGroundTexture(std::shared_ptr<Game::Renderer::Texture> tex) {
|
||||||
|
mGroundTex = std::move(tex);
|
||||||
|
if (!mIsShipMode && mGroundTex) {
|
||||||
|
setTexture(mGroundTex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::respawnRandomSea(float landBoundaryX) {
|
||||||
|
auto spawnTex = mShipTex ? mShipTex : mTex;
|
||||||
|
if (!spawnTex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
const float minCenterX = landBoundaryX + mShoreMargin + halfWidth;
|
||||||
|
const float maxCenterX = w / 2.f - halfWidth - 10.f;
|
||||||
|
const float minCenterY = -h / 2.f + halfHeight;
|
||||||
|
const float maxCenterY = h / 2.f - halfHeight;
|
||||||
|
|
||||||
|
float centerX = minCenterX;
|
||||||
|
if (maxCenterX > minCenterX) {
|
||||||
|
centerX = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(minCenterX), static_cast<int>(maxCenterX)));
|
||||||
|
}
|
||||||
|
|
||||||
|
float centerY = 0.f;
|
||||||
|
if (maxCenterY > minCenterY) {
|
||||||
|
centerY = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(minCenterY), static_cast<int>(maxCenterY)));
|
||||||
|
}
|
||||||
|
|
||||||
|
mTransform.x = centerX - halfWidth;
|
||||||
|
mTransform.y = centerY - halfHeight;
|
||||||
|
|
||||||
|
mIsShipMode = true;
|
||||||
|
if (mShipTex) {
|
||||||
|
setTexture(mShipTex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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();
|
||||||
@@ -12,15 +64,41 @@ namespace Game::AGame {
|
|||||||
Game::GameManager::setSharedData("gameStage", 1);
|
Game::GameManager::setSharedData("gameStage", 1);
|
||||||
Game::GameManager::setSharedData("gameScore", 0);
|
Game::GameManager::setSharedData("gameScore", 0);
|
||||||
|
|
||||||
int w, h;
|
mTransform.scaleX = 5.3f;
|
||||||
SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &w, &h);
|
mTransform.scaleY = 5.3f;
|
||||||
|
|
||||||
mTransform.scaleX = 8.f;
|
if (!mShipTex) {
|
||||||
mTransform.scaleY = 8.f;
|
mShipTex = mTex;
|
||||||
|
}
|
||||||
|
if (!mGroundTex) {
|
||||||
|
mGroundTex = mTex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
const float minX = -w / 2.f + halfWidth;
|
||||||
|
const float maxX = w / 2.f - halfWidth;
|
||||||
|
const float minY = -h / 2.f + halfHeight;
|
||||||
|
const float maxY = h / 2.f - halfHeight;
|
||||||
|
|
||||||
|
const float landBoundaryX = Game::GameManager::getSharedData<float>("terrainLandBoundaryX");
|
||||||
|
mTransform.x = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(minX), static_cast<int>(maxX)));
|
||||||
|
mTransform.y = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(minY), static_cast<int>(maxY)));
|
||||||
|
mIsShipMode = (mTransform.x + halfWidth) >= landBoundaryX;
|
||||||
|
|
||||||
mTransform.x -= mTex->getWidth() * mTransform.adjustedScaleX() / 2.f;
|
mTransform.x -= mTex->getWidth() * mTransform.adjustedScaleX() / 2.f;
|
||||||
mTransform.y -= mTex->getHeight() * mTransform.adjustedScaleY() / 2.f;
|
mTransform.y -= mTex->getHeight() * mTransform.adjustedScaleY() / 2.f;
|
||||||
|
|
||||||
|
if (mIsShipMode && mShipTex) {
|
||||||
|
setTexture(mShipTex);
|
||||||
|
} else if (!mIsShipMode && mGroundTex) {
|
||||||
|
setTexture(mGroundTex);
|
||||||
|
}
|
||||||
|
|
||||||
LOG("W: " << w << " H: " << h);
|
LOG("W: " << w << " H: " << h);
|
||||||
//mSound.~Sound();
|
//mSound.~Sound();
|
||||||
}
|
}
|
||||||
@@ -31,11 +109,67 @@ namespace Game::AGame {
|
|||||||
//mTransform.scaleY = 1.f + 0.5f * std::cos(RUNNING_TIME() / 0.5f); // Pulsate scale for testing
|
//mTransform.scaleY = 1.f + 0.5f * std::cos(RUNNING_TIME() / 0.5f); // Pulsate scale for testing
|
||||||
//Object::Camera::getInstance().move(1.f, 0.f);
|
//Object::Camera::getInstance().move(1.f, 0.f);
|
||||||
|
|
||||||
|
if (mStateTransitionCooldownTimer > 0.f) {
|
||||||
|
mStateTransitionCooldownTimer -= deltaTime;
|
||||||
|
if (mStateTransitionCooldownTimer < 0.f) {
|
||||||
|
mStateTransitionCooldownTimer = 0.f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const float landBoundaryX = Game::GameManager::getSharedData<float>("terrainLandBoundaryX");
|
||||||
|
const float halfWidth = mTex->getWidth() * mTransform.adjustedScaleX() / 2.f;
|
||||||
|
|
||||||
|
if (Input::isKeyPressed(SDL_SCANCODE_E)) {
|
||||||
|
const bool nearShore = std::abs((mTransform.x + halfWidth) - landBoundaryX) <= mShoreMargin;
|
||||||
|
if (nearShore && mStateTransitionCooldownTimer <= 0.f) {
|
||||||
|
mIsShipMode = !mIsShipMode;
|
||||||
|
mStateTransitionCooldownTimer = mStateTransitionCooldown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Simple movement
|
// Simple movement
|
||||||
if (Input::isKeyPressed(SDL_SCANCODE_W)) { mTransform.y -= mSpeed * deltaTime; }
|
if (Input::isKeyPressed(SDL_SCANCODE_W)) { mTransform.y -= mSpeed * deltaTime; }
|
||||||
if (Input::isKeyPressed(SDL_SCANCODE_S)) { mTransform.y += mSpeed * deltaTime; }
|
if (Input::isKeyPressed(SDL_SCANCODE_S)) { mTransform.y += mSpeed * deltaTime; }
|
||||||
if (Input::isKeyPressed(SDL_SCANCODE_A)) { mTransform.x -= mSpeed * deltaTime; mIsFlipped = false; }
|
if (Input::isKeyPressed(SDL_SCANCODE_A)) { mTransform.x -= mSpeed * deltaTime; mIsFlipped = false; }
|
||||||
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;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
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) {
|
||||||
|
mTransform.x = landBoundaryX + mShoreMargin - halfWidth;
|
||||||
|
}
|
||||||
|
if (!mIsShipMode && (mTransform.x + halfWidth) > landBoundaryX - mShoreMargin) {
|
||||||
|
mTransform.x = landBoundaryX - mShoreMargin - halfWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mIsShipMode && mShipTex) {
|
||||||
|
setTexture(mShipTex);
|
||||||
|
} else if (!mIsShipMode && mGroundTex) {
|
||||||
|
setTexture(mGroundTex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push replay
|
||||||
|
GameManager::pushPlayerPosition(mTransform);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::onCollisionEnter(Object::Entity* other) {
|
||||||
|
(void)other;
|
||||||
|
if (GameManager::getSharedData<bool>("gameLost")) {
|
||||||
|
GameManager::destroyEntity(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,40 @@
|
|||||||
#include <game/agame/trash.hpp>
|
#include <game/agame/trash.hpp>
|
||||||
|
#include <game/agame/player.hpp>
|
||||||
|
#include <object/components/boxcollider.hpp>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
namespace Game::AGame {
|
namespace Game::AGame {
|
||||||
void Trash::start() {
|
void Trash::start() {
|
||||||
mZIndex = 20;
|
mZIndex = 20;
|
||||||
|
addComponent<Object::Components::BoxCollider>();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Trash::update(float deltaTime) {
|
void Trash::update(float deltaTime) {
|
||||||
return;
|
(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;
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
auto* player = dynamic_cast<Player*>(other);
|
||||||
|
if (!player || !player->isShipMode()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameManager::setSharedData("trashActiveCount", std::max(0, GameManager::getSharedData<int>("trashActiveCount") - 1));
|
||||||
|
GameManager::setSharedData("gameScore", GameManager::getSharedData<int>("gameScore") + 25);
|
||||||
|
GameManager::destroyEntity(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,6 +67,7 @@ namespace Game {
|
|||||||
std::unordered_map<std::string, int> GameManager::mSharedInts;
|
std::unordered_map<std::string, int> GameManager::mSharedInts;
|
||||||
std::unordered_map<std::string, float> GameManager::mSharedFloats;
|
std::unordered_map<std::string, float> GameManager::mSharedFloats;
|
||||||
std::unordered_map<std::string, bool> GameManager::mSharedBools;
|
std::unordered_map<std::string, bool> GameManager::mSharedBools;
|
||||||
|
std::vector<Object::Transform> GameManager::mPlayerTransformHistory;
|
||||||
|
|
||||||
void GameManager::removeSharedData(const std::string& key, SharedDataType type) {
|
void GameManager::removeSharedData(const std::string& key, SharedDataType type) {
|
||||||
if (type == SharedDataType::STRING) {
|
if (type == SharedDataType::STRING) {
|
||||||
|
|||||||
35
src/main.cpp
35
src/main.cpp
@@ -5,6 +5,8 @@
|
|||||||
#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/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>
|
||||||
@@ -16,20 +18,37 @@ 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 uporabniško ime (pusti prazno 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");
|
||||||
|
|
||||||
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};
|
|
||||||
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));
|
||||||
Object::Entity* 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));
|
|
||||||
player->addComponent<Object::Components::BoxCollider>();
|
|
||||||
//State::GameState::getInstance().addEntity(std::make_unique<AGame::Player>("Player2", std::make_shared<Game::Renderer::Font>("../resources/roboto.ttf", window.getRenderer()->getSDLRenderer(), 128, "Roboto"), t1));
|
|
||||||
|
|
||||||
// Sample textbox
|
auto* player = dynamic_cast<AGame::Player*>(
|
||||||
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));
|
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(), 60, "HUDFont"), Object::Transform{0.f, 0.f, 0.f, 1.f, 1.f}, 320.f, 40.f));
|
||||||
|
|
||||||
window.run();
|
window.run();
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
namespace Game::Object::Components {
|
namespace Game::Object::Components {
|
||||||
BoxCollider::BoxCollider(const BoxCollider& other) : Component(other) {
|
BoxCollider::BoxCollider(const BoxCollider& other) : Component(other) {
|
||||||
LOG("Copied BoxCollider Component: " << mName);
|
LOG("Kopiran BoxCollider komponenta: " << mName);
|
||||||
}
|
}
|
||||||
|
|
||||||
BoxCollider& BoxCollider::operator=(const BoxCollider& other) {
|
BoxCollider& BoxCollider::operator=(const BoxCollider& other) {
|
||||||
@@ -17,7 +17,7 @@ namespace Game::Object::Components {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BoxCollider::BoxCollider(BoxCollider&& other) noexcept : Component(std::move(other)) {
|
BoxCollider::BoxCollider(BoxCollider&& other) noexcept : Component(std::move(other)) {
|
||||||
LOG("Moved BoxCollider Component: " << mName);
|
LOG("Premaknjena BoxCollider komponenta: " << mName);
|
||||||
}
|
}
|
||||||
|
|
||||||
BoxCollider& BoxCollider::operator=(BoxCollider&& other) noexcept {
|
BoxCollider& BoxCollider::operator=(BoxCollider&& other) noexcept {
|
||||||
@@ -50,16 +50,16 @@ 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);
|
||||||
|
|
||||||
// Transform position is used as top-left in rendering, so match that convention for collision bounds.
|
// Entity rendering uses top-left transform coordinates; collider must match that space.
|
||||||
float left = transform->x;
|
float left = transform->x;
|
||||||
float right = transform->x + width;
|
float right = transform->x + width;
|
||||||
float top = transform->y;
|
float top = transform->y;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace Game::Object {
|
|||||||
Entity::~Entity() = default;
|
Entity::~Entity() = default;
|
||||||
|
|
||||||
Entity::Entity(const Entity& other) : mName(other.mName), mTex(other.mTex), mTransform(other.mTransform), mIsActive(other.mIsActive) {
|
Entity::Entity(const Entity& other) : mName(other.mName), mTex(other.mTex), mTransform(other.mTransform), mIsActive(other.mIsActive) {
|
||||||
LOG("Copied Entity: " << mName);
|
LOG("Kopirana entiteta: " << mName);
|
||||||
}
|
}
|
||||||
|
|
||||||
Entity& Entity::operator=(const Entity& other) {
|
Entity& Entity::operator=(const Entity& other) {
|
||||||
@@ -24,7 +24,7 @@ namespace Game::Object {
|
|||||||
|
|
||||||
Entity::Entity(Entity&& other) noexcept : mName(std::move(other.mName)), mTex(other.mTex), mTransform(other.mTransform), mIsActive(other.mIsActive) {
|
Entity::Entity(Entity&& other) noexcept : mName(std::move(other.mName)), mTex(other.mTex), mTransform(other.mTransform), mIsActive(other.mIsActive) {
|
||||||
other.mTex = nullptr;
|
other.mTex = nullptr;
|
||||||
LOG("Moved Entity: " << mName);
|
LOG("Premaknjena entiteta: " << mName);
|
||||||
}
|
}
|
||||||
|
|
||||||
Entity& Entity::operator=(Entity&& other) noexcept {
|
Entity& Entity::operator=(Entity&& other) noexcept {
|
||||||
@@ -46,7 +46,7 @@ namespace Game::Object {
|
|||||||
SDL_GetTextureSize(mTex->getSDLTexture(), &w, &h);
|
SDL_GetTextureSize(mTex->getSDLTexture(), &w, &h);
|
||||||
|
|
||||||
SDL_FRect dst;
|
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;
|
dst.h = h * mTransform.scaleY * UNIVERSAL_SCALE_COEFFICIENT;
|
||||||
|
|
||||||
// Top-left origin; Account for camera position (center the camera on the screen)
|
// 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_GetTextureSize(mTex->getSDLTexture(), &tileW, &tileH);
|
||||||
|
|
||||||
SDL_FRect dst;
|
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;
|
dst.h = tileH * mTiledScale;
|
||||||
|
|
||||||
// Top-left origin; Account for camera position (center the camera on the screen)
|
// Top-left origin; Account for camera position (center the camera on the screen)
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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) {} /* {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include <object/ui/uitextbox.hpp>
|
#include <object/ui/uitextbox.hpp>
|
||||||
#include <renderer/renderer.hpp>
|
#include <renderer/renderer.hpp>
|
||||||
|
#include <object/camera.hpp>
|
||||||
|
|
||||||
namespace Game::Object {
|
namespace Game::Object {
|
||||||
|
|
||||||
@@ -8,15 +9,17 @@ 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;
|
// Compute visual box size first (respecting min sizes and scale), then center the box
|
||||||
mTransform.y -= mTex->getHeight() * mTransform.adjustedScaleY() / 2.f;
|
mBoxWidth = static_cast<float>(mTex ? mTex->getWidth() : 0.f) * mTransform.adjustedScaleX();
|
||||||
|
mBoxHeight = static_cast<float>(mTex ? mTex->getHeight() : 0.f) * mTransform.adjustedScaleY();
|
||||||
mBoxWidth = static_cast<float>(mTex->getWidth()) * mTransform.adjustedScaleX();
|
|
||||||
mBoxHeight = static_cast<float>(mTex->getHeight()) * mTransform.adjustedScaleY();
|
|
||||||
|
|
||||||
if (mBoxWidth < mConfig.minWidth) mBoxWidth = mConfig.minWidth;
|
if (mBoxWidth < mConfig.minWidth) mBoxWidth = mConfig.minWidth;
|
||||||
if (mBoxHeight < mConfig.minHeight) mBoxHeight = mConfig.minHeight;
|
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();
|
refreshVisualText();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,15 +115,29 @@ namespace Game::Object {
|
|||||||
bool UITextBox::isFocused() const { return mIsFocused; }
|
bool UITextBox::isFocused() const { return mIsFocused; }
|
||||||
|
|
||||||
bool UITextBox::isMouseInsideBox() const {
|
bool UITextBox::isMouseInsideBox() const {
|
||||||
const float mouseX = Input::getMouseX();
|
// Get screen-space mouse coordinates
|
||||||
const float mouseY = Input::getMouseY();
|
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 left = mTransform.x - mConfig.paddingX;
|
||||||
const float right = left + mBoxWidth + 2.f * mConfig.paddingX;
|
const float right = left + mBoxWidth + 2.f * mConfig.paddingX;
|
||||||
const float top = mTransform.y - mConfig.paddingY;
|
const float top = mTransform.y - mConfig.paddingY;
|
||||||
const float bottom = top + mBoxHeight + 2.f * 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() {
|
void UITextBox::refreshVisualText() {
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ namespace Game::Renderer {
|
|||||||
void Font::build(SDL_Color color, std::string text) {
|
void Font::build(SDL_Color color, std::string text) {
|
||||||
if (!mFont) { return; }
|
if (!mFont) { return; }
|
||||||
|
|
||||||
|
// Store last build parameters so we can rebuild after device resets
|
||||||
|
mLastText = text;
|
||||||
|
mLastColor = color;
|
||||||
|
|
||||||
SDL_Surface* surf = TTF_RenderText_Blended(mFont, text.c_str(), text.size(), color);
|
SDL_Surface* surf = TTF_RenderText_Blended(mFont, text.c_str(), text.size(), color);
|
||||||
if (!surf) {
|
if (!surf) {
|
||||||
ERROR("TTF_RenderText_Blended Error: " << SDL_GetError() << " (This object may be unusuable)");
|
ERROR("TTF_RenderText_Blended Error: " << SDL_GetError() << " (This object may be unusuable)");
|
||||||
@@ -49,9 +53,22 @@ 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());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Font::reload(SDL_Renderer* renderer) {
|
||||||
|
mRenderer = renderer;
|
||||||
|
if (mLastText.empty()) return false;
|
||||||
|
// Rebuild the texture using the stored last text and color
|
||||||
|
build(mLastColor, mLastText);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
SDL_Texture* Font::getSDLTexture() { return mTex; }
|
SDL_Texture* Font::getSDLTexture() { return mTex; }
|
||||||
std::string Font::getId() { return mId; }
|
std::string Font::getId() { return mId; }
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
#include <renderer/renderer.hpp>
|
#include <renderer/renderer.hpp>
|
||||||
|
#include <algorithm>
|
||||||
#include <utils.hpp>
|
#include <utils.hpp>
|
||||||
#include <state/gamestate.hpp>
|
#include <state/gamestate.hpp>
|
||||||
#include <object/entity.hpp>
|
#include <object/entity.hpp>
|
||||||
@@ -16,32 +17,38 @@ namespace Game::Renderer {
|
|||||||
if (mRenderer) {
|
if (mRenderer) {
|
||||||
SDL_DestroyRenderer(mRenderer);
|
SDL_DestroyRenderer(mRenderer);
|
||||||
mRenderer = nullptr;
|
mRenderer = nullptr;
|
||||||
LOG("Destroyed Renderer");
|
LOG("Renderer uničen");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Renderer::init(SDL_Window* window) {
|
bool Renderer::init(SDL_Window* window) {
|
||||||
// Request VSync before/at renderer setup; some backends honor this hint.
|
// Request VSync before/at renderer setup; some backends honor this hint.
|
||||||
SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1");
|
SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1");
|
||||||
|
// Create renderer using the portable API. Some SDL3 backends may not support the old flags,
|
||||||
|
// so fall back to a software renderer via hint if the first attempt fails.
|
||||||
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());
|
WARN("Renderer creation failed, attempting software renderer: " << SDL_GetError());
|
||||||
ERROR(errorMsg.c_str());
|
SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software");
|
||||||
return false;
|
mRenderer = SDL_CreateRenderer(window, nullptr);
|
||||||
|
if (!mRenderer) {
|
||||||
|
std::string errorMsg = std::string("Neuspešno ustvarjanje rendererja: ") + std::string(SDL_GetError());
|
||||||
|
ERROR(errorMsg.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mVSyncEnabled = SDL_SetRenderVSync(mRenderer, 1);
|
mVSyncEnabled = SDL_SetRenderVSync(mRenderer, 1);
|
||||||
if (!mVSyncEnabled) {
|
if (!mVSyncEnabled) {
|
||||||
WARN("VSync could not be enabled, using software frame pacing fallback: " << SDL_GetError());
|
WARN("VSync ni mogoče omogočiti, uporabljam programsko omejitev okvirjev: " << 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("Neuspelo nastavitev barve rendererja: " << SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG("Renderer created successfully");
|
LOG("Renderer uspešno ustvarjen");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -53,21 +60,48 @@ namespace Game::Renderer {
|
|||||||
Object::Camera::getInstance().getPosition(camX, camY);
|
Object::Camera::getInstance().getPosition(camX, camY);
|
||||||
int screenW, screenH;
|
int screenW, screenH;
|
||||||
SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &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 {
|
try {
|
||||||
auto entities = Game::State::GameState::getInstance().getEntitiesSnapshot(true);
|
Game::State::GameState::getInstance().withEntitiesLocked([&](auto& entities) {
|
||||||
for (auto* entity : entities) {
|
std::vector<Game::Object::Entity*> renderOrder;
|
||||||
if (entity) {
|
renderOrder.reserve(entities.size());
|
||||||
entity->render(this, config);
|
for (auto& [name, entity] : entities) {
|
||||||
|
(void)name;
|
||||||
|
if (entity) {
|
||||||
|
renderOrder.push_back(entity.get());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
std::sort(renderOrder.begin(), renderOrder.end(), [](Game::Object::Entity* a, Game::Object::Entity* b) {
|
||||||
|
return a->getZIndex() < b->getZIndex();
|
||||||
|
});
|
||||||
|
|
||||||
|
for (auto* entity : renderOrder) {
|
||||||
|
if (entity && entity->isActive()) {
|
||||||
|
entity->render(this, config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
ERROR("Exception while rendering frame: " << e.what());
|
ERROR("Exception while rendering frame: " << e.what());
|
||||||
}
|
}
|
||||||
|
|
||||||
mPresent();
|
mPresent();
|
||||||
|
|
||||||
|
// Reset render scale for next frame
|
||||||
|
SDL_SetRenderScale(mRenderer, 1.0f, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::mClear() {
|
void Renderer::mClear() {
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ namespace Game::Renderer {
|
|||||||
|
|
||||||
Texture::Texture(const std::string& path, SDL_Renderer* renderer, std::string id)
|
Texture::Texture(const std::string& path, SDL_Renderer* renderer, std::string id)
|
||||||
: mTex(nullptr), mId(id) {
|
: mTex(nullptr), mId(id) {
|
||||||
|
mPath = path;
|
||||||
|
mIsFromFile = true;
|
||||||
|
|
||||||
SDL_Surface* surf = IMG_Load(path.c_str());
|
SDL_Surface* surf = IMG_Load(path.c_str());
|
||||||
if (!surf) {
|
if (!surf) {
|
||||||
ERROR("Failed to load image at " << path);
|
ERROR("Failed to load image at " << path);
|
||||||
@@ -41,7 +44,35 @@ namespace Game::Renderer {
|
|||||||
Texture::~Texture() {
|
Texture::~Texture() {
|
||||||
if (mTex)
|
if (mTex)
|
||||||
SDL_DestroyTexture(mTex);
|
SDL_DestroyTexture(mTex);
|
||||||
LOG("Destroyed texture '" << mId << "'")
|
LOG("Tekstura '" << mId << "' uničena")
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Texture::reload(SDL_Renderer* renderer) {
|
||||||
|
if (!mIsFromFile || mPath.empty()) return false;
|
||||||
|
if (mTex) {
|
||||||
|
SDL_DestroyTexture(mTex);
|
||||||
|
mTex = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Surface* surf = IMG_Load(mPath.c_str());
|
||||||
|
if (!surf) {
|
||||||
|
ERROR("Failed to reload image at " << mPath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mTex = SDL_CreateTextureFromSurface(renderer, surf);
|
||||||
|
SDL_DestroySurface(surf);
|
||||||
|
if (!mTex) {
|
||||||
|
ERROR("Failed to create texture from surface when reloading " << mPath << ": " << SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore scale mode
|
||||||
|
if (!SDL_SetTextureScaleMode(mTex, SDL_SCALEMODE_NEAREST)) {
|
||||||
|
WARN("Failed to set texture scale mode to NEAREST for '" << mId << "' during reload: " << SDL_GetError());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_Texture* Texture::getSDLTexture() {
|
SDL_Texture* Texture::getSDLTexture() {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include <window/window.hpp>
|
#include <window/window.hpp>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <renderer/texture.hpp>
|
||||||
|
|
||||||
namespace Game::Window {
|
namespace Game::Window {
|
||||||
std::mutex Window::sMutex;
|
std::mutex Window::sMutex;
|
||||||
@@ -14,14 +15,14 @@ namespace Game::Window {
|
|||||||
if (mGameThread.joinable()) {
|
if (mGameThread.joinable()) {
|
||||||
mGameThread.request_stop();
|
mGameThread.request_stop();
|
||||||
mGameThread.join();
|
mGameThread.join();
|
||||||
LOG("Game thread stopped successfully");
|
LOG("Nit igre uspešno ustavljena");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mWindow) {
|
if (mWindow) {
|
||||||
SDL_DestroyWindow(mWindow);
|
SDL_DestroyWindow(mWindow);
|
||||||
mWindow = nullptr;
|
mWindow = nullptr;
|
||||||
sWindowBackend = nullptr;
|
sWindowBackend = nullptr;
|
||||||
LOG("Window destroyed successfully");
|
LOG("Okno uspešno uničeno");
|
||||||
}
|
}
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
}
|
}
|
||||||
@@ -40,7 +41,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, SDL_WINDOW_FULLSCREEN);
|
||||||
if (!mWindow) {
|
if (!mWindow) {
|
||||||
ERROR("Failed to create window: " << SDL_GetError());
|
ERROR("Failed to create window: " << SDL_GetError());
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
@@ -50,7 +51,7 @@ namespace Game::Window {
|
|||||||
mLastWindowHeight = height;
|
mLastWindowHeight = height;
|
||||||
sWindowBackend = mWindow;
|
sWindowBackend = mWindow;
|
||||||
|
|
||||||
LOG("Window created successfully");
|
LOG("Okno uspešno ustvarjeno");
|
||||||
|
|
||||||
if (!mRenderer.init(mWindow)) {
|
if (!mRenderer.init(mWindow)) {
|
||||||
SDL_DestroyWindow(mWindow);
|
SDL_DestroyWindow(mWindow);
|
||||||
@@ -64,17 +65,17 @@ namespace Game::Window {
|
|||||||
if (mRenderer.isVSyncEnabled()) {
|
if (mRenderer.isVSyncEnabled()) {
|
||||||
const int vsyncCap = std::max(1, mTargetFPS - VSYNC_FPS_OFFSET);
|
const int vsyncCap = std::max(1, mTargetFPS - VSYNC_FPS_OFFSET);
|
||||||
mEffectiveFrameCap = vsyncCap;
|
mEffectiveFrameCap = vsyncCap;
|
||||||
LOG("Low-latency VSync mode enabled. Target FPS: " << mTargetFPS << ", cap: " << mEffectiveFrameCap);
|
LOG("Low-latency VSync mode vključen. Target FPS: " << mTargetFPS << ", cap: " << mEffectiveFrameCap);
|
||||||
} else {
|
} else {
|
||||||
mEffectiveFrameCap = std::max(1, mTargetFPS);
|
mEffectiveFrameCap = std::max(1, mTargetFPS);
|
||||||
LOG("VSync unavailable, using software cap: " << mEffectiveFrameCap);
|
LOG("VSync ni na voljo, uporabljam programski limit: " << mEffectiveFrameCap);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
mEffectiveFrameCap = std::max(1, mTargetFPS);
|
mEffectiveFrameCap = std::max(1, mTargetFPS);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
mGameManager.setTargetUpdatesPerSecond(TARGET_UPDATE_RATE);
|
mGameManager.setTargetUpdatesPerSecond(TARGET_UPDATE_RATE);
|
||||||
LOG("Target updates per second: " << mGameManager.getTargetUpdatesPerSecond());
|
LOG("Ciljna hitrost posodobitev na sekundo: " << mGameManager.getTargetUpdatesPerSecond());
|
||||||
|
|
||||||
mGameThread = std::jthread(std::bind_front(&Game::GameManager::run, &mGameManager));
|
mGameThread = std::jthread(std::bind_front(&Game::GameManager::run, &mGameManager));
|
||||||
|
|
||||||
@@ -97,31 +98,56 @@ namespace Game::Window {
|
|||||||
// 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;
|
State::GameState::getInstance().withEntitiesLocked([&](auto& entities) {
|
||||||
SDL_GetWindowSizeInPixels(mWindow, &newWidth, &newHeight);
|
for (auto& [name, entity] : entities) {
|
||||||
|
(void)name;
|
||||||
const int oldWidth = mLastWindowWidth;
|
if (entity) {
|
||||||
const int oldHeight = mLastWindowHeight;
|
entity->onWindowResized(mLastWindowWidth, 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;
|
|
||||||
|
|
||||||
auto entities = State::GameState::getInstance().getEntitiesSnapshot();
|
|
||||||
for (auto* entity : entities) {
|
|
||||||
if (entity) {
|
|
||||||
if (canScale) {
|
|
||||||
Object::Transform* transform = entity->getTransform();
|
|
||||||
transform->x *= scaleX;
|
|
||||||
transform->y *= scaleY;
|
|
||||||
}
|
}
|
||||||
entity->onWindowResized(newWidth, newHeight);
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Renderer device/targets reset (GPU device removed) - attempt to re-create renderer and reload textures
|
||||||
|
if (event.type == SDL_EVENT_RENDER_DEVICE_RESET || event.type == SDL_EVENT_RENDER_TARGETS_RESET) {
|
||||||
|
LOG("Renderer device reset event received: attempting to reinitialize renderer and reload textures");
|
||||||
|
|
||||||
|
// Destroy current renderer and try to re-init
|
||||||
|
mRenderer.destroy();
|
||||||
|
bool reinitOk = mRenderer.init(mWindow);
|
||||||
|
|
||||||
|
// If reinit failed, try forcing the software renderer
|
||||||
|
if (!reinitOk) {
|
||||||
|
WARN("Renderer re-init failed, forcing software renderer fallback");
|
||||||
|
SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software");
|
||||||
|
reinitOk = mRenderer.init(mWindow);
|
||||||
|
if (!reinitOk) {
|
||||||
|
ERROR("Software renderer fallback also failed: " << SDL_GetError());
|
||||||
|
// Unable to recover; stop running to avoid crashes
|
||||||
|
mRunning = false;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mLastWindowWidth = newWidth;
|
// Rebuild GPU textures for all entities (fonts and file-based textures)
|
||||||
mLastWindowHeight = newHeight;
|
State::GameState::getInstance().withEntitiesLocked([&](auto& entities) {
|
||||||
|
for (auto& [name, entity] : entities) {
|
||||||
|
(void)name;
|
||||||
|
if (entity) {
|
||||||
|
auto tex = entity->getTexture();
|
||||||
|
if (tex) {
|
||||||
|
try {
|
||||||
|
tex->reload(mRenderer.getSDLRenderer());
|
||||||
|
} catch (...) {
|
||||||
|
WARN("Exception while reloading texture for entity: " << entity->getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entity->onWindowResized(mLastWindowWidth, mLastWindowHeight); // ensure layout is correct
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user