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;
}
}