FINAL: v1
This commit is contained in:
51
README.md
51
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.
|
||||
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.
|
||||
@@ -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()
|
||||
}
|
||||
12
include/game/agame/enemy.hpp
Normal file
12
include/game/agame/enemy.hpp
Normal 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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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
11
src/game/agame/enemy.cpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#include <game/agame/enemy.hpp>
|
||||
|
||||
namespace Game::AGame {
|
||||
void Enemy::start() {
|
||||
mZIndex = 20;
|
||||
}
|
||||
|
||||
void Enemy::update(float deltaTime) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Game::AGame {
|
||||
void Trash::start() {
|
||||
mZIndex = 50;
|
||||
mZIndex = 20;
|
||||
}
|
||||
|
||||
void Trash::update(float deltaTime) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user