From c98d556af87ceab45dd824dda0174669b278d013 Mon Sep 17 00:00:00 2001 From: ShadesAndGrays Date: Tue, 21 Jul 2026 17:00:54 +0100 Subject: [PATCH] inital commit --- .clang-format | 4 + .clangd | 5 + .gitignore | 11 ++ CMakeLists.txt | 31 +++ README.md | 1 + html/README.md | 9 + html/shell.css | 96 ++++++++++ html/shell.html | 115 +++++++++++ html/shell_custom.html | 51 +++++ html/shell_minimal.html | 152 +++++++++++++++ html/shell_minimal_runtime.html | 26 +++ src/engine/asset/asset.cpp | 189 +++++++++++++++++++ src/engine/asset/asset.hpp | 54 ++++++ src/engine/asset/raylib-asset.hpp | 35 ++++ src/engine/core/application.cpp | 86 +++++++++ src/engine/core/application.hpp | 29 +++ src/engine/core/engine_variant.hpp | 109 +++++++++++ src/engine/core/engine_variant_operation.hpp | 74 ++++++++ src/engine/core/service-locator.hpp | 68 +++++++ src/engine/ecs/components.hpp | 28 +++ src/engine/render/render-system.cpp | 50 +++++ src/engine/render/render-system.hpp | 8 + src/engine/scene/scene.cpp | 65 +++++++ src/engine/scene/scene.hpp | 82 ++++++++ src/engine/util/math_util.hpp | 22 +++ src/game/ecs/components.hpp | 1 + src/game/main.cpp | 30 +++ src/game/scenes/menu.cpp | 49 +++++ src/game/scenes/menu.hpp | 13 ++ 29 files changed, 1493 insertions(+) create mode 100644 .clang-format create mode 100644 .clangd create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 html/README.md create mode 100644 html/shell.css create mode 100644 html/shell.html create mode 100644 html/shell_custom.html create mode 100644 html/shell_minimal.html create mode 100644 html/shell_minimal_runtime.html create mode 100644 src/engine/asset/asset.cpp create mode 100644 src/engine/asset/asset.hpp create mode 100644 src/engine/asset/raylib-asset.hpp create mode 100644 src/engine/core/application.cpp create mode 100644 src/engine/core/application.hpp create mode 100644 src/engine/core/engine_variant.hpp create mode 100644 src/engine/core/engine_variant_operation.hpp create mode 100644 src/engine/core/service-locator.hpp create mode 100644 src/engine/ecs/components.hpp create mode 100644 src/engine/render/render-system.cpp create mode 100644 src/engine/render/render-system.hpp create mode 100644 src/engine/scene/scene.cpp create mode 100644 src/engine/scene/scene.hpp create mode 100644 src/engine/util/math_util.hpp create mode 100644 src/game/ecs/components.hpp create mode 100644 src/game/main.cpp create mode 100644 src/game/scenes/menu.cpp create mode 100644 src/game/scenes/menu.hpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..960bc8c --- /dev/null +++ b/.clang-format @@ -0,0 +1,4 @@ +BasedOnStyle: LLVM +IndentWidth: 4 +TabWidth: 4 +UseTab: Never diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..b476515 --- /dev/null +++ b/.clangd @@ -0,0 +1,5 @@ +CompileFlags: + Remove: + - "-fmodules-ts" + - "-fdeps-format=*" + - "-fmodule-mapper=*" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b3046dc --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# output dir +build/ +build-desktop/ +build-web/ +bin/ +web/ +debug/ + +# artifacts +compile_commands.json +web* diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..eabe560 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 4.4.0) + + +project(my_app LANGUAGES CXX) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# set(CMAKE_CXX_STANDARD_REQUIRED ON) + +file( GLOB_RECURSE + SRC + CONFIGURE + "src/*cpp" + "src/*.hpp" +) + +message(VERBOSE ${SRC}) + +add_executable(my_app ${SRC}) + +INCLUDE_DIRECTORIES("src") + +set_target_properties( my_app PROPERTIES + CXX_STANDARD 23 + CXX_STANDARD_REQUIRED ON + CXX_EXTENXSIONS OFF + +) + +target_link_libraries(my_app PRIVATE stdc++exp ) + diff --git a/README.md b/README.md new file mode 100644 index 0000000..71d3c95 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Varicle Framework diff --git a/html/README.md b/html/README.md new file mode 100644 index 0000000..faefa40 --- /dev/null +++ b/html/README.md @@ -0,0 +1,9 @@ +HTML Template Files +=================== + +The files in this directory are html templates used by emscripten when +generating html output. These are only used when the output of the compiler +has the `.html` suffix (or when `-oformat=html` is specified). + +By default the `shell.html` file in this directory is used but an alternative +can be specified using the `--shell-file` flag. diff --git a/html/shell.css b/html/shell.css new file mode 100644 index 0000000..d93a92d --- /dev/null +++ b/html/shell.css @@ -0,0 +1,96 @@ +body { + font-family: arial; + margin: 0; + padding: none; +} + +.emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; } +div.emscripten { text-align: center; } +div.emscripten_border { border: 1px solid black; } +/* the canvas *must not* have any border or padding, or mouse coords will be wrong */ +canvas.emscripten { border: 0px none; background-color: black; } + +#emscripten_logo { + display: inline-block; + margin: 0; + padding: 6px; + width: 265px; +} + +.spinner { + height: 30px; + width: 30px; + margin: 0; + margin-top: 20px; + margin-left: 20px; + display: inline-block; + vertical-align: top; + + -webkit-animation: rotation .8s linear infinite; + -moz-animation: rotation .8s linear infinite; + -o-animation: rotation .8s linear infinite; + animation: rotation 0.8s linear infinite; + + border-left: 5px solid rgb(235, 235, 235); + border-right: 5px solid rgb(235, 235, 235); + border-bottom: 5px solid rgb(235, 235, 235); + border-top: 5px solid rgb(120, 120, 120); + + border-radius: 100%; + background-color: rgb(189, 215, 46); +} + +@-webkit-keyframes rotation { + from {-webkit-transform: rotate(0deg);} + to {-webkit-transform: rotate(360deg);} +} +@-moz-keyframes rotation { + from {-moz-transform: rotate(0deg);} + to {-moz-transform: rotate(360deg);} +} +@-o-keyframes rotation { + from {-o-transform: rotate(0deg);} + to {-o-transform: rotate(360deg);} +} +@keyframes rotation { + from {transform: rotate(0deg);} + to {transform: rotate(360deg);} +} + +#status { + display: inline-block; + vertical-align: top; + margin-top: 30px; + margin-left: 20px; + font-weight: bold; + color: rgb(120, 120, 120); +} + +#progress { + height: 20px; + width: 300px; +} + +#controls { + display: inline-block; + float: right; + vertical-align: top; + margin-top: 30px; + margin-right: 20px; +} + +#output { + width: 100%; + height: 200px; + margin: 0 auto; + margin-top: 10px; + border-left: 0px; + border-right: 0px; + padding-left: 0px; + padding-right: 0px; + display: block; + background-color: black; + color: white; + font-family: 'Lucida Console', Monaco, monospace; + outline: none; +} diff --git a/html/shell.html b/html/shell.html new file mode 100644 index 0000000..8e5f42b --- /dev/null +++ b/html/shell.html @@ -0,0 +1,115 @@ + + + + + + Emscripten-Generated Code + + + + {{{ SHELL_LOGO }}} + +
+
Downloading...
+ + + Resize canvas + Lock/hide mouse pointer     + + + + +
+ +
+ +
+ +
+ + + + {{{ SCRIPT }}} +#if MODULARIZE && !EXPORT_ES6 + +#endif + + diff --git a/html/shell_custom.html b/html/shell_custom.html new file mode 100644 index 0000000..26a6ceb --- /dev/null +++ b/html/shell_custom.html @@ -0,0 +1,51 @@ + + + + + + Flappy Bird Bullet Hell + + + + + + + + + {{{ SCRIPT }}} + + diff --git a/html/shell_minimal.html b/html/shell_minimal.html new file mode 100644 index 0000000..6a07b34 --- /dev/null +++ b/html/shell_minimal.html @@ -0,0 +1,152 @@ + + + + + + Emscripten-Generated Code + + + +
+
emscripten
+
Downloading...
+
+ +
+
+ +
+
+
+ Resize canvas + Lock/hide mouse pointer +     + +
+ +
+ +
+ + {{{ SCRIPT }}} +#if MODULARIZE && !EXPORT_ES6 + +#endif + + diff --git a/html/shell_minimal_runtime.html b/html/shell_minimal_runtime.html new file mode 100644 index 0000000..74d50f0 --- /dev/null +++ b/html/shell_minimal_runtime.html @@ -0,0 +1,26 @@ + + + + + + diff --git a/src/engine/asset/asset.cpp b/src/engine/asset/asset.cpp new file mode 100644 index 0000000..987aa9b --- /dev/null +++ b/src/engine/asset/asset.cpp @@ -0,0 +1,189 @@ +#include "engine/asset/asset.hpp" +#include "engine/asset/raylib-asset.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include + + +namespace fs = std::filesystem; + +// Helper to determine if the asset path implies streaming music +bool should_stream_as_music(const std::filesystem::path& asset_path) { + for (const auto& part : asset_path) { + if (part == "music" || part == "bgm") { + return true; + } + } + return false; +} + +namespace varicle { + + AssetLoader::AssetLoader(std::string asset_data) : asset_reader(asset_data){ + if (!asset_reader.load_index()){ + std::cerr << "Failed to load assets!!!" << std::endl; + } + } + + AssetReader AssetLoader::get_reader(){ + return asset_reader; + } + + RaylibAssetLoader::RaylibAssetLoader(std::string asset_data) : AssetLoader(asset_data) { + + } + + RaylibAssetLoader::~RaylibAssetLoader(){ + unload_all_assets(); + } + + + + void RaylibAssetLoader::load_asset(std::string path){ + fs::path file_path(path); + Asset new_asset; + new_asset.name = file_path.filename().string() ; + + // std::cout << file_path.extension() << std::endl; + auto bytes = get_reader().extract_asset(path); + + if (file_path.extension() == ".png"){ + Image img = LoadImageFromMemory(".png", bytes.data(), bytes.size()); + auto texture = LoadTextureFromImage(img); + UnloadImage(img); + new_asset.data = ImageData { new Texture(texture)}; + + + }else if(file_path.extension() == ".ogg"){ + + if (should_stream_as_music(file_path)){ + auto music = LoadMusicStreamFromMemory(".ogg",bytes.data(),bytes.size()); + new_asset.data = MusicData{new Music(music)}; + }else{ + auto wave = LoadWaveFromMemory(".ogg", bytes.data(),bytes.size()); + auto sound = LoadSoundFromWave(wave); + UnloadWave(wave); + new_asset.data = SoundData{new Sound(sound)}; + } + + } + else if(file_path.extension() == ".mp3"){ + if (should_stream_as_music(file_path)){ + auto music = LoadMusicStreamFromMemory(".mp3",bytes.data(),bytes.size()); + new_asset.data = MusicData{new Music(music)}; + }else{ + auto wave = LoadWaveFromMemory(".mp3", bytes.data(),bytes.size()); + auto sound = LoadSoundFromWave(wave); + UnloadWave(wave); + new_asset.data = SoundData{new Sound(sound)}; + } + + } + else if(file_path.extension() == ".wav"){ + if (should_stream_as_music(file_path)){ + auto music = LoadMusicStreamFromMemory(".wav",bytes.data(),bytes.size()); + new_asset.data = MusicData{new Music(music)}; + }else{ + auto wave = LoadWaveFromMemory(".wav", bytes.data(),bytes.size()); + auto sound = LoadSoundFromWave(wave); + UnloadWave(wave); + new_asset.data = SoundData{new Sound(sound)}; + } + } + + else if(file_path.extension() == ".json"){ + auto json = std::string(bytes.begin(),bytes.end()); + new_asset.data = JsonData {json}; + } + + assets[path] = new_asset; + } + + void RaylibAssetLoader::unload_asset(std::string path){ + if (!assets.contains(path)) return; + Asset asset = assets[path]; + + std::visit([](auto&& arg) { + using T = std::decay_t; + + if constexpr (std::is_same_v) { + Texture* tex = static_cast(arg.texture); + UnloadTexture(*tex); + delete tex; + arg.texture = nullptr; + } + else if constexpr (std::is_same_v) { + Sound* sound = static_cast(arg.sound); + UnloadSound(*sound); + delete sound; + arg.sound = nullptr; + } + else if constexpr (std::is_same_v) { + Music* music = static_cast(arg.music); + UnloadMusicStream(*music); + delete music; + arg.music = nullptr; + } + // JsonData requires no manual step because std::string cleans itself up! + }, asset.data); + + assets.erase(path); + + } + + + void RaylibAssetLoader::unload_all_assets(){ + std::vector assets_to_unload; + std::ranges::copy(std::views::keys(assets),std::back_inserter(assets_to_unload)); + for (const auto &path : assets_to_unload){ + unload_asset(path); + } + } + + Asset RaylibAssetLoader::get_asset(std::string path){ + if (assets.contains(path)){ + return assets[path]; + }else{ + return {}; + } + } + + + Texture* RaylibAssetLoader::get_texture(std::string path){ + Asset asset = get_asset(path); + if (auto* ptr = std::get_if(&asset.data)) { + return (Texture*)ptr->texture; + } + return nullptr; + } + + Sound* RaylibAssetLoader::get_sound(std::string path){ + Asset asset = get_asset(path); + if (auto* ptr = std::get_if(&asset.data)) { + return (Sound*)ptr->sound; + } + return nullptr; + } + Music* RaylibAssetLoader::get_music(std::string path){ + Asset asset = get_asset(path); + if (auto* ptr = std::get_if(&asset.data)) { + return (Music*)ptr->music; + } + return nullptr; + } + + std::string RaylibAssetLoader::get_json(std::string path){ + Asset asset = get_asset(path); + if (auto* ptr = std::get_if(&asset.data)) { + return ptr->json; + } + return "{}"; + } +} diff --git a/src/engine/asset/asset.hpp b/src/engine/asset/asset.hpp new file mode 100644 index 0000000..4b91f0b --- /dev/null +++ b/src/engine/asset/asset.hpp @@ -0,0 +1,54 @@ +#pragma once +#include + +#include +#include +#include + + + +namespace varicle { + + struct ImageData { + void* texture; + }; + + struct SoundData { + void* sound; + }; + + struct MusicData { + void* music; + }; + + struct JsonData { + std::string json; + }; + + struct Asset { + std::string name; + std::variant data; + }; + + + class AssetLoader{ + + private: + AssetReader asset_reader; + + protected: + std::unordered_map assets; + virtual void load_all_assets(std::string data_path) = 0; + virtual void unload_all_assets() = 0; + + public: + AssetLoader(std::string asset_data); + ~AssetLoader() = default; + virtual void load_asset(std::string path) = 0; + virtual void unload_asset(std::string path) = 0; + virtual Asset get_asset(std::string path) = 0;; + AssetReader get_reader(); + + }; + +} diff --git a/src/engine/asset/raylib-asset.hpp b/src/engine/asset/raylib-asset.hpp new file mode 100644 index 0000000..c23b9a8 --- /dev/null +++ b/src/engine/asset/raylib-asset.hpp @@ -0,0 +1,35 @@ +#pragma once +// #include "asset_packer/asset.hpp" +#include "engine/asset/asset.hpp" +#include + +namespace varicle { + + + class RaylibAssetLoader : public AssetLoader{ + protected: + void load_all_assets(std::string asset_list) override {}; + void unload_all_assets() override; + + public: + RaylibAssetLoader(std::string asset_data = "data.dat"); + ~RaylibAssetLoader(); + + + void load_asset(std::string path) override; + void unload_asset(std::string path) override; + Asset get_asset(std::string path) override; + + Texture* get_texture(std::string path); + Sound* get_sound(std::string path); + Music* get_music(std::string path); + std::string get_json(std::string path); + + }; + + + // extern RaylibAssetLoader asset_loader; + + + +} diff --git a/src/engine/core/application.cpp b/src/engine/core/application.cpp new file mode 100644 index 0000000..3fe3ef7 --- /dev/null +++ b/src/engine/core/application.cpp @@ -0,0 +1,86 @@ +// src/engine/core/application.cpp + +#include "engine/core/application.hpp" +#include "engine/core/service-locator.hpp" +#include "engine/asset/raylib-asset.hpp" + +#include "engine/scene/scene.hpp" +#include "raylib.h" +#include "rlImGui.h" +#include + + +#ifdef __EMSCRIPTEN__ +#include +#endif + +namespace varicle { + + void game_loop(void){ + + auto& scene_manager = ServiceLocator::get(); + + float dt = GetFrameTime(); + + // Update + scene_manager.update(dt); + + // Draw + BeginDrawing(); + ClearBackground(RAYWHITE); // Or a configurable engine default color + + scene_manager.render(); + + rlImGuiBegin(); + scene_manager.ui(); + rlImGuiEnd(); + + EndDrawing(); + + scene_manager.process_scene_switch(); + } + + void Application::run() { + // 1. Initialize Raylib + SetTraceLogLevel(LOG_NONE); + SetConfigFlags(FLAG_VSYNC_HINT); + InitWindow(m_window_width, m_window_height, m_window_title); + SetTargetFPS(60); + rlImGuiSetup(true); // initialize gui + + ServiceLocator::provide(std::make_unique()); + ServiceLocator::provide(std::make_unique()); + + // 2. Call game-specific startup (where the game creates its first scene) + on_init(); + + auto& scene_manager = ServiceLocator::get(); + + +#ifdef __EMSCRIPTEN__ + emscripten_set_main_loop(game_loop, 0, 1); +#else + while (!scene_manager.should_game_close()){ + game_loop(); + } +#endif + + // 4. Call game-specific cleanup + on_shutdown(); + + ServiceLocator::shutdown(); // Clean up services + + // 5. Close Raylib + rlImGuiShutdown(); + CloseWindow(); + } + + void Application::change_scene(std::string scene_id) { + ServiceLocator::get().switch_to_scene(scene_id); + } + + void Application::quit() { + ServiceLocator::get().quit(); + } + +} diff --git a/src/engine/core/application.hpp b/src/engine/core/application.hpp new file mode 100644 index 0000000..badd20b --- /dev/null +++ b/src/engine/core/application.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "engine/scene/scene.hpp" + +#include "raylib.h" + +namespace varicle { + + class Application{ + + // Window configuration variables that a derived game can tweak in its constructor + int m_window_width = 1280; + int m_window_height = 720; + const char* m_window_title = "My Raylib Engine Game"; + + public: + Application() = default; + virtual ~Application() = default; + + virtual void on_init() = 0; + virtual void on_shutdown() = 0; + + void run(); + void change_scene(std::string scene_id); + void quit(); + + + }; +} diff --git a/src/engine/core/engine_variant.hpp b/src/engine/core/engine_variant.hpp new file mode 100644 index 0000000..d80bf74 --- /dev/null +++ b/src/engine/core/engine_variant.hpp @@ -0,0 +1,109 @@ +#pragma once +#include +#include +#include +#include + +struct Vec2 { + float x = 0.0f; + float y = 0.0f; + + bool operator==(const Vec2& o) const { return x == o.x && y == o.y; } + bool operator!=(const Vec2& o) const { return !(*this == o); } + Vec2 operator+(const Vec2& o) const { return { x + o.x, y + o.y }; } + Vec2 operator-(const Vec2& o) const { return { x - o.x, y - o.y }; } + Vec2 operator*(float scalar) const { return { x * scalar, y * scalar }; } +}; + +struct Vec3 { + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + + bool operator==(const Vec3& o) const { return x == o.x && y == o.y && z == o.z; } + bool operator!=(const Vec3& o) const { return !(*this == o); } + Vec3 operator+(const Vec3& o) const { return { x + o.x, y + o.y, z + o.z }; } + Vec3 operator-(const Vec3& o) const { return { x - o.x, y - o.y, z - o.z }; } + Vec3 operator*(float scalar) const { return { x * scalar, y * scalar, z * scalar }; } +}; + +struct Vec4 { + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + float w = 1.0f; + + bool operator==(const Vec4& o) const { return x == o.x && y == o.y && z == o.z && w == o.w; } + bool operator!=(const Vec4& o) const { return !(*this == o); } + Vec4 operator+(const Vec4& o) const { return { x + o.x, y + o.y, z + o.z, w + o.w }; } + Vec4 operator-(const Vec4& o) const { return { x - o.x, y - o.y, z - o.z, w - o.w }; } + Vec4 operator*(float scalar) const { return { x * scalar, y * scalar, z * scalar, w * scalar }; } +}; + + +// Standalone Interpolation Helpers +inline Vec2 Vec2Lerp(const Vec2& s, const Vec2& e, float a) { return { s.x + (e.x - s.x) * a, s.y + (e.y - s.y) * a }; } +inline Vec3 Vec3Lerp(const Vec3& s, const Vec3& e, float a) { return { s.x + (e.x - s.x) * a, s.y + (e.y - s.y) * a, s.z + (e.z - s.z) * a }; } +inline Vec4 Vec4Lerp(const Vec4& s, const Vec4& e, float a) { return { s.x + (e.x - s.x) * a, s.y + (e.y - s.y) * a, s.z + (e.z - s.z) * a, s.w + (e.w - s.w) * a }; } + + + + +enum class VariantType { Null, Float, Vector2, Vector3, Vector4, String }; + +class EngineVariant{ + + public: + using InternalVariant = std::variant; + + EngineVariant() : data(std::monostate{}), type(VariantType::Null){} + EngineVariant(float v) : data(v), type(VariantType::Float){} + EngineVariant(Vec2 v) : data(v), type(VariantType::Vector2){} + EngineVariant(Vec3 v) : data(v), type(VariantType::Vector3){} + EngineVariant(Vec4 v) : data(v), type(VariantType::Vector4){} + EngineVariant(std::string v) : data(v), type(VariantType::String){} + + VariantType GetType() const { return type; } + + template + T Get() const { + std::lock_guard lock(v_mutex); + return std::get(data); + } + + void Print() const { + + std::lock_guard lock(v_mutex); + std::cout << "[Variant " << TypeToString(type) << "]: "; + std::visit([](auto &&arg){ + using T = std::decay_t; + if constexpr (std::is_same_v) std::cout << "Null"; + else if constexpr (std::is_same_v) std::cout << arg; + else if constexpr (std::is_same_v) std::cout << "(" << arg.x << ", " << arg.y << ")"; + else if constexpr (std::is_same_v) std::cout << "(" << arg.x << ", " << arg.y << ", " << arg.z << ")"; + else if constexpr (std::is_same_v) std::cout << "(" << arg.x << ", " << arg.y << ", " << arg.z << ", " << arg.w << ")"; + else if constexpr (std::is_same_v) std::cout << "\"" << arg << "\""; + + },data); + + std::cout << "\n"; + } + + private: + InternalVariant data; + VariantType type; + mutable std::mutex v_mutex; + + static std::string TypeToString(VariantType t) { + switch(t) { + case VariantType::Float: return "Float"; + case VariantType::Vector2: return "Vector2"; + case VariantType::Vector3: return "Vector3"; + case VariantType::Vector4: return "Vector4"; + case VariantType::String: return "String"; + default: return "Null"; + } + } + + friend class VariantOpManager; +}; diff --git a/src/engine/core/engine_variant_operation.hpp b/src/engine/core/engine_variant_operation.hpp new file mode 100644 index 0000000..9216ecb --- /dev/null +++ b/src/engine/core/engine_variant_operation.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include + +template struct overloaded : Ts... { using Ts::operator()...; }; +template overloaded(Ts...) -> overloaded; + +enum class OpType { + Assign, // Replace the old value entirely + Add, // Add to the current value + Multiply, // Add to the current value + Lerp // Smoothly blend toward a value +}; + +struct VariantOpRequest { + EngineVariant* target; // The variable we want to change + OpType operation; // How we want to change it + EngineVariant operand; // The value we are using to make the change + float alpha; // Used ONLY for Lerp (0.0 to 1.0) +}; + +class VariantOpManager{ + + public: + VariantOpManager(){} + void ExecuteOperation(const VariantOpRequest& req) { + + if (req.target == nullptr) return; + + switch (req.operation){ + + case OpType::Assign: + req.target->data = req.operand.data; + req.target->type = req.operand.type; + break; + case OpType::Add: + req.target->data = std::visit( + overloaded{ + [](float p, float n) -> EngineVariant::InternalVariant {return p + n;}, + [](Vec2 p, Vec2 n) -> EngineVariant::InternalVariant {return p + n;}, + [](Vec3 p, Vec3 n) -> EngineVariant::InternalVariant {return p + n;}, + [](Vec4 p, Vec4 n) -> EngineVariant::InternalVariant {return p + n;}, + + // Fallback + [](auto p, auto n) -> EngineVariant::InternalVariant {return p;}, + },req.target->data,req.operand.data); + break; + + case OpType::Lerp: + req.target->data = std::visit(overloaded{ + [&](float p, float n) -> EngineVariant::InternalVariant { return p + (n - p) * req.alpha; }, + [&](Vec2 p, Vec2 n) -> EngineVariant::InternalVariant { return Vec2Lerp(p, n, req.alpha); }, + [&](Vec3 p, Vec3 n) -> EngineVariant::InternalVariant { return Vec3Lerp(p, n, req.alpha); }, + [&](Vec4 p, Vec4 n) -> EngineVariant::InternalVariant { return Vec4Lerp(p, n, req.alpha); }, + [&](const std::string& p, const std::string& n) -> EngineVariant::InternalVariant { + return req.alpha >= 0.5f ? n : p; // Discrete switch for strings + }, + + // Fallback + [](auto p, auto n) -> EngineVariant::InternalVariant { return p; } + }, req.target->data, req.operand.data); + break; + + case OpType::Multiply: + req.target->data = std::visit(overloaded{ + [](float p, float n) -> EngineVariant::InternalVariant { return p * n; }, + [](Vec2 p, float n) -> EngineVariant::InternalVariant { return p * n; }, // Scale a vector! + [](auto p, auto n) -> EngineVariant::InternalVariant { return p; } + }, req.target->data, req.operand.data); + break; + } + + } +}; diff --git a/src/engine/core/service-locator.hpp b/src/engine/core/service-locator.hpp new file mode 100644 index 0000000..6ab3c83 --- /dev/null +++ b/src/engine/core/service-locator.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace varicle{ + + class ServiceLocator{ + private: + + // generic storage + struct ServiceContainerBase { + virtual ~ServiceContainerBase() = default; + }; + + template + struct ServiceContainer : public ServiceContainerBase { + std::unique_ptr instance; + ServiceContainer(std::unique_ptr inst) : instance(std::move(inst)) {} + }; + + + static inline std::unordered_map> s_services; + + // Track registration order to ensure safe LIFO destruction + static inline std::vector s_registration_order; + + public: + // Wrap and pass ownership of a unique_ptr to the locator + template + static void provide(std::unique_ptr service) { + s_services[typeid(T)] = std::make_unique>(std::move(service)); + } + + template + static T& get(){ + auto it = s_services.find(typeid(T)); + + assert( it != s_services.end() && "Requested service was never provided!"); + + // Cast the container back, take a peek inside the container and send the address of the reference + // auto container = static_cast >(it->second); + + auto* container = static_cast*>(it->second.get()); + return *(container->instance); + } + + // Explicitly destroy a specific service + template + static void remove() { + s_services.erase(typeid(T)); // Automatically calls the destructor of T! + auto it = std::find(s_registration_order.begin(), s_registration_order.end(), typeid(T)); + if (it != s_registration_order.end()) s_registration_order.erase(it); + } + + static inline void shutdown(){ + for (auto it = s_registration_order.rbegin(); it != s_registration_order.rend(); ++it) { + s_services.erase(*it); + } + s_registration_order.clear(); + s_services.clear(); + } + }; +}; diff --git a/src/engine/ecs/components.hpp b/src/engine/ecs/components.hpp new file mode 100644 index 0000000..45845b9 --- /dev/null +++ b/src/engine/ecs/components.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include + +namespace varicle{ + + struct Position{ + float x; + float y; + }; + + struct Velocity{ + float dx; + float dy; + }; + + struct Sprite{ + Texture *texture; + float offset_x; + float offset_y; + float width; + float height; + bool flip_h; + bool flip_v; + float rotation; + }; + +} diff --git a/src/engine/render/render-system.cpp b/src/engine/render/render-system.cpp new file mode 100644 index 0000000..d9d1c15 --- /dev/null +++ b/src/engine/render/render-system.cpp @@ -0,0 +1,50 @@ +#include "engine/render/render-system.hpp" +#include "engine/ecs/components.hpp" + +#include +#include + +namespace varicle { + + void update_render_system(entt::registry ®istry){ + auto view = registry.view(); + + for (entt::entity entity : view){ + const Sprite &sprite = view.get(entity); + const Position &pos = view.get(entity); + + if (sprite.texture){ + DrawTexturePro( + *(sprite.texture), + Rectangle{ + 0, + 0, + (float)sprite.texture->width, + (float)sprite.texture->height, + }, + Rectangle { + sprite.offset_x + pos.x, + sprite.offset_y + pos.y, + sprite.width, + sprite.height + }, + Vector2 {sprite.width * 0.5f, sprite.height*0.5f}, + sprite.rotation, + WHITE); + + }else{ + DrawRectanglePro(Rectangle { + sprite.offset_x + pos.x, + sprite.offset_y + pos.y, + sprite.width, + sprite.height + }, + Vector2 {sprite.width * 0.5f, sprite.height*0.5f}, + sprite.rotation, + PURPLE + ); + } + } + + } +} diff --git a/src/engine/render/render-system.hpp b/src/engine/render/render-system.hpp new file mode 100644 index 0000000..3577a25 --- /dev/null +++ b/src/engine/render/render-system.hpp @@ -0,0 +1,8 @@ +#pragma once +#include +#include + +namespace varicle { + + void update_render_system(entt::registry ®istry); +} diff --git a/src/engine/scene/scene.cpp b/src/engine/scene/scene.cpp new file mode 100644 index 0000000..180ceb4 --- /dev/null +++ b/src/engine/scene/scene.cpp @@ -0,0 +1,65 @@ +#include "raylib.h" +#include "engine/scene/scene.hpp" + +namespace varicle { + + + SceneManager::SceneManager(){ + // current_scene = std::make_unique(); + } + + SceneManager::~SceneManager(){ + // delete current_scene; + } + + Scene& SceneManager::get_current_scene(){ + return *current_scene; + } + + void SceneManager::process_scene_switch(){ + + if (next_scene_id.empty()) return; + + auto it = scene_registry.find(next_scene_id); + if (it != scene_registry.end()){ + + current_scene.reset(); + current_scene = it->second(); + current_scene->init(); + } + + + next_scene_id.clear(); + } + + void SceneManager::update(float dt){ + if (!current_scene) return; + current_scene->update(dt); + } + + void SceneManager::render(){ + if (!current_scene) return; + current_scene->render(); + } + + void SceneManager::ui(){ + if (!current_scene) return; + current_scene->ui(); + } + + bool SceneManager::should_game_close(){ + return should_quit || WindowShouldClose(); + } + + void SceneManager::switch_to_scene(std::string scene_id){ + next_scene_id = scene_id; + } + + void SceneManager::register_scene(std::string scene_id, SceneFactory factory){ + scene_registry[std::string(scene_id)] = factory; + } + + void SceneManager::quit(){ + should_quit = true; + } +} diff --git a/src/engine/scene/scene.hpp b/src/engine/scene/scene.hpp new file mode 100644 index 0000000..a4aea9d --- /dev/null +++ b/src/engine/scene/scene.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include + +#include +#include + +namespace varicle { + + + // enum class SceneType{ + // MENU, + // GAMEPLAY + // }; + // + + + class Scene{ + + public: + Scene() = default; + virtual ~Scene() = default; + + virtual void init() = 0; + virtual void update(float dt) = 0; + virtual void render() = 0; + virtual void ui() = 0; + }; + + // class GamePlayScene : public Scene{ + // + // private: + // entt::registry registry; + // + // public: + // GamePlayScene(); + // ~GamePlayScene(); + // + // void update(float dt) override; + // void render() override; + // void ui() override; + // + // }; + // + // class MenuScene : public Scene{ + // + // public: + // MenuScene(); + // ~MenuScene() = default; + // + // void update(float dt) override; + // void render() override; + // void ui() override; + // + // }; + + class SceneManager{ + private: + using SceneFactory = std::function()>; + std::unordered_map scene_registry; + + std::unique_ptr current_scene = nullptr; + std::string next_scene_id; + bool pending_switch = false; + bool should_quit = false; + + public: + + SceneManager(); + ~SceneManager(); + + void update(float dt); + void render(); + void ui(); + void process_scene_switch(); + bool should_game_close(); + void switch_to_scene(std::string scene_id); + void register_scene(std::string scene_id, SceneFactory factory); + void quit(); + Scene& get_current_scene(); + }; +} diff --git a/src/engine/util/math_util.hpp b/src/engine/util/math_util.hpp new file mode 100644 index 0000000..23066ea --- /dev/null +++ b/src/engine/util/math_util.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include + + +namespace varicle { + // Generate a random float between min and max + inline float RandomRange(float min, float max) { + static std::random_device rd; + static std::mt19937 gen(rd()); + std::uniform_real_distribution dist(min, max); + return dist(gen); + } + + // Generate a random int between min and max + inline int RandomRange(int min, int max) { + static std::random_device rd; + static std::mt19937 gen(rd()); + std::uniform_int_distribution dist(min, max); + return dist(gen); + } +} diff --git a/src/game/ecs/components.hpp b/src/game/ecs/components.hpp new file mode 100644 index 0000000..6f70f09 --- /dev/null +++ b/src/game/ecs/components.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/src/game/main.cpp b/src/game/main.cpp new file mode 100644 index 0000000..8279912 --- /dev/null +++ b/src/game/main.cpp @@ -0,0 +1,30 @@ +#include "engine/core/application.hpp" +#include "engine/core/service-locator.hpp" +#include "engine/scene/scene.hpp" + +#include "game/scenes/game-over-scene/game-over.hpp" +#include "game/scenes/main-scene/main-scene.hpp" +#include "game/scenes/menu-scene/menu.hpp" + +#include + +class Game : public varicle::Application { + + public: + void on_init() override { + auto &scene_manager = + varicle::ServiceLocator::get(); + scene_manager.register_scene( + "menu", []() { return std::make_unique(); }); + + change_scene("menu"); + } + + void on_shutdown() override {} +}; + +int main() { + Game game; + game.run(); + return 0; +} diff --git a/src/game/scenes/menu.cpp b/src/game/scenes/menu.cpp new file mode 100644 index 0000000..1e52a82 --- /dev/null +++ b/src/game/scenes/menu.cpp @@ -0,0 +1,49 @@ +#include "menu.hpp" + +#include "engine/core/service-locator.hpp" +#include "raylib.h" + +#include + +void MenuScene::init() {} + +void MenuScene::update(float dt) {} + +void MenuScene::render() {} + +void MenuScene::ui() { + + // Get the current screen/viewport size + ImVec2 screen_size = ImGui::GetIO().DisplaySize; + + // size of button container + ImVec2 vbox_size = ImVec2(200.0f, 250.0f); + + // set next container to be center of the screen + ImVec2 screen_center = ImVec2(screen_size.x * 0.5f, screen_size.y * 0.5f); + ImGui::SetNextWindowPos(screen_center, ImGuiCond_Always, + ImVec2(0.5f, 0.5f)); + ImGui::SetNextWindowSize(vbox_size); + + // remove sub window decoration + ImGuiWindowFlags flags = + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoBackground; + + ImGui::Begin("VBoxContainer", nullptr, flags); + + // set button width to be size fill container + ImVec2 button_size = ImVec2(ImGui::GetContentRegionAvail().x, 50.0f); + + // ImGui::SetCursorPos(centeredPos); + if (ImGui::Button("Play", button_size)) { + v::ServiceLocator::get().switch_to_scene("main"); + } + + ImGui::Dummy(ImVec2(0, 10)); // Gap + + if (ImGui::Button("Quit", button_size)) { + v::ServiceLocator::get().quit(); + } + + ImGui::End(); +} diff --git a/src/game/scenes/menu.hpp b/src/game/scenes/menu.hpp new file mode 100644 index 0000000..60a3d7b --- /dev/null +++ b/src/game/scenes/menu.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "engine/scene/scene.hpp" + +namespace v = varicle; + +class MenuScene : public v::Scene { + public: + void init() override; + void update(float dt) override; + void render() override; + void ui() override; +};