Compare commits

...

3 Commits

Author SHA1 Message Date
En Yi 0c540d5053 Move frame counter out of sprite component
This allows individual animation
2023-11-04 20:32:29 +08:00
En Yi 97f7afc401 Add more particle effects
Changelog:
- Add a dedicated tile destroy function
    Similar reason to destroy entity
- Add more sprites for particles
2023-11-04 17:05:54 +08:00
En Yi ebecc68941 Add better support for particle system
Changelog:
- Move sprite to emitter itself to allow better reusing emitter config
- Dedicate a function for destroying entity. This is helpful for dealing
  with events that only occurs during entity destruction
2023-11-04 14:36:59 +08:00
12 changed files with 138 additions and 56 deletions

View File

@ -31,6 +31,7 @@ int main(void)
add_font(&assets, "testfont", "res/test_font.ttf"); add_font(&assets, "testfont", "res/test_font.ttf");
Font* fnt = get_font(&assets, "testfont"); Font* fnt = get_font(&assets, "testfont");
int current_frame = 0;
while(!WindowShouldClose()) while(!WindowShouldClose())
{ {
if (IsKeyReleased(KEY_C)) if (IsKeyReleased(KEY_C))
@ -43,16 +44,16 @@ int main(void)
DrawTextEx(*fnt, "Press C to play a sound", (Vector2){64, 64}, 24, 1, RED); DrawTextEx(*fnt, "Press C to play a sound", (Vector2){64, 64}, 24, 1, RED);
// Draw the static Sprite and animated Sprite // Draw the static Sprite and animated Sprite
draw_sprite(spr, (Vector2){64,128}, 0.0f, false); draw_sprite(spr, 0, (Vector2){64,128}, 0.0f, false);
draw_sprite(spr2, (Vector2){64,180}, 0.0f, true); draw_sprite(spr2, current_frame, (Vector2){64,180}, 0.0f, true);
EndDrawing(); EndDrawing();
// Update the animated Sprite // Update the animated Sprite
spr2->elapsed++; spr2->elapsed++;
if (spr2->elapsed == spr2->speed) if (spr2->elapsed == spr2->speed)
{ {
spr2->current_frame++; current_frame++;
spr2->current_frame %= spr2->frame_count; current_frame %= spr2->frame_count;
spr2->elapsed = 0; spr2->elapsed = 0;
} }
} }

View File

@ -185,7 +185,6 @@ typedef struct Sprite {
Vector2 origin; Vector2 origin;
Vector2 anchor; Vector2 anchor;
int frame_count; int frame_count;
int current_frame;
int elapsed; int elapsed;
int speed; int speed;
char* name; char* name;
@ -205,6 +204,7 @@ typedef struct _CSprite_t {
bool flip_x; bool flip_x;
bool flip_y; bool flip_y;
bool pause; bool pause;
int current_frame;
} CSprite_t; } CSprite_t;
typedef struct _CMoveable_t { typedef struct _CMoveable_t {
@ -229,6 +229,7 @@ struct Entity {
unsigned long components[N_COMPONENTS]; unsigned long components[N_COMPONENTS];
EntityManager_t* manager; EntityManager_t* manager;
bool m_alive; bool m_alive;
bool m_active;
}; };
enum EntityUpdateEvent enum EntityUpdateEvent

View File

@ -147,7 +147,6 @@ EmitterConfig_t* add_emitter_conf(Assets_t* assets, const char* name, Sprite_t*
uint8_t emitter_idx = n_loaded[5]; uint8_t emitter_idx = n_loaded[5];
assert(emitter_idx < MAX_EMITTER_CONF); assert(emitter_idx < MAX_EMITTER_CONF);
memset(emitter_confs + emitter_idx, 0, sizeof(EmitterConfData_t)); memset(emitter_confs + emitter_idx, 0, sizeof(EmitterConfData_t));
emitter_confs[emitter_idx].conf.spr = sprite;
strncpy(emitter_confs[emitter_idx].name, name, MAX_NAME_LEN); strncpy(emitter_confs[emitter_idx].name, name, MAX_NAME_LEN);
sc_map_put_s64(&assets->m_emitter_confs, emitter_confs[emitter_idx].name, emitter_idx); sc_map_put_s64(&assets->m_emitter_confs, emitter_confs[emitter_idx].name, emitter_idx);
n_loaded[5]++; n_loaded[5]++;
@ -462,10 +461,10 @@ LevelPack_t* get_level_pack(Assets_t* assets, const char* name)
return NULL; return NULL;
} }
void draw_sprite(Sprite_t* spr, Vector2 pos, float rotation, bool flip_x) void draw_sprite(Sprite_t* spr, int frame_num, Vector2 pos, float rotation, bool flip_x)
{ {
Rectangle rec = { Rectangle rec = {
spr->origin.x + spr->frame_size.x * spr->current_frame, spr->origin.x + spr->frame_size.x * frame_num,
spr->origin.y, spr->origin.y,
spr->frame_size.x * (flip_x ? -1:1), spr->frame_size.x * (flip_x ? -1:1),
spr->frame_size.y spr->frame_size.y

View File

@ -68,7 +68,7 @@ Sound* get_sound(Assets_t* assets, const char* name);
Font* get_font(Assets_t* assets, const char* name); Font* get_font(Assets_t* assets, const char* name);
LevelPack_t* get_level_pack(Assets_t* assets, const char* name); LevelPack_t* get_level_pack(Assets_t* assets, const char* name);
void draw_sprite(Sprite_t* spr, Vector2 pos, float rotation, bool flip_x); void draw_sprite(Sprite_t* spr, int frame_num, Vector2 pos, float rotation, bool flip_x);
typedef struct SFX typedef struct SFX
{ {

View File

@ -121,7 +121,7 @@ void draw_particle_system(ParticleSystem_t* system)
{ {
if (part->alive) if (part->alive)
{ {
if (emitter->config->spr == NULL) if (emitter->spr == NULL)
{ {
Rectangle rect = { Rectangle rect = {
.x = part->position.x, .x = part->position.x,
@ -137,7 +137,7 @@ void draw_particle_system(ParticleSystem_t* system)
} }
else else
{ {
draw_sprite(emitter->config->spr, part->position, part->rotation, false); draw_sprite(emitter->spr, 0, part->position, part->rotation, false);
} }
} }
} }

View File

@ -32,13 +32,13 @@ typedef struct EmitterConfig
float speed_range[2]; float speed_range[2];
uint32_t particle_lifetime[2]; uint32_t particle_lifetime[2];
PartEmitterType_t type; PartEmitterType_t type;
Sprite_t* spr;
bool one_shot; bool one_shot;
}EmitterConfig_t; }EmitterConfig_t;
typedef struct ParticleEmitter typedef struct ParticleEmitter
{ {
const EmitterConfig_t* config; const EmitterConfig_t* config;
Sprite_t* spr;
Vector2 position; Vector2 position;
Particle_t particles[MAX_PARTICLES]; Particle_t particles[MAX_PARTICLES];
uint32_t n_particles; uint32_t n_particles;

View File

@ -58,7 +58,6 @@ int main(void)
.origin = (Vector2){0, 0}, .origin = (Vector2){0, 0},
.anchor = (Vector2){tex.width / 2, tex.height / 2}, .anchor = (Vector2){tex.width / 2, tex.height / 2},
.frame_count = 0, .frame_count = 0,
.current_frame = 0,
.elapsed = 0, .elapsed = 0,
.speed = 0, .speed = 0,
.name = "test_spr" .name = "test_spr"
@ -69,13 +68,13 @@ int main(void)
.launch_range = {0, 360}, .launch_range = {0, 360},
.speed_range = {400, 2000}, .speed_range = {400, 2000},
.particle_lifetime = {30, 110}, .particle_lifetime = {30, 110},
.spr = (tex.width == 0) ? NULL : &spr,
}; };
ParticleEmitter_t emitter = { ParticleEmitter_t emitter = {
.config = &conf, .config = &conf,
.n_particles = MAX_PARTICLES, .n_particles = MAX_PARTICLES,
.update_func = &simple_particle_system_update .update_func = &simple_particle_system_update,
.spr = (tex.width == 0) ? NULL : &spr,
}; };
bool key_press = false; bool key_press = false;

View File

@ -68,7 +68,7 @@ int main(void)
scenes[0] = &scene.scene; scenes[0] = &scene.scene;
change_scene(&engine, 0); change_scene(&engine, 0);
EmitterConfig_t* conf = add_emitter_conf(&engine.assets, "pe_wood", get_sprite(&engine.assets, "bomb")); EmitterConfig_t* conf = add_emitter_conf(&engine.assets, "pe_burst", get_sprite(&engine.assets, "bomb"));
conf->launch_range[0] = 240; conf->launch_range[0] = 240;
conf->launch_range[1] = 300; conf->launch_range[1] = 300;
conf->one_shot = true; conf->one_shot = true;

View File

@ -225,7 +225,7 @@ static void render_editor_game_scene(Scene_t* scene)
uint8_t tile_sprite_idx = tilemap.tiles[i].tile_type + tilemap.tiles[i].rotation; uint8_t tile_sprite_idx = tilemap.tiles[i].tile_type + tilemap.tiles[i].rotation;
if (data->tile_sprites[tile_sprite_idx] != NULL) if (data->tile_sprites[tile_sprite_idx] != NULL)
{ {
draw_sprite(data->tile_sprites[tile_sprite_idx], (Vector2){x,y}, 0.0f, false); draw_sprite(data->tile_sprites[tile_sprite_idx], 0, (Vector2){x,y}, 0.0f, false);
} }
else if (tilemap.tiles[i].tile_type == SOLID_TILE) else if (tilemap.tiles[i].tile_type == SOLID_TILE)
{ {
@ -429,7 +429,7 @@ static void render_editor_game_scene(Scene_t* scene)
if (spr.sprite != NULL) if (spr.sprite != NULL)
{ {
Vector2 pos = Vector2Add(p_ct->position, spr.offset); Vector2 pos = Vector2Add(p_ct->position, spr.offset);
draw_sprite(spr.sprite, pos, 0.0f, p_cspr->flip_x); draw_sprite(spr.sprite, p_cspr->current_frame, pos, 0.0f, p_cspr->flip_x);
} }
} }
} }

View File

@ -131,7 +131,7 @@ static void render_regular_game_scene(Scene_t* scene)
if (data->tile_sprites[tilemap.tiles[i].tile_type] != NULL) if (data->tile_sprites[tilemap.tiles[i].tile_type] != NULL)
{ {
draw_sprite(data->tile_sprites[tilemap.tiles[i].tile_type], (Vector2){x,y}, 0.0f, false); draw_sprite(data->tile_sprites[tilemap.tiles[i].tile_type], 0, (Vector2){x,y}, 0.0f, false);
} }
else if (tilemap.tiles[i].tile_type == SOLID_TILE) else if (tilemap.tiles[i].tile_type == SOLID_TILE)
{ {
@ -223,7 +223,7 @@ static void render_regular_game_scene(Scene_t* scene)
if (spr.sprite != NULL) if (spr.sprite != NULL)
{ {
Vector2 pos = Vector2Add(p_ct->position, spr.offset); Vector2 pos = Vector2Add(p_ct->position, spr.offset);
draw_sprite(spr.sprite, pos, 0.0f, p_cspr->flip_x); draw_sprite(spr.sprite, p_cspr->current_frame, pos, 0.0f, p_cspr->flip_x);
} }
continue; continue;
} }

View File

@ -29,6 +29,50 @@ static inline unsigned int get_tile_idx(int x, int y, unsigned int tilemap_width
return tile_y * tilemap_width + tile_x; return tile_y * tilemap_width + tile_x;
} }
static inline void destroy_tile(LevelSceneData_t* lvl_data, unsigned int tile_idx)
{
Scene_t* scene = &CONTAINER_OF(lvl_data, LevelScene_t, data)->scene;
TileGrid_t tilemap = lvl_data->tilemap;
Sprite_t* spr = NULL;
switch (tilemap.tiles[tile_idx].tile_type)
{
case LADDER:
spr = get_sprite(&scene->engine->assets, "p_ladder");
break;
case ONEWAY_TILE:
spr = get_sprite(&scene->engine->assets, "p_wood");
break;
case SPIKES:
spr = get_sprite(&scene->engine->assets, "p_spike");
break;
default:
break;
}
if (spr != NULL)
{
ParticleEmitter_t emitter = {
.spr = spr,
.config = get_emitter_conf(&scene->engine->assets, "pe_burst"),
.position = {
.x = tile_idx % tilemap.width * tilemap.tile_size + tilemap.tile_size / 2,
.y = tile_idx / tilemap.width * tilemap.tile_size + tilemap.tile_size / 2,
},
.n_particles = 5,
.user_data = lvl_data,
.update_func = &simple_particle_system_update,
};
play_particle_emitter(&scene->part_sys, &emitter);
}
tilemap.tiles[tile_idx].tile_type = EMPTY_TILE;
tilemap.tiles[tile_idx].solid = NOT_SOLID;
tilemap.tiles[tile_idx].moveable = true;
}
// ------------------------- Collision functions ------------------------------------ // ------------------------- Collision functions ------------------------------------
// Do not subtract one for the size for any collision check, just pass normally. The extra one is important for AABB test // Do not subtract one for the size for any collision check, just pass normally. The extra one is important for AABB test
@ -194,6 +238,53 @@ static Vector2 shift_bbox(Vector2 bbox, Vector2 new_bbox, AnchorPoint_t anchor)
return offset; return offset;
} }
void destroy_entity(Scene_t* scene, TileGrid_t* tilemap, Entity_t* p_ent)
{
if (p_ent->m_tag == BOULDER_ENT_TAG)
{
const CTransform_t* p_ctransform = get_component(p_ent, CTRANSFORM_COMP_T);
//const CBBox_t* p_bbox = get_component(p_ent, CBBOX_COMP_T);
ParticleEmitter_t emitter = {
.spr = get_sprite(&scene->engine->assets, "p_rock"),
.config = get_emitter_conf(&scene->engine->assets, "pe_burst"),
.position = p_ctransform->position,
.n_particles = 5,
.user_data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data),
.update_func = &simple_particle_system_update,
};
play_particle_emitter(&scene->part_sys, &emitter);
}
else if (p_ent->m_tag == CRATES_ENT_TAG)
{
const CContainer_t* p_container = get_component(p_ent, CCONTAINER_T);
const CTransform_t* p_ctransform = get_component(p_ent, CTRANSFORM_COMP_T);
//const CBBox_t* p_bbox = get_component(p_ent, CBBOX_COMP_T);
ParticleEmitter_t emitter = {
.spr = get_sprite(&scene->engine->assets, (p_container->material == WOODEN_CONTAINER) ? "p_wood" : "p_metal"),
.config = get_emitter_conf(&scene->engine->assets, "pe_burst"),
.position = p_ctransform->position,
.n_particles = 5,
.user_data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data),
.update_func = &simple_particle_system_update,
};
play_particle_emitter(&scene->part_sys, &emitter);
}
else if (p_ent->m_tag == CHEST_ENT_TAG)
{
const CTransform_t* p_ctransform = get_component(p_ent, CTRANSFORM_COMP_T);
ParticleEmitter_t emitter = {
.spr = get_sprite(&scene->engine->assets, "p_wood"),
.config = get_emitter_conf(&scene->engine->assets, "pe_burst"),
.position = p_ctransform->position,
.n_particles = 5,
.user_data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data),
.update_func = &simple_particle_system_update,
};
play_particle_emitter(&scene->part_sys, &emitter);
}
remove_entity_from_tilemap(&scene->ent_manager, tilemap, p_ent);
}
void player_respawn_system(Scene_t* scene) void player_respawn_system(Scene_t* scene)
{ {
Entity_t* p_player; Entity_t* p_player;
@ -605,7 +696,7 @@ void spike_collision_system(Scene_t* scene)
} }
else else
{ {
tilemap.tiles[tile_idx].tile_type = EMPTY_TILE; destroy_tile(data, tile_idx);
} }
} }
} }
@ -1219,7 +1310,7 @@ void movement_update_system(Scene_t* scene)
|| p_ctransform->position.y < 0 || p_ctransform->position.y > level_height || p_ctransform->position.y < 0 || p_ctransform->position.y > level_height
) )
{ {
remove_entity_from_tilemap(&scene->ent_manager, &tilemap, p_ent); destroy_entity(scene, &tilemap, p_ent);
} }
} }
@ -1366,6 +1457,15 @@ void state_transition_update_system(Scene_t* scene)
if (p_mstate->water_state == 0b01) if (p_mstate->water_state == 0b01)
{ {
play_sfx(scene->engine, WATER_IN_SFX); play_sfx(scene->engine, WATER_IN_SFX);
ParticleEmitter_t emitter = {
.spr = get_sprite(&scene->engine->assets, "p_water"),
.config = get_emitter_conf(&scene->engine->assets, "pe_burst"),
.position = p_ctransform->position,
.n_particles = 5,
.user_data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data),
.update_func = &simple_particle_system_update,
};
play_particle_emitter(&scene->part_sys, &emitter);
} }
} }
} }
@ -1475,7 +1575,7 @@ void hitbox_update_system(Scene_t* scene)
hit = true; hit = true;
if (p_hitbox->atk > tilemap.tiles[tile_idx].def) if (p_hitbox->atk > tilemap.tiles[tile_idx].def)
{ {
change_a_tile(&tilemap, tile_idx, EMPTY_TILE); destroy_tile(data, tile_idx);
continue; continue;
} }
} }
@ -1555,13 +1655,13 @@ void hitbox_update_system(Scene_t* scene)
{ {
remove_component(p_other_ent, CHURTBOX_T); remove_component(p_other_ent, CHURTBOX_T);
CLifeTimer_t* p_clifetimer = add_component(p_other_ent, CLIFETIMER_T); CLifeTimer_t* p_clifetimer = add_component(p_other_ent, CLIFETIMER_T);
p_clifetimer->life_time = 6; p_clifetimer->life_time = 3;
} }
} }
else else
{ {
// Need to remove immediately, otherwise will interfere with bomb spawning // Need to remove immediately, otherwise will interfere with bomb spawning
remove_entity_from_tilemap(&scene->ent_manager, &tilemap, p_other_ent); destroy_entity(scene, &tilemap, p_other_ent);
if (p_other_ent->m_tag == CHEST_ENT_TAG) if (p_other_ent->m_tag == CHEST_ENT_TAG)
{ {
data->coins.current++; data->coins.current++;
@ -1576,11 +1676,12 @@ void hitbox_update_system(Scene_t* scene)
} }
if (p_hitbox->one_hit && hit) if (p_hitbox->one_hit && hit)
{ {
remove_entity_from_tilemap(&scene->ent_manager, &tilemap, p_ent); destroy_entity(scene, &tilemap, p_ent);
} }
} }
} }
void boulder_destroy_wooden_tile_system(Scene_t* scene) void boulder_destroy_wooden_tile_system(Scene_t* scene)
{ {
LevelSceneData_t* data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data); LevelSceneData_t* data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data);
@ -1598,24 +1699,19 @@ void boulder_destroy_wooden_tile_system(Scene_t* scene)
p_ctransform->position.y + p_bbox->size.y, p_ctransform->position.y + p_bbox->size.y,
tilemap.width tilemap.width
); );
unsigned int tile_x = (p_ctransform->position.x + p_bbox->half_size.x) / TILE_SIZE; unsigned int tile_x = (p_ctransform->position.x + p_bbox->half_size.x) / tilemap.tile_size;
if (tilemap.tiles[tile_idx].tile_type == ONEWAY_TILE) if (tilemap.tiles[tile_idx].tile_type == ONEWAY_TILE)
{ {
tilemap.tiles[tile_idx].tile_type = EMPTY_TILE; destroy_tile(data, tile_idx);
tilemap.tiles[tile_idx].solid = NOT_SOLID;
tilemap.tiles[tile_idx].moveable = true;
if (tile_x > 0 && tilemap.tiles[tile_idx - 1].tile_type == ONEWAY_TILE) if (tile_x > 0 && tilemap.tiles[tile_idx - 1].tile_type == ONEWAY_TILE)
{ {
tilemap.tiles[tile_idx - 1].tile_type = EMPTY_TILE; destroy_tile(data, tile_idx - 1);
tilemap.tiles[tile_idx - 1].solid = NOT_SOLID;
tilemap.tiles[tile_idx - 1].moveable = true;
} }
if (tile_x < tilemap.width && tilemap.tiles[tile_idx + 1].tile_type == ONEWAY_TILE) if (tile_x < tilemap.width && tilemap.tiles[tile_idx + 1].tile_type == ONEWAY_TILE)
{ {
tilemap.tiles[tile_idx + 1].tile_type = EMPTY_TILE; destroy_tile(data, tile_idx + 1);
tilemap.tiles[tile_idx + 1].solid = NOT_SOLID;
tilemap.tiles[tile_idx + 1].moveable = true;
} }
} }
} }
@ -1630,20 +1726,6 @@ void container_destroy_system(Scene_t* scene)
Entity_t* p_ent = get_entity(&scene->ent_manager, ent_idx); Entity_t* p_ent = get_entity(&scene->ent_manager, ent_idx);
if (!p_ent->m_alive) if (!p_ent->m_alive)
{ {
if (p_ent->m_tag == CRATES_ENT_TAG)
{
const CTransform_t* p_ctransform = get_component(p_ent, CTRANSFORM_COMP_T);
//const CBBox_t* p_bbox = get_component(p_ent, CBBOX_COMP_T);
ParticleEmitter_t emitter = {
.config = get_emitter_conf(&scene->engine->assets, "pe_wood"),
.position = p_ctransform->position,
.n_particles = 3,
.user_data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data),
.update_func = &simple_particle_system_update,
};
play_particle_emitter(&scene->part_sys, &emitter);
}
Entity_t* dmg_src = NULL; Entity_t* dmg_src = NULL;
CHurtbox_t* p_hurtbox = get_component(p_ent, CHURTBOX_T); CHurtbox_t* p_hurtbox = get_component(p_ent, CHURTBOX_T);
if(p_hurtbox != NULL) if(p_hurtbox != NULL)
@ -1717,7 +1799,7 @@ void lifetimer_update_system(Scene_t* scene)
p_lifetimer->life_time--; p_lifetimer->life_time--;
if (p_lifetimer->life_time == 0) if (p_lifetimer->life_time == 0)
{ {
remove_entity_from_tilemap(&scene->ent_manager, &tilemap, get_entity(&scene->ent_manager, ent_idx)); destroy_entity(scene, &tilemap, get_entity(&scene->ent_manager, ent_idx));
} }
} }
} }
@ -1786,7 +1868,7 @@ void airtimer_update_system(Scene_t* scene)
} }
else else
{ {
remove_entity_from_tilemap(&scene->ent_manager, &tilemap, get_entity(&scene->ent_manager, ent_idx)); destroy_entity(scene, &tilemap, get_entity(&scene->ent_manager, ent_idx));
} }
} }
@ -1816,14 +1898,14 @@ void sprite_animation_system(Scene_t* scene)
SpriteRenderInfo_t spr = p_cspr->sprites[p_cspr->current_idx]; SpriteRenderInfo_t spr = p_cspr->sprites[p_cspr->current_idx];
if (spr.sprite == NULL) continue; if (spr.sprite == NULL) continue;
if (reset) spr.sprite->current_frame = 0; if (reset) p_cspr->current_frame = 0;
// Animate it (handle frame count) // Animate it (handle frame count)
spr.sprite->elapsed++; spr.sprite->elapsed++;
if (spr.sprite->elapsed == spr.sprite->speed) if (spr.sprite->elapsed == spr.sprite->speed)
{ {
spr.sprite->current_frame++; p_cspr->current_frame++;
spr.sprite->current_frame %= spr.sprite->frame_count; p_cspr->current_frame %= spr.sprite->frame_count;
spr.sprite->elapsed = 0; spr.sprite->elapsed = 0;
} }
} }

View File

@ -109,7 +109,7 @@ static void level_scene_render_func(Scene_t* scene)
if (spr.sprite != NULL) if (spr.sprite != NULL)
{ {
Vector2 pos = Vector2Add(p_ct->position, spr.offset); Vector2 pos = Vector2Add(p_ct->position, spr.offset);
draw_sprite(spr.sprite, pos, 0.0f, p_cspr->flip_x); draw_sprite(spr.sprite, p_cspr->current_frame, pos, 0.0f, p_cspr->flip_x);
} }
} }
} }