FINAL: v1

This commit is contained in:
2026-03-26 19:34:47 +01:00
parent 031c0e7293
commit 0f776347f4
11 changed files with 181 additions and 17 deletions

View File

@@ -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.
Vse avtorske pravice (copyright) so rezervirane k avtorju te izvorne kode/slik.
<hr>
<hr>
# 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.

View File

@@ -4,12 +4,19 @@
#include <renderer/texture.hpp>
#include <renderer/font.hpp>
#include <object/sound.hpp>
#include <game/gamemanager.hpp>
#include <game/agame/enemy.hpp>
#include <game/agame/trash.hpp>
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<Game::Renderer::Texture> mEnemyTex;
std::shared_ptr<Game::Renderer::Texture> mTrashTex;
END_GAME_ENTITY()
}

View File

@@ -0,0 +1,12 @@
#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(Enemy)
END_GAME_ENTITY()
}

View File

@@ -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()
}

View File

@@ -2,6 +2,9 @@
#include <iostream>
#include <SDL3/SDL.h>
#include <shared_mutex>
#include <random>
#include <cstdlib>
#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
#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;
};

View File

@@ -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

View File

@@ -1,13 +1,18 @@
#include <game/agame/background.hpp>
#include <window/window.hpp>
#include <object/camera.hpp>
#include <algorithm>
namespace Game::AGame {
void Background::start() {
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");
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<int>("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<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 (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;
}
}

11
src/game/agame/enemy.cpp Normal file
View File

@@ -0,0 +1,11 @@
#include <game/agame/enemy.hpp>
namespace Game::AGame {
void Enemy::start() {
mZIndex = 20;
}
void Enemy::update(float deltaTime) {
return;
}
}

View File

@@ -2,7 +2,7 @@
namespace Game::AGame {
void Trash::start() {
mZIndex = 50;
mZIndex = 20;
}
void Trash::update(float deltaTime) {

View File

@@ -1,6 +1,8 @@
#include <object/components/component.hpp>
namespace Game::Object::Components {
Component::~Component() = default;
Component::Component(const Component& other) : mName(other.mName), mIsActive(other.mIsActive) {
LOG("Copied Component: " << mName);
}

View File

@@ -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<float>(newWidth) / static_cast<float>(oldWidth) : 1.f;
const float scaleY = canScale ? static_cast<float>(newHeight) / static_cast<float>(oldHeight) : 1.f;
auto entities = State::GameState::getInstance().getEntitiesSnapshot();
for (auto* entity : entities) {
if (entity) {
if (canScale) {
Object::Transform* transform = entity->getTransform();
transform->x *= scaleX;
transform->y *= scaleY;
}
entity->onWindowResized(newWidth, newHeight);
}
}
mLastWindowWidth = newWidth;
mLastWindowHeight = newHeight;
}
}