phantasia

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

render.c (9123B)


      1 #include "engine/render.h"
      2 
      3 #include <math.h>
      4 #include <stdio.h>
      5 #include <string.h>
      6 
      7 #include "config.h"
      8 
      9 #if PH_USE_SDL
     10 static float
     11 ph_world_pixel(float position, float camera)
     12 {
     13 	return floorf(position + 0.5f) - floorf(camera + 0.5f);
     14 }
     15 
     16 static SDL_Texture *
     17 ph_load_sprite_sheet(SDL_Renderer *renderer, const PhAssetDef *def)
     18 {
     19 	SDL_Surface *surface;
     20 	SDL_Texture *texture;
     21 
     22 	surface = SDL_LoadBMP(def->path);
     23 	if (!surface) {
     24 		fprintf(stderr, "SDL_LoadBMP failed for %s: %s\n",
     25 			def->path, SDL_GetError());
     26 		return NULL;
     27 	}
     28 
     29 	SDL_SetSurfaceColorKey(surface, true, SDL_MapSurfaceRGB(surface,
     30 		def->key.r, def->key.g, def->key.b));
     31 	texture = SDL_CreateTextureFromSurface(renderer, surface);
     32 	SDL_DestroySurface(surface);
     33 	if (!texture) {
     34 		fprintf(stderr, "SDL_CreateTextureFromSurface failed: %s\n", SDL_GetError());
     35 	}
     36 	return texture;
     37 }
     38 
     39 int
     40 ph_render_assets_load(PhRenderAssets *assets, SDL_Renderer *renderer,
     41 	const PhAssetDef *defs, int def_count)
     42 {
     43 	int i;
     44 
     45 	memset(assets, 0, sizeof(*assets));
     46 	if (!renderer || def_count < 0 || def_count > PH_MAX_ASSETS ||
     47 			(def_count > 0 && !defs)) return -1;
     48 	assets->texture_count = def_count;
     49 	for (i = 0; i < def_count; ++i) {
     50 		if (!defs[i].path) continue;
     51 		assets->textures[i] = ph_load_sprite_sheet(renderer, &defs[i]);
     52 		if (!assets->textures[i]) {
     53 			ph_render_assets_destroy(assets);
     54 			return -1;
     55 		}
     56 	}
     57 	return 0;
     58 }
     59 
     60 void
     61 ph_render_assets_destroy(PhRenderAssets *assets)
     62 {
     63 	int i;
     64 
     65 	for (i = 0; i < assets->texture_count; ++i)
     66 		SDL_DestroyTexture(assets->textures[i]);
     67 	memset(assets, 0, sizeof(*assets));
     68 }
     69 
     70 SDL_Texture *
     71 ph_render_asset(const PhRenderAssets *assets, int asset_id)
     72 {
     73 	return asset_id >= 0 && asset_id < assets->texture_count ?
     74 		assets->textures[asset_id] : NULL;
     75 }
     76 
     77 void
     78 ph_render_sprite(SDL_Renderer *renderer, SDL_Texture *texture,
     79 	int sprite_tile_x, int sprite_tile_y, SDL_FRect dst, SDL_FlipMode flip)
     80 {
     81 	SDL_FRect src = {
     82 		.x = (float)(sprite_tile_x * PH_TILE_SIZE),
     83 		.y = (float)(sprite_tile_y * PH_TILE_SIZE),
     84 		.w = (float)PH_TILE_SIZE,
     85 		.h = (float)PH_TILE_SIZE,
     86 	};
     87 
     88 	SDL_RenderTextureRotated(renderer, texture, &src, &dst, 0.0, NULL, flip);
     89 }
     90 
     91 void
     92 ph_render_entity_frame(const PhEntityDef *def, const PhEntity *entity,
     93 	int *sprite_tile_x, int *sprite_tile_y, SDL_FlipMode *flip)
     94 {
     95 	int direction = entity->motion.facing_y > 0 ? PH_SPRITE_DOWN :
     96 		entity->motion.facing_y < 0 ? PH_SPRITE_UP : PH_SPRITE_SIDE;
     97 	int frame = ph_entity_animation_frame(def, entity);
     98 
     99 	*sprite_tile_x = def->sprite_tiles[direction][frame][0];
    100 	*sprite_tile_y = def->sprite_tiles[direction][frame][1];
    101 	*flip = direction == PH_SPRITE_SIDE && entity->motion.facing_x > 0 ?
    102 		SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
    103 }
    104 
    105 static void
    106 ph_draw_area(SDL_Renderer *renderer, const PhRenderAssets *assets, const PhWorld *world)
    107 {
    108 	int tx;
    109 	int ty;
    110 
    111 	for (ty = (int)(world->camera.pos.y / PH_TILE_SIZE);
    112 			ty < world->area.height && (float)(ty * PH_TILE_SIZE) <
    113 			world->camera.pos.y + world->camera.viewport_h; ++ty) {
    114 		for (tx = (int)(world->camera.pos.x / PH_TILE_SIZE);
    115 				tx < world->area.width && (float)(tx * PH_TILE_SIZE) <
    116 				world->camera.pos.x + world->camera.viewport_w; ++tx) {
    117 			const PhTileDef *def = ph_area_tile_def(&world->area, tx, ty);
    118 			SDL_Texture *texture;
    119 			SDL_FRect rect = {
    120 				.x = ph_world_pixel((float)(tx * PH_TILE_SIZE), world->camera.pos.x),
    121 				.y = ph_world_pixel((float)(ty * PH_TILE_SIZE), world->camera.pos.y),
    122 				.w = (float)PH_TILE_SIZE,
    123 				.h = (float)PH_TILE_SIZE,
    124 			};
    125 			SDL_FRect src;
    126 			int sprite_tile_x;
    127 			int sprite_tile_y;
    128 
    129 			if (!def) continue;
    130 			if (def->background_asset_id) {
    131 				SDL_Texture *background = ph_render_asset(assets,
    132 					def->background_asset_id);
    133 				SDL_FRect background_src = {
    134 					.x = (float)(def->background_sprite_tile_x * PH_TILE_SIZE),
    135 					.y = (float)(def->background_sprite_tile_y * PH_TILE_SIZE),
    136 					.w = (float)PH_TILE_SIZE,
    137 					.h = (float)PH_TILE_SIZE,
    138 				};
    139 
    140 				if (background)
    141 					SDL_RenderTexture(renderer, background, &background_src, &rect);
    142 			}
    143 			texture = ph_render_asset(assets, def->asset_id);
    144 			if (!texture) continue;
    145 			sprite_tile_x = def->sprite_tile_x;
    146 			sprite_tile_y = def->sprite_tile_y;
    147 			if (def->autotile == PH_TILE_AUTOTILE_3X3) {
    148 				const PhTileDef *north = ph_area_tile_def(&world->area, tx, ty - 1);
    149 				const PhTileDef *east = ph_area_tile_def(&world->area, tx + 1, ty);
    150 				const PhTileDef *south = ph_area_tile_def(&world->area, tx, ty + 1);
    151 				const PhTileDef *west = ph_area_tile_def(&world->area, tx - 1, ty);
    152 				int joins_north = north && north->symbol == def->symbol;
    153 				int joins_east = east && east->symbol == def->symbol;
    154 				int joins_south = south && south->symbol == def->symbol;
    155 				int joins_west = west && west->symbol == def->symbol;
    156 
    157 				sprite_tile_x += joins_west == joins_east ? 1 : joins_west ? 2 : 0;
    158 				sprite_tile_y += joins_north == joins_south ? 1 : joins_north ? 2 : 0;
    159 			} else if (def->autotile == PH_TILE_AUTOTILE_FIXED &&
    160 					def->autotile_width > 0 && def->autotile_height > 0) {
    161 				const PhTileDef *neighbor;
    162 				int offset_x = 0;
    163 				int offset_y = 0;
    164 
    165 				while (offset_x < def->autotile_width - 1 &&
    166 						(neighbor = ph_area_tile_def(&world->area,
    167 						tx - offset_x - 1, ty)) &&
    168 						neighbor->autotile == def->autotile &&
    169 						neighbor->asset_id == def->asset_id &&
    170 						neighbor->sprite_tile_x == def->sprite_tile_x &&
    171 						neighbor->sprite_tile_y == def->sprite_tile_y &&
    172 						neighbor->autotile_width == def->autotile_width &&
    173 						neighbor->autotile_height == def->autotile_height) ++offset_x;
    174 				while (offset_y < def->autotile_height - 1 &&
    175 						(neighbor = ph_area_tile_def(&world->area,
    176 						tx, ty - offset_y - 1)) &&
    177 						neighbor->autotile == def->autotile &&
    178 						neighbor->asset_id == def->asset_id &&
    179 						neighbor->sprite_tile_x == def->sprite_tile_x &&
    180 						neighbor->sprite_tile_y == def->sprite_tile_y &&
    181 						neighbor->autotile_width == def->autotile_width &&
    182 						neighbor->autotile_height == def->autotile_height) ++offset_y;
    183 				sprite_tile_x += offset_x;
    184 				sprite_tile_y += offset_y;
    185 			}
    186 			src = (SDL_FRect){
    187 				.x = (float)(sprite_tile_x * PH_TILE_SIZE),
    188 				.y = (float)(sprite_tile_y * PH_TILE_SIZE),
    189 				.w = (float)PH_TILE_SIZE,
    190 				.h = (float)PH_TILE_SIZE,
    191 			};
    192 
    193 			SDL_RenderTexture(renderer, texture, &src, &rect);
    194 		}
    195 	}
    196 }
    197 
    198 static void
    199 ph_draw_items(SDL_Renderer *renderer, const PhRenderAssets *assets, const PhWorld *world)
    200 {
    201 	int i;
    202 
    203 	for (i = 0; i < world->ground_item_count; ++i) {
    204 		const PhGroundItem *drop = &world->ground_items[i];
    205 		const PhItemDef *def = ph_world_item_def(world, drop->item_id);
    206 		SDL_Texture *texture = NULL;
    207 		SDL_FRect rect;
    208 
    209 		if (!drop->active || drop->area_id != world->area_id) {
    210 			continue;
    211 		}
    212 
    213 		rect.x = ph_world_pixel(drop->pos.x, world->camera.pos.x) - 3.0f;
    214 		rect.y = ph_world_pixel(drop->pos.y, world->camera.pos.y) - 3.0f;
    215 		rect.w = 6.0f;
    216 		rect.h = 6.0f;
    217 
    218 		if (def) texture = ph_render_asset(assets, def->asset_id);
    219 		if (texture && def->sprite_tile_x >= 0 && def->sprite_tile_y >= 0) {
    220 			rect.x = ph_world_pixel(drop->pos.x, world->camera.pos.x) -
    221 				PH_TILE_SIZE * 0.5f;
    222 			rect.y = ph_world_pixel(drop->pos.y, world->camera.pos.y) -
    223 				PH_TILE_SIZE * 0.5f;
    224 			rect.w = PH_TILE_SIZE;
    225 			rect.h = PH_TILE_SIZE;
    226 			ph_render_sprite(renderer, texture,
    227 				def->sprite_tile_x, def->sprite_tile_y, rect, SDL_FLIP_NONE);
    228 		} else {
    229 			SDL_SetRenderDrawColor(renderer, PH_ITEM_COLOR);
    230 			SDL_RenderFillRect(renderer, &rect);
    231 		}
    232 	}
    233 }
    234 
    235 static void
    236 ph_draw_entities(SDL_Renderer *renderer, const PhRenderAssets *assets, const PhWorld *world)
    237 {
    238 	int i;
    239 
    240 	for (i = 0; i < world->entity_count; ++i) {
    241 		const PhEntity *entity = &world->entities[i];
    242 		const PhEntityDef *def = ph_world_entity_def(world, entity->type_id);
    243 		SDL_Texture *texture = NULL;
    244 		SDL_FRect rect;
    245 		int sprite_tile_x;
    246 		int sprite_tile_y;
    247 		SDL_FlipMode flip;
    248 
    249 		if (!entity->active || entity->area_id != world->area_id || !def) {
    250 			continue;
    251 		}
    252 
    253 		texture = ph_render_asset(assets, def->asset_id);
    254 		ph_render_entity_frame(def, entity, &sprite_tile_x, &sprite_tile_y, &flip);
    255 		if (texture && sprite_tile_x >= 0 && sprite_tile_y >= 0) {
    256 
    257 			rect.x = ph_world_pixel(entity->pos.x, world->camera.pos.x) -
    258 				PH_TILE_SIZE * 0.5f;
    259 			rect.y = ph_world_pixel(entity->pos.y, world->camera.pos.y) -
    260 				PH_TILE_SIZE * 0.75f;
    261 			rect.w = PH_TILE_SIZE;
    262 			rect.h = PH_TILE_SIZE;
    263 
    264 			ph_render_sprite(renderer, texture, sprite_tile_x, sprite_tile_y, rect, flip);
    265 		} else {
    266 			rect.x = ph_world_pixel(entity->pos.x, world->camera.pos.x) - 7.0f;
    267 			rect.y = ph_world_pixel(entity->pos.y, world->camera.pos.y) - 12.0f;
    268 			rect.w = 14.0f;
    269 			rect.h = 18.0f;
    270 
    271 			if (def->kind == PH_ENTITY_MONSTER) {
    272 				SDL_SetRenderDrawColor(renderer, PH_MONSTER_COLOR);
    273 			} else {
    274 				SDL_SetRenderDrawColor(renderer, PH_NPC_COLOR);
    275 			}
    276 			SDL_RenderFillRect(renderer, &rect);
    277 		}
    278 	}
    279 }
    280 void
    281 ph_render_world(SDL_Renderer *renderer, const PhRenderAssets *assets,
    282 	const PhWorld *world)
    283 {
    284 	ph_draw_area(renderer, assets, world);
    285 	ph_draw_items(renderer, assets, world);
    286 	ph_draw_entities(renderer, assets, world);
    287 }
    288 #endif