commit 06bcac9bf72db67f1037476177da475a6751648e Author: ShadesAndGrays Date: Thu Jul 9 00:27:26 2026 +0100 inital commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a25d4ad --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +assets/ +bin/ +debug/ +external/ +compile_commands.json diff --git a/.vscode/imgui.ini b/.vscode/imgui.ini new file mode 100644 index 0000000..8f5ce5d --- /dev/null +++ b/.vscode/imgui.ini @@ -0,0 +1,8 @@ +[Window][Debug##Default] +Pos=60,60 +Size=400,400 + +[Window][VBoxContainer] +Pos=540,275 +Size=200,250 + diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..7631b4c --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,34 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "(gdb) Launch", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/debug/d.exe", + "args": [], + "stopAtEntry": false, + "cwd": "${fileDirname}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "miDebuggerPath": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Set Disassembly Flavor to Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ] + } + + ] +} \ No newline at end of file diff --git a/data.dat b/data.dat new file mode 100644 index 0000000..38c52b6 Binary files /dev/null and b/data.dat differ diff --git a/imgui.ini b/imgui.ini new file mode 100644 index 0000000..b49fefd --- /dev/null +++ b/imgui.ini @@ -0,0 +1,21 @@ +[Window][Debug##Default] +Pos=143,124 +Size=400,400 + +[Window][CenterWindow] +Pos=0,0 +Size=1280,800 +Collapsed=1 + +[Window][VBoxContainer] +Pos=540,275 +Size=200,250 + +[Window][Box] +Pos=156,64 +Size=180,105 + +[Window][Debug] +Pos=882,174 +Size=320,170 + diff --git a/makefile b/makefile new file mode 100644 index 0000000..0f7cf36 --- /dev/null +++ b/makefile @@ -0,0 +1,103 @@ +.PHONY: all build-gui build-game run + +CXX = g++ + +LDFLAGS = -L./external/raylib/lib \ + -L./external/box2d/build/src \ + -L./external/asset_packer/bin \ + +LIBS = -lraylib -lwinmm -lgdi32 -lbox2d -lassetpacker + +CXXFLAGS = -std=c++20 -O2 \ + -MMD -MP + +DEBUGFLAGS = -std=c++20 -g -Og + +INCLUDES = -I./external/raylib/include \ + -I./external/entt/single_include \ + -I./src/include \ + -I./external/imgui \ + -I./external/rlImGui \ + -I./external/box2d/include \ + -I./external/nhlohmann_json_3.12.0/ \ + -I./external/asset_packer/bin/include \ + +IMGUI_SRCS = ./external/imgui/imgui.cpp \ + ./external/imgui/imgui_demo.cpp \ + ./external/imgui/imgui_draw.cpp \ + ./external/imgui/imgui_tables.cpp \ + ./external/imgui/imgui_widgets.cpp \ + +IMGUI_BACKEND = ./external/rlImGui/rlImGui.cpp + + + +SRC_FILES = ./src/main.cpp \ + ./src/asset.cpp \ + ./src/movement.cpp \ + ./src/player.cpp \ + ./src/render.cpp \ + ./src/scenes/scene.cpp \ + ./src/scenes/game_play_scene.cpp \ + ./src/scenes/menu_scene.cpp + +IMGUI_OBJS = $(patsubst ./external/imgui/%.cpp,./bin/%.o,$(IMGUI_SRCS)) +IMGUI_BACKEND_OBJS = $(patsubst ./external/rlImGui/%.cpp,./bin/%.o,$(IMGUI_BACKEND)) +OBJS_FILES = $(patsubst ./src/%.cpp,./bin/%.o,$(SRC_FILES)) + +DEP_FILES = $(patsubst ./bin/%.o,./bin/%.d,$(OBJS_FILES)) + + +all: build-game run + +./bin/%.o: ./external/imgui/%.cpp + @mkdir -p bin + $(CXX) $(CXXFLAGS) $(INCLUDES) -c $< -o $@ + +./bin/%.o: ./external/rlImGui/%.cpp + @mkdir -p bin + $(CXX) $(CXXFLAGS) $(INCLUDES) -c $< -o $@ + +./bin/%.o: ./src/%.cpp + @mkdir -p bin + @mkdir -p $(dir $@) + $(CXX) $(CXXFLAGS) $(INCLUDES) -c $< -o $@ + +compile_commands.json: clean + @echo "Generating compile_commands.json..." + bear -- $(MAKE) + +-include $(DEP_FILES) + +build-gui: $(IMGUI_OBJS) + +build-gui-backend: $(IMGUI_BACKEND_OBJS) + +build-core: $(OBJS_FILES) + +build-game: $(OBJS_FILES) $(IMGUI_OBJS) $(IMGUI_BACKEND_OBJS) + @mkdir -p bin + $(CXX) $(CXXFLAGS) $(OBJS_FILES) $(IMGUI_OBJS) $(IMGUI_BACKEND_OBJS) $(LDFLAGS) $(LIBS) -o ./bin/main.exe + cp ./assets ./bin -rf + +build-debug: + @mkdir -p debug + $(CXX) $(DEBUGFLAGS) $(INCLUDES) $(SRC_FILES) $(IMGUI_SRCS) $(IMGUI_BACKEND) $(LDFLAGS) $(LIBS) -o ./debug/d.exe + cp ./assets ./debug -rf + + + +clean: + @rm -r -f ./bin + @rm -f compile_commands.json + +clean-debug: + @rm -r -f ./debug + +clean-all: clean clean-debug + +run: + ./bin/main.exe + +run-debug: + ./debug/d.exe diff --git a/src/asset.cpp b/src/asset.cpp new file mode 100644 index 0000000..47e5f85 --- /dev/null +++ b/src/asset.cpp @@ -0,0 +1,132 @@ + +#include + +#include +#include + +#include + +#include +#include +#include +#include + +namespace fs = std::filesystem; + +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; + + if (file_path.extension() == ".png"){ + auto texture_data = get_reader().extract_asset(path); + Image img = LoadImageFromMemory(".png", texture_data.data(), texture_data.size()); + auto texture = LoadTextureFromImage(img); + UnloadImage(img); + new_asset.data = ImageData { new Texture(texture)}; + + + }else if(file_path.extension() == ".ogg"){ + + }else if(file_path.extension() == ".wav"){ + + }else if(file_path.extension() == ".json"){ + + } + + + assets[path] = new_asset; + +} + +void RaylibAssetLoader::unload_asset(std::string path){ + Asset asset = assets[path]; + + std::visit([](auto&& arg) { + using T = std::decay_t; + + if constexpr (std::is_same_v) { + + UnloadTexture(*((Texture*) arg.texture)); + delete (Texture*) arg.texture; + } + else if constexpr (std::is_same_v) { + UnloadSound(*((Sound*) arg.sound)); + delete (Sound*) arg.sound; + } + else if constexpr (std::is_same_v) { + UnloadMusicStream(*((Music*) arg.music)); + delete (Music*) arg.music; + } + // JsonData requires no manual step because std::string cleans itself up! + }, asset.data); + +} + + +void RaylibAssetLoader::unload_all_assets(){ + for (auto [path,_] : assets){ + 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/imgui.ini b/src/imgui.ini new file mode 100644 index 0000000..8f5ce5d --- /dev/null +++ b/src/imgui.ini @@ -0,0 +1,8 @@ +[Window][Debug##Default] +Pos=60,60 +Size=400,400 + +[Window][VBoxContainer] +Pos=540,275 +Size=200,250 + diff --git a/src/include/asset/asset.hpp b/src/include/asset/asset.hpp new file mode 100644 index 0000000..a58e544 --- /dev/null +++ b/src/include/asset/asset.hpp @@ -0,0 +1,54 @@ +#pragma once +#include + +#include +#include +#include + + + + + + +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; + AssetReader get_reader(); + + 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;; + +}; + diff --git a/src/include/asset/raylib_asset.hpp b/src/include/asset/raylib_asset.hpp new file mode 100644 index 0000000..1b80d59 --- /dev/null +++ b/src/include/asset/raylib_asset.hpp @@ -0,0 +1,31 @@ +#pragma once +#include "asset_packer/asset.hpp" +#include +#include + +class RaylibAssetLoader : 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/include/components.hpp b/src/include/components.hpp new file mode 100644 index 0000000..678bfb8 --- /dev/null +++ b/src/include/components.hpp @@ -0,0 +1,18 @@ +#pragma once +#include + +struct Position{ + float x; + float y; +}; + +struct Velocity{ + float dx; + float dy; +}; + +struct Player{}; + +struct Sprite{ + Texture *texture; +}; diff --git a/src/include/movement.hpp b/src/include/movement.hpp new file mode 100644 index 0000000..a927b95 --- /dev/null +++ b/src/include/movement.hpp @@ -0,0 +1,4 @@ +#pragma once +#include + +void update_movement_system(entt::registry& registry, float dt); diff --git a/src/include/player.hpp b/src/include/player.hpp new file mode 100644 index 0000000..91a41a0 --- /dev/null +++ b/src/include/player.hpp @@ -0,0 +1,6 @@ +#pragma once +#include +#include + +void create_player(entt::registry& registry,Texture* texture); +void update_player_system(entt::registry& registry,float dt); diff --git a/src/include/render.hpp b/src/include/render.hpp new file mode 100644 index 0000000..585b961 --- /dev/null +++ b/src/include/render.hpp @@ -0,0 +1,5 @@ +#pragma once +#include + +void update_render_system(entt::registry ®istry); + diff --git a/src/include/scene.hpp b/src/include/scene.hpp new file mode 100644 index 0000000..6c50d02 --- /dev/null +++ b/src/include/scene.hpp @@ -0,0 +1,86 @@ +#pragma once + +#include + +#include + +enum class SceneType{ + MENU, + GAMEPLAY +}; + + +// Simple global data structure to hold the switch request +struct EngineState { + bool pendingSwitch = false; + SceneType nextScene; + + bool shouldQuit = false; + + void request_switch(SceneType type) { + nextScene = type; + pendingSwitch = true; + } + + void quit(){ + shouldQuit = true; + } +}; + +// Expose the global state to any file that includes scene.hpp +extern EngineState g_Engine; + + +class Scene{ + + public: + Scene() = default; + virtual ~Scene() = default; + + 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: + Scene* current_scene = nullptr; + SceneType next_scene_type; + bool should_switch = false; + + public: + SceneManager(); + ~SceneManager(); + + void update(float dt); + void render(); + void ui(); + void process_scene_switch(); +}; diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..2651858 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,84 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + + +// Globals +EngineState g_Engine; +RaylibAssetLoader asset_loader; + +int main(){ + +// + int screen_width = 1280; + int screen_height = 800; + + + + // initialized scene manager + SceneManager scene_manager; + + entt::registry registry; + + rlImGuiSetup(true); + + ImGuiStyle& style = ImGui::GetStyle(); + + // Rounded corners + style.WindowRounding = 6.0f; + style.ChildRounding = 4.0f; + style.FrameRounding = 4.0f; + style.PopupRounding = 4.0f; + style.ScrollbarRounding = 9.0f; + style.GrabRounding = 4.0f; + style.TabRounding = 4.0f; + + // Sizes and Spacing + style.WindowPadding = ImVec2(15.0f, 15.0f); + style.FramePadding = ImVec2(6.0f, 6.0f); + style.ItemSpacing = ImVec2(10.0f, 8.0f); + style.ScrollbarSize = 15.0f; + + SetTargetFPS(60); + SetTraceLogLevel(TraceLogLevel::LOG_NONE); + + InitWindow(screen_width, screen_height, "Bullet bird"); + SetWindowState(FLAG_WINDOW_RESIZABLE); + + + while (!WindowShouldClose() && !g_Engine.shouldQuit){ + + float dt = GetFrameTime(); + if (dt > 0.1f) dt = 0.1f; // cap dt just for now + + scene_manager.update(dt); + + BeginDrawing(); + ClearBackground(RAYWHITE); + + scene_manager.render(); + + rlImGuiBegin(); + scene_manager.ui(); + rlImGuiEnd(); + + EndDrawing(); + + scene_manager.process_scene_switch(); // !!! After scene_manager is done with the current loop + } + + CloseWindow(); + rlImGuiShutdown(); + return 0; +} + diff --git a/src/movement.cpp b/src/movement.cpp new file mode 100644 index 0000000..812eb80 --- /dev/null +++ b/src/movement.cpp @@ -0,0 +1,14 @@ +// src\movement.cpp +#include +#include + +void update_movement_system(entt::registry& registry, float dt) { + // We get Position and Velocity, but EXCLUDE entities that have the Player tag + auto view = registry.view(); + + view.each([dt](Position &position, Velocity &velocity) { + // This will only run for pipes, enemies, or particles! + position.x += velocity.dx * dt; + position.y += velocity.dy * dt; + }); +} diff --git a/src/player.cpp b/src/player.cpp new file mode 100644 index 0000000..437ae78 --- /dev/null +++ b/src/player.cpp @@ -0,0 +1,34 @@ +// src\player.cpp +#include +#include + +#include + +entt::entity player; + + + +void create_player(entt::registry& registry,Texture* texture){ + const auto entity = registry.create(); + registry.emplace(entity,100.0f,0.0f); + registry.emplace(entity,0.0f,0.0f); + registry.emplace(entity); + registry.emplace(entity,texture); +} + +void update_player_system(entt::registry& registry,float dt){ + + auto view = registry.view(); + + // Constants for fine-tuning game feel + const float GRAVITY = 900.0f; // Constant downward pull (pixels/sec^2) + const float JUMP_FORCE = -350.0f; // Negative force to burst UPWARD + + view.each([dt,GRAVITY,JUMP_FORCE](const auto &player, auto &velocity){ + if (IsKeyPressed(KEY_SPACE)){ + velocity.dy = JUMP_FORCE; + } + velocity.dy += GRAVITY * dt; + }); + +} diff --git a/src/render.cpp b/src/render.cpp new file mode 100644 index 0000000..60fe6c5 --- /dev/null +++ b/src/render.cpp @@ -0,0 +1,18 @@ + +#include +#include + +#include +#include + +void update_render_system(entt::registry ®istry){ + auto view = registry.view(); + + view.each([] (const auto &sprite, const auto &position){ + if (sprite.texture){ + DrawTexture(*(sprite.texture),position.x,position.y,WHITE); + }else{ + DrawRectangle(position.x,position.y,50, 70,BLACK); + } + }); +} diff --git a/src/scenes/game_play_scene.cpp b/src/scenes/game_play_scene.cpp new file mode 100644 index 0000000..15f4b6c --- /dev/null +++ b/src/scenes/game_play_scene.cpp @@ -0,0 +1,33 @@ +#include +#include +#include +#include +#include + + +GamePlayScene::GamePlayScene() { + registry = entt::registry(); + + asset_loader.load_asset("assets/bird.png"); + create_player(registry,asset_loader.get_texture("assets/bird.png")); + +} + +void GamePlayScene::update(float dt) { + update_movement_system(registry, dt); + update_player_system(registry, dt); +} + + + +void GamePlayScene::render() { + update_render_system(registry); +} + +void GamePlayScene::ui() { +} + +GamePlayScene::~GamePlayScene(){ + asset_loader.unload_asset("assets/bird.png"); + +} diff --git a/src/scenes/menu_scene.cpp b/src/scenes/menu_scene.cpp new file mode 100644 index 0000000..f0893d1 --- /dev/null +++ b/src/scenes/menu_scene.cpp @@ -0,0 +1,56 @@ +#include +#include +#include + +#include + +// #include + + +MenuScene::MenuScene() { +} + +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)){ + g_Engine.request_switch(SceneType::GAMEPLAY); + } + + ImGui::Dummy(ImVec2(0, 10)); // Gap + + if (ImGui::Button("Quit",button_size)){ + g_Engine.quit(); + } + + ImGui::End(); + + // ImGui::Begin("Debug"); + // ImGui::Button("Hello",button_size); + // ImGui::TextUnformatted(std::format("window size: {} {} ", screen_size.x,screen_size.y).c_str()); + // ImGui::End(); +} diff --git a/src/scenes/scene.cpp b/src/scenes/scene.cpp new file mode 100644 index 0000000..6b3cdb2 --- /dev/null +++ b/src/scenes/scene.cpp @@ -0,0 +1,39 @@ +#include + +SceneManager::SceneManager(){ + current_scene = new MenuScene(); +} + +SceneManager::~SceneManager(){ + delete current_scene; +} + +void SceneManager::process_scene_switch(){ + if (!g_Engine.pendingSwitch) return; + + delete current_scene; + + switch(g_Engine.nextScene){ + case SceneType::MENU: + current_scene = new MenuScene(); + break; + case SceneType::GAMEPLAY: + current_scene = new GamePlayScene(); + break; + } + g_Engine.pendingSwitch = false; +} + +void SceneManager::update(float dt){ + if (current_scene){ + current_scene->update(dt); + } +} + +void SceneManager::render(){ + current_scene->render(); +} + +void SceneManager::ui(){ + current_scene->ui(); +}