implemented tilemap rendering
This commit is contained in:
parent
124d379a3a
commit
4ebe759030
29 changed files with 3377 additions and 8223 deletions
92
src/assets.c
92
src/assets.c
|
|
@ -1,6 +1,8 @@
|
|||
#include "assets.h"
|
||||
#include "debug.h"
|
||||
#include <stdlib.h>
|
||||
#include <memory.h>
|
||||
#include <stdio.h>
|
||||
|
||||
typedef
|
||||
struct AssetData {
|
||||
|
|
@ -9,11 +11,29 @@ struct AssetData {
|
|||
AssetDestructor destructor;
|
||||
} AssetData;
|
||||
|
||||
cJSON* g_levels_json;
|
||||
|
||||
static AssetData* _assets = NULL;
|
||||
static size_t _assets_len = 0;
|
||||
static size_t _assets_cap = 0;
|
||||
static asset_id _next_id = 0;
|
||||
|
||||
static
|
||||
size_t file_length(FILE* fp) {
|
||||
size_t start = ftell(fp);
|
||||
fseek(fp, 0, SEEK_END);
|
||||
size_t r = ftell(fp);
|
||||
fseek(fp, start, SEEK_SET);
|
||||
return r;
|
||||
}
|
||||
|
||||
static
|
||||
void read_file(FILE* fp, char* out_buffer, size_t out_size) {
|
||||
size_t start = ftell(fp);
|
||||
fread(out_buffer, 1, out_size, fp);
|
||||
fseek(fp, start, SEEK_SET);
|
||||
}
|
||||
|
||||
static
|
||||
void _resize_if_needed(size_t requested_len) {
|
||||
if(requested_len < _assets_cap) {
|
||||
|
|
@ -32,8 +52,45 @@ void _resize_if_needed(size_t requested_len) {
|
|||
}
|
||||
|
||||
void assets_init() {
|
||||
LOG_INFO("assets_init");
|
||||
_assets = malloc(_assets_cap * sizeof(AssetData));
|
||||
_next_id = 1;
|
||||
|
||||
g_levels_json = load_json_from_file("assets/levels/levels.ldtk");
|
||||
}
|
||||
|
||||
// clear all assets without shrinking the assets array.
|
||||
static
|
||||
void _empty_assets() {
|
||||
AssetData* start = _assets;
|
||||
AssetData* end = _assets + _assets_len;
|
||||
for(AssetData* asset = start; asset < end; ++asset) {
|
||||
asset->destructor(asset->asset);
|
||||
}
|
||||
_assets_len = 0;
|
||||
}
|
||||
|
||||
void assets_clean() {
|
||||
_empty_assets();
|
||||
|
||||
free(_assets);
|
||||
_assets_len = 0;
|
||||
_assets_cap = 0;
|
||||
_assets = NULL;
|
||||
|
||||
cJSON_Delete(g_levels_json);
|
||||
g_levels_json = NULL;
|
||||
}
|
||||
|
||||
void assets_reset() {
|
||||
_empty_assets();
|
||||
|
||||
size_t desired = 8;
|
||||
AssetData* new = realloc(_assets, desired * sizeof(AssetData));
|
||||
if(new == NULL)
|
||||
return;
|
||||
_assets_cap = desired;
|
||||
_assets = new;
|
||||
}
|
||||
|
||||
asset_id store_asset(void* asset, AssetDestructor destructor) {
|
||||
|
|
@ -96,5 +153,40 @@ void free_asset(asset_id id) {
|
|||
AssetData* next = found + 1;
|
||||
size_t dif = next - (_assets + _assets_len);
|
||||
memmove(found, next, dif);
|
||||
--_assets_len;
|
||||
}
|
||||
|
||||
cJSON* load_json_from_file(const char* filename) {
|
||||
FILE* fp = fopen(filename, "r");
|
||||
if(fp == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t len = file_length(fp);
|
||||
char* buffer = malloc(len + 1);
|
||||
if(buffer == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
read_file(fp, buffer, len);
|
||||
cJSON* out = cJSON_Parse(buffer);
|
||||
free(buffer);
|
||||
|
||||
if(out == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
size_t json_array_len(cJSON* json) {
|
||||
if(!cJSON_IsArray(json)) {
|
||||
return 0;
|
||||
}
|
||||
size_t len = 0;
|
||||
cJSON* itr;
|
||||
cJSON_ArrayForEach(itr, json) {
|
||||
++len;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
|
|
|||
25
src/assets.h
25
src/assets.h
|
|
@ -1,18 +1,21 @@
|
|||
#ifndef _fencer_assets_h
|
||||
#define _fencer_assets_h
|
||||
|
||||
#include "vmath.h"
|
||||
#include <cjson/cJSON.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef void(*AssetDestructor)(void*);
|
||||
|
||||
typedef uintptr_t asset_id;
|
||||
|
||||
extern cJSON* g_levels_json;
|
||||
|
||||
// Prepare asset management.
|
||||
// Other asset management functions are invalid until assets_init is called.
|
||||
void assets_init();
|
||||
// Clean up and shut down assets management.
|
||||
void assets_clean();
|
||||
// Clear all assets and reset to initial state.
|
||||
// Clear all loaded assets
|
||||
void assets_reset();
|
||||
// Submit an object to be managed as an asset. If the destructor is NULL, free will be used.
|
||||
// Returns zero if asset could not be stored.
|
||||
|
|
@ -24,5 +27,23 @@ void* get_asset(asset_id id);
|
|||
asset_id get_asset_id(void* asset);
|
||||
// Free an asset managed by the asset manager using it's destructor, or free(..) if NULL.
|
||||
void free_asset(asset_id id);
|
||||
// load a file's contents into json
|
||||
cJSON* load_json_from_file(const char* filepath);
|
||||
// Get the length of a cJSON array
|
||||
size_t json_array_len(cJSON* array);
|
||||
static inline
|
||||
Vector json_array_to_vector(cJSON* array) {
|
||||
return (Vector) {
|
||||
cJSON_GetArrayItem(array, 0)->valuedouble,
|
||||
cJSON_GetArrayItem(array, 1)->valuedouble,
|
||||
};
|
||||
}
|
||||
static inline
|
||||
IVector json_array_to_ivector(cJSON* array) {
|
||||
return (IVector) {
|
||||
cJSON_GetArrayItem(array, 0)->valueint,
|
||||
cJSON_GetArrayItem(array, 1)->valueint
|
||||
};
|
||||
}
|
||||
|
||||
#endif // !_fencer_assets_h
|
||||
|
|
|
|||
22
src/camera.c
22
src/camera.c
|
|
@ -1,9 +1,11 @@
|
|||
#include "camera.h"
|
||||
#include "debug.h"
|
||||
#include "render.h"
|
||||
|
||||
Camera g_camera;
|
||||
|
||||
void camera_init() {
|
||||
LOG_INFO("camera_init");
|
||||
g_camera.centre = ZeroVector;
|
||||
g_camera.width = 10;
|
||||
}
|
||||
|
|
@ -14,28 +16,16 @@ float _camera_height(Camera* self) {
|
|||
}
|
||||
|
||||
static inline
|
||||
float _one() {
|
||||
float _cam_to_world_conversion() {
|
||||
return g_render_resolution.x / g_camera.width;
|
||||
}
|
||||
|
||||
SDL_FRect camera_world_to_screen_space(Camera* self, SDL_FRect* world_space) {
|
||||
float unit = _one();
|
||||
float unit = _cam_to_world_conversion();
|
||||
return (SDL_FRect) {
|
||||
.x = (world_space->x - g_camera.centre.x + g_camera.width/2.0) * unit,
|
||||
.y = (world_space->y - g_camera.centre.y + _camera_height(&g_camera)/2.0) * unit,
|
||||
.x = (g_camera.centre.x - world_space->x + g_camera.width/2.0) * unit,
|
||||
.y = (g_camera.centre.y - world_space->y + _camera_height(&g_camera)/2.0) * unit,
|
||||
.w = world_space->w * unit,
|
||||
.h = world_space->h * unit
|
||||
};
|
||||
}
|
||||
|
||||
SDL_FRect camera_calculate_unit_rect() {
|
||||
// If the camera's width is 1, this rectangle should fill the width of the screen.
|
||||
// If the camera's width is 2, this rectangle should fill half the width of the screen.
|
||||
float unit = _one();
|
||||
return (SDL_FRect) {
|
||||
.x = (-g_camera.centre.x + g_camera.width/2) * unit,
|
||||
.y = (-g_camera.centre.y + _camera_height(&g_camera)/2) * unit,
|
||||
.w = unit,
|
||||
.h = unit
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,9 +15,6 @@ typedef struct Camera Camera;
|
|||
extern Camera g_camera;
|
||||
|
||||
extern void camera_init();
|
||||
// generate a screen-space rectangle that is exactly 1x1 in world units.
|
||||
// With it's centre on the world origin.
|
||||
extern SDL_FRect camera_calculate_world_unit_rect(Camera* self);
|
||||
extern SDL_FRect camera_screen_to_world_space(Camera* self, SDL_FRect* camera_space);
|
||||
extern SDL_FRect camera_world_to_screen_space(Camera* self, SDL_FRect* world_space);
|
||||
|
||||
|
|
|
|||
7
src/debug.c
Normal file
7
src/debug.c
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#include "debug.h"
|
||||
|
||||
#if NDEBUG
|
||||
int g_debug_error_abort = 0;
|
||||
#else
|
||||
int g_debug_error_abort = 1;
|
||||
#endif
|
||||
25
src/debug.h
Normal file
25
src/debug.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#ifndef _fencer_debug_h
|
||||
#define _fencer_debug_h
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
extern int g_debug_error_abort;
|
||||
|
||||
#define LOG_INFO(...) do {\
|
||||
printf("[%s:%d] ", __FILE__, __LINE__);\
|
||||
printf("INFO | ");\
|
||||
printf(__VA_ARGS__);\
|
||||
printf("\n");\
|
||||
fflush(stdout);\
|
||||
} while(0)
|
||||
|
||||
#define LOG_ERROR(...) do {\
|
||||
printf("[%s:%d] ", __FILE__, __LINE__);\
|
||||
printf("ERROR | ");\
|
||||
printf(__VA_ARGS__);\
|
||||
printf("\n");\
|
||||
fflush(stdout);\
|
||||
if(g_debug_error_abort != 0) abort();\
|
||||
} while(0)
|
||||
|
||||
#endif // !_fencer_debug_h
|
||||
130
src/level.c
Normal file
130
src/level.c
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
#include "level.h"
|
||||
#include "assets.h"
|
||||
#include "debug.h"
|
||||
#include "tilemap.h"
|
||||
#include <cjson/cJSON.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define LEVEL_EXT ".ldtkl"
|
||||
#define WORLD_EXT ".ldtk"
|
||||
#define LEVEL_DIR "assets/levels/"
|
||||
|
||||
struct Level {
|
||||
asset_id asset_id;
|
||||
size_t level_iid;
|
||||
|
||||
Tilemap** tilemaps;
|
||||
|
||||
size_t tilemaps_len;
|
||||
|
||||
Vector player_start;
|
||||
};
|
||||
|
||||
static
|
||||
void _deallocate_level(void* self_void) {
|
||||
Level* self = self_void;
|
||||
|
||||
free(self);
|
||||
}
|
||||
|
||||
static
|
||||
size_t _count_layers(cJSON* layers, size_t* entities, size_t* automap) {
|
||||
size_t ent_ = 0, auto_ = 0, tot_ = 0;
|
||||
|
||||
cJSON* layer;
|
||||
cJSON_ArrayForEach(layer, layers) {
|
||||
cJSON* type = cJSON_GetObjectItem(layer, "__type");
|
||||
|
||||
if(type == NULL) {
|
||||
continue;
|
||||
} else if(strcmp(type->valuestring, "AutoLayer") == 0) {
|
||||
auto_++;
|
||||
} else if(strcmp(type->valuestring, "Entities") == 0) {
|
||||
ent_++;
|
||||
}
|
||||
tot_++;
|
||||
}
|
||||
|
||||
if(entities != NULL)
|
||||
*entities = ent_;
|
||||
if(automap != NULL)
|
||||
*automap = auto_;
|
||||
return tot_;
|
||||
}
|
||||
|
||||
Level* level_from_json(cJSON* json) {
|
||||
// allocate a new level struct
|
||||
Level* self = malloc(sizeof(Level));
|
||||
|
||||
// ensure allocations succeeded and initialize
|
||||
if(self == NULL) {
|
||||
LOG_ERROR("Failed to allocate level");
|
||||
return NULL;
|
||||
}
|
||||
*self = (Level) {
|
||||
.asset_id = store_asset(self, &_deallocate_level)
|
||||
};
|
||||
|
||||
// fetch the instance id
|
||||
cJSON* iid = cJSON_GetObjectItem(json, "iid");
|
||||
if(iid != NULL && cJSON_IsNumber(iid))
|
||||
self->level_iid = iid->valueint;
|
||||
|
||||
// get the level's layers
|
||||
cJSON* layers = cJSON_GetObjectItem(json, "layerInstances");
|
||||
|
||||
// count the different kinds of layers
|
||||
size_t entity_layer_count;
|
||||
_count_layers(layers, &entity_layer_count, &self->tilemaps_len);
|
||||
// allocate tilemap array
|
||||
self->tilemaps = malloc(self->tilemaps_len * sizeof(Tilemap*));
|
||||
|
||||
Tilemap** next_tilemap = self->tilemaps;
|
||||
if(layers != NULL && cJSON_IsArray(layers)) {
|
||||
cJSON* layer;
|
||||
cJSON_ArrayForEach(layer, layers) {
|
||||
cJSON* type = cJSON_GetObjectItem(layer, "__type");
|
||||
|
||||
if(strcmp(type->valuestring, "AutoLayer") == 0) {
|
||||
// load a tilemap as an autolayer
|
||||
LOG_INFO("loading autolayer");
|
||||
*next_tilemap = tilemap_from_autolayer(layer);
|
||||
++next_tilemap;
|
||||
} else if(strcmp(type->valuestring, "Entities")) {
|
||||
// load entities
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
Level* level_load(const char* level_id) {
|
||||
// Convert the level ID to a filepath
|
||||
size_t filename_len = strlen(LEVEL_DIR) + strlen(level_id) + strlen(LEVEL_EXT) + 1;
|
||||
char* filename = malloc(filename_len);
|
||||
sprintf(filename, "%s%s%s", LEVEL_DIR, level_id, LEVEL_EXT);
|
||||
LOG_INFO("loading level %s at %s", level_id, filename);
|
||||
|
||||
cJSON* level = load_json_from_file(filename);
|
||||
|
||||
if(level == NULL) {
|
||||
LOG_ERROR("Failed to load level file %s", filename);
|
||||
free(filename);
|
||||
return NULL;
|
||||
}
|
||||
free(filename);
|
||||
|
||||
return level_from_json(level);
|
||||
}
|
||||
|
||||
void level_destroy(Level* self) {
|
||||
free_asset(self->asset_id);
|
||||
}
|
||||
|
||||
void level_draw(Level* self) {
|
||||
Tilemap** end = self->tilemaps + self->tilemaps_len;
|
||||
for(Tilemap** map = self->tilemaps; map != end; ++map) {
|
||||
tilemap_draw(*map);
|
||||
}
|
||||
}
|
||||
19
src/level.h
Normal file
19
src/level.h
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#ifndef _fencer_level_h
|
||||
#define _fencer_level_h
|
||||
|
||||
#include "sprite.h"
|
||||
#include <SDL2/SDL_render.h>
|
||||
#include <cjson/cJSON.h>
|
||||
|
||||
typedef struct Level Level;
|
||||
|
||||
extern void world_init();
|
||||
extern void world_close();
|
||||
|
||||
extern Level* level_from_json(cJSON* json);
|
||||
extern Level* level_load(const char* level_id);
|
||||
extern void level_destroy(Level* self);
|
||||
|
||||
extern void level_draw(Level* self);
|
||||
|
||||
#endif // !_fencer_level_h
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
#include "render.h"
|
||||
#include "program.h"
|
||||
#include "debug.h"
|
||||
#include <SDL2/SDL_pixels.h>
|
||||
#include <SDL2/SDL_render.h>
|
||||
|
||||
|
|
@ -9,6 +10,7 @@ SDL_Rect g_render_area;
|
|||
IVector g_render_resolution;
|
||||
|
||||
void render_init(SDL_Window* window, const struct ProgramSettings* settings) {
|
||||
LOG_INFO("render_init");
|
||||
// create renderer, needs to be able to target textures, preferably hardware accelerated
|
||||
g_renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
|
||||
// create render target texture
|
||||
|
|
|
|||
51
src/sprite.c
51
src/sprite.c
|
|
@ -1,5 +1,6 @@
|
|||
#include "sprite.h"
|
||||
#include "camera.h"
|
||||
#include "debug.h"
|
||||
#include "render.h"
|
||||
#include "spritesheet.h"
|
||||
#include <SDL2/SDL_image.h>
|
||||
|
|
@ -7,7 +8,7 @@
|
|||
|
||||
struct Sprite {
|
||||
// The animation sheet to sample sprites from.
|
||||
Spritesheet* sheet;
|
||||
Spritesheet* spritesheet;
|
||||
|
||||
// The current frame of animation.
|
||||
size_t current_frame;
|
||||
|
|
@ -15,17 +16,20 @@ struct Sprite {
|
|||
float current_frame_time;
|
||||
|
||||
// The local transformation of this sprite.
|
||||
Transform transform;
|
||||
Vector origin;
|
||||
};
|
||||
|
||||
Sprite* sprite_from_spritesheet(Spritesheet* sheet) {
|
||||
Sprite* sprite_from_spritesheet(Spritesheet* sheet, size_t initial_frame) {
|
||||
Sprite* self = malloc(sizeof(Sprite));
|
||||
|
||||
self->sheet = sheet;
|
||||
self->transform = IdentityTransform;
|
||||
if(self == NULL) {
|
||||
LOG_ERROR("Failed to allocate memory for new sprite");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
self->spritesheet = sheet;
|
||||
self->origin = (Vector){0.5f, 1.0f};
|
||||
self->current_frame = 0;
|
||||
self->current_frame = initial_frame;
|
||||
// TODO: replace with a getter for the current game time.
|
||||
self->current_frame_time = 0;
|
||||
|
||||
|
|
@ -37,12 +41,10 @@ void sprite_destroy(Sprite* self) {
|
|||
}
|
||||
|
||||
void sprite_draw(Sprite* self, Transform transform) {
|
||||
SDL_Texture* texture = spritesheet_get_texture(self->sheet);
|
||||
SDL_Rect source = spritesheet_get_frame_rect(self->sheet, self->current_frame);
|
||||
transform = transform_apply(transform, self->transform);
|
||||
SDL_Texture* texture = spritesheet_get_texture(self->spritesheet);
|
||||
SDL_Rect source = spritesheet_get_frame_rect(self->spritesheet, self->current_frame);
|
||||
|
||||
Vector left_top = vinvf(transform_point(&transform, self->origin));
|
||||
printf("lt: %f %f\n", left_top.x, left_top.y);
|
||||
printf("or: %f %f\n", self->origin.x, self->origin.y);
|
||||
|
||||
SDL_FRect destination = (SDL_FRect) {
|
||||
left_top.x, left_top.y,
|
||||
|
|
@ -51,9 +53,32 @@ void sprite_draw(Sprite* self, Transform transform) {
|
|||
|
||||
destination = camera_world_to_screen_space(&g_camera, &destination);
|
||||
|
||||
Vector origin = vmulf(self->origin, vmulff(transform.scale, 0.5f));
|
||||
self->transform.rotation += 0.01f;
|
||||
Vector origin = vmulff(vmulf(self->origin, transform.scale), 0.5f);
|
||||
|
||||
SDL_RenderCopyExF(g_renderer, texture, &source, &destination,
|
||||
transform.rotation * 57.2958, &origin, SDL_FLIP_NONE);
|
||||
}
|
||||
|
||||
Vector sprite_get_origin(Sprite* self) {
|
||||
return self->origin;
|
||||
}
|
||||
|
||||
void sprite_set_origin(Sprite* self, Vector origin) {
|
||||
self->origin = origin;
|
||||
}
|
||||
|
||||
size_t sprite_get_frame(const Sprite* self) {
|
||||
return self->current_frame;
|
||||
}
|
||||
|
||||
void sprite_set_frame(Sprite* self, size_t size) {
|
||||
self->current_frame = size;
|
||||
}
|
||||
|
||||
Spritesheet* sprite_get_spritesheet(const Sprite* self) {
|
||||
return self->spritesheet;
|
||||
}
|
||||
|
||||
void sprite_set_spritesheet(Sprite* self, Spritesheet* spritesheet) {
|
||||
self->spritesheet = spritesheet;
|
||||
}
|
||||
16
src/sprite.h
16
src/sprite.h
|
|
@ -10,16 +10,18 @@
|
|||
// Forward declaration of the private sprite struct
|
||||
typedef struct Sprite Sprite;
|
||||
|
||||
extern Sprite* sprite_from_spritesheet(Spritesheet* sheet);
|
||||
extern Sprite* sprite_from_spritesheet(Spritesheet* sheet, size_t initial_frame);
|
||||
extern void sprite_destroy(Sprite* sprite);
|
||||
|
||||
extern void sprite_draw(Sprite* self, Transform transform);
|
||||
|
||||
extern void sprite_set_position(Sprite* self, Vector position);
|
||||
extern void sprite_translate(Sprite* self, Vector delta_position);
|
||||
extern void sprite_set_scale(Sprite* self, Vector scale);
|
||||
extern void sprite_scale(Sprite* self, Vector scale);
|
||||
extern void sprite_set_rotation(Sprite* self, float rotation);
|
||||
extern void sprite_rotate(Sprite* self, float delta_rotation);
|
||||
extern Vector sprite_get_origin(Sprite* self);
|
||||
extern void sprite_set_origin(Sprite* self, Vector origin);
|
||||
|
||||
extern size_t sprite_get_frame(const Sprite* self);
|
||||
extern void sprite_set_frame(Sprite* self, size_t size);
|
||||
|
||||
extern Spritesheet* sprite_get_spritesheet(const Sprite* self);
|
||||
extern void sprite_set_spritesheet(Sprite* self, Spritesheet* spritesheet);
|
||||
|
||||
#endif // !_fencer_sprite_h
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
#include <SDL2/SDL_image.h>
|
||||
|
||||
struct Spritesheet {
|
||||
asset_id asset_id;
|
||||
|
||||
// The texture to sample.
|
||||
SDL_Texture* texture;
|
||||
// The resolution of the texture.
|
||||
|
|
@ -28,17 +30,25 @@ void _spritesheet_destroy_asset(void* self_void) {
|
|||
free(self_void);
|
||||
}
|
||||
|
||||
Spritesheet* spritesheet_from_texture(const char* texture_name, IVector frame_resolution) {
|
||||
Spritesheet* spritesheet_load(const char* texture_name, IVector frame_resolution) {
|
||||
SDL_Texture* texture = IMG_LoadTexture(g_renderer, texture_name);
|
||||
if(texture == NULL)
|
||||
return NULL;
|
||||
else
|
||||
return spritesheet_from_texture(texture, frame_resolution);
|
||||
}
|
||||
|
||||
Spritesheet* spritesheet_from_texture(SDL_Texture* texture, IVector frame_resolution) {
|
||||
Spritesheet* self = malloc(sizeof(Spritesheet));
|
||||
|
||||
if(self == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
store_asset(self, _spritesheet_destroy_asset);
|
||||
self->asset_id = store_asset(self, _spritesheet_destroy_asset);
|
||||
|
||||
// Load the texture image and query it's size
|
||||
self->texture = IMG_LoadTexture(g_renderer, texture_name);
|
||||
self->texture = texture;
|
||||
SDL_QueryTexture(self->texture, NULL, NULL, &self->resolution.x, &self->resolution.y);
|
||||
|
||||
self->animation_type = ANIMTYPE_ONCE;
|
||||
|
|
@ -51,10 +61,7 @@ Spritesheet* spritesheet_from_texture(const char* texture_name, IVector frame_re
|
|||
}
|
||||
|
||||
void spritesheet_destroy(Spritesheet* self) {
|
||||
asset_id id = get_asset_id(self);
|
||||
if(id != 0) {
|
||||
free_asset(id);
|
||||
}
|
||||
free_asset(self->asset_id);
|
||||
}
|
||||
|
||||
SDL_Texture* spritesheet_get_texture(const Spritesheet* self) {
|
||||
|
|
@ -69,4 +76,8 @@ SDL_Rect spritesheet_get_frame_rect(const Spritesheet* self, size_t index) {
|
|||
tile_coord.x, tile_coord.y,
|
||||
self->frame_size.x, self->frame_size.y
|
||||
};
|
||||
}
|
||||
|
||||
IVector spritesheet_get_resolution(const Spritesheet* self) {
|
||||
return self->resolution;
|
||||
}
|
||||
|
|
@ -12,10 +12,12 @@ enum AnimationType {
|
|||
|
||||
typedef struct Spritesheet Spritesheet;
|
||||
|
||||
extern Spritesheet* spritesheet_from_texture(const char* texture_name, IVector frame_resolution);
|
||||
extern Spritesheet* spritesheet_load(const char* texture_name, IVector frame_resolution);
|
||||
extern Spritesheet* spritesheet_from_texture(SDL_Texture* texture, IVector frame_resolution);
|
||||
extern void spritesheet_destroy(Spritesheet* self);
|
||||
|
||||
extern SDL_Texture* spritesheet_get_texture(const Spritesheet* self);
|
||||
extern SDL_Rect spritesheet_get_frame_rect(const Spritesheet* self, size_t index);
|
||||
extern IVector spritesheet_get_resolution(const Spritesheet* self);
|
||||
|
||||
#endif // !_fencer_spritesheet_h
|
||||
|
|
|
|||
205
src/tilemap.c
205
src/tilemap.c
|
|
@ -1,109 +1,114 @@
|
|||
#include "tilemap.h"
|
||||
#include "debug.h"
|
||||
#include "tileset.h"
|
||||
#include "assets.h"
|
||||
#include "camera.h"
|
||||
#include "program.h"
|
||||
#include <stdio.h>
|
||||
#include "level.h"
|
||||
#include "render.h"
|
||||
#include "transform.h"
|
||||
#include <SDL2/SDL_image.h>
|
||||
#include <cjson/cJSON.h>
|
||||
|
||||
#define XML(__str) (const xmlChar*)__str
|
||||
struct TileInstance {
|
||||
Transform transform;
|
||||
TileDef* tiledef;
|
||||
};
|
||||
|
||||
struct Tilemap tilemap_load(const char* tilemap_file) {
|
||||
struct Tilemap self = {
|
||||
.dimensions = {0,0},
|
||||
.tiles = NULL
|
||||
};
|
||||
|
||||
return self;
|
||||
}
|
||||
struct Tilemap {
|
||||
Transform transform;
|
||||
|
||||
static inline
|
||||
int is_path_char(int n) {
|
||||
return isalnum(n) || n == '/';
|
||||
}
|
||||
Tileset* set;
|
||||
|
||||
struct Tileset tileset_load(const char* filename) {
|
||||
char* texture_path = NULL;
|
||||
char* writer = NULL;
|
||||
char num_buffer[6];
|
||||
num_buffer[5] = '\0';
|
||||
int n = 0;
|
||||
struct Tileset self = {
|
||||
.texture = NULL,
|
||||
.tile_size = {0,0}
|
||||
};
|
||||
size_t len = 1;
|
||||
TileInstance* map;
|
||||
size_t map_num;
|
||||
IVector map_size;
|
||||
};
|
||||
|
||||
FILE* fs = fopen(filename, "r");
|
||||
|
||||
do {
|
||||
n = fgetc(fs);
|
||||
if(is_path_char(n)) {
|
||||
++len;
|
||||
}
|
||||
} while(n != ',');
|
||||
|
||||
texture_path = malloc(len * sizeof(char));
|
||||
|
||||
rewind(fs);
|
||||
writer = texture_path;
|
||||
|
||||
do {
|
||||
n = fgetc(fs);
|
||||
if(is_path_char(n)) {
|
||||
*writer = n;
|
||||
++writer;
|
||||
} else if(n == ',') {
|
||||
*writer = '\0';
|
||||
}
|
||||
} while(n != ',');
|
||||
|
||||
self.texture = IMG_LoadTexture(g_renderer, texture_path);
|
||||
SDL_QueryTexture(self.texture, NULL, NULL, &self.texture_resolution.x, &self.texture_resolution.y);
|
||||
self.shear = floor((float)self.texture_resolution.x / self.tile_size.x);
|
||||
|
||||
writer = num_buffer;
|
||||
|
||||
do {
|
||||
n = fgetc(fs);
|
||||
if(isdigit(n) || n == '-') {
|
||||
*writer= n;
|
||||
++writer;
|
||||
} else if(n == ',' || feof(fs) || n == '\n') {
|
||||
*writer = '\0';
|
||||
writer = num_buffer;
|
||||
if(self.tile_size.x == 0) {
|
||||
self.tile_size.x = atoi(num_buffer);
|
||||
} else {
|
||||
self.tile_size.y = atoi(num_buffer);
|
||||
}
|
||||
}
|
||||
} while(!feof(fs));
|
||||
|
||||
fclose(fs);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
SDL_Rect tileset_index_to_rect(struct Tileset* self, size_t index) {
|
||||
SDL_Rect rect = {0,0, self->tile_size.x, self->tile_size.y};
|
||||
rect.x = (index % self->shear) * rect.w;
|
||||
rect.y = (index / self->shear) * rect.h;
|
||||
return rect;
|
||||
}
|
||||
|
||||
void tilemap_render(struct Tilemap* self) {
|
||||
size_t num_tiles = self->dimensions.x * self->dimensions.y;
|
||||
|
||||
SDL_Rect source_rect = {0, 0, self->tileset.tile_size.x, self->tileset.tile_size.y};
|
||||
SDL_FRect world_dest_rect = { .w = 1, .h = 1 };
|
||||
|
||||
for(int i = 0; i < num_tiles; ++i) {
|
||||
source_rect = tileset_index_to_rect(&self->tileset, self->tiles[i]);
|
||||
|
||||
world_dest_rect.x = (i % self->dimensions.x) * world_dest_rect.w;
|
||||
world_dest_rect.y = (float)floor((float)i / self->dimensions.y) * world_dest_rect.h;
|
||||
|
||||
SDL_FRect camera_dest_rect = camera_world_to_screen_space(&g_camera, &world_dest_rect);
|
||||
|
||||
SDL_RenderCopyF(g_renderer, self->tileset.texture, &source_rect, &camera_dest_rect);
|
||||
Tilemap* tilemap_from_autolayer(cJSON* json) {
|
||||
cJSON* const tileset = cJSON_GetObjectItem(json, "__tilesetDefUid");
|
||||
if(tileset == NULL || !cJSON_IsNumber(tileset)) {
|
||||
// no tileset means we cannot point at tile definitions later
|
||||
LOG_ERROR("Could not find __tilesetDefUid while loading tilemap");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
cJSON* const tiles = cJSON_GetObjectItem(json, "autoLayerTiles");
|
||||
if(tiles == NULL || !cJSON_IsArray(tiles)) {
|
||||
LOG_ERROR("Could not find autoLayerTiles while loading tilemap");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cJSON* const wid = cJSON_GetObjectItem(json, "__cWid");
|
||||
cJSON* const hei = cJSON_GetObjectItem(json, "__cHei");
|
||||
if(wid == NULL || hei == NULL || !cJSON_IsNumber(wid) || !cJSON_IsNumber(hei)) {
|
||||
// no width and height mean we cannot allocate the tile map
|
||||
LOG_ERROR("Could not find __cWid or __cHei while loading tilemap");
|
||||
return NULL;
|
||||
}
|
||||
cJSON* const grid = cJSON_GetObjectItem(json, "__gridSize");
|
||||
if(grid == NULL || !cJSON_IsNumber(grid)) {
|
||||
// can't know how to scale sprites
|
||||
LOG_ERROR("Could not find __gridSize while loading tilemap");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// the minimum requirements for a valid tilemap are met
|
||||
|
||||
// allocate a tilemap and load the tileset
|
||||
Tilemap* self = malloc(sizeof(Tilemap));
|
||||
self->transform = IdentityTransform;
|
||||
self->set = tileset_load(tileset->valueint);
|
||||
self->map_size = (IVector) {
|
||||
.x = wid->valueint,
|
||||
.y = hei->valueint
|
||||
};
|
||||
|
||||
self->map_num = json_array_len(tiles);
|
||||
|
||||
self->map = malloc(self->map_num * sizeof(TileInstance));
|
||||
if(self->map == NULL) {
|
||||
tileset_destroy(self->set);
|
||||
free(self);
|
||||
LOG_ERROR("Failed to allocate map memory");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const double px_to_ws = 1.0 / grid->valuedouble;
|
||||
|
||||
cJSON* tile; TileInstance* writer = self->map;
|
||||
cJSON_ArrayForEach(tile, tiles) {
|
||||
cJSON* t = cJSON_GetObjectItem(tile, "t");
|
||||
writer->tiledef = tileset_get_tiledef(self->set, t->valueint);
|
||||
writer->transform = IdentityTransform;
|
||||
cJSON* px = cJSON_GetObjectItem(tile, "px");
|
||||
writer->transform.position = vmulff(json_array_to_vector(px), px_to_ws);
|
||||
// sprite_get_spritesheet(tiledef_get_sprite(writer->tiledef));
|
||||
|
||||
// LOG_INFO("Loading tile");
|
||||
// LOG_INFO("tid = %d", t->valueint);
|
||||
// LOG_INFO("transform =");
|
||||
// LOG_INFO(".position = %f %f", writer->transform.position.x, writer->transform.position.y);
|
||||
// LOG_INFO(".scale = %f %f", writer->transform.scale.x, writer->transform.scale.y);
|
||||
++writer;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
void tilemap_draw(Tilemap* self) {
|
||||
Transform tiletrans = self->transform;
|
||||
TileInstance* tile;
|
||||
|
||||
for(int i = 0; i < self->map_num; ++i) {
|
||||
tile = self->map + i;
|
||||
tiletrans = transform_apply(self->transform, tile->transform);
|
||||
Sprite* sprite = tiledef_get_sprite(tile->tiledef);
|
||||
// LOG_INFO("sprite: %p", sprite);
|
||||
// LOG_INFO("trans: %f %f ; %f %f ; %f",
|
||||
// tiletrans.position.x, tiletrans.position.y, tiletrans.scale.x, tiletrans.scale.y, tiletrans.rotation);
|
||||
// LOG_INFO("self->transform: %f %f ; %f %f ; %f",
|
||||
// self->transform.position.x, self->transform.position.y, self->transform.scale.x, self->transform.scale.y, self->transform.rotation);
|
||||
if(sprite != NULL)
|
||||
sprite_draw(sprite, tiletrans);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,19 @@
|
|||
#ifndef _fencer_tilemap_h
|
||||
#define _fencer_tilemap_h
|
||||
|
||||
#include "vmath.h"
|
||||
#include "render.h"
|
||||
#include <SDL2/SDL.h>
|
||||
#include "spritesheet.h"
|
||||
#include "tileset.h"
|
||||
#include "level.h"
|
||||
|
||||
struct Tileset {
|
||||
SDL_Texture* texture;
|
||||
IVector tile_size;
|
||||
IVector texture_resolution;
|
||||
int shear;
|
||||
};
|
||||
typedef struct TileInstance TileInstance;
|
||||
typedef struct Tilemap Tilemap;
|
||||
|
||||
struct Tilemap {
|
||||
IVector dimensions;
|
||||
int* tiles;
|
||||
struct Tileset tileset;
|
||||
};
|
||||
|
||||
extern struct Tilemap tilemap_load(const char* tilemap_file);
|
||||
extern struct Tileset tileset_load(const char* filename);
|
||||
extern SDL_Rect tileset_index_to_rect(struct Tileset* self, size_t index);
|
||||
extern Tilemap* tilemap_from_autolayer(cJSON* json);
|
||||
extern void tilemap_destroy(Tilemap* self);
|
||||
|
||||
extern void tilemap_render(struct Tilemap* map);
|
||||
extern void tilemap_set_tileset(Tilemap* self, Tileset* set);
|
||||
|
||||
extern void tilemap_draw(Tilemap* tilemap);
|
||||
|
||||
#endif // !_fencer_tilemap_h
|
||||
|
|
|
|||
184
src/tileset.c
Normal file
184
src/tileset.c
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
#include "tileset.h"
|
||||
#include "debug.h"
|
||||
#include "spritesheet.h"
|
||||
#include "assets.h"
|
||||
#include "render.h"
|
||||
#include "SDL2/SDL_image.h"
|
||||
|
||||
struct TileDef {
|
||||
size_t tid;
|
||||
Sprite* sprite;
|
||||
// Shape* collision;
|
||||
};
|
||||
|
||||
struct Tileset {
|
||||
size_t uid;
|
||||
|
||||
Spritesheet* atlas;
|
||||
|
||||
TileDef* tiledefs;
|
||||
size_t tiledefs_len;
|
||||
};
|
||||
|
||||
typedef struct TilesetStore {
|
||||
Tileset* set;
|
||||
size_t iid;
|
||||
} TilesetStore;
|
||||
|
||||
static TilesetStore* _tilesets = NULL;
|
||||
static size_t _tilesets_len = 0;
|
||||
static size_t _tilesets_cap = 0;
|
||||
|
||||
static
|
||||
int _tilesets_resize_if_needed(size_t required_cap) {
|
||||
if(required_cap < _tilesets_cap) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t needed = _tilesets_cap ? _tilesets_cap : 2;
|
||||
while(needed < required_cap) {
|
||||
needed *= 2;
|
||||
}
|
||||
|
||||
TilesetStore* new = realloc(_tilesets, needed * sizeof(TilesetStore));
|
||||
if(new == NULL) {
|
||||
LOG_ERROR("Failed to resize tileset store array.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
_tilesets_cap = needed;
|
||||
_tilesets = new;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static
|
||||
void _tileset_deallocate(void* self_void) {
|
||||
Tileset* self = self_void;
|
||||
TilesetStore* store = _tilesets;
|
||||
for(; store < _tilesets + _tilesets_len; ++store) {
|
||||
if(store->set == self) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
size_t diff = _tilesets_len - (size_t)(store - _tilesets);
|
||||
memmove(store, store+1, diff-1);
|
||||
--_tilesets_len;
|
||||
|
||||
spritesheet_destroy(self->atlas);
|
||||
free(self->tiledefs);
|
||||
}
|
||||
|
||||
static
|
||||
void _tileset_store(Tileset* self) {
|
||||
_tilesets_resize_if_needed(_tilesets_len + 1);
|
||||
_tilesets[_tilesets_len].set = self;
|
||||
_tilesets[_tilesets_len].iid = self->uid;
|
||||
}
|
||||
|
||||
Tileset* tileset_from_json(cJSON* json) {
|
||||
cJSON* uid = cJSON_GetObjectItem(json, "uid");
|
||||
if(uid == NULL || !cJSON_IsNumber(uid)) {
|
||||
LOG_ERROR("Failed to find uid while loading tileset");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cJSON* path = cJSON_GetObjectItem(json, "relPath");
|
||||
if(path == NULL || !cJSON_IsString(path)) {
|
||||
LOG_ERROR("Failed to find relPath while loading tileset");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cJSON* gridsize = cJSON_GetObjectItem(json, "tileGridSize");
|
||||
if(gridsize == NULL || !cJSON_IsNumber(gridsize)) {
|
||||
LOG_ERROR("Failed to find tileGridSize while loading tileset");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_Surface* atlas_surface = IMG_Load(path->valuestring);
|
||||
if(atlas_surface == NULL) {
|
||||
LOG_ERROR("Failed to load atlas image while loading tileset");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Tileset* self = malloc(sizeof(Tileset));
|
||||
SDL_Texture* texture = SDL_CreateTextureFromSurface(g_renderer, atlas_surface);
|
||||
IVector grid = {gridsize->valueint, gridsize->valueint};
|
||||
|
||||
if(self == NULL) {
|
||||
LOG_ERROR("Failed to allocate space for Tileset");
|
||||
return NULL;
|
||||
}
|
||||
_tileset_store(self);
|
||||
*self = (Tileset) {
|
||||
.uid = uid->valueint,
|
||||
.atlas = spritesheet_from_texture(texture, grid),
|
||||
};
|
||||
|
||||
IVector resolution = spritesheet_get_resolution(self->atlas);
|
||||
self->tiledefs_len = resolution.x / grid.x * resolution.y / grid.y;
|
||||
self->tiledefs = malloc(self->tiledefs_len * sizeof(TileDef));
|
||||
|
||||
LOG_INFO("tiledefs_len: %zu", self->tiledefs_len);
|
||||
LOG_INFO("resolution: %d, %d", resolution.x, resolution.y);
|
||||
LOG_INFO("grid: %d, %d", grid.x, grid.y);
|
||||
|
||||
for(size_t tid = 0; tid < self->tiledefs_len; ++tid) {
|
||||
self->tiledefs[tid] = (TileDef) {
|
||||
.tid = tid,
|
||||
.sprite = sprite_from_spritesheet(self->atlas, tid)
|
||||
};
|
||||
// TODO: generate/read collision information
|
||||
}
|
||||
|
||||
SDL_FreeSurface(atlas_surface);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
Tileset* tileset_load(size_t uid) {
|
||||
for(TilesetStore* store = _tilesets; store < _tilesets + _tilesets_len; ++store) {
|
||||
if(store->iid == uid) {
|
||||
return store->set;
|
||||
}
|
||||
}
|
||||
|
||||
cJSON* defs = cJSON_GetObjectItem(g_levels_json, "defs");
|
||||
if(defs == NULL) {
|
||||
LOG_ERROR("Failed to find defs element in levels json");
|
||||
return NULL;
|
||||
}
|
||||
cJSON* tilesets = cJSON_GetObjectItem(defs, "tilesets");
|
||||
if(tilesets == NULL) {
|
||||
LOG_ERROR("Failed to find tilesets def region in levels json");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cJSON* tileset = NULL;
|
||||
cJSON_ArrayForEach(tileset, tilesets) {
|
||||
cJSON* tileset_uid = cJSON_GetObjectItem(tileset, "uid");
|
||||
if(tileset_uid == NULL) continue;
|
||||
LOG_INFO("finding uid: %d", tileset_uid->valueint);
|
||||
if(uid == tileset_uid->valueint) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(tileset == NULL) {
|
||||
LOG_ERROR("Failed to find a tileset with the uid matching %zu", uid);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return tileset_from_json(tileset);
|
||||
}
|
||||
|
||||
void tileset_destroy(Tileset* self) {
|
||||
_tileset_deallocate(self);
|
||||
}
|
||||
|
||||
TileDef* tileset_get_tiledef(Tileset* self, size_t t) {
|
||||
return &self->tiledefs[t];
|
||||
}
|
||||
|
||||
Sprite* tiledef_get_sprite(const TileDef* self) {
|
||||
return self->sprite;
|
||||
}
|
||||
17
src/tileset.h
Normal file
17
src/tileset.h
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#ifndef _fencer_tileset_h
|
||||
#define _fencer_tileset_h
|
||||
|
||||
#include "cjson/cJSON.h"
|
||||
#include "sprite.h"
|
||||
|
||||
typedef struct TileDef TileDef;
|
||||
typedef struct Tileset Tileset;
|
||||
|
||||
extern Tileset* tileset_from_json(cJSON* json);
|
||||
extern Tileset* tileset_load(size_t uid);
|
||||
extern TileDef* tileset_get_tiledef(Tileset* self, size_t t);
|
||||
extern void tileset_destroy(Tileset* self);
|
||||
|
||||
extern Sprite* tiledef_get_sprite(const TileDef* self);
|
||||
|
||||
#endif // !_fencer_tileset_h
|
||||
|
|
@ -12,12 +12,17 @@ struct Transform {
|
|||
|
||||
#define IdentityTransform (Transform){ZeroVector, OneVector, 0.0f}
|
||||
|
||||
static inline Transform transform_apply(Transform parent, Transform child);
|
||||
static inline Transform transform_invert(Transform a);
|
||||
static inline Vector transform_direction(Transform* self, Vector direction);
|
||||
static inline Vector transform_point(Transform* self, Vector point);
|
||||
|
||||
static inline
|
||||
Transform transform_apply(Transform a, Transform b) {
|
||||
Transform transform_apply(Transform parent, Transform child) {
|
||||
return (Transform) {
|
||||
.position = vaddf(a.position, b.position),
|
||||
.scale = vmulf(a.scale, b.scale),
|
||||
.rotation = a.rotation + b.rotation
|
||||
.position = transform_point(&parent, child.position),
|
||||
.scale = vmulf(parent.scale, child.scale),
|
||||
.rotation = parent.rotation + child.rotation
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue