From c3847160daeaa0a2e99e875b017c4a55e1b59c1d Mon Sep 17 00:00:00 2001 From: Andrew Kesterson Date: Wed, 6 May 2026 11:12:42 -0400 Subject: [PATCH] Added a font registry, a text rendering helper, and an FPS counter function --- CMakeLists.txt | 22 +++++++++++-------- TODO.txt | 2 -- include/sdl3game/SDL_GameControllerDB.h | 2 +- include/sdl3game/game.h | 15 +++++++++++-- include/sdl3game/registry.h | 4 ++++ src/character.c | 2 +- src/game.c | 29 +++++++++++++++++++++++-- src/registry.c | 22 ++++++++++++++++++- src/sprite.c | 2 +- 9 files changed, 81 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 93775ec..31726f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,7 @@ find_package(PkgConfig REQUIRED) find_package(SDL3 REQUIRED) find_package(SDL3_image REQUIRED) find_package(SDL3_mixer REQUIRED) +find_package(SDL3_ttf REQUIRED) find_package(akerror REQUIRED) find_package(jansson REQUIRED) find_package(box2d REQUIRED) @@ -36,6 +37,7 @@ include_directories(${SDL3_INCLUDE_DIRS}) add_library(sdl3game SHARED src/actor.c src/actor_state_string_names.c + src/text.c src/assets.c src/character.c src/draw.c @@ -81,23 +83,25 @@ target_link_libraries(sdl3game SDL3::SDL3 SDL3_image::SDL3_image SDL3_mixer::SDL3_mixer + SDL3_ttf::SDL3_ttf ) -target_link_libraries(test_actor PRIVATE akerror::akerror sdl3game SDL3::SDL3 SDL3_image::SDL3_image SDL3_mixer::SDL3_mixer box2d::box2d jansson::jansson -lm) -target_link_libraries(test_bitmasks PRIVATE akerror::akerror sdl3game SDL3::SDL3 SDL3_image::SDL3_image SDL3_mixer::SDL3_mixer box2d::box2d jansson::jansson -lm) -target_link_libraries(test_character PRIVATE akerror::akerror sdl3game SDL3::SDL3 SDL3_image::SDL3_image SDL3_mixer::SDL3_mixer box2d::box2d jansson::jansson -lm) -target_link_libraries(test_registry PRIVATE akerror::akerror sdl3game SDL3::SDL3 SDL3_image::SDL3_image SDL3_mixer::SDL3_mixer box2d::box2d jansson::jansson -lm) -target_link_libraries(test_sprite PRIVATE akerror::akerror sdl3game SDL3::SDL3 SDL3_image::SDL3_image SDL3_mixer::SDL3_mixer box2d::box2d jansson::jansson -lm) -target_link_libraries(test_staticstring PRIVATE akerror::akerror sdl3game SDL3::SDL3 SDL3_image::SDL3_image SDL3_mixer::SDL3_mixer box2d::box2d jansson::jansson -lm) -target_link_libraries(test_tilemap PRIVATE akerror::akerror sdl3game SDL3::SDL3 SDL3_image::SDL3_image SDL3_mixer::SDL3_mixer box2d::box2d jansson::jansson -lm) -target_link_libraries(test_util PRIVATE akerror::akerror sdl3game SDL3::SDL3 SDL3_image::SDL3_image SDL3_mixer::SDL3_mixer box2d::box2d jansson::jansson -lm) +target_link_libraries(test_actor PRIVATE akerror::akerror sdl3game SDL3::SDL3 SDL3_ttf::SDL3_ttf SDL3_image::SDL3_image SDL3_mixer::SDL3_mixer box2d::box2d jansson::jansson -lm) +target_link_libraries(test_bitmasks PRIVATE akerror::akerror sdl3game SDL3::SDL3 SDL3_ttf::SDL3_ttf SDL3_image::SDL3_image SDL3_mixer::SDL3_mixer box2d::box2d jansson::jansson -lm) +target_link_libraries(test_character PRIVATE akerror::akerror sdl3game SDL3::SDL3 SDL3_ttf::SDL3_ttf SDL3_image::SDL3_image SDL3_mixer::SDL3_mixer box2d::box2d jansson::jansson -lm) +target_link_libraries(test_registry PRIVATE akerror::akerror sdl3game SDL3::SDL3 SDL3_ttf::SDL3_ttf SDL3_image::SDL3_image SDL3_mixer::SDL3_mixer box2d::box2d jansson::jansson -lm) +target_link_libraries(test_sprite PRIVATE akerror::akerror sdl3game SDL3::SDL3 SDL3_ttf::SDL3_ttf SDL3_image::SDL3_image SDL3_mixer::SDL3_mixer box2d::box2d jansson::jansson -lm) +target_link_libraries(test_staticstring PRIVATE akerror::akerror sdl3game SDL3::SDL3 SDL3_ttf::SDL3_ttf SDL3_image::SDL3_image SDL3_mixer::SDL3_mixer box2d::box2d jansson::jansson -lm) +target_link_libraries(test_tilemap PRIVATE akerror::akerror sdl3game SDL3::SDL3 SDL3_ttf::SDL3_ttf SDL3_image::SDL3_image SDL3_mixer::SDL3_mixer box2d::box2d jansson::jansson -lm) +target_link_libraries(test_util PRIVATE akerror::akerror sdl3game SDL3::SDL3 SDL3_ttf::SDL3_ttf SDL3_image::SDL3_image SDL3_mixer::SDL3_mixer box2d::box2d jansson::jansson -lm) -target_link_libraries(charviewer PRIVATE akerror::akerror sdl3game SDL3::SDL3 SDL3_image::SDL3_image SDL3_mixer::SDL3_mixer box2d::box2d jansson::jansson -lm) +target_link_libraries(charviewer PRIVATE akerror::akerror sdl3game SDL3::SDL3 SDL3_ttf::SDL3_ttf SDL3_image::SDL3_image SDL3_mixer::SDL3_mixer box2d::box2d jansson::jansson -lm) set(main_lib_dest "lib/sdl3game-${MY_LIBRARY_VERSION}") install(FILES ${CMAKE_CURRENT_BINARY_DIR}/sdl3game.pc DESTINATION "lib/pkgconfig/") install(TARGETS sdl3game DESTINATION "lib/") install(FILES "include/sdl3game/actor.h" DESTINATION "include/sdl3game/") +install(FILES "include/sdl3game/text.h" DESTINATION "include/sdl3game/") install(FILES "include/sdl3game/assets.h" DESTINATION "include/sdl3game/") install(FILES "include/sdl3game/character.h" DESTINATION "include/sdl3game/") install(FILES "include/sdl3game/error.h" DESTINATION "include/sdl3game/") diff --git a/TODO.txt b/TODO.txt index 64fba19..b0ee02b 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,5 +1,3 @@ -Character velocity is tied to CPU speed, not constant - Rendering should move to the SDL GPU renderer so i can do lighting and particles etc - Example suitable for my most primitive use case: https://github.com/TheSpydog/SDL_gpu_examples/blob/main/Examples/Blit2DArray.c - Try vulkan and D3D tutorials to come up to speed on the moving pieces, then figure ou the details from the examples and API docs diff --git a/include/sdl3game/SDL_GameControllerDB.h b/include/sdl3game/SDL_GameControllerDB.h index 368f07a..4d87bb9 100644 --- a/include/sdl3game/SDL_GameControllerDB.h +++ b/include/sdl3game/SDL_GameControllerDB.h @@ -1,7 +1,7 @@ #ifndef _SDL_GAMECONTROLLERDB_H_ #define _SDL_GAMECONTROLLERDB_H_ -// Taken from https://raw.githubusercontent.com/mdqinc/SDL_GameControllerDB/refs/heads/master/gamecontrollerdb.txt on Tue May 5 08:39:19 PM EDT 2026 +// Taken from https://raw.githubusercontent.com/mdqinc/SDL_GameControllerDB/refs/heads/master/gamecontrollerdb.txt on Wed May 6 11:06:20 AM EDT 2026 #define SDL_GAMECONTROLLER_DB_LEN 2225 diff --git a/include/sdl3game/game.h b/include/sdl3game/game.h index e3fc8ec..37f6f3a 100644 --- a/include/sdl3game/game.h +++ b/include/sdl3game/game.h @@ -4,6 +4,12 @@ #include #include "tilemap.h" +#define GAME_AUDIO_TRACK_BGM 1 +#define GAME_AUDIO_MAX_TRACKS 64 + +#define TIME_ONESEC_NS 1000000000 +#define TIME_ONESEC_MS 1000000 + /* ==================== GAME STATE VARIABLES =================== */ typedef struct { @@ -23,11 +29,15 @@ typedef struct { int screenwidth; int screenheight; GameState state; + int fps; + SDL_Time gameStartTime; + SDL_Time lastIterTime; + SDL_Time lastFPSTime; + int framesSinceUpdate; MIX_Mixer *mixer; - MIX_Track *tracks[64]; + MIX_Track *tracks[GAME_AUDIO_MAX_TRACKS]; } Game; -#define GAME_AUDIO_TRACK_BGM 1 extern SDL_Window *window; extern SDL_Renderer *renderer; @@ -42,5 +52,6 @@ extern Game game; #define BITMASK_CLEAR(x) x = 0; akerr_ErrorContext AKERR_NOIGNORE *GAME_init(); +void GAME_updateFPS(); #endif //_GAME_H_ diff --git a/include/sdl3game/registry.h b/include/sdl3game/registry.h index 278af85..3f1e6fe 100644 --- a/include/sdl3game/registry.h +++ b/include/sdl3game/registry.h @@ -8,8 +8,12 @@ extern SDL_PropertiesID REGISTRY_SPRITE; extern SDL_PropertiesID REGISTRY_SPRITESHEET; extern SDL_PropertiesID REGISTRY_CHARACTER; extern SDL_PropertiesID REGISTRY_ACTOR_STATE_STRINGS; +extern SDL_PropertiesID REGISTRY_FONT; +extern SDL_PropertiesID REGISTRY_MUSIC; akerr_ErrorContext AKERR_NOIGNORE *registry_init(); +akerr_ErrorContext AKERR_NOIGNORE *registry_init_music(); +akerr_ErrorContext AKERR_NOIGNORE *registry_init_font(); akerr_ErrorContext AKERR_NOIGNORE *registry_init_actor(); akerr_ErrorContext AKERR_NOIGNORE *registry_init_sprite(); akerr_ErrorContext AKERR_NOIGNORE *registry_init_spritesheet(); diff --git a/src/character.c b/src/character.c index e8d0f99..37e4252 100644 --- a/src/character.c +++ b/src/character.c @@ -186,7 +186,7 @@ akerr_ErrorContext *character_load_json(char *filename) ); CATCH(errctx, character_load_json_inner(json, obj)); CATCH(errctx, get_json_integer_value(json, "movementspeed", (int *)&obj->movementspeed)); - obj->movementspeed = obj->movementspeed * 1000000; + obj->movementspeed = obj->movementspeed * TIME_ONESEC_MS; CATCH(errctx, get_json_number_value(json, "velocity_x", &obj->vx)); CATCH(errctx, get_json_number_value(json, "velocity_y", &obj->vy)); } CLEANUP { diff --git a/src/game.c b/src/game.c index e30970b..f06fe00 100644 --- a/src/game.c +++ b/src/game.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -32,6 +33,9 @@ akerr_ErrorContext AKERR_NOIGNORE *GAME_init() int i = 0; PREPARE_ERROR(errctx); ATTEMPT { + game.gameStartTime = SDL_GetTicksNS(); + game.lastIterTime = game.gameStartTime; + game.lastFPSTime = game.gameStartTime; FAIL_ZERO_BREAK(errctx, strlen((char *)&game.name), AKERR_NULLPOINTER, "Must provide game name"); FAIL_ZERO_BREAK(errctx, strlen((char *)&game.version), AKERR_NULLPOINTER, "Must provide game version"); FAIL_ZERO_BREAK(errctx, strlen((char *)&game.uri), AKERR_NULLPOINTER, "Must provide game uri"); @@ -40,6 +44,8 @@ akerr_ErrorContext AKERR_NOIGNORE *GAME_init() CATCH(errctx, registry_init_sprite()); CATCH(errctx, registry_init_spritesheet()); CATCH(errctx, registry_init_character()); + CATCH(errctx, registry_init_font()); + CATCH(errctx, registry_init_music()); CATCH(errctx, registry_init_actor_state_strings()); } CLEANUP { } PROCESS(errctx) { @@ -87,9 +93,28 @@ akerr_ErrorContext AKERR_NOIGNORE *GAME_init() "Unable to create mixer device: %s", SDL_GetError()); + FAIL_ZERO_RETURN( + errctx, + TTF_Init(), + AKERR_SDL, + "Couldn't initialize front engine: %s", + SDL_GetError()); + camera.x = 0; camera.y = 0; camera.w = game.screenwidth; - camera.h = game.screenheight; - + camera.h = game.screenheight; +} + +void GAME_updateFPS() +{ + SDL_Time curTime; + curTime = SDL_GetTicksNS(); + if ( (curTime - game.lastFPSTime) > TIME_ONESEC_NS ) { + game.fps = game.framesSinceUpdate; + game.framesSinceUpdate = 0; + game.lastFPSTime = curTime; + } + game.framesSinceUpdate += 1; + game.lastIterTime = curTime; } diff --git a/src/registry.c b/src/registry.c index 0d03a16..e7d5156 100644 --- a/src/registry.c +++ b/src/registry.c @@ -11,6 +11,8 @@ SDL_PropertiesID REGISTRY_ACTOR_STATE_STRINGS; SDL_PropertiesID REGISTRY_SPRITE; SDL_PropertiesID REGISTRY_SPRITESHEET; SDL_PropertiesID REGISTRY_CHARACTER; +SDL_PropertiesID REGISTRY_MUSIC; +SDL_PropertiesID REGISTRY_FONT; akerr_ErrorContext *registry_init() { @@ -21,6 +23,8 @@ akerr_ErrorContext *registry_init() CATCH(errctx, registry_init_character()); CATCH(errctx, registry_init_actor()); CATCH(errctx, registry_init_actor_state_strings()); + CATCH(errctx, registry_init_font()); + CATCH(errctx, registry_init_music()); } CLEANUP { } PROCESS(errctx) { } FINISH(errctx, true); @@ -35,6 +39,22 @@ akerr_ErrorContext *registry_init_actor() SUCCEED_RETURN(errctx); } +akerr_ErrorContext AKERR_NOIGNORE *registry_init_font() +{ + PREPARE_ERROR(errctx); + REGISTRY_FONT = SDL_CreateProperties(); + FAIL_ZERO_RETURN(errctx, REGISTRY_FONT, AKERR_NULLPOINTER, "Error initializing font registry"); + SUCCEED_RETURN(errctx); +} + +akerr_ErrorContext *registry_init_music() +{ + PREPARE_ERROR(errctx); + REGISTRY_MUSIC = SDL_CreateProperties(); + FAIL_ZERO_RETURN(errctx, REGISTRY_MUSIC, AKERR_NULLPOINTER, "Error initializing music registry"); + SUCCEED_RETURN(errctx); +} + akerr_ErrorContext *registry_init_actor_state_strings() { int i = 0; @@ -54,7 +74,7 @@ akerr_ErrorContext *registry_init_actor_state_strings() akerr_ErrorContext *registry_init_sprite() { - PREPARE_ERROR(errctx); + PREPARE_ERROR(errctx); REGISTRY_SPRITE = SDL_CreateProperties(); FAIL_ZERO_RETURN(errctx, REGISTRY_SPRITE, AKERR_NULLPOINTER, "Error initializing sprite registry"); SUCCEED_RETURN(errctx); diff --git a/src/sprite.c b/src/sprite.c index 1173119..87ff67e 100644 --- a/src/sprite.c +++ b/src/sprite.c @@ -99,7 +99,7 @@ akerr_ErrorContext *sprite_load_json(char *filename) CATCH(errctx, get_json_integer_value((json_t *)json, "width", &obj->width)); CATCH(errctx, get_json_integer_value((json_t *)json, "height", &obj->height)); CATCH(errctx, get_json_integer_value((json_t *)json, "speed", (int *)&obj->speed)); - obj->speed = obj->speed * 1000000; + obj->speed = obj->speed * TIME_ONESEC_MS; CATCH(errctx, get_json_boolean_value((json_t *)json, "loop", &obj->loop)); CATCH(errctx, get_json_boolean_value((json_t *)json, "loopReverse", &obj->loopReverse));