Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Generated by CLion for LLVM
BasedOnStyle: LLVM
IndentWidth: 4
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/cmake-build-debug/
22 changes: 15 additions & 7 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
cmake_minimum_required(VERSION 3.11)
cmake_minimum_required(VERSION 3.16)
project(sandbox2D)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# Gather sources
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS src/*.cpp)
file(GLOB_RECURSE HEADER_FILES CONFIGURE_DEPENDS src/*.h src/*.hpp)
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/src/*.cpp)

add_executable(${PROJECT_NAME} ${SOURCE_FILES} ${HEADER_FILES})
target_include_directories(${PROJECT_NAME} PRIVATE /opt/homebrew/include)
target_link_directories(${PROJECT_NAME} PRIVATE /opt/homebrew/lib)
add_executable(${PROJECT_NAME} ${SOURCE_FILES})

target_include_directories(${PROJECT_NAME}
PRIVATE
${CMAKE_SOURCE_DIR}/include
)

# SDL2 and extensions
find_package(SDL2 REQUIRED)
find_package(SDL2_image REQUIRED)
find_package(SDL2_ttf REQUIRED)

target_link_libraries(${PROJECT_NAME} PRIVATE SDL2::SDL2 SDL2_image::SDL2_image SDL2_ttf::SDL2_ttf)
target_link_libraries(${PROJECT_NAME}
PRIVATE
SDL2::SDL2
SDL2_image::SDL2_image
SDL2_ttf::SDL2_ttf
)
Comment on lines +23 to +28
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Windows entry-point linkage: add SDL2::SDL2main or define SDL_MAIN_HANDLED.

With #include <SDL.h> in main.cpp, the main symbol is usually remapped to SDL_main and requires linking SDL2::SDL2main on Windows. Without it, MSVC builds often fail at link time. Two options:

  • Link SDL2::SDL2main (recommended when you include <SDL.h> in your main TU):
 target_link_libraries(${PROJECT_NAME}
         PRIVATE
-        SDL2::SDL2
+        SDL2::SDL2
+        SDL2::SDL2main
         SDL2_image::SDL2_image
         SDL2_ttf::SDL2_ttf
 )
  • Or define SDL_MAIN_HANDLED and include <SDL.h> for APIs only (then you typically don’t link SDL2main). If you go this route, add:
target_compile_definitions(${PROJECT_NAME} PRIVATE SDL_MAIN_HANDLED)

and call SDL_SetMainReady() before SDL_Init() (desktop often works without it, but it’s the documented sequence).


🏁 Script executed:

#!/usr/bin/env bash
# Verify Windows entrypoint setup in the repo.
set -euo pipefail
echo "Searching for SDL2::SDL2main in CMakeLists.txt:"
rg -nP '\bSDL2::SDL2main\b' CMakeLists.txt || true
echo
echo "Looking for SDL_MAIN_HANDLED / SDL_SetMainReady in sources:"
rg -nP 'SDL_MAIN_HANDLED|SDL_SetMainReady' -g '!**/build/**' || true

Length of output: 383


Link Windows entry-point library or define SDL_MAIN_HANDLED

On Windows, SDL remaps main() to SDL_main, which requires either linking against SDL2::SDL2main or defining SDL_MAIN_HANDLED and calling SDL_SetMainReady() before SDL_Init(). Our verification shows you’re currently linking only SDL2::SDL2, SDL2_image, and SDL2_ttf, and there’s no evidence of SDL2::SDL2main or the SDL_MAIN_HANDLED macro in the codebase.

Please address one of the following in your CMakeLists.txt and source:

• Add the SDL2main library so that the default remapping works:

 target_link_libraries(${PROJECT_NAME}
         PRIVATE
-        SDL2::SDL2
+        SDL2::SDL2main
+        SDL2::SDL2
         SDL2_image::SDL2_image
         SDL2_ttf::SDL2_ttf
 )

• Or keep your custom main() signature by defining SDL_MAIN_HANDLED and initializing correctly:

  1. In CMakeLists.txt:
    target_compile_definitions(${PROJECT_NAME}
        PRIVATE
        SDL_MAIN_HANDLED
    )
  2. In your main.cpp, before any SDL calls:
    #include <SDL.h>
    SDL_SetMainReady();
    // then SDL_Init(…);

Adjusting one of these options will resolve Windows link errors related to the SDL entry-point.

🤖 Prompt for AI Agents
CMakeLists.txt lines ~23-28: Windows needs SDL entry-point handling; either link
SDL2main or define SDL_MAIN_HANDLED and call SDL_SetMainReady() before SDL_Init.
Fix option A: add SDL2::SDL2main to target_link_libraries alongside
SDL2::SDL2/SDL2_image/SDL2_ttf. Fix option B: add
target_compile_definitions(${PROJECT_NAME} PRIVATE SDL_MAIN_HANDLED) in
CMakeLists.txt and in main.cpp include <SDL.h> and call SDL_SetMainReady()
before any SDL calls (e.g., before SDL_Init).


# Assets include directory
set(ASSETS_DIR "${CMAKE_SOURCE_DIR}/assets")
Expand Down
31 changes: 31 additions & 0 deletions include/game/Config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// Created by Jacopo Uggeri on 15/08/2025.
//
#pragma once

#include "physics/Vec2.h"
#include <filesystem>
#include <string_view>

namespace config {

inline constexpr std::string_view GAME_NAME {"Sandbox 2D"};

namespace resources {
// resources (should move in namespace or a more relevant file later)
inline const std::filesystem::path ASSETS_PATH = "assets";
inline const std::filesystem::path TEX_PATH = ASSETS_PATH / "textures";
inline const std::filesystem::path FONT_PATH = ASSETS_PATH / "fonts";
inline constexpr std::string_view FONT {"Hack-Regular.ttf"};
inline constexpr std::string_view PLAYER_TEX {"bacon.png"};
inline constexpr std::string_view WALL_TEX {"wall.png"};
}

namespace graphics {
// graphics (should move in namespace or a more relevant file later)
inline constexpr phys::Vec2i WINDOW_SIZE {1280, 720};
inline constexpr int TILE_SIZE = 16;
inline constexpr float DRAW_SCALE = 4.0;
}

}
31 changes: 31 additions & 0 deletions include/game/Game.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// Created by Jacopo Uggeri on 15/08/2025.
//
#pragma once

#include "game/GameState.h"
#include "graphics/Renderer.h"
#include "input/InputManager.h"

class Game {
public:
bool init();
void loop();

private:
GameState gameState_ {};
Renderer renderer_ {};
InputManager inputManager_ {};

void step(double deltaSeconds);

public:
Game() = default;
~Game() = default;
// Delete copy operations
Game(const Game&) = delete;
Game& operator=(const Game&) = delete;
// Delete move operations
Game(Game&&) = delete;
Game& operator=(Game&&) = delete;
};
17 changes: 17 additions & 0 deletions include/game/GameState.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// Created by Jacopo Uggeri on 20/08/2025.
//
#pragma once
#include "Player.h"
#include "world/World.h"

struct GameState {
uint64_t gameTicks = 0;
bool running = true;
bool paused = true;
bool debugMode = true;
Player player {};
World world {};

bool unpaused() const { return !paused; }
};
16 changes: 16 additions & 0 deletions include/game/Player.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// Created by Jacopo Uggeri on 20/08/2025.
//
#pragma once
#include "physics/Vec2.h"
#include "world/World.h"

struct Player {
constexpr static float SPEED = 10.0f;
phys::Vec2f pos {};
phys::Vec2f vel {};
Sprite sprite {std::string(config::resources::PLAYER_TEX)};

void set_velocity(phys::Vec2f v);
void move(double deltaSeconds);
};
23 changes: 23 additions & 0 deletions include/game/graphics/Renderer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// Created by Jacopo Uggeri on 21/08/2025.
//
#pragma once
#include "game/GameState.h"
#include "platform/sdl/GraphicsDevice.h"

class Renderer {
public:
bool init(int winW, int winH, std::string_view windowTitle);
void render(const GameState& gameState, double deltaSeconds);

private:
void drawSprite(const Sprite &sprite, phys::Vec2f pos) const;
void drawWorld(const World& world) const;
void cameraSnap(phys::Vec2f pos);
void cameraFollow(phys::Vec2f pos, double deltaSeconds);
phys::Vec2f toScreenCoords(const phys::Vec2f& pos) const;
void drawDebugInfo(const GameState &gameState, double deltaSeconds) const;

GraphicsDevice graphicsDevice_ {};
struct Camera { phys::Vec2f pos {0, 0}; } camera_;
};
15 changes: 15 additions & 0 deletions include/game/input/InputManager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// Created by Jacopo Uggeri on 21/08/2025.
//

#pragma once
#include "platform/sdl/InputSource.h"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was initially using abstract interfaces but it seemed overkill, the current refactor already positions the code with much better separation that would make further steps easier. Since the code is still developing, going the full way might be premature. Do you have suggestions to ease into that?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jacopouggeri That's a very pragmatic approach! You're right that premature abstraction can be more hindrance than help during active development. Let me suggest some incremental steps that ease toward better separation without requiring a major overhaul:

Phase 1: Minimize the include footprint (immediate, low-risk)

// In InputManager.h - just move the include to the .cpp file
#pragma once

struct GameState; // forward declaration
class InputSource; // forward declaration  

class InputManager {
public:
    bool init() const;
    void handleEvents(GameState& state) const;
private:
    InputSource* inputSource_; // pointer instead of value
};

This keeps the same concrete type but removes the header dependency. The implementation stays in the .cpp file where platform headers are fine.

Phase 2: Simple factory approach (when you add another platform)
When you eventually want to support another platform (or just better testability), you can introduce a simple factory function in the platform layer:

// platform/InputSourceFactory.h
std::unique_ptr<InputSource> createInputSource();

Phase 3: Interface when you actually need it
Only introduce IInputSource when you have a concrete second implementation or testing need.

The key insight is that your current directory structure already provides the right boundaries - you just need to be more careful about include dependencies crossing those boundaries. Would this incremental approach work better for your current development pace?


class InputManager {
public:
bool init();
void handleEvents(GameState& state);

private:
InputSource inputSource_;
};
7 changes: 1 addition & 6 deletions src/core/physics/Vec2.h → include/game/physics/Vec2.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
//
// Created by Jacopo Uggeri on 16/08/2025.
//

#ifndef SANDBOX2D_VEC2_H
#define SANDBOX2D_VEC2_H
#pragma once
#include <cmath>
#include <type_traits>
Expand Down Expand Up @@ -69,6 +66,4 @@ namespace phys
using Vec2i = Vec2<int>;
using Vec2f = Vec2<float>;

}

#endif //SANDBOX2D_VEC2_H
}
12 changes: 12 additions & 0 deletions include/game/resources/Sprite.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// Created by Jacopo Uggeri on 21/08/2025.
//

#pragma once
#include <string>

struct Sprite {
std::string textureName;
int width {16};
int height {16};
};
53 changes: 53 additions & 0 deletions include/game/world/World.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// Created by Jacopo Uggeri on 19/08/2025.
//
#pragma once

#include "game/Config.h"
#include "game/resources/Sprite.h"
#include <array>

namespace world {
constexpr int CHUNK_SIZE = 16;
constexpr int WORLD_WIDTH_CHUNKS = 8;
constexpr int WORLD_HEIGHT_CHUNKS = 4;
constexpr phys::Vec2i WORLD_SIZE {WORLD_WIDTH_CHUNKS * CHUNK_SIZE, WORLD_HEIGHT_CHUNKS * CHUNK_SIZE};
constexpr int SEA_LEVEL = 0;
constexpr int CHUNK_COUNT = WORLD_WIDTH_CHUNKS * WORLD_HEIGHT_CHUNKS;

}


struct Tile {
Sprite sprite;
};

struct Chunk {
std::array<Tile, world::CHUNK_SIZE* world::CHUNK_SIZE> tiles;

Tile& getTile(int tileX, int tileY);
};

struct World {
std::array<Chunk, world::CHUNK_COUNT> chunks;

void init();

Tile& getTileGlobal(int worldX, int worldY);

Chunk& getChunk(int chunkX, int chunkY);

[[nodiscard]] static constexpr phys::Vec2i chunkCoords(int chunkIdx) noexcept {
return { chunkIdx % world::WORLD_WIDTH_CHUNKS, chunkIdx / world::WORLD_WIDTH_CHUNKS };
}

[[nodiscard]] static constexpr phys::Vec2i tileCoords(int tileIdx) noexcept {
return { tileIdx % world::CHUNK_SIZE, tileIdx / world::CHUNK_SIZE };
}

[[nodiscard]] static constexpr phys::Vec2i worldCoords(int chunkIdx, int tileIdx) noexcept {
const phys::Vec2i chunkPos = chunkCoords(chunkIdx);
const phys::Vec2i tilePos = tileCoords(tileIdx);
return chunkPos * world::CHUNK_SIZE + tilePos - world::WORLD_SIZE / 2; // Center the world
}
};
35 changes: 35 additions & 0 deletions include/platform/sdl/AssetLoader.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// Created by Jacopo Uggeri on 19/08/2025.
//
#pragma once

#include "platform/sdl/Resource.h"
#include <string>
#include <string_view>
#include <unordered_map>

class AssetLoader {
public:
void loadTextures(SDL_Renderer* renderer);
void loadFonts();
[[nodiscard]] const Texture& getTexture(std::string_view textureName) const noexcept;
[[nodiscard]] const Font& getFont(std::string_view fontName) const noexcept;
void clear() noexcept {
textures.clear();
fonts.clear();
}

private:
void loadTexture(std::string_view textureName, SDL_Renderer* renderer);
void loadFont(std::string_view fontName, int size);

std::unordered_map<std::string, Texture> textures;
std::unordered_map<std::string, Font> fonts;

public:
AssetLoader() = default;
~AssetLoader() = default;
// Prevent copying
AssetLoader(const AssetLoader&) = delete;
AssetLoader& operator=(const AssetLoader&) = delete;
};
32 changes: 32 additions & 0 deletions include/platform/sdl/GraphicsDevice.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// Created by Jacopo Uggeri on 20/08/2025.
//
#pragma once

#include "game/resources/Sprite.h"
#include "platform/sdl/AssetLoader.h"
#include "platform/sdl/SDLContext.h"
#include <SDL_render.h>
#include <SDL_video.h>
#include <string_view>

class GraphicsDevice final {
public:
bool init(int winW, int winH, std::string_view windowTitle);
void beginFrame() const;
void endFrame() const;
void drawTexture(const Sprite &sprite, float screenX, float screenY) const;
void drawText(std::string_view text, float x, float y) const;
Comment on lines +18 to +19
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Bug in implementation: srcRect uses scaled size (in .cpp)

In GraphicsDevice.cpp, srcRect width/height are computed after applying DRAW_SCALE. srcRect should use the unscaled sprite pixel size; only destRect should be scaled. This causes incorrect sampling/cropping.

Apply in src/platform/sdl/GraphicsDevice.cpp:

-    const float width = sprite.width * config::graphics::DRAW_SCALE;
-    const float height = sprite.height * config::graphics::DRAW_SCALE;
-    const SDL_Rect srcRect {0, 0, static_cast<int>(width), static_cast<int>(height)};
-    screenX -= width / 2.0f;
-    screenY -= height / 2.0f;
-    const SDL_FRect destRect {screenX, screenY, width, height};
+    const int srcW = sprite.width;
+    const int srcH = sprite.height;
+    const float dstW = srcW * config::graphics::DRAW_SCALE;
+    const float dstH = srcH * config::graphics::DRAW_SCALE;
+    const SDL_Rect srcRect {0, 0, srcW, srcH};
+    screenX -= dstW / 2.0f;
+    screenY -= dstH / 2.0f;
+    const SDL_FRect destRect {screenX, screenY, dstW, dstH};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
void drawTexture(const Sprite &sprite, float screenX, float screenY) const;
void drawText(std::string_view text, float x, float y) const;
// Before: both srcRect and destRect used scaled width/height
- const float width = sprite.width * config::graphics::DRAW_SCALE;
- const float height = sprite.height * config::graphics::DRAW_SCALE;
- const SDL_Rect srcRect {0, 0, static_cast<int>(width), static_cast<int>(height)};
- screenX -= width / 2.0f;
- screenY -= height / 2.0f;
- const SDL_FRect destRect {screenX, screenY, width, height};
// After: use unscaled sprite dimensions for srcRect; scale only destRect
const int srcW = sprite.width;
const int srcH = sprite.height;
const float dstW = srcW * config::graphics::DRAW_SCALE;
const float dstH = srcH * config::graphics::DRAW_SCALE;
const SDL_Rect srcRect { 0, 0, srcW, srcH };
screenX -= dstW / 2.0f;
screenY -= dstH / 2.0f;
const SDL_FRect destRect { screenX, screenY, dstW, dstH };
🤖 Prompt for AI Agents
In include/platform/sdl/GraphicsDevice.h around lines 18 to 19 and corresponding
src/platform/sdl/GraphicsDevice.cpp, the bug is that srcRect is being computed
using the scaled size (DRAW_SCALE) causing wrong sampling/cropping; compute
srcRect using the sprite's raw pixel dimensions (unscaled width/height or frame
size) and only apply DRAW_SCALE to destRect (position and size on screen).
Update the implementation to build srcRect from sprite pixel coordinates/size
(no DRAW_SCALE), then set destRect.x/y/width/height = screenX/screenY and sprite
size multiplied by DRAW_SCALE, and ensure types match SDL_Rect/SDL_FRect
expectations.

void drawOverlay(uint8_t r, uint8_t g, uint8_t b, uint8_t a) const;
Comment on lines +19 to +20
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle missing font in drawText (in .cpp)

drawText dereferences a potentially null font. Guard against it to avoid crashes if asset loading failed.

Apply in src/platform/sdl/GraphicsDevice.cpp:

-    TTF_Font* font = assetLoader_.getFont(config::resources::FONT).get();
-    if (const auto textSurface = TTF_RenderText_Blended(font, text.data(), color)) {
+    TTF_Font* font = assetLoader_.getFont(config::resources::FONT).get();
+    if (!font) return;
+    if (const auto textSurface = TTF_RenderText_Blended(font, text.data(), color)) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
void drawText(std::string_view text, float x, float y) const;
void drawOverlay(uint8_t r, uint8_t g, uint8_t b, uint8_t a) const;
// In src/platform/sdl/GraphicsDevice.cpp, inside GraphicsDevice::drawText(...)
TTF_Font* font = assetLoader_.getFont(config::resources::FONT).get();
if (!font)
return;
if (const auto textSurface = TTF_RenderText_Blended(font, text.data(), color)) {
// existing rendering logic…
}
🤖 Prompt for AI Agents
In include/platform/sdl/GraphicsDevice.h lines 19-20 and corresponding
implementation in src/platform/sdl/GraphicsDevice.cpp, drawText currently
dereferences a possibly-null font; update the implementation to check the font
pointer (or font resource handle) before using it and early-return (or skip
rendering) if it is null, optionally logging a warning/error; ensure no
dereference occurs, and preserve function constness and behavior when a valid
font exists (i.e., proceed with the existing draw steps).

std::pair<int, int> getWindowSize() const;

private:
SDLContext sdlContext_ {};
SDL_Window* window_ = nullptr;
SDL_Renderer* renderer_ = nullptr;
AssetLoader assetLoader_ {};
bool vsyncEnabled_ = true;

public:
~GraphicsDevice();
};
15 changes: 15 additions & 0 deletions include/platform/sdl/InputSource.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// Created by Jacopo Uggeri on 20/08/2025.
//
#pragma once

struct GameState;

class InputSource final {
public:
bool init();
void handleEvents(GameState& state);
void handlePlayerInput(GameState& state);

~InputSource();
Comment on lines +6 to +14
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Platform-to-game coupling: avoid exposing GameState in the platform API.

Having platform headers reference GameState binds platform to game. Replace GameState& parameters with a platform-agnostic event sink or an InputEvent stream; move state mutations to the game layer.

Example interface sketch (outside this file for illustration):

// include/platform/interfaces/IInputSource.h
struct InputEvent {
    enum class Type { Quit, KeyDown, MouseDown, MouseMove };
    Type type;
    // key/button/mouse payload...
};

class IInputSource {
public:
    virtual ~IInputSource() = default;
    virtual bool init() noexcept = 0;
    virtual bool pollEvent(InputEvent& out) noexcept = 0; // returns false when queue empty
};

Then InputManager consumes InputEvents and updates GameState.

🤖 Prompt for AI Agents
In include/platform/sdl/InputSource.h around lines 6 to 14, the header currently
exposes GameState (platform-to-game coupling) — replace the GameState&
parameters with a platform-agnostic event interface: remove or stop
forward-declaring GameState and change the API to produce InputEvent instances
or feed an IInputSink/IInputQueue instead (e.g. add bool pollEvent(InputEvent&
out) noexcept or void setSink(IInputSink*) and keep init() noexcept), eliminate
any methods that directly mutate game state (handlePlayerInput) and move all
state mutations into the game layer; update callers to consume InputEvent
streams from the InputSource and apply changes to GameState in the game code.

};
30 changes: 30 additions & 0 deletions include/platform/sdl/Resource.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// Created by Jacopo Uggeri on 21/08/2025.
//

#pragma once

#include <SDL_render.h>
#include <SDL_ttf.h>
#include <memory>

template<typename T, auto Deleter>
class Resource {
using ResourcePtr = std::unique_ptr<T, decltype(Deleter)>;
public:
explicit Resource(T* resource = nullptr)
: resource_(resource, Deleter) {}

explicit operator bool() const { return resource_ != nullptr; }

[[nodiscard]] T* get() const { return resource_.get(); }

T* operator->() const { return resource_.get(); }
T& operator*() const { return *resource_; }

private:
ResourcePtr resource_;
};

using Texture = Resource<SDL_Texture, &SDL_DestroyTexture>;
using Font = Resource<TTF_Font, &TTF_CloseFont>;
Loading