From 0f776347f43a13a0c87d440d5b53dc2e2efec67b Mon Sep 17 00:00:00 2001 From: DcruBro Date: Thu, 26 Mar 2026 19:34:47 +0100 Subject: [PATCH] FINAL: v1 --- README.md | 51 ++++++++++++++++++++- include/game/agame/background.hpp | 9 +++- include/game/agame/enemy.hpp | 12 +++++ include/game/agame/player.hpp | 1 + include/utils.hpp | 20 ++++++++- include/window/window.hpp | 3 +- src/game/agame/background.cpp | 69 ++++++++++++++++++++++++----- src/game/agame/enemy.cpp | 11 +++++ src/game/agame/trash.cpp | 2 +- src/object/components/component.cpp | 2 + src/window/window.cpp | 18 +++++++- 11 files changed, 181 insertions(+), 17 deletions(-) create mode 100644 include/game/agame/enemy.hpp create mode 100644 src/game/agame/enemy.cpp diff --git a/README.md b/README.md index e09b640..c90eb9b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +# SLOVENSKO: + # Končni Projekt za 3. Letnik - PRAP ## Uporabljene knjižnice @@ -36,4 +38,51 @@ Vse slike (v direktorijo resources/) so podane pod "Creative Commons Attribution Font "Roboto" je licenciran pod "SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007" (Na voljo na spletu). ## Avtorske pravice -Vse avtorske pravice (copyright) so rezervirane k avtorju te izvorne kode/slik. \ No newline at end of file +Vse avtorske pravice (copyright) so rezervirane k avtorju te izvorne kode/slik. + +
+
+ +# ENGLISH: + +# Finals Project for Junior Year - PRAP + +## Uporabljene knjižnice +- SDL3 +- SDL3_image +- SDL3_ttf +- SDL3_mixer +- stdc++23 + +## Build +gcc compiler + cmake + +```bash +mkdir build +cd build +cmake .. +make -j +``` + +(For Windows, use MSYS2 with the MINGW64 terminal) + +## Operation + +### Master Thread (Rendering Thread) +This thread creates the main window and calls rendering methods. It also handles events and updates the game state. + +### Game Thread (Slave Thread) +This thread runs the main game loop, updating the game state and entities. It is responsible for game logic, while the master thread handles rendering and events. + +### Synchronization +A `std::shared_mutex` is used between the two threads to synchronize access to shared resources, such as entities in `GameState`. The master thread uses `std::shared_lock` to read entities during rendering, while the game thread uses `std::unique_lock` to update entities. + +## License +All source code (unless otherwise stated or used) is licensed under the "Lesser General Public License v2.1 only" (abbreviated as "LGPL v2.1-only"). More information about the license can be found in the LICENSE file. + +All images (in the `resources/` directory) are provided under the "Creative Commons Attribution-ShareAlike" (CC BY-SA) license. + +The font "Roboto" is licensed under the "SIL Open Font License Version 1.1 - 26 February 2007" (available online). + +## Copyright +All copyrights are reserved by the author of this source code/images. \ No newline at end of file diff --git a/include/game/agame/background.hpp b/include/game/agame/background.hpp index 2c89631..8b809a6 100644 --- a/include/game/agame/background.hpp +++ b/include/game/agame/background.hpp @@ -4,12 +4,19 @@ #include #include #include +#include +#include +#include namespace Game::AGame { GAME_ENTITY(Background) public: void onWindowResized(int newWidth, int newHeight) override; private: - Object::Sound mSound; + float mEnemySpawnTimer = 0.f; + float mTimeToSpawn = 5.f; + int mW, mH; + std::shared_ptr mEnemyTex; + std::shared_ptr mTrashTex; END_GAME_ENTITY() } \ No newline at end of file diff --git a/include/game/agame/enemy.hpp b/include/game/agame/enemy.hpp new file mode 100644 index 0000000..c4c08b6 --- /dev/null +++ b/include/game/agame/enemy.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace Game::AGame { + GAME_ENTITY(Enemy) + END_GAME_ENTITY() +} \ No newline at end of file diff --git a/include/game/agame/player.hpp b/include/game/agame/player.hpp index 297009a..7dc89ce 100644 --- a/include/game/agame/player.hpp +++ b/include/game/agame/player.hpp @@ -10,5 +10,6 @@ GAME_ENTITY(Player) private: Object::Sound mSound; float mSpeed = 200.f; // Pixels per second + float mHealth = 100.f; END_GAME_ENTITY() } \ No newline at end of file diff --git a/include/utils.hpp b/include/utils.hpp index a51c506..d660fe0 100644 --- a/include/utils.hpp +++ b/include/utils.hpp @@ -2,6 +2,9 @@ #include #include +#include +#include +#include #define DISABLE_COPY(Class) \ Class(const Class&) = delete; \ @@ -46,4 +49,19 @@ #define TARGET_FPS 60 #define TARGET_UPDATE_RATE 120 #define ENABLE_LOW_LATENCY_VSYNC 1 -#define VSYNC_FPS_OFFSET 2 \ No newline at end of file +#define VSYNC_FPS_OFFSET 2 + +class Utils { + public: + static Utils& getUtils() { static Utils instance; return instance; } + // Random in range 32 bits + int rirng32(int a, int b) { + std::lock_guard lock(mMutex); + std::mt19937 mGen(mRd()); + std::uniform_int_distribution<> distr(a, b); + return distr(mGen); + } + private: + mutable std::shared_mutex mMutex; + std::random_device mRd; +}; \ No newline at end of file diff --git a/include/window/window.hpp b/include/window/window.hpp index 26e6a32..577cc1b 100644 --- a/include/window/window.hpp +++ b/include/window/window.hpp @@ -27,7 +27,6 @@ namespace Game::Window { int getTargetFPS() { return mTargetFPS; } static SDL_Window* getSDLWindowBackend() { std::scoped_lock lock(sMutex); return sWindowBackend; } - Renderer::Renderer* getRenderer() { std::scoped_lock lock(mMutex); return &mRenderer; } private: @@ -39,6 +38,8 @@ namespace Game::Window { Game::GameManager mGameManager; std::jthread mGameThread; bool mRunning; + int mLastWindowWidth = 0; + int mLastWindowHeight = 0; int mTargetFPS = TARGET_FPS; int mEffectiveFrameCap = TARGET_FPS; #if DEBUG diff --git a/src/game/agame/background.cpp b/src/game/agame/background.cpp index e4b520d..a83ae7c 100644 --- a/src/game/agame/background.cpp +++ b/src/game/agame/background.cpp @@ -1,13 +1,18 @@ #include #include +#include +#include namespace Game::AGame { void Background::start() { + mEnemyTex = std::make_shared("../resources/l3enemy.png", SDL_GetRenderer(Window::Window::getSDLWindowBackend()), "enemyTex"); + mTrashTex = std::make_shared("../resources/l3trash.png", SDL_GetRenderer(Window::Window::getSDLWindowBackend()), "trashTex"); + GameManager::setSharedData("enemyActiveCount", 0); + mZIndex = -1; // Ensure background renders behind other entities mTex->setTiled(true); // Set the background texture to be tiled mTiledScale = 0.5f; - int w, h; - SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &w, &h); + SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &mW, &mH); mTransform.scaleX *= 10.f; mTransform.scaleY *= 10.f; @@ -15,16 +20,59 @@ namespace Game::AGame { mTransform.x -= mTex->getWidth() * mTransform.adjustedScaleX() / 2.f; mTransform.y -= mTex->getHeight() * mTransform.adjustedScaleY() / 2.f; - LOG("W: " << w << " H: " << h); + LOG("W: " << mW << " H: " << mH); - mTransform.x = w / 2.f - (w / 3.f); - mTransform.y = -h; - - mSound.~Sound(); + mTransform.x = mW / 2.f - (mW / 3.f); + mTransform.y = -mH; } void Background::update(float deltaTime) { - return; + mEnemySpawnTimer += deltaTime; + + int cnt = GameManager::getSharedData("enemyActiveCount"); + if (mEnemySpawnTimer >= mTimeToSpawn && cnt < 5) { + mEnemySpawnTimer = 0.f; // RESET + 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; + Object::Camera::getInstance().getPosition(camX, camY); + + const float halfEnemyW = mEnemyTex->getWidth() * tS.adjustedScaleX() / 2.f; + const float halfEnemyH = mEnemyTex->getHeight() * tS.adjustedScaleY() / 2.f; + + const float viewLeft = camX - (mW / 2.f); + const float viewRight = camX + (mW / 2.f); + const float viewTop = camY - (mH / 2.f); + const float viewBottom = camY + (mH / 2.f); + + // Right 1/3 of the currently visible screen, in world coordinates. + int spawnMinX = static_cast(viewLeft + (2.f * mW / 3.f) + halfEnemyW); + int spawnMaxX = static_cast(viewRight - halfEnemyW - 25.f); + int spawnMinY = static_cast(viewTop + halfEnemyH + 100.f); + int spawnMaxY = static_cast(viewBottom - halfEnemyH - 100.f); + + // Safety for tiny windows / huge sprites. + if (spawnMinX > spawnMaxX) spawnMinX = spawnMaxX = static_cast(camX); + if (spawnMinY > spawnMaxY) spawnMinY = spawnMaxY = static_cast(camY); + + tS.x = static_cast(Utils::getUtils().rirng32(spawnMinX, spawnMaxX)); + tS.y = static_cast(Utils::getUtils().rirng32(spawnMinY, spawnMaxY)); + GameManager::instantiateEntity(std::make_unique("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(Utils::getUtils().rirng32(spawnMinY, spawnMaxY)); + GameManager::instantiateEntity(std::make_unique("Trash" + std::to_string(cnt + 1), mTrashTex, tS)); + } + /*const bool* state = SDL_GetKeyboardState(nullptr); if (state[SDL_SCANCODE_P]) { mTransform.scaleX *= 2.f; @@ -42,8 +90,7 @@ namespace Game::AGame { } void Background::onWindowResized(int newWidth, int newHeight) { - // Re-center the background on window resize - mTransform.x = newWidth / 2.f - (newWidth / 3.f); - mTransform.y = -newHeight; + mW = newWidth; + mH = newHeight; } } \ No newline at end of file diff --git a/src/game/agame/enemy.cpp b/src/game/agame/enemy.cpp new file mode 100644 index 0000000..e814e61 --- /dev/null +++ b/src/game/agame/enemy.cpp @@ -0,0 +1,11 @@ +#include + +namespace Game::AGame { + void Enemy::start() { + mZIndex = 20; + } + + void Enemy::update(float deltaTime) { + return; + } +} \ No newline at end of file diff --git a/src/game/agame/trash.cpp b/src/game/agame/trash.cpp index 8f7c327..1ce1273 100644 --- a/src/game/agame/trash.cpp +++ b/src/game/agame/trash.cpp @@ -2,7 +2,7 @@ namespace Game::AGame { void Trash::start() { - mZIndex = 50; + mZIndex = 20; } void Trash::update(float deltaTime) { diff --git a/src/object/components/component.cpp b/src/object/components/component.cpp index c8198bd..a6d8544 100644 --- a/src/object/components/component.cpp +++ b/src/object/components/component.cpp @@ -1,6 +1,8 @@ #include namespace Game::Object::Components { + Component::~Component() = default; + Component::Component(const Component& other) : mName(other.mName), mIsActive(other.mIsActive) { LOG("Copied Component: " << mName); } diff --git a/src/window/window.cpp b/src/window/window.cpp index c61ebec..713a89f 100644 --- a/src/window/window.cpp +++ b/src/window/window.cpp @@ -46,6 +46,8 @@ namespace Game::Window { SDL_Quit(); return false; } + mLastWindowWidth = width; + mLastWindowHeight = height; sWindowBackend = mWindow; LOG("Window created successfully"); @@ -97,15 +99,29 @@ namespace Game::Window { std::scoped_lock lock(mMutex); SDL_SetRenderViewport(mRenderer.getSDLRenderer(), nullptr); - // Notify entities of the window resize so they can adjust if necessary int newWidth, newHeight; SDL_GetWindowSizeInPixels(mWindow, &newWidth, &newHeight); + + const int oldWidth = mLastWindowWidth; + const int oldHeight = mLastWindowHeight; + const bool canScale = oldWidth > 0 && oldHeight > 0; + const float scaleX = canScale ? static_cast(newWidth) / static_cast(oldWidth) : 1.f; + const float scaleY = canScale ? static_cast(newHeight) / static_cast(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); } } + + mLastWindowWidth = newWidth; + mLastWindowHeight = newHeight; } }