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 0000000..0f11c74 Binary files /dev/null and b/resources/missing_texture.png differ diff --git a/src/game/player.cpp b/src/game/player.cpp new file mode 100644 index 0000000..d487e58 --- /dev/null +++ b/src/game/player.cpp @@ -0,0 +1,14 @@ +#include + +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