Compare commits
5 Commits
master
...
d9769bdbbb
| Author | SHA1 | Date | |
|---|---|---|---|
| d9769bdbbb | |||
| c46443e2f4 | |||
| fcc598adb1 | |||
| e4389f035d | |||
| 56d567b77d |
@@ -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 = 7; // 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,11 @@ 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;
|
||||||
END_GAME_ENTITY()
|
END_GAME_ENTITY()
|
||||||
}
|
}
|
||||||
18
include/game/agame/friendly.hpp
Normal file
18
include/game/agame/friendly.hpp
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#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;
|
||||||
|
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)
|
||||||
|
public:
|
||||||
|
void setShipTexture(std::shared_ptr<Game::Renderer::Texture> tex);
|
||||||
|
void setGroundTexture(std::shared_ptr<Game::Renderer::Texture> tex);
|
||||||
|
void respawnRandomSea(float landBoundaryX);
|
||||||
|
bool isShipMode() const { return mIsShipMode; }
|
||||||
|
void onCollisionEnter(Object::Entity* other) override;
|
||||||
private:
|
private:
|
||||||
Object::Sound mSound;
|
Object::Sound mSound;
|
||||||
float mSpeed = 200.f; // Pixels per second
|
float mSpeed = 200.f; // Pixels per second
|
||||||
[[maybe_unused]] float mHealth = 100.f;
|
[[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()
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -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,19 +1,70 @@
|
|||||||
#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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &mW, &mH);
|
||||||
|
|
||||||
|
// 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,72 +72,251 @@ 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.rotation = 0.f;
|
||||||
|
tS.scaleX = 6.f;
|
||||||
|
tS.scaleY = 6.f;
|
||||||
|
|
||||||
|
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 = 7.f;
|
||||||
|
tS.scaleY = 7.f;
|
||||||
|
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;
|
||||||
|
|
||||||
|
const int enemyCount = GameManager::getSharedData<int>("enemyActiveCount");
|
||||||
|
const int trashCount = GameManager::getSharedData<int>("trashActiveCount");
|
||||||
|
const int stage = GameManager::getSharedData<int>("gameStage");
|
||||||
|
|
||||||
|
// Periodically spawn a friendly on land or sea with a small probability
|
||||||
|
// evaluated each update using deltaTime so the average interval is respected.
|
||||||
|
const int activeFriendlies = GameManager::getSharedData<int>("friendlyActiveCount");
|
||||||
|
if (activeFriendlies < mMaxAutoFriendlies) {
|
||||||
|
// Compute chance = deltaTime / avgInterval
|
||||||
|
const float chance = deltaTime / std::max(0.0001f, mFriendlySpawnAvgInterval);
|
||||||
|
const int thresh = static_cast<int>(chance * 10000.f);
|
||||||
|
if (thresh > 0) {
|
||||||
|
const int roll = Utils::getUtils().rirng32(0, 9999);
|
||||||
|
if (roll < thresh) {
|
||||||
|
// Decide side: sea probability increases with stage
|
||||||
|
float seaProb = (stage > 1) ? 0.3f : 0.1f;
|
||||||
|
const int sideRoll = Utils::getUtils().rirng32(0, 99);
|
||||||
|
const bool spawnSea = sideRoll < static_cast<int>(seaProb * 100.f);
|
||||||
|
|
||||||
int cnt = GameManager::getSharedData<int>("enemyActiveCount");
|
|
||||||
if (mEnemySpawnTimer >= mTimeToSpawn && cnt < 5) {
|
|
||||||
mEnemySpawnTimer = 0.f; // RESET
|
|
||||||
GameManager::setSharedData("enemyActiveCount", cnt + 1);
|
|
||||||
// Spawn Enemy on grass
|
|
||||||
Object::Transform tS;
|
Object::Transform tS;
|
||||||
tS.scaleY = 7.f;
|
|
||||||
tS.scaleX = 7.f;
|
|
||||||
tS.rotation = 0.f;
|
tS.rotation = 0.f;
|
||||||
|
tS.scaleX = 6.f;
|
||||||
|
tS.scaleY = 6.f;
|
||||||
|
const float viewLeft = -mW / 2.f;
|
||||||
|
const float viewRight = mW / 2.f;
|
||||||
|
const float viewTop = -mH / 2.f;
|
||||||
|
const float viewBottom = mH / 2.f;
|
||||||
|
const float halfFriendlyW = mFriendlyTex->getWidth() * tS.adjustedScaleX() / 2.f;
|
||||||
|
const float halfFriendlyH = mFriendlyTex->getHeight() * tS.adjustedScaleY() / 2.f;
|
||||||
|
|
||||||
float camX, camY;
|
if (!spawnSea) {
|
||||||
Object::Camera::getInstance().getPosition(camX, camY);
|
tS.x = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(viewLeft + halfFriendlyW + 25.f), static_cast<int>(mLandBoundaryX - halfFriendlyW - 25.f)));
|
||||||
|
} else {
|
||||||
|
tS.x = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(mLandBoundaryX + halfFriendlyW + 25.f), static_cast<int>(viewRight - halfFriendlyW - 25.f)));
|
||||||
|
}
|
||||||
|
tS.y = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(viewTop + halfFriendlyH + 100.f), static_cast<int>(viewBottom - halfFriendlyH - 100.f)));
|
||||||
|
|
||||||
const float halfEnemyW = mEnemyTex->getWidth() * tS.adjustedScaleX() / 2.f;
|
const int id = Utils::getUtils().rirng32(0, 1000000);
|
||||||
const float halfEnemyH = mEnemyTex->getHeight() * tS.adjustedScaleY() / 2.f;
|
const std::string name = std::string("Friendly_Auto_") + std::to_string(id);
|
||||||
|
auto* friendly = State::GameState::getInstance().addEntity(std::make_unique<AGame::Friendly>(name, mFriendlyTex, tS));
|
||||||
const float viewLeft = camX - (mW / 2.f);
|
if (friendly) {
|
||||||
const float viewRight = camX + (mW / 2.f);
|
if (auto* collider = friendly->getComponent<Object::Components::BoxCollider>()) {
|
||||||
const float viewTop = camY - (mH / 2.f);
|
collider->setScale(0.75f);
|
||||||
const float viewBottom = camY + (mH / 2.f);
|
}
|
||||||
|
GameManager::setSharedData("friendlyActiveCount", GameManager::getSharedData<int>("friendlyActiveCount") + 1);
|
||||||
// Right 1/3 of the currently visible screen, in world coordinates.
|
}
|
||||||
int spawnMinX = static_cast<int>(viewLeft + (2.f * mW / 3.f) + halfEnemyW);
|
}
|
||||||
int spawnMaxX = static_cast<int>(viewRight - halfEnemyW - 25.f);
|
}
|
||||||
int spawnMinY = static_cast<int>(viewTop + halfEnemyH + 100.f);
|
|
||||||
int spawnMaxY = static_cast<int>(viewBottom - halfEnemyH - 100.f);
|
|
||||||
|
|
||||||
// Safety for tiny windows / huge sprites.
|
|
||||||
if (spawnMinX > spawnMaxX) spawnMinX = spawnMaxX = static_cast<int>(camX);
|
|
||||||
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) {
|
||||||
|
|||||||
@@ -1,22 +1,158 @@
|
|||||||
#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) {
|
||||||
|
(void)deltaTime;
|
||||||
|
|
||||||
|
auto* player = GameManager::getEntityByName<Player>("Player");
|
||||||
|
if (!player) {
|
||||||
|
mIsVisible = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Enemy::onCollisionEnter(Object::Entity* other) {
|
// Enemies are visible only within a reveal radius around the player
|
||||||
LOG("Enemy '" << getName() << "' collided with '" << other->getName() << "' (onCollisionEnter); Killing myself now!");
|
const float revealRadius = GameManager::getSharedData<float>("enemyRevealRadius");
|
||||||
GameManager::setSharedData("enemyActiveCount", GameManager::getSharedData<int>("enemyActiveCount") - 1);
|
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);
|
||||||
|
|
||||||
// Find in state
|
// 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;
|
||||||
|
|
||||||
|
// Get window dimensions for boundary calculations
|
||||||
|
int w = 0, h = 0;
|
||||||
|
SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &w, &h);
|
||||||
|
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) {
|
||||||
|
auto* player = dynamic_cast<Player*>(other);
|
||||||
|
if (!player || !mIsVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
81
src/game/agame/friendly.cpp
Normal file
81
src/game/agame/friendly.cpp
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
#include <game/agame/friendly.hpp>
|
||||||
|
#include <game/gamemanager.hpp>
|
||||||
|
#include <game/agame/player.hpp>
|
||||||
|
#include <object/components/boxcollider.hpp>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <utils.hpp>
|
||||||
|
#include <window/window.hpp>
|
||||||
|
|
||||||
|
namespace Game::AGame {
|
||||||
|
void Friendly::start() {
|
||||||
|
mZIndex = 20;
|
||||||
|
addComponent<Object::Components::BoxCollider>();
|
||||||
|
LOG("Zaveznik 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 Friendly::update(float deltaTime) {
|
||||||
|
(void)deltaTime;
|
||||||
|
|
||||||
|
// 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 friendly
|
||||||
|
mTransform.x += mMoveSpeedX * deltaTime;
|
||||||
|
mTransform.y += mMoveSpeedY * deltaTime;
|
||||||
|
|
||||||
|
// Clamp to land section
|
||||||
|
const float landBoundaryX = GameManager::getSharedData<float>("terrainLandBoundaryX");
|
||||||
|
const float entityWidth = getTexture() ? getTexture()->getWidth() * mTransform.adjustedScaleX() : 0.f;
|
||||||
|
const float entityHeight = getTexture() ? getTexture()->getHeight() * mTransform.adjustedScaleY() : 0.f;
|
||||||
|
const float halfWidth = entityWidth / 2.f;
|
||||||
|
const float halfHeight = entityHeight / 2.f;
|
||||||
|
|
||||||
|
// Get window dimensions for boundary calculations
|
||||||
|
int w = 0, h = 0;
|
||||||
|
SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &w, &h);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
|
||||||
|
int windowW = 0;
|
||||||
|
int windowH = 0;
|
||||||
|
SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &windowW, &windowH);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
int w = 0;
|
||||||
|
int h = 0;
|
||||||
|
SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &w, &h);
|
||||||
|
|
||||||
|
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;
|
|
||||||
SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &w, &h);
|
|
||||||
|
|
||||||
mTransform.scaleX = 8.f;
|
mTransform.scaleX = 8.f;
|
||||||
mTransform.scaleY = 8.f;
|
mTransform.scaleY = 8.f;
|
||||||
|
|
||||||
|
if (!mShipTex) {
|
||||||
|
mShipTex = mTex;
|
||||||
|
}
|
||||||
|
if (!mGroundTex) {
|
||||||
|
mGroundTex = mTex;
|
||||||
|
}
|
||||||
|
|
||||||
|
int w = 0;
|
||||||
|
int h = 0;
|
||||||
|
SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &w, &h);
|
||||||
|
|
||||||
|
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,64 @@ 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;
|
||||||
|
|
||||||
|
int w = 0;
|
||||||
|
int h = 0;
|
||||||
|
SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &w, &h);
|
||||||
|
const float entityWidth = mTex ? mTex->getWidth() * mTransform.adjustedScaleX() : 0.f;
|
||||||
|
const float entityHeight = mTex ? mTex->getHeight() * mTransform.adjustedScaleY() : 0.f;
|
||||||
|
const float minX = -w / 2.f;
|
||||||
|
const float maxX = w / 2.f - entityWidth;
|
||||||
|
const float minY = -h / 2.f;
|
||||||
|
const float maxY = h / 2.f - entityHeight;
|
||||||
|
|
||||||
|
if (mTransform.x < minX) mTransform.x = minX;
|
||||||
|
if (mTransform.x > maxX) mTransform.x = maxX;
|
||||||
|
if (mTransform.y < minY) mTransform.y = minY;
|
||||||
|
if (mTransform.y > maxY) mTransform.y = maxY;
|
||||||
|
|
||||||
|
if (mIsShipMode && (mTransform.x + halfWidth) < landBoundaryX + mShoreMargin) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::onCollisionEnter(Object::Entity* other) {
|
||||||
|
(void)other;
|
||||||
|
if (GameManager::getSharedData<bool>("gameLost")) {
|
||||||
|
GameManager::destroyEntity(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,35 @@
|
|||||||
#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) {
|
||||||
|
(void)deltaTime;
|
||||||
|
if (mSeaOnly) {
|
||||||
|
const float landBoundaryX = GameManager::getSharedData<float>("terrainLandBoundaryX");
|
||||||
|
const float margin = 25.f;
|
||||||
|
const float halfWidth = getTexture() ? getTexture()->getWidth() * mTransform.adjustedScaleX() / 2.f : 0.f;
|
||||||
|
if (mTransform.x - halfWidth < landBoundaryX + margin) {
|
||||||
|
mTransform.x = landBoundaryX + margin + halfWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
24
src/main.cpp
24
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,26 @@ 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::Player>("Player", std::make_shared<Game::Renderer::Texture>("../resources/l3ladja.png", window.getRenderer()->getSDLRenderer()), Object::DEFAULT_TRANSFORM)));
|
||||||
State::GameState::getInstance().addEntity(std::make_unique<AGame::SampleTextBox>("Sample Text Box", std::make_shared<Game::Renderer::Font>("../resources/roboto.ttf", window.getRenderer()->getSDLRenderer(), 48, "Roboto"), Object::DEFAULT_TRANSFORM, 640.f, 360.f));
|
if (player) {
|
||||||
|
player->addComponent<Object::Components::BoxCollider>();
|
||||||
|
player->setShipTexture(std::make_shared<Game::Renderer::Texture>("../resources/l3ladja.png", window.getRenderer()->getSDLRenderer()));
|
||||||
|
player->setGroundTexture(std::make_shared<Game::Renderer::Texture>("../resources/l3player.png", window.getRenderer()->getSDLRenderer()));
|
||||||
|
}
|
||||||
|
|
||||||
|
State::GameState::getInstance().addEntity(std::make_unique<AGame::HUDText>("HUD", std::make_shared<Game::Renderer::Font>("../resources/roboto.ttf", window.getRenderer()->getSDLRenderer(), 72, "HUDFont"), Object::Transform{0.f, 0.f, 0.f, 1.f, 1.f}, 320.f, 40.f));
|
||||||
|
|
||||||
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 {
|
||||||
|
|||||||
@@ -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) {} /* {
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ namespace Game::Object {
|
|||||||
: Entity(name, font, transform), mX(x), mY(y), mConfig(config) { }
|
: Entity(name, font, transform), mX(x), mY(y), mConfig(config) { }
|
||||||
|
|
||||||
void UITextBox::start() {
|
void UITextBox::start() {
|
||||||
mTransform.x -= mTex->getWidth() * mTransform.adjustedScaleX() / 2.f;
|
mTransform.x = mX - mTex->getWidth() * mTransform.adjustedScaleX() / 2.f;
|
||||||
mTransform.y -= mTex->getHeight() * mTransform.adjustedScaleY() / 2.f;
|
mTransform.y = mY - mTex->getHeight() * mTransform.adjustedScaleY() / 2.f;
|
||||||
|
|
||||||
mBoxWidth = static_cast<float>(mTex->getWidth()) * mTransform.adjustedScaleX();
|
mBoxWidth = static_cast<float>(mTex->getWidth()) * mTransform.adjustedScaleX();
|
||||||
mBoxHeight = static_cast<float>(mTex->getHeight()) * mTransform.adjustedScaleY();
|
mBoxHeight = static_cast<float>(mTex->getHeight()) * mTransform.adjustedScaleY();
|
||||||
|
|||||||
@@ -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)");
|
||||||
@@ -50,6 +54,19 @@ namespace Game::Renderer {
|
|||||||
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; }
|
||||||
|
|||||||
@@ -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());
|
||||||
|
SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software");
|
||||||
|
mRenderer = SDL_CreateRenderer(window, nullptr);
|
||||||
|
if (!mRenderer) {
|
||||||
|
std::string errorMsg = std::string("Neuspešno ustvarjanje rendererja: ") + std::string(SDL_GetError());
|
||||||
ERROR(errorMsg.c_str());
|
ERROR(errorMsg.c_str());
|
||||||
return false;
|
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;
|
||||||
}
|
}
|
||||||
@@ -57,12 +64,26 @@ namespace Game::Renderer {
|
|||||||
RendererConfig config{ camX, camY, screenW, screenH };
|
RendererConfig config{ camX, camY, screenW, screenH };
|
||||||
|
|
||||||
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;
|
||||||
|
renderOrder.reserve(entities.size());
|
||||||
|
for (auto& [name, entity] : entities) {
|
||||||
|
(void)name;
|
||||||
if (entity) {
|
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);
|
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());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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, 0);
|
||||||
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));
|
||||||
|
|
||||||
@@ -94,34 +95,63 @@ namespace Game::Window {
|
|||||||
mRunning = false;
|
mRunning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.type == SDL_EVENT_WINDOW_ENTER_FULLSCREEN) {
|
||||||
|
SDL_SetWindowFullscreen(mWindow, false);
|
||||||
|
}
|
||||||
|
|
||||||
// Window resize event - update the renderer's viewport
|
// Window resize event - update the renderer's viewport
|
||||||
if (event.type == SDL_EVENT_WINDOW_RESIZED) {
|
if (event.type == SDL_EVENT_WINDOW_RESIZED) {
|
||||||
std::scoped_lock lock(mMutex);
|
std::scoped_lock lock(mMutex);
|
||||||
|
SDL_SetWindowSize(mWindow, mLastWindowWidth, mLastWindowHeight);
|
||||||
SDL_SetRenderViewport(mRenderer.getSDLRenderer(), nullptr);
|
SDL_SetRenderViewport(mRenderer.getSDLRenderer(), nullptr);
|
||||||
|
|
||||||
int newWidth, newHeight;
|
State::GameState::getInstance().withEntitiesLocked([&](auto& entities) {
|
||||||
SDL_GetWindowSizeInPixels(mWindow, &newWidth, &newHeight);
|
for (auto& [name, entity] : entities) {
|
||||||
|
(void)name;
|
||||||
const int oldWidth = mLastWindowWidth;
|
|
||||||
const int oldHeight = mLastWindowHeight;
|
|
||||||
const bool canScale = oldWidth > 0 && oldHeight > 0;
|
|
||||||
const float scaleX = canScale ? static_cast<float>(newWidth) / static_cast<float>(oldWidth) : 1.f;
|
|
||||||
const float scaleY = canScale ? static_cast<float>(newHeight) / static_cast<float>(oldHeight) : 1.f;
|
|
||||||
|
|
||||||
auto entities = State::GameState::getInstance().getEntitiesSnapshot();
|
|
||||||
for (auto* entity : entities) {
|
|
||||||
if (entity) {
|
if (entity) {
|
||||||
if (canScale) {
|
entity->onWindowResized(mLastWindowWidth, mLastWindowHeight);
|
||||||
Object::Transform* transform = entity->getTransform();
|
|
||||||
transform->x *= scaleX;
|
|
||||||
transform->y *= scaleY;
|
|
||||||
}
|
}
|
||||||
entity->onWindowResized(newWidth, newHeight);
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 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