inital commit

This commit is contained in:
2026-07-21 17:00:54 +01:00
commit c98d556af8
29 changed files with 1493 additions and 0 deletions
+189
View File
@@ -0,0 +1,189 @@
#include "engine/asset/asset.hpp"
#include "engine/asset/raylib-asset.hpp"
#include <algorithm>
#include <raylib.h>
#include <asset_packer/asset.hpp>
#include <string>
#include <iostream>
#include <filesystem>
#include <variant>
#include <ranges>
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<decltype(arg)>;
if constexpr (std::is_same_v<T, ImageData>) {
Texture* tex = static_cast<Texture*>(arg.texture);
UnloadTexture(*tex);
delete tex;
arg.texture = nullptr;
}
else if constexpr (std::is_same_v<T, SoundData>) {
Sound* sound = static_cast<Sound*>(arg.sound);
UnloadSound(*sound);
delete sound;
arg.sound = nullptr;
}
else if constexpr (std::is_same_v<T, MusicData>) {
Music* music = static_cast<Music*>(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<std::string> 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<ImageData>(&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<SoundData>(&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<MusicData>(&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<JsonData>(&asset.data)) {
return ptr->json;
}
return "{}";
}
}
+54
View File
@@ -0,0 +1,54 @@
#pragma once
#include <asset_packer/asset.hpp>
#include <string>
#include <variant>
#include <unordered_map>
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<ImageData, SoundData, MusicData, JsonData> data;
};
class AssetLoader{
private:
AssetReader asset_reader;
protected:
std::unordered_map<std::string,Asset> 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();
};
}
+35
View File
@@ -0,0 +1,35 @@
#pragma once
// #include "asset_packer/asset.hpp"
#include "engine/asset/asset.hpp"
#include <raylib.h>
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;
}
+86
View File
@@ -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 <memory>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
namespace varicle {
void game_loop(void){
auto& scene_manager = ServiceLocator::get<SceneManager>();
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<RaylibAssetLoader>());
ServiceLocator::provide(std::make_unique<SceneManager>());
// 2. Call game-specific startup (where the game creates its first scene)
on_init();
auto& scene_manager = ServiceLocator::get<SceneManager>();
#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<SceneManager>().switch_to_scene(scene_id);
}
void Application::quit() {
ServiceLocator::get<SceneManager>().quit();
}
}
+29
View File
@@ -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();
};
}
+109
View File
@@ -0,0 +1,109 @@
#pragma once
#include <variant>
#include <string>
#include <mutex>
#include <iostream>
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<std::monostate,float,Vec2,Vec3,Vec4,std::string>;
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<typename T>
T Get() const {
std::lock_guard<std::mutex> lock(v_mutex);
return std::get<T>(data);
}
void Print() const {
std::lock_guard<std::mutex> lock(v_mutex);
std::cout << "[Variant " << TypeToString(type) << "]: ";
std::visit([](auto &&arg){
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, std::monostate>) std::cout << "Null";
else if constexpr (std::is_same_v<T, float>) std::cout << arg;
else if constexpr (std::is_same_v<T, Vec2>) std::cout << "(" << arg.x << ", " << arg.y << ")";
else if constexpr (std::is_same_v<T, Vec3>) std::cout << "(" << arg.x << ", " << arg.y << ", " << arg.z << ")";
else if constexpr (std::is_same_v<T, Vec4>) std::cout << "(" << arg.x << ", " << arg.y << ", " << arg.z << ", " << arg.w << ")";
else if constexpr (std::is_same_v<T, std::string>) 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;
};
@@ -0,0 +1,74 @@
#pragma once
#include <engine_variant.hpp>
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
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;
}
}
};
+68
View File
@@ -0,0 +1,68 @@
#pragma once
#include <cassert>
#include<vector>
#include<algorithm>
#include <memory>
#include <typeindex>
#include <unordered_map>
namespace varicle{
class ServiceLocator{
private:
// generic storage
struct ServiceContainerBase {
virtual ~ServiceContainerBase() = default;
};
template <typename T>
struct ServiceContainer : public ServiceContainerBase {
std::unique_ptr<T> instance;
ServiceContainer(std::unique_ptr<T> inst) : instance(std::move(inst)) {}
};
static inline std::unordered_map<std::type_index, std::unique_ptr<ServiceContainerBase>> s_services;
// Track registration order to ensure safe LIFO destruction
static inline std::vector<std::type_index> s_registration_order;
public:
// Wrap and pass ownership of a unique_ptr to the locator
template <typename T>
static void provide(std::unique_ptr<T> service) {
s_services[typeid(T)] = std::make_unique<ServiceContainer<T>>(std::move(service));
}
template <typename T>
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 <ServiceContainer<T>>(it->second);
auto* container = static_cast<ServiceContainer<T>*>(it->second.get());
return *(container->instance);
}
// Explicitly destroy a specific service
template <typename T>
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();
}
};
};
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include <raylib.h>
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;
};
}
+50
View File
@@ -0,0 +1,50 @@
#include "engine/render/render-system.hpp"
#include "engine/ecs/components.hpp"
#include<raylib.h>
#include <entt/entt.hpp>
namespace varicle {
void update_render_system(entt::registry &registry){
auto view = registry.view<const Sprite, const Position>();
for (entt::entity entity : view){
const Sprite &sprite = view.get<Sprite>(entity);
const Position &pos = view.get<Position>(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
);
}
}
}
}
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#include <entt/entt.hpp>
#include <raylib.h>
namespace varicle {
void update_render_system(entt::registry &registry);
}
+65
View File
@@ -0,0 +1,65 @@
#include "raylib.h"
#include "engine/scene/scene.hpp"
namespace varicle {
SceneManager::SceneManager(){
// current_scene = std::make_unique<MenuScene>();
}
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;
}
}
+82
View File
@@ -0,0 +1,82 @@
#pragma once
#include <functional>
#include <entt/entt.hpp>
#include <memory>
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::unique_ptr<Scene>()>;
std::unordered_map<std::string, SceneFactory> scene_registry;
std::unique_ptr<Scene> 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();
};
}
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include <random>
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<float> 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<int> dist(min, max);
return dist(gen);
}
}