phantasia

Phantasia - 2D SDL3 RPG prototype.
git clone git://git.beep.wimdupont.com/phantasia.git
Log | Files | Refs | README | LICENSE

commit d928191f038dc512524523aab1d407d3871e198b
parent 42592a9842d3a8d0e4646424abb3bf1a3a0dc9b4
Author: beep <beep@wimdupont.com>
Date:   Sat,  4 Apr 2026 17:04:31 +0000

Add CC0 hero and item sprites

Diffstat:
MMakefile | 16++++++++++++++--
Aassets/vendor/16x16-rpg-items/SOURCE.txt | 4++++
Aassets/vendor/16x16-rpg-items/items.png | 0
Aassets/vendor/base-character-16x16/Hero.png | 0
Aassets/vendor/base-character-16x16/SOURCE.txt | 4++++
Msrc/engine/world.h | 2++
Msrc/game/main.c | 144+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
7 files changed, 132 insertions(+), 38 deletions(-)

diff --git a/Makefile b/Makefile @@ -6,6 +6,10 @@ MAPSRC = data/maps/ashen-meadow.txt MAPBIN = $(MAPSRC:data/maps/%.txt=$(MAPDIR)/%.phmap) TILESET_SRC = assets/vendor/overworld-grass-biome/TilesetGrass/overworld_tileset_grass.png TILESET_BMP = $(OBJDIR)/assets/overworld_tileset_grass.bmp +HERO_SRC = assets/vendor/base-character-16x16/Hero.png +HERO_BMP = $(OBJDIR)/assets/hero.bmp +ITEMS_SRC = assets/vendor/16x16-rpg-items/items.png +ITEMS_BMP = $(OBJDIR)/assets/items.bmp SRC = \ src/engine/world.c \ @@ -33,9 +37,9 @@ endif .PHONY: all clean render-smoke smoke -all: $(BIN) $(MAPBIN) $(TILESET_BMP) +all: $(BIN) $(MAPBIN) $(TILESET_BMP) $(HERO_BMP) $(ITEMS_BMP) -$(BIN): $(OBJ) $(MAPBIN) $(TILESET_BMP) +$(BIN): $(OBJ) $(MAPBIN) $(TILESET_BMP) $(HERO_BMP) $(ITEMS_BMP) @mkdir -p $(dir $@) $(CC) $(OBJ) -o $@ $(LDLIBS) @@ -55,6 +59,14 @@ $(TILESET_BMP): $(TILESET_SRC) @mkdir -p $(dir $@) magick $< -alpha off BMP3:$@ +$(HERO_BMP): $(HERO_SRC) + @mkdir -p $(dir $@) + magick $< -alpha off BMP3:$@ + +$(ITEMS_BMP): $(ITEMS_SRC) + @mkdir -p $(dir $@) + magick $< BMP3:$@ + smoke: $(BIN) $(BIN) --smoke-test diff --git a/assets/vendor/16x16-rpg-items/SOURCE.txt b/assets/vendor/16x16-rpg-items/SOURCE.txt @@ -0,0 +1,4 @@ +16x16 RPG Items +Author: Jetrel +License: CC0 +Source: https://opengameart.org/content/16x16-rpg-items diff --git a/assets/vendor/16x16-rpg-items/items.png b/assets/vendor/16x16-rpg-items/items.png Binary files differ. diff --git a/assets/vendor/base-character-16x16/Hero.png b/assets/vendor/base-character-16x16/Hero.png Binary files differ. diff --git a/assets/vendor/base-character-16x16/SOURCE.txt b/assets/vendor/base-character-16x16/SOURCE.txt @@ -0,0 +1,4 @@ +Base Character Spritesheet 16x16 +Author: Cough-E +License: CC0 +Source: https://opengameart.org/content/base-character-spritesheet-16x16 diff --git a/src/engine/world.h b/src/engine/world.h @@ -38,6 +38,8 @@ typedef struct { int value; int power; int talent_points; + int sprite_tile_x; + int sprite_tile_y; } PhItemDef; typedef struct { diff --git a/src/game/main.c b/src/game/main.c @@ -25,14 +25,26 @@ enum { #define PH_START_MAP_PATH "obj/maps/ashen-meadow.phmap" #define PH_TILESET_PATH "obj/assets/overworld_tileset_grass.bmp" +#define PH_HERO_PATH "obj/assets/hero.bmp" +#define PH_ITEMS_PATH "obj/assets/items.bmp" #define PH_RENDER_SMOKE_PATH "obj/render-smoke.bmp" enum { PH_TILESET_COLUMNS = 12, + PH_HERO_COLUMNS = 8, + PH_ITEMS_COLUMNS = 8, PH_TILE_GRASS = 13, PH_TILE_BLOCKED = 61, }; +#if PH_USE_SDL +typedef struct { + SDL_Texture *tileset; + SDL_Texture *hero; + SDL_Texture *items; +} PhAssets; +#endif + static int ph_make_world(PhWorld *world) { @@ -51,7 +63,7 @@ ph_make_world(PhWorld *world) .max_hp = 40, .move_speed = 90, .sprite_tile_x = 0, - .sprite_tile_y = 0, + .sprite_tile_y = 2, .kind = PH_ENTITY_PLAYER, }); ph_world_add_entity_def(world, (PhEntityDef){ @@ -59,8 +71,8 @@ ph_make_world(PhWorld *world) .name = "Mire Slime", .max_hp = 12, .move_speed = 45, - .sprite_tile_x = 1, - .sprite_tile_y = 0, + .sprite_tile_x = -1, + .sprite_tile_y = -1, .kind = PH_ENTITY_MONSTER, }); ph_world_add_item_def(world, (PhItemDef){ @@ -69,6 +81,8 @@ ph_make_world(PhWorld *world) .value = 30, .power = 0, .talent_points = 1, + .sprite_tile_x = 2, + .sprite_tile_y = 6, }); player = ph_world_spawn_entity(world, PH_DEF_PLAYER, (PhVec2){ 80.0f, 80.0f }); @@ -115,7 +129,7 @@ ph_run_smoke_test(void) #if PH_USE_SDL static SDL_Texture * -ph_load_tileset(SDL_Renderer *renderer, const char *path) +ph_load_sprite_sheet(SDL_Renderer *renderer, const char *path, Uint8 key_r, Uint8 key_g, Uint8 key_b) { SDL_Surface *surface; SDL_Texture *texture; @@ -126,7 +140,7 @@ ph_load_tileset(SDL_Renderer *renderer, const char *path) return NULL; } - SDL_SetSurfaceColorKey(surface, true, SDL_MapSurfaceRGB(surface, 0, 0, 0)); + SDL_SetSurfaceColorKey(surface, true, SDL_MapSurfaceRGB(surface, key_r, key_g, key_b)); texture = SDL_CreateTextureFromSurface(renderer, surface); SDL_DestroySurface(surface); if (!texture) { @@ -135,8 +149,51 @@ ph_load_tileset(SDL_Renderer *renderer, const char *path) return texture; } +static int +ph_load_assets(PhAssets *assets, SDL_Renderer *renderer) +{ + memset(assets, 0, sizeof(*assets)); + + assets->tileset = ph_load_sprite_sheet(renderer, PH_TILESET_PATH, 0, 0, 0); + assets->hero = ph_load_sprite_sheet(renderer, PH_HERO_PATH, 0, 0, 0); + assets->items = ph_load_sprite_sheet(renderer, PH_ITEMS_PATH, 111, 119, 109); + + if (!assets->tileset || !assets->hero || !assets->items) { + SDL_DestroyTexture(assets->tileset); + SDL_DestroyTexture(assets->hero); + SDL_DestroyTexture(assets->items); + memset(assets, 0, sizeof(*assets)); + return -1; + } + return 0; +} + static void -ph_draw_area(SDL_Renderer *renderer, SDL_Texture *tileset, const PhWorld *world) +ph_destroy_assets(PhAssets *assets) +{ + SDL_DestroyTexture(assets->tileset); + SDL_DestroyTexture(assets->hero); + SDL_DestroyTexture(assets->items); + memset(assets, 0, sizeof(*assets)); +} + +static void +ph_draw_sprite(SDL_Renderer *renderer, SDL_Texture *texture, int columns, + int sprite_tile_x, int sprite_tile_y, SDL_FRect dst) +{ + SDL_FRect src = { + .x = (float)(sprite_tile_x * PH_TILE_SIZE), + .y = (float)(sprite_tile_y * PH_TILE_SIZE), + .w = (float)PH_TILE_SIZE, + .h = (float)PH_TILE_SIZE, + }; + + (void)columns; + SDL_RenderTexture(renderer, texture, &src, &dst); +} + +static void +ph_draw_area(SDL_Renderer *renderer, const PhAssets *assets, const PhWorld *world) { int tx; int ty; @@ -158,18 +215,19 @@ ph_draw_area(SDL_Renderer *renderer, SDL_Texture *tileset, const PhWorld *world) .h = (float)PH_TILE_SIZE, }; - SDL_RenderTexture(renderer, tileset, &src, &rect); + SDL_RenderTexture(renderer, assets->tileset, &src, &rect); } } } static void -ph_draw_items(SDL_Renderer *renderer, const PhWorld *world) +ph_draw_items(SDL_Renderer *renderer, const PhAssets *assets, const PhWorld *world) { int i; for (i = 0; i < world->ground_item_count; ++i) { const PhGroundItem *drop = &world->ground_items[i]; + const PhItemDef *def = ph_world_item_def(world, drop->item_id); SDL_FRect rect; if (!drop->active) { @@ -181,13 +239,22 @@ ph_draw_items(SDL_Renderer *renderer, const PhWorld *world) rect.w = 6.0f; rect.h = 6.0f; - SDL_SetRenderDrawColor(renderer, 242, 214, 80, 255); - SDL_RenderFillRect(renderer, &rect); + if (def && def->sprite_tile_x >= 0 && def->sprite_tile_y >= 0) { + rect.x = drop->pos.x - world->camera.pos.x - 8.0f; + rect.y = drop->pos.y - world->camera.pos.y - 8.0f; + rect.w = 16.0f; + rect.h = 16.0f; + ph_draw_sprite(renderer, assets->items, PH_ITEMS_COLUMNS, + def->sprite_tile_x, def->sprite_tile_y, rect); + } else { + SDL_SetRenderDrawColor(renderer, 242, 214, 80, 255); + SDL_RenderFillRect(renderer, &rect); + } } } static void -ph_draw_entities(SDL_Renderer *renderer, const PhWorld *world) +ph_draw_entities(SDL_Renderer *renderer, const PhAssets *assets, const PhWorld *world) { int i; @@ -200,32 +267,39 @@ ph_draw_entities(SDL_Renderer *renderer, const PhWorld *world) continue; } - rect.x = entity->pos.x - world->camera.pos.x - 7.0f; - rect.y = entity->pos.y - world->camera.pos.y - 12.0f; - rect.w = 14.0f; - rect.h = 18.0f; - - if (def->kind == PH_ENTITY_PLAYER) { - SDL_SetRenderDrawColor(renderer, 110, 180, 255, 255); - } else if (def->kind == PH_ENTITY_MONSTER) { - SDL_SetRenderDrawColor(renderer, 168, 108, 224, 255); + if (def->kind == PH_ENTITY_PLAYER && def->sprite_tile_x >= 0 && def->sprite_tile_y >= 0) { + rect.x = entity->pos.x - world->camera.pos.x - 8.0f; + rect.y = entity->pos.y - world->camera.pos.y - 12.0f; + rect.w = 16.0f; + rect.h = 16.0f; + ph_draw_sprite(renderer, assets->hero, PH_HERO_COLUMNS, + def->sprite_tile_x, def->sprite_tile_y, rect); } else { - SDL_SetRenderDrawColor(renderer, 220, 180, 90, 255); + rect.x = entity->pos.x - world->camera.pos.x - 7.0f; + rect.y = entity->pos.y - world->camera.pos.y - 12.0f; + rect.w = 14.0f; + rect.h = 18.0f; + + if (def->kind == PH_ENTITY_MONSTER) { + SDL_SetRenderDrawColor(renderer, 168, 108, 224, 255); + } else { + SDL_SetRenderDrawColor(renderer, 220, 180, 90, 255); + } + SDL_RenderFillRect(renderer, &rect); } - SDL_RenderFillRect(renderer, &rect); } } static void -ph_render_frame(SDL_Renderer *renderer, SDL_Texture *tileset, PhWorld *world, PhInput input, float dt) +ph_render_frame(SDL_Renderer *renderer, const PhAssets *assets, PhWorld *world, PhInput input, float dt) { ph_world_tick(world, input, dt); SDL_SetRenderDrawColor(renderer, 16, 18, 24, 255); SDL_RenderClear(renderer); - ph_draw_area(renderer, tileset, world); - ph_draw_items(renderer, world); - ph_draw_entities(renderer, world); + ph_draw_area(renderer, assets, world); + ph_draw_items(renderer, assets, world); + ph_draw_entities(renderer, assets, world); SDL_RenderPresent(renderer); } @@ -285,7 +359,7 @@ ph_run_render_smoke_test(void) { SDL_Surface *surface; SDL_Renderer *renderer; - SDL_Texture *tileset; + PhAssets assets; PhWorld world; int i; @@ -316,8 +390,7 @@ ph_run_render_smoke_test(void) return 1; } - tileset = ph_load_tileset(renderer, PH_TILESET_PATH); - if (!tileset) { + if (ph_load_assets(&assets, renderer) < 0) { ph_area_free(&world.area); SDL_DestroyRenderer(renderer); SDL_DestroySurface(surface); @@ -326,14 +399,14 @@ ph_run_render_smoke_test(void) } for (i = 0; i < 8; ++i) { - ph_render_frame(renderer, tileset, &world, (PhInput){ 0 }, 1.0f / 60.0f); + ph_render_frame(renderer, &assets, &world, (PhInput){ 0 }, 1.0f / 60.0f); } if (!SDL_SaveBMP(surface, PH_RENDER_SMOKE_PATH)) { fprintf(stderr, "SDL_SaveBMP failed: %s\n", SDL_GetError()); } - SDL_DestroyTexture(tileset); + ph_destroy_assets(&assets); ph_area_free(&world.area); SDL_DestroyRenderer(renderer); SDL_DestroySurface(surface); @@ -346,7 +419,7 @@ ph_run_game(int frame_limit) { SDL_Window *window; SDL_Renderer *renderer; - SDL_Texture *tileset; + PhAssets assets; PhWorld world; Uint64 last_ticks; int frame_count = 0; @@ -368,8 +441,7 @@ ph_run_game(int frame_limit) return 1; } - tileset = ph_load_tileset(renderer, PH_TILESET_PATH); - if (!tileset) { + if (ph_load_assets(&assets, renderer) < 0) { SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); ph_area_free(&world.area); @@ -403,7 +475,7 @@ ph_run_game(int frame_limit) last_ticks = now_ticks; input = ph_read_input(SDL_GetKeyboardState(NULL)); - ph_render_frame(renderer, tileset, &world, input, dt); + ph_render_frame(renderer, &assets, &world, input, dt); ++frame_count; if (frame_limit > 0 && frame_count >= frame_limit) { running = 0; @@ -411,7 +483,7 @@ ph_run_game(int frame_limit) SDL_Delay(16); } - SDL_DestroyTexture(tileset); + ph_destroy_assets(&assets); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); ph_area_free(&world.area);