Nimbin[12]?SDL & Graphics / mapcreator / commits / 57c1ef0

mapcreator git · master

SDL3 2.5D game and engine using assets, with map editor

sdl3 c++ game engine map-editor cmake · first commit 2025-10-31 · last commit 2026-09-07 (1 month ago) · synced 3 days ago · upstream: git.ide3.de/hsnr/sdl-spieleentwicklung/map_creator

C++ 99.3%
git clone https://git.christianimmanuel.de/sdl-graphics/mapcreator.gitwget https://git.christianimmanuel.de/sdl-graphics/mapcreator/archive/mapcreator.tar.gz

Propper Cursor

Christian Immanuel · 2026-02-11 03:40 · 57c1ef0423f7add132531ca5273544ed6635ae56

 src/building/building.cpp      |  11 +-
 src/building/building.hpp      |   4 +-
 src/character/character.cpp    |  36 ++++--
 src/character/character.hpp    |   2 +-
 src/cursor/animated_cursor.cpp | 283 +++++++++++++++++++++++++++++++++++++++++
 src/cursor/animated_cursor.hpp |  75 +++++++++++
 src/defaults/defaults.hpp      |   1 -
 src/defaults/input.hpp         |   2 +
 src/game/game.cpp              |  19 ++-
 src/game/game.hpp              |   3 +
 src/game/game_state.hpp        |   2 +
 src/game/state_menu_main.cpp   |   2 +
 src/game/state_modify.cpp      |   2 +
 src/game/state_playing.cpp     |  13 +-
 14 files changed, 429 insertions(+), 26 deletions(-)

diff --git a/src/building/building.cpp b/src/building/building.cpp
index 50a8ba9..0788e63 100644
--- a/src/building/building.cpp
+++ b/src/building/building.cpp
@@ -362,10 +362,10 @@ Building::has_Menu() {
     return false;
 }
 
-void
+CharacterMenuType
 Building::open_Menu(Character* player) {
     if (building_status != BuildingStatus::CONSTRUCTED)
-        return;
+        return CharacterMenuType::NONE;
 
     if (BuildingData::is_ResearchTable(building_type)) {
         Research::research_buildings.clear();
@@ -383,16 +383,15 @@ Building::open_Menu(Character* player) {
         std::cout << "Open Research menu\n";
         std::cout << "Build Items: " << Research::research_buildings.size() << std::endl;
         std::cout << "Item  Items: " << Research::research_items.size() << std::endl;
-        player->active_menu = CharacterMenuType::RESEARCH;
         selected_building_type = building_type;
-        return;
+        return CharacterMenuType::RESEARCH;
     }
     if (BuildingData::is_CraftingTable(building_type)) {
         std::cout << "Open Crafting menu\n";
-        player->active_menu = CharacterMenuType::WORKBENCH;
         selected_building_type = building_type;
-        return;
+        return CharacterMenuType::WORKBENCH;
     }
+    return CharacterMenuType::NONE;
 }
 
 void
diff --git a/src/building/building.hpp b/src/building/building.hpp
index 330d3c1..08d4f95 100644
--- a/src/building/building.hpp
+++ b/src/building/building.hpp
@@ -20,7 +20,7 @@ enum class BuildingStatus : uint32_t {
 };
 
 
-
+enum class CharacterMenuType;
 
 class Building : public Object {
 public:
@@ -70,7 +70,7 @@ public:
 
     bool
     has_Menu();
-    void
+    CharacterMenuType
     open_Menu(Character* player);
     void
     close_Menu();
diff --git a/src/character/character.cpp b/src/character/character.cpp
index 92991d2..9a134f4 100644
--- a/src/character/character.cpp
+++ b/src/character/character.cpp
@@ -11,6 +11,7 @@
 #include "building/craft.hpp"
 #include "farm/farm.hpp"
 #include "character/message.hpp"
+#include "cursor/animated_cursor.hpp"
 
 
 
@@ -634,15 +635,30 @@ Character::build_Start() {
 }
 
 
+inline void setActiveMenu(CharacterMenuType& active_menu, const CharacterMenuType& new_menu, AnimatedCursor* cursor) {
+    switch (active_menu) {
+        case CharacterMenuType::NONE:
+            active_menu = new_menu;
+            cursor->set_State(CursorState::ANIMATED);
+            break;
+        default:
+            active_menu = CharacterMenuType::NONE;
+            cursor->set_State(CursorState::HIDDEN);
+            break;
+    }
+}
+
 void
-Character::interact_WithBuilding(Building* building) {
+Character::interact_WithBuilding(Building* building, Input& input) {
     if (active_menu == CharacterMenuType::WORKBENCH) {
         building->close_Menu();
-        active_menu = CharacterMenuType::NONE;
+        setActiveMenu(active_menu, CharacterMenuType::NONE, input.cursor);
     } else
     if (building->has_Menu()) {
         std::cout << "INTERACT OPEN" << std::endl;
-        building->open_Menu(this);
+        CharacterMenuType type = building->open_Menu(this);
+        if (type != CharacterMenuType::NONE)
+            setActiveMenu(active_menu, type, input.cursor);
     }
 }
 
@@ -1412,7 +1428,8 @@ Character::handle_Input(Input& input, SDL_Rect win_size) {
     // WORKBENCH
     if (active_menu == CharacterMenuType::WORKBENCH) {
         if (input.pressed_c_released )
-            active_menu = CharacterMenuType::NONE;
+            setActiveMenu(active_menu, CharacterMenuType::NONE, input.cursor);
+
         Building::draw_menu_curser_grid = Research::cursor_ResearchGridTileIndex(input.mouse_state_raw, win_size);
         // CRAFT
         if (input.click_left_released) {
@@ -1441,7 +1458,8 @@ Character::handle_Input(Input& input, SDL_Rect win_size) {
     // RESEARCH
     if (active_menu == CharacterMenuType::RESEARCH) {
         if (input.pressed_c_released )
-            active_menu = CharacterMenuType::NONE;
+            setActiveMenu(active_menu, CharacterMenuType::NONE, input.cursor);
+
         Building::draw_menu_curser_grid = Research::cursor_ResearchGridTileIndex(input.mouse_state_raw, win_size);
         if (input.click_left_released) {
             Research::research_Stop();
@@ -1461,7 +1479,9 @@ Character::handle_Input(Input& input, SDL_Rect win_size) {
                 set_BuildingDiscovery();
         }
     } else {
-        active_menu = CharacterMenuType::NONE;
+        if (active_menu != CharacterMenuType::NONE)
+            setActiveMenu(active_menu, CharacterMenuType::NONE, input.cursor);
+
         Inventory::selected_build_item = ItemType::NONE;
         Item::crafting_item.is_crafting = false;
     }
@@ -1475,7 +1495,7 @@ Character::handle_Input(Input& input, SDL_Rect win_size) {
         selected_item = inventory->get_SelectedHotbarItem();
         building_Stop();
         if (active_menu == CharacterMenuType::BUILDING)
-            active_menu = CharacterMenuType::NONE;
+            setActiveMenu(active_menu, CharacterMenuType::NONE, input.cursor);
         std::cout << "SWITCH " << selected_item->id << std::endl;
         return;
     }
@@ -1529,7 +1549,7 @@ Character::handle_Input(Input& input, SDL_Rect win_size) {
                 std::cout << "STAT: " << (int)status << std::endl;
                 return;
             case ObjectCategory::BUILDING:
-                interact_WithBuilding(static_cast<Building*>(object));
+                interact_WithBuilding(static_cast<Building*>(object), input);
                 return;
             default: break;
         }
diff --git a/src/character/character.hpp b/src/character/character.hpp
index 21e1f1f..c845648 100644
--- a/src/character/character.hpp
+++ b/src/character/character.hpp
@@ -265,7 +265,7 @@ class Character {
 
     void thirst_Drain();
 
-    void interact_WithBuilding(Building* building);
+    void interact_WithBuilding(Building* building, Input& input);
 
 
     void getting_Hit(float demage);
diff --git a/src/cursor/animated_cursor.cpp b/src/cursor/animated_cursor.cpp
new file mode 100644
index 0000000..29b3467
--- /dev/null
+++ b/src/cursor/animated_cursor.cpp
@@ -0,0 +1,283 @@
+// g++ -std=c++23 -O2 -Wall -Wextra animated_cursor.hpp.cpp  $(pkg-config --cflags --libs sdl3 sdl3-image) -o cursor_particles && ./cursor_particles
+
+#include <SDL3/SDL.h>
+#include <SDL3_image/SDL_image.h>
+#include <vector>
+#include <algorithm>
+#include <random>
+#include <cmath>
+#include <print>
+
+#include "cursor/animated_cursor.hpp"
+
+
+AnimatedCursor::AnimatedCursor() : spriteTexture(nullptr),
+    spriteSurface(nullptr), isReversing(false), reverseProgress(0.0f),
+    time(0.0f), clickX(400.0f), clickY(300.0f),
+    currentFrame(0), isPlayingAnimation(false), mouseX(400.0f), mouseY(300.0f) {}
+
+    AnimatedCursor::~AnimatedCursor() {
+        Cleanup();
+    }
+
+bool AnimatedCursor::Init(SDL_Renderer* renderer, const char* filename) {
+    spriteSurface = IMG_Load(filename);
+    if (!spriteSurface) {
+        SDL_Log("IMG_Load failed: %s", SDL_GetError());
+        return false;
+    }
+
+    spriteTexture = SDL_CreateTextureFromSurface(renderer, spriteSurface);
+    if (!spriteTexture) {
+        SDL_Log("SDL_CreateTextureFromSurface failed: %s", SDL_GetError());
+        return false;
+    }
+
+    frame_ExtractFirst();
+    return true;
+}
+
+void AnimatedCursor::set_State(const CursorState& state_new) {
+    std::print("MOUSE STATE {0}\n", (int)state_new);
+
+    bool is_visible = SDL_CursorVisible();
+    if (state == CursorState::ANIMATED || state == CursorState::HIDDEN) {
+        if (is_visible && !isReversing)
+            SDL_HideCursor();
+    } else if (state == CursorState::NORMAL){
+        if (!is_visible)
+            SDL_ShowCursor();
+    }
+    state = state_new;
+}
+
+void AnimatedCursor::frame_ExtractFirst() {
+    pixels.clear();
+    allFrames.clear();
+    allFrames.resize(TOTAL_FRAMES);
+
+    // Lock surface for pixel access
+    SDL_LockSurface(spriteSurface);
+    Uint32* surfacePixels = (Uint32*)spriteSurface->pixels;
+    int pitch = spriteSurface->pitch / 4;
+
+    const SDL_PixelFormatDetails* details =
+        SDL_GetPixelFormatDetails(spriteSurface->format);
+    if (!details) {
+        SDL_UnlockSurface(spriteSurface);
+        return;
+    }
+
+    // Extract non-transparent pixels from all 8 frames
+    for (size_t frame = 0; frame < TOTAL_FRAMES; frame++) {
+        int frameOffsetX = static_cast<int>(frame) * FRAME_WIDTH;
+
+        for (int y = 0; y < FRAME_HEIGHT; y++) {
+            for (int x = 0; x < FRAME_WIDTH; x++) {
+                Uint32 pixel = surfacePixels[y * pitch + (frameOffsetX + x)];
+                Uint8 r, g, b, a;
+                SDL_GetRGBA(pixel, details, NULL, &r, &g, &b, &a);
+
+                if (a > 0) { // Non-transparent pixel
+                    Pixel p;
+                    p.srcX = x;
+                    p.srcY = y;
+                    p.originalColor = {r, g, b, a};
+                    allFrames[frame].push_back(p);
+                }
+            }
+        }
+    }
+
+    SDL_UnlockSurface(spriteSurface);
+
+    // Start with first frame
+    pixels = allFrames[0];
+    currentFrame = 0;
+
+    // Generate shuffled positions and colors
+    state_GenerateShuffle();
+}
+
+void AnimatedCursor::state_GenerateShuffle() {
+    std::random_device rd;
+    std::mt19937 gen(rd());
+    std::uniform_real_distribution<float> angleDist(0.0f, 2.0f * M_PI);
+    std::uniform_real_distribution<float> radiusDist(0.0f, SPHERE_RADIUS);
+    std::uniform_real_distribution<float> speedDist(0.3f, 4.5f);
+    std::uniform_int_distribution<> colorDist(0, 255);
+
+    // Initialize each pixel with random position and movement
+    for (auto& p : pixels) {
+        // Initial position in sphere
+        p.currentAngle = angleDist(gen);
+        p.currentRadius = radiusDist(gen);
+
+        // Target for smooth movement
+        p.targetAngle = angleDist(gen);
+        p.targetRadius = radiusDist(gen);
+
+        // Random movement speeds
+        p.angleSpeed = speedDist(gen);
+        p.radiusSpeed = speedDist(gen) * 0.5f;
+
+        // Generate random shuffled color
+        p.shuffledColor = {
+            (Uint8)colorDist(gen),
+            (Uint8)colorDist(gen),
+            (Uint8)colorDist(gen),
+            p.originalColor.a
+        };
+    }
+}
+
+SDL_Color lerpColor(const SDL_Color& a, const SDL_Color& b, float t) {
+    return {
+        (Uint8)(a.r + (b.r - a.r) * t),
+        (Uint8)(a.g + (b.g - a.g) * t),
+        (Uint8)(a.b + (b.b - a.b) * t),
+        (Uint8)(a.a + (b.a - a.a) * t)
+    };
+}
+
+void AnimatedCursor::Render(SDL_Renderer* renderer) {
+    if (state != CursorState::ANIMATED && !isReversing) return;
+    SDL_SetRenderDrawColor(renderer, 20, 20, 30, 255);
+
+    // Calculate current state based on reverse progress
+    float progress = isReversing ? reverseProgress : 0.0f;
+
+    // Ease function for smooth animation
+    float eased = progress * progress * (3.0f - 2.0f * progress); // Smoothstep
+
+    // Position where animation happens (click position or following mouse)
+    // float centerX = isReversing ? clickX : mouseX;
+    // float centerY = isReversing ? clickY : mouseY;
+    float centerX =  mouseX;
+    float centerY =  mouseY;
+
+    for (const auto& p : pixels) {
+        // Calculate position in sphere
+        float sphereX = centerX + cos(p.currentAngle) * p.currentRadius;
+        float sphereY = centerY + sin(p.currentAngle) * p.currentRadius;
+
+        // Interpolate position
+        int x, y;
+        if (isReversing) {
+            // During animation: sphere -> sprite at click position
+            x = (int)(sphereX + (centerX - FRAME_WIDTH/2 + p.srcX - sphereX) * eased);
+            y = (int)(sphereY + (centerY - FRAME_HEIGHT/2 + p.srcY - sphereY) * eased);
+        } else {
+            // Shuffled state (sphere following mouse)
+            x = (int)sphereX;
+            y = (int)sphereY;
+        }
+
+        // Interpolate color
+        SDL_Color color;
+        if (isReversing) {
+            color = lerpColor(p.shuffledColor, p.originalColor, eased);
+        } else {
+            color = p.shuffledColor;
+        }
+
+        SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
+        SDL_RenderPoint(renderer, x, y);
+    }
+
+}
+
+void AnimatedCursor::Update(float deltaTime, float mx, float my) {
+    if (state != CursorState::ANIMATED && !isReversing) return;
+    mouseX = mx; mouseY = my;
+    time += deltaTime;
+
+    // Update pixel positions (movement within sphere)
+    if (!isReversing) {
+        std::random_device rd;
+        std::mt19937 gen(rd());
+        std::uniform_real_distribution<float> angleDist(0.0f, 2.0f * M_PI);
+        std::uniform_real_distribution<float> radiusDist(0.0f, SPHERE_RADIUS);
+
+        for (auto& p : pixels) {
+            // Smoothly move toward target
+            float angleDiff = p.targetAngle - p.currentAngle;
+            // Normalize angle difference to [-PI, PI]
+            while (angleDiff > M_PI) angleDiff -= 2.0f * M_PI;
+            while (angleDiff < -M_PI) angleDiff += 2.0f * M_PI;
+
+            p.currentAngle  += angleDiff * p.angleSpeed * deltaTime;
+            p.currentRadius += (p.targetRadius - p.currentRadius) * p.radiusSpeed * deltaTime;
+
+            // Check if close to target, if so pick new target
+            if (fabs(angleDiff) < 0.1f && fabs(p.targetRadius - p.currentRadius) < 1.0f) {
+                p.targetAngle  = angleDist(gen);
+                p.targetRadius = radiusDist(gen);
+            }
+        }
+    }
+
+    // Handle reverse animation and frame playback
+    if (isReversing) {
+        reverseProgress += deltaTime / ANIMATION_DURATION;
+
+        if (reverseProgress >= 0.2f) {
+            // Unshuffle complete, now play through frames
+            if (!isPlayingAnimation) {
+                isPlayingAnimation = true;
+                currentFrame       = 0;
+                reverseProgress    = 0.0f;
+            }
+        }
+
+        // Play through animation frames
+        if (isPlayingAnimation) {
+            // Play all 8 frames over remaining time
+            float frameTime = ANIMATION_DURATION / TOTAL_FRAMES;
+            currentFrame = (int)(reverseProgress / frameTime);
+
+            if (currentFrame >= (int)TOTAL_FRAMES) {
+                // Animation complete, go back to frame 0 and reshuffle
+                currentFrame = 0;
+                pixels = allFrames[0];
+                state_GenerateShuffle();
+                isReversing        = false;
+                isPlayingAnimation = false;
+                reverseProgress    = 0.0f;
+            } else {
+                // Switch to current frame
+                if (currentFrame < (int)TOTAL_FRAMES && currentFrame >= 0) {
+                    pixels = allFrames[(size_t)currentFrame];
+                    // Keep the shuffle state for smooth transition
+                    for (size_t i = 0; i < pixels.size() && i < allFrames[0].size(); i++) {
+                        pixels[i].currentAngle  = allFrames[0][i].currentAngle;
+                        pixels[i].currentRadius = allFrames[0][i].currentRadius;
+                        pixels[i].targetAngle   = allFrames[0][i].targetAngle;
+                        pixels[i].targetRadius  = allFrames[0][i].targetRadius;
+                        pixels[i].angleSpeed    = allFrames[0][i].angleSpeed;
+                        pixels[i].radiusSpeed   = allFrames[0][i].radiusSpeed;
+                        pixels[i].shuffledColor = allFrames[0][i].shuffledColor;
+                    }
+                }
+            }
+
+            reverseProgress += deltaTime / ANIMATION_DURATION;
+        }
+    }
+}
+
+void AnimatedCursor::mouse_OnClick() {
+    if (state != CursorState::ANIMATED) return;
+    if (isReversing) return;
+
+    isReversing = true;
+    reverseProgress = 0.0f;
+    clickX = mouseX;
+    clickY = mouseY;
+}
+
+
+void AnimatedCursor::Cleanup() {
+    if (spriteTexture) SDL_DestroyTexture(spriteTexture);
+    if (spriteSurface) SDL_DestroySurface(spriteSurface);
+}
diff --git a/src/cursor/animated_cursor.hpp b/src/cursor/animated_cursor.hpp
new file mode 100644
index 0000000..7d960d5
--- /dev/null
+++ b/src/cursor/animated_cursor.hpp
@@ -0,0 +1,75 @@
+// g++ -std=c++23 -O2 -Wall -Wextra animated_cursor.hpp.cpp  $(pkg-config --cflags --libs sdl3 sdl3-image) -o cursor_particles && ./cursor_particles
+
+#pragma once
+
+#include <SDL3/SDL.h>
+#include <SDL3_image/SDL_image.h>
+#include <vector>
+#include <algorithm>
+#include <random>
+#include <cmath>
+
+
+
+enum class CursorState : uint32_t {
+    NORMAL,
+    ANIMATED,
+    HIDDEN
+};
+
+struct Pixel {
+    int srcX, srcY;
+    float currentAngle;  // Current angle in sphere
+    float currentRadius; // Current radius in sphere
+    float targetAngle;   // Target angle for movement
+    float targetRadius;  // Target radius for movement
+    float angleSpeed;    // Speed of angle change
+    float radiusSpeed;   // Speed of radius change
+    SDL_Color originalColor;
+    SDL_Color shuffledColor;
+};
+
+class AnimatedCursor {
+private:
+    SDL_Texture* spriteTexture;
+    SDL_Surface* spriteSurface;
+
+    std::vector<Pixel> pixels;
+    std::vector<std::vector<Pixel>> allFrames; // Store all 8 frames
+    bool isReversing;
+    float reverseProgress;
+    float time; // For continuous animation
+    float clickX, clickY; // Position where click happened
+    int currentFrame; // 0-7 for the 8 frames
+    bool isPlayingAnimation; // True when playing through frames
+
+    const int FRAME_WIDTH = 64;  // 512 / 8 frames
+    const int FRAME_HEIGHT = 64;
+    const int SPHERE_RADIUS = 16; // 32px diameter sphere
+    const size_t TOTAL_FRAMES = 8;
+    const float ANIMATION_DURATION = 1.2f; // Total time for unshuffle + frame play
+
+public:
+    CursorState  state = CursorState::NORMAL;
+    float mouseX, mouseY; // Sphere follows cursor
+    AnimatedCursor();
+
+    ~AnimatedCursor();
+
+    bool Init(SDL_Renderer* renderer, const char* filename);
+
+    void set_State(const CursorState& state_new);
+
+    void frame_ExtractFirst();
+
+    void state_GenerateShuffle();
+
+    void Render(SDL_Renderer* renderer);
+
+    void Update(float deltaTime, float mx, float my);
+
+    void mouse_OnClick();
+
+
+    void Cleanup();
+};
diff --git a/src/defaults/defaults.hpp b/src/defaults/defaults.hpp
index 2d2d477..fd9b6b0 100644
--- a/src/defaults/defaults.hpp
+++ b/src/defaults/defaults.hpp
@@ -4,7 +4,6 @@
 #include <vector>
 #include <stddef.h>
 
-
 enum class WalkDirection {
     UP,
     LEFT,
diff --git a/src/defaults/input.hpp b/src/defaults/input.hpp
index 41cda38..f1a5636 100644
--- a/src/defaults/input.hpp
+++ b/src/defaults/input.hpp
@@ -3,6 +3,7 @@
 #include <SDL3/SDL.h>
 #include <iostream>
 #include "defaults/defaults.hpp"
+#include "cursor/animated_cursor.hpp"
 
 class Character;
 
@@ -25,6 +26,7 @@ class Input {
     bool pressed_return_released = false;
     bool pressed_arrowdown_released = false;
     bool pressed_arrowup_released = false;
+    AnimatedCursor* cursor;
     SDL_FPoint mouse_state;
     SDL_FPoint mouse_state_raw;
     SDL_Point  mouse_game_offset;
diff --git a/src/game/game.cpp b/src/game/game.cpp
index 71668e0..e9443ea 100644
--- a/src/game/game.cpp
+++ b/src/game/game.cpp
@@ -24,6 +24,7 @@
 #include "building/research.hpp"
 #include "defaults/input.hpp"
 #include "start_screen/start_screen.hpp"
+#include "cursor/animated_cursor.hpp"
 
 
 // Game class encapsulating SDL initialization and loop
@@ -63,6 +64,12 @@ Game::Game() : running(true) {
 
     lastTicks = SDL_GetTicks();
 
+    // INIT Cursor
+    SDL_HideCursor();
+    cursor = input.cursor = new AnimatedCursor();
+    cursor->Init(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Slice_Base/Slice_Up.png");
+
+
     // Music
     audio = new Audio();
     Animation::audio = audio;
@@ -114,13 +121,13 @@ void Game::set_State(GameState_e new_state, NewGameState_e game_state_e) {
     bool ret = false;
     switch (new_state) {
         case GameState_e::STATE_MODIFY:
-            ret = SDL_ShowCursor();
+            cursor->set_State(CursorState::NORMAL);
             break;
         case GameState_e::STATE_MENU_MAIN:
-            ret = SDL_ShowCursor();
+            cursor->set_State(CursorState::ANIMATED);
             break;
         case GameState_e::STATE_PLAYING:
-            ret = SDL_HideCursor();
+            cursor->set_State(CursorState::HIDDEN);
             break;
     }
     if (!ret) {
@@ -272,6 +279,9 @@ void Game::handle_Events() {
                 win_size.h = h;
                 break;
             }
+            case SDL_EVENT_MOUSE_BUTTON_DOWN:
+                cursor->mouse_OnClick();
+                break;
             case SDL_EVENT_MOUSE_BUTTON_UP:
                 input.click_left_released = true;
                 input.click_left = false;
@@ -370,6 +380,7 @@ void Game::handle_Events() {
 
 void Game::Update() {
     state_current->Update(this);
+    cursor->Update(Default::delta_time, input.mouse_state_raw.x, input.mouse_state_raw.y);
 }
 
 void Game::Render() {
@@ -385,6 +396,8 @@ void Game::Render() {
 
     if (Ui::draw_key_info) Ui::draw_KeyInfo(this);
 
+    cursor->Render(renderer);
+
     SDL_RenderPresent(renderer);
 }
 
diff --git a/src/game/game.hpp b/src/game/game.hpp
index d65fb94..09dfe3f 100644
--- a/src/game/game.hpp
+++ b/src/game/game.hpp
@@ -15,6 +15,7 @@
 #include "audio/audio.hpp"
 
 class Input;
+class AnimatedCursor;
 
 
 enum class NewGameState_e {
@@ -36,6 +37,8 @@ public:
 
     Audio* audio = nullptr;
 
+    AnimatedCursor* cursor = nullptr;
+
     bool is_cheating = false;
 
     std::unique_ptr<StatePlaying> state_playing;
diff --git a/src/game/game_state.hpp b/src/game/game_state.hpp
index bdb127a..144424f 100644
--- a/src/game/game_state.hpp
+++ b/src/game/game_state.hpp
@@ -4,6 +4,7 @@
 #include <SDL3/SDL_mouse.h>
 
 #include "audio/audio.hpp"
+#include "cursor/animated_cursor.hpp"
 
 class Game;
 class Input;
@@ -24,5 +25,6 @@ public:
     bool is_Finished() const { return finished; }
 protected:
     Audio* audio = nullptr;
+    AnimatedCursor* cursor = nullptr;
     bool finished = false;
 };
diff --git a/src/game/state_menu_main.cpp b/src/game/state_menu_main.cpp
index f3f8855..b074fed 100644
--- a/src/game/state_menu_main.cpp
+++ b/src/game/state_menu_main.cpp
@@ -22,6 +22,8 @@ bool StateMenuMain::Init(Game* game) {
     buttons.push_back({ MenuState::NEW,   "New Game",  {300, 200, 200, 50}, false});
     buttons.push_back({ MenuState::QUIT,  "Quit",      {300, 320, 200, 50}, false});
     buttons.push_back({ MenuState::NEW_CHEAT,   "New Cheat",  {300, 200, 200, 50}, false});
+    cursor = game->cursor;
+    cursor->set_State(CursorState::ANIMATED);
     return true;
 }
 
diff --git a/src/game/state_modify.cpp b/src/game/state_modify.cpp
index 80ae20b..f30a757 100644
--- a/src/game/state_modify.cpp
+++ b/src/game/state_modify.cpp
@@ -14,6 +14,8 @@ bool StateModify::Init(Game* game) {
 
     //Tile::tileset_offset.x = Map::offset.x;
     //Tile::tileset_offset.y = Map::offset.y;
+    cursor = game->cursor;
+    cursor->state = CursorState::NORMAL;
 
     return true;
 }
diff --git a/src/game/state_playing.cpp b/src/game/state_playing.cpp
index df71b6a..c991c1b 100644
--- a/src/game/state_playing.cpp
+++ b/src/game/state_playing.cpp
@@ -24,6 +24,9 @@ bool StatePlaying::Init(Game* game) {
     Default::last = 0;
     Default::delta_time = 0.0;
 
+    cursor = game->cursor;
+    cursor->set_State(CursorState::HIDDEN);
+
     player = std::make_unique<Character>();
     player->Init(game->renderer);
     player->researched_items[static_cast<size_t>(ItemType::BUILDING_TOOL)] = true;
@@ -92,15 +95,15 @@ bool StatePlaying::Init(Game* game) {
     return true;
 }
 
-inline void setActiveMenu(CharacterMenuType& active_menu, const CharacterMenuType& new_menu) {
+inline void setActiveMenu(CharacterMenuType& active_menu, const CharacterMenuType& new_menu, AnimatedCursor* cursor) {
     switch (active_menu) {
         case CharacterMenuType::NONE:
             active_menu = new_menu;
-            SDL_ShowCursor();
+            cursor->set_State(CursorState::ANIMATED);
             break;
         default:
             active_menu = CharacterMenuType::NONE;
-            SDL_HideCursor();
+            cursor->set_State(CursorState::HIDDEN);
             break;
     }
 }
@@ -131,7 +134,7 @@ void StatePlaying::handle_Events(SDL_Event& event, Game* game) {
                     if (player->selected_item->object_type == InventoryObjectType::ITEM) {
                         sel_item = static_cast<InventoryItem*>(player->selected_item);
                         if (sel_item->type == ItemType::BUILDING_TOOL)
-                            setActiveMenu(player->active_menu, CharacterMenuType::BUILDING);
+                            setActiveMenu(player->active_menu, CharacterMenuType::BUILDING, cursor);
                         else
                             player->messages->Add(MessageKey::BUILDING_TOOL_REQUIRED);
                     } else
@@ -142,7 +145,7 @@ void StatePlaying::handle_Events(SDL_Event& event, Game* game) {
                     Inventory::selected_hotbar = !Inventory::selected_hotbar ? Inventory::selected_hotbar_last : 0;
                     break;
                 case SDL_SCANCODE_E:
-                    setActiveMenu(player->active_menu, CharacterMenuType::INVENTORY);
+                    setActiveMenu(player->active_menu, CharacterMenuType::INVENTORY, cursor);
                     Inventory::draw_menu = !Inventory::draw_menu;
                     break;
                 case SDL_SCANCODE_K: {