From 834f0b29c3c4c03e55b8654430d5ca201c1240b7 Mon Sep 17 00:00:00 2001 From: DcruBro Date: Wed, 11 Mar 2026 20:19:15 +0100 Subject: [PATCH] renderiranje - nazaj na singlethreaded ker me SDL ne mara :( --- CMakeLists.txt | 3 ++ README.md | 6 ++- include/game/player.hpp | 13 +++++++ include/object/entity.hpp | 33 ++++++++++++++--- include/object/transform.hpp | 11 ++++++ include/renderer/renderer.hpp | 2 +- include/renderer/texture.hpp | 6 ++- include/state/gamestate.hpp | 11 +++--- include/utils.hpp | 8 ++-- include/window/window.hpp | 7 ++-- resources/missing_texture.png | Bin 0 -> 39853 bytes src/game/player.cpp | 14 +++++++ src/main.cpp | 11 +++++- src/object/entity.cpp | 68 ++++++++++++++++++++++++++++++++++ src/renderer/renderer.cpp | 18 ++++++--- src/renderer/texture.cpp | 57 +++++++++++++++------------- src/state/gamestate.cpp | 18 ++++----- src/window/window.cpp | 19 +++++----- 18 files changed, 230 insertions(+), 75 deletions(-) create mode 100644 include/game/player.hpp create mode 100644 include/object/transform.hpp create mode 100644 resources/missing_texture.png create mode 100644 src/game/player.cpp create mode 100644 src/object/entity.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 952832b..18317c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,9 @@ FetchContent_MakeAvailable(SDL3_image) # Collect all source files from src/ and nested directories file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "src/*.cpp") +# Compile flags +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror -O2") + # Create the executable add_executable(${PROJECT_NAME} ${SOURCES}) diff --git a/README.md b/README.md index 99e63c4..6c8fa67 100644 --- a/README.md +++ b/README.md @@ -16,4 +16,8 @@ make -j (Za Windows uporabite MSYS2 MINGW64 terminal) ## Licenca -Vsa koda (razen kadar je drugače navedeno ali uporabljeno) je licencirana pod "Lesser General Public License v2.1" edino (okrajšano na "LGPL v2.1-only"). Več informacij o licenci najdete v datoteki LICENSE. \ No newline at end of file +Vsa izvorna koda (razen kadar je drugače navedeno ali uporabljeno) je licencirana pod "Lesser General Public License v2.1" edino (okrajšano na "LGPL v2.1-only"). Več informacij o licenci najdete v datoteki LICENSE. +Vse slike (v direktorijo resources/) so podane pod "Creative Commons Attribution-ShareAlike" (CC BY-SA) licenco. + +## Avtorske pravice +Vse avtorske pravice (copyright) so rezervirane k avtorju te izvorne kode/slik. \ No newline at end of file diff --git a/include/game/player.hpp b/include/game/player.hpp new file mode 100644 index 0000000..0bfe828 --- /dev/null +++ b/include/game/player.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace Game::AGame { + class Player : public Object::Entity { + using Object::Entity::Entity; + + public: + void start() override; + void update() override; + }; +} \ No newline at end of file diff --git a/include/object/entity.hpp b/include/object/entity.hpp index 822e6be..2ae1e79 100644 --- a/include/object/entity.hpp +++ b/include/object/entity.hpp @@ -2,24 +2,47 @@ #include #include +#include +#include +#include +#include +#include namespace Game::Renderer { + class Renderer; class Texture; } namespace Game::Object { class Entity { public: - Entity(std::string& name, Game::Renderer::Texture* tex) : mName(name), mTex(tex) {} + Entity(std::string name, std::shared_ptr tex, Transform transform) : mName(name), mTex(tex), mTransform(transform), mIsActive(true) { LOG("Created Entity: " << mName); } // I will define the copy and move constructors later - just deleted for now - DISABLE_COPY_AND_MOVE(Entity); + Entity(const Entity&); + Entity& operator=(const Entity&); + Entity(Entity&&) noexcept; + Entity& operator=(Entity&&) noexcept; ~Entity(); - void start(); - void update(); + virtual void start() = 0; + virtual void update() = 0; + void render(Game::Renderer::Renderer* renderer); + + // Setters and getters + void setTexture(std::shared_ptr tex) { mTex = tex; } + void setName(const std::string& name) { mName = name; } + void setTransform(const Transform& transform) { mTransform = transform; } + void setActive(bool active) { mIsActive = active; } + std::shared_ptr getTexture() { return mTex; } + std::string getName() { return mName; } + Transform* getTransform() { return &mTransform; } + bool isActive() { return mIsActive; } protected: std::string mName; - Game::Renderer::Texture* mTex; + std::shared_ptr mTex; + Transform mTransform; + bool mIsActive; private: + float mScaleConstant = 0.25f; }; } \ No newline at end of file diff --git a/include/object/transform.hpp b/include/object/transform.hpp new file mode 100644 index 0000000..96ea910 --- /dev/null +++ b/include/object/transform.hpp @@ -0,0 +1,11 @@ +#pragma once + +namespace Game::Object { + typedef struct { + float x, y; + float rotation; // In degrees, clockwise + float scaleX, scaleY; + } Transform; + + constexpr Transform DEFAULT_TRANSFORM{0.f, 0.f, 0.f, 1.f, 1.f}; +} \ No newline at end of file diff --git a/include/renderer/renderer.hpp b/include/renderer/renderer.hpp index f46b4d1..c26daed 100644 --- a/include/renderer/renderer.hpp +++ b/include/renderer/renderer.hpp @@ -13,7 +13,7 @@ namespace Game::Renderer { ~Renderer(); bool init(SDL_Window* window); - void run(std::stop_token stoken); + void renderFrame(); void destroy(); SDL_Renderer* getSDLRenderer() { return mRenderer; } diff --git a/include/renderer/texture.hpp b/include/renderer/texture.hpp index 56b0df6..9a3b9f4 100644 --- a/include/renderer/texture.hpp +++ b/include/renderer/texture.hpp @@ -4,16 +4,18 @@ #include #include #include -#include namespace Game::Renderer { class Texture { public: - Texture(std::string& path, Renderer* renderer, std::string id = "noname"); + Texture(const std::string& path, SDL_Renderer* renderer, std::string id = "noname"); Texture(const Texture&); Texture& operator=(const Texture&); DISABLE_MOVE(Texture); ~Texture(); + + SDL_Texture* getSDLTexture(); + std::string getId(); private: SDL_Texture* mTex; std::string mId; diff --git a/include/state/gamestate.hpp b/include/state/gamestate.hpp index aa8439b..a5fbb41 100644 --- a/include/state/gamestate.hpp +++ b/include/state/gamestate.hpp @@ -1,7 +1,7 @@ #pragma once -#include #include +#include #include #include @@ -10,15 +10,14 @@ namespace Game::State { public: static GameState& getInstance() { static GameState instance; return instance; } - // Retrieve a COPY of the entities - Only used by renderer, use getAtIndex() for generic access - const std::vector& getEntities(); // Retrieve a REFERENCE of the entities; DANGEROUS! - std::vector* getEntitiesRef(); + std::vector>* getEntitiesRef(); // Update entity at index, by REFERENCE Object::Entity* getAtIndex(size_t at); + void addEntity(std::unique_ptr entity); + private: - mutable std::shared_mutex mMutex; - std::vector mEntities; + std::vector> mEntities; }; } \ No newline at end of file diff --git a/include/utils.hpp b/include/utils.hpp index 2f9d794..ff5da7d 100644 --- a/include/utils.hpp +++ b/include/utils.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #define DISABLE_COPY(Class) \ Class(const Class&) = delete; \ Class& operator=(const Class&) = delete; @@ -23,9 +25,7 @@ #define GAME_ENTITY(ClassName) \ class ClassName : public Object::Entity { \ - using Object::Entity::Entity; // Inherit constructors \ - - + using Object::Entity::Entity; #define END_GAME_ENTITY() \ - }; \ No newline at end of file + }; diff --git a/include/window/window.hpp b/include/window/window.hpp index 06e6784..fa0e0f6 100644 --- a/include/window/window.hpp +++ b/include/window/window.hpp @@ -6,9 +6,7 @@ #include #include -#include -#include -#include +#include namespace Game::Window { class Window { @@ -20,10 +18,11 @@ namespace Game::Window { bool init(int width, int height, const std::string& title); void run(); + Renderer::Renderer* getRenderer() { return &mRenderer; } + private: SDL_Window* mWindow; Renderer::Renderer mRenderer; - std::jthread mRenderThread; bool mRunning; }; } \ No newline at end of file diff --git a/resources/missing_texture.png b/resources/missing_texture.png new file mode 100644 index 0000000000000000000000000000000000000000..0f11c744f64ea2a1a165013c81b087621b88fcc5 GIT binary patch literal 39853 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 + +namespace Game::AGame { + void Player::start() { + LOG("Created the Player"); + } + + void Player::update() { + if (!mIsActive) return; + //LOG("Updated Player"); + mTransform.x += 0.5f; // Move right at a constant speed for testing + mTransform.rotation += 1.f; // Rotate clockwise for testing + } +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index bc2d069..6a2c04c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,11 +1,20 @@ #include #include +#include +#include +#include +#include +#include +#include using namespace Game; -int main(int argc, char* argv[]) { +int main() { Window::Window window = Window::Window(); window.init(1280, 720, "Game Window"); + + State::GameState::getInstance().addEntity(std::make_unique("Player", std::make_shared("../resources/missing_texture.png", window.getRenderer()->getSDLRenderer()), Object::DEFAULT_TRANSFORM)); + window.run(); return 0; diff --git a/src/object/entity.cpp b/src/object/entity.cpp new file mode 100644 index 0000000..baad982 --- /dev/null +++ b/src/object/entity.cpp @@ -0,0 +1,68 @@ +#include +#include +#include + +namespace Game::Object { + Entity::~Entity() { + LOG("Destroyed Entity: " << mName); + } + + Entity::Entity(const Entity& other) : mName(other.mName), mTex(other.mTex), mTransform(other.mTransform), mIsActive(other.mIsActive) { + LOG("Copied Entity: " << mName); + } + + Entity& Entity::operator=(const Entity& other) { + if (this != &other) { + mName = other.mName; + mTex = other.mTex; + mTransform = other.mTransform; + mIsActive = other.mIsActive; + } + return *this; + } + + Entity::Entity(Entity&& other) noexcept : mName(std::move(other.mName)), mTex(other.mTex), mTransform(other.mTransform), mIsActive(other.mIsActive) { + other.mTex = nullptr; + LOG("Moved Entity: " << mName); + } + + Entity& Entity::operator=(Entity&& other) noexcept { + if (this != &other) { + mName = std::move(other.mName); + mTex = other.mTex; + mTransform = other.mTransform; + mIsActive = other.mIsActive; + other.mTex = nullptr; + } + return *this; + } + + void Entity::render(Game::Renderer::Renderer* renderer) { + if (!mIsActive || !mTex) return; // Don't render if not active or if there's no texture + + float w, h; + SDL_GetTextureSize(mTex->getSDLTexture(), &w, &h); + + SDL_FRect dst; + dst.w = w * mTransform.scaleX * mScaleConstant; // 1.f is HUGE, so this is just a constant to make the default scale more reasonable + dst.h = h * mTransform.scaleY * mScaleConstant; + + // Top-left origin + dst.x = mTransform.x; + dst.y = mTransform.y; + + SDL_FPoint center; + center.x = dst.w / 2.f; + center.y = dst.h / 2.f; + + SDL_RenderTextureRotated( + renderer->getSDLRenderer(), + mTex->getSDLTexture(), + nullptr, + &dst, + mTransform.rotation, + ¢er, + SDL_FLIP_NONE + ); + } +} \ No newline at end of file diff --git a/src/renderer/renderer.cpp b/src/renderer/renderer.cpp index 6a3a47f..f7f5628 100644 --- a/src/renderer/renderer.cpp +++ b/src/renderer/renderer.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include namespace Game::Renderer { Renderer::Renderer() : mRenderer(nullptr) {} @@ -34,12 +36,18 @@ namespace Game::Renderer { return true; } - void Renderer::run(std::stop_token stoken) { - while (!stoken.stop_requested()) { - mClear(); - // Get gamestate mutex and render the objects here; GameState::getState().objects or something, idk - mPresent(); + void Renderer::renderFrame() { + mClear(); + + // Get gamestate and render the objects here; GameState::getState().objects or something, idk + auto entities = Game::State::GameState::getInstance().getEntitiesRef(); + //LOG("Entity count: " << entities->size()); + + for (auto& entity : *entities) { + entity->render(this); } + + mPresent(); } void Renderer::mClear() { diff --git a/src/renderer/texture.cpp b/src/renderer/texture.cpp index 39e31d5..30f660b 100644 --- a/src/renderer/texture.cpp +++ b/src/renderer/texture.cpp @@ -1,33 +1,38 @@ #include -namespace Game::Renderer { - Texture::Texture(std::string& path, Renderer* renderer, std::string id) - : mTex(nullptr), mId(id) { - SDL_Surface* surf = IMG_Load(path.c_str()); - if (!surf) { - ERROR("Failed to load image at " << path); - return; - } - - mTex = SDL_CreateTextureFromSurface(renderer->getSDLRenderer(), surf); - SDL_DestroySurface(surf); - +Game::Renderer::Texture::Texture(const std::string& path, SDL_Renderer* renderer, std::string id) + : mTex(nullptr), mId(id) { + SDL_Surface* surf = IMG_Load(path.c_str()); + if (!surf) { + ERROR("Failed to load image at " << path); + return; } - Texture::Texture(const Texture& other) { - // Copy the references, since copying memory would require re-initing a bunch of things - for now - this->mTex = other.mTex; - } + mTex = SDL_CreateTextureFromSurface(renderer, surf); + SDL_DestroySurface(surf); +} - Texture& Texture::operator=(const Texture& other) { - // Same reasoning - this->mTex = other.mTex; - return *this; - } +Game::Renderer::Texture::Texture(const Texture& other) { + // Copy the references, since copying memory would require re-initing a bunch of things - for now + this->mTex = other.mTex; +} - Texture::~Texture() { - if (mTex) - SDL_DestroyTexture(mTex); - LOG("Destroyed texture '" << mId << "'") - } +Game::Renderer::Texture& Game::Renderer::Texture::operator=(const Texture& other) { + // Same reasoning + this->mTex = other.mTex; + return *this; +} + +Game::Renderer::Texture::~Texture() { + if (mTex) + SDL_DestroyTexture(mTex); + LOG("Destroyed texture '" << mId << "'") +} + +SDL_Texture* Game::Renderer::Texture::getSDLTexture() { + return mTex; +} + +std::string Game::Renderer::Texture::getId() { + return mId; } \ No newline at end of file diff --git a/src/state/gamestate.cpp b/src/state/gamestate.cpp index 810174d..19f38e8 100644 --- a/src/state/gamestate.cpp +++ b/src/state/gamestate.cpp @@ -2,24 +2,22 @@ #include namespace Game::State { - // TODO: Caller should also hold these locks - const std::vector& GameState::getEntities() { - std::shared_lock lock(mMutex); - return mEntities; - } - - std::vector* GameState::getEntitiesRef() { - std::shared_lock lock(mMutex); + std::vector>* GameState::getEntitiesRef() { return &mEntities; } Object::Entity* GameState::getAtIndex(size_t at) { - std::shared_lock lock(mMutex); try { - return &mEntities.at(at); + return mEntities.at(at).get(); } catch (const std::out_of_range& e) { WARN("Tried to access entity from GameState out of range!"); return nullptr; } } + + void GameState::addEntity(std::unique_ptr entity) { + mEntities.push_back(std::move(entity)); + GameState::getInstance().getAtIndex(mEntities.size() - 1)->start(); // Call start() on the newly added entity + LOG("Added entity '" << GameState::getInstance().getAtIndex(mEntities.size() - 1)->getName() << "' to GameState"); + } } \ No newline at end of file diff --git a/src/window/window.cpp b/src/window/window.cpp index 0e189f9..4623983 100644 --- a/src/window/window.cpp +++ b/src/window/window.cpp @@ -1,16 +1,9 @@ #include namespace Game::Window { - Window::Window() : mWindow(nullptr), mRunning(false) { - } + Window::Window() : mWindow(nullptr), mRenderer(), mRunning(false) { } Window::~Window() { - // Stop render thread - if (mRenderThread.joinable()) { - mRenderThread.request_stop(); - mRenderThread.join(); - } - mRenderer.destroy(); if (mWindow) { @@ -36,14 +29,12 @@ namespace Game::Window { LOG("Window created successfully"); - // Spawn new thread for renderer if (!mRenderer.init(mWindow)) { SDL_DestroyWindow(mWindow); mWindow = nullptr; SDL_Quit(); return false; } - mRenderThread = std::jthread(std::bind_front(&Renderer::Renderer::run, &mRenderer)); mRunning = true; @@ -60,6 +51,14 @@ namespace Game::Window { // Handle other events (e.g., keyboard, mouse) here } + + auto entities = State::GameState::getInstance().getEntitiesRef(); + for (auto& entity : *entities) { + entity->update(); + } + + mRenderer.renderFrame(); + SDL_Delay(16); // ~60 FPS target, maybe make dynamic based on avg. frame time - TODO } } } \ No newline at end of file