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
+4
View File
@@ -0,0 +1,4 @@
BasedOnStyle: LLVM
IndentWidth: 4
TabWidth: 4
UseTab: Never
+5
View File
@@ -0,0 +1,5 @@
CompileFlags:
Remove:
- "-fmodules-ts"
- "-fdeps-format=*"
- "-fmodule-mapper=*"
+11
View File
@@ -0,0 +1,11 @@
# output dir
build/
build-desktop/
build-web/
bin/
web/
debug/
# artifacts
compile_commands.json
web*
+31
View File
@@ -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 )
+1
View File
@@ -0,0 +1 @@
# Varicle Framework
+9
View File
@@ -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.
+96
View File
@@ -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;
}
+115
View File
@@ -0,0 +1,115 @@
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Emscripten-Generated Code</title>
<style>{{{ SHELL_CSS }}}</style>
</head>
<body>
<a href="http://emscripten.org">{{{ SHELL_LOGO }}}</a>
<div class="spinner" id='spinner'></div>
<div class="emscripten" id="status">Downloading...</div>
<span id='controls'>
<span><input type="checkbox" id="resize">Resize canvas</span>
<span><input type="checkbox" id="pointerLock" checked>Lock/hide mouse pointer &nbsp;&nbsp;&nbsp;</span>
<span><input type="button" value="Fullscreen" onclick="Module.requestFullscreen(document.getElementById('pointerLock').checked,
document.getElementById('resize').checked)">
</span>
</span>
<div class="emscripten">
<progress value="0" max="100" id="progress" hidden=1></progress>
</div>
<div class="emscripten_border">
<canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()" tabindex=-1></canvas>
</div>
<textarea id="output" rows="8"></textarea>
<script type='text/javascript'>
var statusElement = document.getElementById('status');
var progressElement = document.getElementById('progress');
var spinnerElement = document.getElementById('spinner');
var canvasElement = document.getElementById('canvas');
var outputElement = document.getElementById('output');
if (outputElement) outputElement.value = ''; // clear browser cache
// As a default initial behavior, pop up an alert when webgl context is lost. To make your
// application robust, you may want to override this behavior before shipping!
// See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
canvasElement.addEventListener('webglcontextlost', (e) => {
alert('WebGL context lost. You will need to reload the page.');
e.preventDefault();
}, false);
var setStatus = (text) => {
if (!setStatus.last) setStatus.last = { time: Date.now(), text: '' };
if (text === setStatus.last.text) return;
var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
var now = Date.now();
// if this is a progress update, skip it if too soon
if (m && now - setStatus.last.time < 30) return;
setStatus.last.time = now;
setStatus.last.text = text;
if (m) {
text = m[1];
progressElement.value = parseInt(m[2])*100;
progressElement.max = parseInt(m[4])*100;
progressElement.hidden = false;
spinnerElement.hidden = false;
} else {
progressElement.value = null;
progressElement.max = null;
progressElement.hidden = true;
if (!text) spinnerElement.style.display = 'none';
}
statusElement.innerHTML = text;
};
#if MODULARIZE && !EXPORT_ES6
var moduleArgs = {
#else
var Module = {
#endif
print(...args) {
console.log(...args);
// These replacements are necessary if you render to raw HTML
//text = text.replace(/&/g, "&amp;");
//text = text.replace(/</g, "&lt;");
//text = text.replace(/>/g, "&gt;");
//text = text.replace('\n', '<br>', 'g');
if (outputElement) {
var text = args.join(' ');
outputElement.value += text + "\n";
outputElement.scrollTop = outputElement.scrollHeight; // focus on bottom
}
},
canvas: canvasElement,
setStatus: setStatus,
totalDependencies: 0,
monitorRunDependencies(left) {
this.totalDependencies = Math.max(this.totalDependencies, left);
setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');
}
};
setStatus('Downloading...');
window.onerror = window.onunhandledrejection = () => {
// TODO: do not warn on ok events like simulating an infinite loop or exitStatus
setStatus('Exception thrown, see JavaScript console');
spinnerElement.style.display = 'none';
setStatus = (text) => {
if (text) console.error('[post-exception status] ' + text);
};
};
</script>
{{{ SCRIPT }}}
#if MODULARIZE && !EXPORT_ES6
<script type='text/javascript'>
{{{ EXPORT_NAME }}}(moduleArgs);
</script>
#endif
</body>
</html>
+51
View File
@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Flappy Bird Bullet Hell</title>
<style>
/* Dark theme, removes scrollbars, and blocks standard browser drag behaviors */
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background-color: #111111;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
touch-action: none;
}
/* Dynamically fits the container window while preserving your Raylib aspect ratio */
canvas {
border: none;
outline: none;
background-color: #000000;
display: block;
image-rendering: pixelated; /* Keeps 2D pixel-art crisp if it stretches */
image-rendering: crisp-edges;
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
</style>
</head>
<body>
<canvas id="canvas" oncontextmenu="event.preventDefault()" tabindex="-1"></canvas>
<script type='text/javascript'>
var Module = {
canvas: (function() {
var canvas = document.getElementById('canvas');
return canvas;
})()
};
</script>
{{{ SCRIPT }}}
</body>
</html>
+152
View File
@@ -0,0 +1,152 @@
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Emscripten-Generated Code</title>
<style>
.emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }
textarea.emscripten { font-family: monospace; width: 80%; }
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; }
.spinner {
height: 50px;
width: 50px;
margin: 0px auto;
-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: 10px solid rgb(0,150,240);
border-right: 10px solid rgb(0,150,240);
border-bottom: 10px solid rgb(0,150,240);
border-top: 10px solid rgb(100,0,200);
border-radius: 100%;
background-color: rgb(200,100,250);
}
@-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);}
}
</style>
</head>
<body>
<hr/>
<figure style="overflow:visible;" id="spinner"><div class="spinner"></div><center style="margin-top:0.5em"><strong>emscripten</strong></center></figure>
<div class="emscripten" id="status">Downloading...</div>
<div class="emscripten">
<progress value="0" max="100" id="progress" hidden=1></progress>
</div>
<div class="emscripten_border">
<canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()" tabindex=-1></canvas>
</div>
<hr/>
<div class="emscripten">
<input type="checkbox" id="resize">Resize canvas
<input type="checkbox" id="pointerLock" checked>Lock/hide mouse pointer
&nbsp;&nbsp;&nbsp;
<input type="button" value="Fullscreen" onclick="Module.requestFullscreen(document.getElementById('pointerLock').checked,
document.getElementById('resize').checked)">
</div>
<hr/>
<textarea class="emscripten" id="output" rows="8"></textarea>
<hr>
<script type='text/javascript'>
var statusElement = document.getElementById('status');
var progressElement = document.getElementById('progress');
var spinnerElement = document.getElementById('spinner');
var canvasElement = document.getElementById('canvas');
var outputElement = document.getElementById('output');
if (outputElement) outputElement.value = ''; // clear browser cache
// As a default initial behavior, pop up an alert when webgl context is lost. To make your
// application robust, you may want to override this behavior before shipping!
// See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
canvasElement.addEventListener("webglcontextlost", (e) => {
alert('WebGL context lost. You will need to reload the page.');
e.preventDefault();
}, false);
function setStatus(text) {
if (!setStatus.last) setStatus.last = { time: Date.now(), text: '' };
if (text === setStatus.last.text) return;
var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
var now = Date.now();
if (m && now - setStatus.last.time < 30) return; // if this is a progress update, skip it if too soon
setStatus.last.time = now;
setStatus.last.text = text;
if (m) {
text = m[1];
progressElement.value = parseInt(m[2])*100;
progressElement.max = parseInt(m[4])*100;
progressElement.hidden = false;
spinnerElement.hidden = false;
} else {
progressElement.value = null;
progressElement.max = null;
progressElement.hidden = true;
if (!text) spinnerElement.hidden = true;
}
statusElement.innerHTML = text;
}
#if MODULARIZE && !EXPORT_ES6
var moduleArgs = {
#else
var Module = {
#endif
print(...args) {
// These replacements are necessary if you render to raw HTML
//text = text.replace(/&/g, "&amp;");
//text = text.replace(/</g, "&lt;");
//text = text.replace(/>/g, "&gt;");
//text = text.replace('\n', '<br>', 'g');
console.log(...args);
if (outputElement) {
var text = args.join(' ');
outputElement.value += text + "\n";
outputElement.scrollTop = outputElement.scrollHeight; // focus on bottom
}
},
canvas: canvasElement,
setStatus: setStatus,
totalDependencies: 0,
monitorRunDependencies(left) {
this.totalDependencies = Math.max(this.totalDependencies, left);
setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');
}
};
setStatus('Downloading...');
window.onerror = window.onunhandledrejection = () => {
setStatus('Exception thrown, see JavaScript console');
spinnerElement.style.display = 'none';
setStatus = (text) => {
if (text) console.error('[post-exception status] ' + text);
};
};
</script>
{{{ SCRIPT }}}
#if MODULARIZE && !EXPORT_ES6
<script type='text/javascript'>
{{{ EXPORT_NAME }}}(moduleArgs);
</script>
#endif
</body>
</html>
+26
View File
@@ -0,0 +1,26 @@
<!doctype html><html lang="en-us"><head><meta charset="utf-8"></head>
<body>
<canvas style='display:block; margin:auto;'></canvas>
<script>
#if !MODULARIZE
var {{{ EXPORT_NAME }}} = {};
#endif
#if WASM == 2
var supportsWasm = window.WebAssembly && location.search.indexOf('_rwasm=0') < 0;
#endif
// Depending on the build flags that one uses, different files need to be downloaded
// to load the compiled page. The right set of files will be expanded to be downloaded
// via the directive below.
{{{ DOWNLOAD_JS_AND_WASM_FILES }}}
#if SINGLE_FILE
// If we are doing a SINGLE_FILE=1 build, inlined JS runtime code follows here:
{{{ JS_CONTENTS_IN_SINGLE_FILE_BUILD }}}
#endif
</script>
</body>
</html>
+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);
}
}
+1
View File
@@ -0,0 +1 @@
#pragma once
+30
View File
@@ -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 <raylib.h>
class Game : public varicle::Application {
public:
void on_init() override {
auto &scene_manager =
varicle::ServiceLocator::get<varicle::SceneManager>();
scene_manager.register_scene(
"menu", []() { return std::make_unique<MenuScene>(); });
change_scene("menu");
}
void on_shutdown() override {}
};
int main() {
Game game;
game.run();
return 0;
}
+49
View File
@@ -0,0 +1,49 @@
#include "menu.hpp"
#include "engine/core/service-locator.hpp"
#include "raylib.h"
#include <imgui.h>
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<v::SceneManager>().switch_to_scene("main");
}
ImGui::Dummy(ImVec2(0, 10)); // Gap
if (ImGui::Button("Quit", button_size)) {
v::ServiceLocator::get<v::SceneManager>().quit();
}
ImGui::End();
}
+13
View File
@@ -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;
};