Compare commits

..

6 Commits

Author SHA1 Message Date
En Yi c2fe0fda8a Add level preview generation 2025-07-30 21:57:13 +08:00
En Yi e90856f8c6 Add game over text & reset camera mode on restart 2025-07-29 21:46:45 +08:00
En Yi f35320cfe6 Add climbing SFX 2025-07-29 21:45:59 +08:00
En Yi acc299c612 Add SFX when destroying boulder 2025-07-27 15:14:54 +08:00
En Yi 5b1d7b8a8f Fix camera update
Changelog:
- Add a delay before shifting the camera when changing facing direction
- Fix camera shifting higher when crate jumping
2025-07-27 15:14:31 +08:00
En Yi 3c9e425bf1 Tweak water movement 2025-07-26 17:11:58 +08:00
12 changed files with 183 additions and 13 deletions

View File

@ -44,6 +44,7 @@ int main(int argc, char** argv)
load_sfx(&engine, "snd_step", PLAYER_STEP_SFX);
load_sfx(&engine, "snd_dead", PLAYER_DEAD_SFX);
load_sfx(&engine, "snd_drwg", PLAYER_DROWNING_SFX);
load_sfx(&engine, "snd_climb", PLAYER_CLIMB_SFX);
load_sfx(&engine, "snd_mdestroy", METAL_DESTROY_SFX);
load_sfx(&engine, "snd_wdestroy", WOOD_DESTROY_SFX);
load_sfx(&engine, "snd_cland", WOOD_LAND_SFX);

6
main.c
View File

@ -45,9 +45,14 @@ int main(void)
load_sfx(&engine, "snd_bubble", BUBBLE_SFX);
load_sfx(&engine, "snd_mdestroy", METAL_DESTROY_SFX);
load_sfx(&engine, "snd_wdestroy", WOOD_DESTROY_SFX);
load_sfx(&engine, "snd_bdestroy", BOULDER_DESTROY_SFX);
load_sfx(&engine, "snd_cland", WOOD_LAND_SFX);
load_sfx(&engine, "snd_explsn", EXPLOSION_SFX);
load_sfx(&engine, "snd_coin", COIN_SFX);
load_sfx(&engine, "snd_step", PLAYER_STEP_SFX);
load_sfx(&engine, "snd_dead", PLAYER_DEAD_SFX);
load_sfx(&engine, "snd_drwg", PLAYER_DROWNING_SFX);
load_sfx(&engine, "snd_climb", PLAYER_CLIMB_SFX);
load_sfx(&engine, "snd_arrhit", ARROW_DESTROY_SFX);
load_sfx(&engine, "snd_launch", ARROW_RELEASE_SFX);
load_sfx(&engine, "snd_launch", BOMB_RELEASE_SFX);
@ -109,7 +114,6 @@ int main(void)
float delta_time = fminf(frame_time, DT);
update_scene(curr_scene, delta_time);
update_entity_manager(&curr_scene->ent_manager);
// This is needed to advance time delta
render_scene(curr_scene);
update_sfx_list(&engine);

View File

@ -61,12 +61,14 @@ int main(void)
load_sfx(&engine, "snd_bubble", BUBBLE_SFX);
load_sfx(&engine, "snd_mdestroy", METAL_DESTROY_SFX);
load_sfx(&engine, "snd_wdestroy", WOOD_DESTROY_SFX);
load_sfx(&engine, "snd_bdestroy", BOULDER_DESTROY_SFX);
load_sfx(&engine, "snd_cland", WOOD_LAND_SFX);
load_sfx(&engine, "snd_explsn", EXPLOSION_SFX);
load_sfx(&engine, "snd_coin", COIN_SFX);
load_sfx(&engine, "snd_step", PLAYER_STEP_SFX);
load_sfx(&engine, "snd_dead", PLAYER_DEAD_SFX);
load_sfx(&engine, "snd_drwg", PLAYER_DROWNING_SFX);
load_sfx(&engine, "snd_climb", PLAYER_CLIMB_SFX);
load_sfx(&engine, "snd_arrhit", ARROW_DESTROY_SFX);
load_sfx(&engine, "snd_launch", ARROW_RELEASE_SFX);
load_sfx(&engine, "snd_launch", BOMB_RELEASE_SFX);

View File

@ -23,6 +23,7 @@ typedef enum SFXTag {
PLAYER_STEP_SFX,
PLAYER_DEAD_SFX,
PLAYER_DROWNING_SFX,
PLAYER_CLIMB_SFX,
WATER_IN_SFX,
WATER_OUT_SFX,
WOOD_LAND_SFX,

View File

@ -20,7 +20,18 @@ void camera_update_system(Scene_t* scene)
target_vel = p_ctransform->velocity;
CMovementState_t* p_movement = get_component(p_player, CMOVEMENTSTATE_T);
CPlayerState_t* p_pstate = get_component(p_player, CPLAYERSTATE_T);
data->camera.target_pos.x += (p_movement->x_dir == 1) ? width/6: -width/6;
if (data->camera.offset_dir != p_movement->x_dir) {
data->camera.delay += scene->delta_time;
if (data->camera.delay >= 0.25f) {
data->camera.delay = 0.0f;
data->camera.offset_dir = p_movement->x_dir;
}
}
else
{
data->camera.delay = 0.0f;
}
data->camera.target_pos.x += width * 1.0f * data->camera.offset_dir / 6;
if (p_movement->ground_state == 0b01
|| (p_movement->water_state & 1)
@ -32,7 +43,8 @@ void camera_update_system(Scene_t* scene)
if (p_player->position.y >= data->camera.base_y)
{
data->camera.target_pos.y = p_player->position.y;
data->camera.target_pos.y += p_ctransform->velocity.y * 0.2;
data->camera.target_pos.y +=
(p_ctransform->velocity.y > 0) ? p_ctransform->velocity.y * 0.2 : 0;
}
}
data->camera.target_pos.x = Clamp(data->camera.target_pos.x, data->game_rec.width / 2,

View File

@ -17,6 +17,7 @@ static RenderInfoNode all_tile_rendernodes[MAX_N_TILES] = {0};
#define CONTROL_LAYER 1
static void level_scene_render_func(Scene_t* scene)
{
Font* menu_font = get_font(&scene->engine->assets, "MenuFont");
LevelSceneData_t* data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data);
static char buffer[512];
@ -61,6 +62,10 @@ static void level_scene_render_func(Scene_t* scene)
air_pos.x -= 32;
}
}
if (sc_map_size_64v(&scene->ent_manager.entities_map[PLAYER_ENT_TAG]) == 0)
{
DrawTextEx(*menu_font, "Press R to Try Again", (Vector2){32, data->game_rec.height/2 - 64}, 64, 4, WHITE);
}
// For DEBUG
int gui_x = 5;
sprintf(buffer, "%u %u", sc_map_size_64v(&scene->ent_manager.entities), GetFPS());
@ -156,6 +161,7 @@ static void level_do_action(Scene_t* scene, ActionType_t action, bool pressed)
static void render_regular_game_scene(Scene_t* scene)
{
TracyCZoneN(ctx, "GameRender", true)
update_entity_manager(&scene->ent_manager);
// This function will render the game scene outside of the intended draw function
// Just for clarity and separation of logic
LevelSceneData_t* data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data);

View File

@ -203,6 +203,7 @@ static void destroy_entity(Scene_t* scene, TileGrid_t* tilemap, Entity_t* p_ent)
.emitter_update_func = NULL,
};
play_particle_emitter(&scene->part_sys, &emitter);
play_sfx(scene->engine, BOULDER_DESTROY_SFX);
}
else if (p_ent->m_tag == CRATES_ENT_TAG)
{
@ -431,7 +432,7 @@ void player_movement_input_system(Scene_t* scene)
{
// Although this can be achieved via higher friction, i'll explain away as the player is not
// good with swimming, resulting in lower movement acceleration
p_ctransform->accel = Vector2Scale(Vector2Normalize(p_pstate->player_dir), MOVE_ACCEL / (1.0f + 0.12f * p_mstate->water_overlap));
p_ctransform->accel = Vector2Scale(Vector2Normalize(p_pstate->player_dir), MOVE_ACCEL / (1.0f + 0.05f * p_mstate->water_overlap));
if (p_pstate->is_crouch & 1)
{
@ -1760,7 +1761,7 @@ void hitbox_update_system(Scene_t* scene)
p_clifetimer->life_time = 0.15f;
}
else if (p_ent->m_tag == DESTRUCTABLE_ENT_TAG) {
p_clifetimer->life_time = 0.12f;
p_clifetimer->life_time = 0.11f;
}
}
else

View File

@ -63,7 +63,7 @@ Entity_t* create_crate(EntityManager_t* ent_manager, bool metal, ContainerItem_t
CTransform_t* p_ctransform = add_component(p_crate, CTRANSFORM_COMP_T);
p_ctransform->grav_delay = 0.10f;
p_ctransform->shape_factor = metal ? (Vector2){0.7,0.7} : (Vector2){0.8,0.8} ;
p_ctransform->shape_factor = metal ? (Vector2){0.53,0.53} : (Vector2){0.65,0.65} ;
add_component(p_crate, CMOVEMENTSTATE_T);
add_component(p_crate, CTILECOORD_COMP_T);

View File

@ -4,27 +4,156 @@
#include "raymath.h"
#include <stdio.h>
#define LEVEL_PREVIEW_SIZE 400
static void level_select_render_func(Scene_t* scene)
{
LevelSelectSceneData_t* data = &(CONTAINER_OF(scene, LevelSelectScene_t, scene)->data);
Sprite_t* level_board = get_sprite(&scene->engine->assets, "lvl_board");
Sprite_t* level_select = get_sprite(&scene->engine->assets, "lvl_select");
Sprite_t* preview = get_sprite(&scene->engine->assets, "lvlprvw");
//Sprite_t* preview = get_sprite(&scene->engine->assets, "lvlprvw");
Font* menu_font = get_font(&scene->engine->assets, "MenuFont");
BeginTextureMode(scene->layers.render_layers[0].layer_tex);
ClearBackground(BLANK);
draw_sprite(level_select, 0, (Vector2){0,0},0, false);
draw_sprite(level_board, 0, (Vector2){level_select->frame_size.x,0},0, false);
draw_sprite(preview, data->scroll_area.curr_selection, (Vector2){
level_select->frame_size.x + (level_board->frame_size.x - preview->frame_size.x) / 2,
(level_board->frame_size.y - preview->frame_size.y) / 2,
},0, false);
Rectangle draw_rec = {0,0,LEVEL_PREVIEW_SIZE,LEVEL_PREVIEW_SIZE * -1};
Vector2 draw_pos = {
level_select->frame_size.x + (level_board->frame_size.x - LEVEL_PREVIEW_SIZE) / 2,
(level_board->frame_size.y - LEVEL_PREVIEW_SIZE) / 2
};
DrawTextureRec(
data->preview.texture,
draw_rec,
draw_pos,
WHITE
);
DrawTextEx(*menu_font, "Level Select", (Vector2){60, 20}, 40, 4, BLACK);
vert_scrollarea_render(&data->scroll_area);
EndTextureMode();
}
static void level_preview_render_func(Scene_t* scene)
{
LevelSelectSceneData_t* data = &(CONTAINER_OF(scene, LevelSelectScene_t, scene)->data);
if (!data->update_preview) return;
LevelMap_t level = data->level_pack->levels[data->scroll_area.curr_selection];
const uint32_t n_tiles = level.width * level.height;
uint16_t max_dim = (level.width > level.height ? level.width : level.height);
max_dim = (max_dim == 0)? 1: max_dim;
uint32_t tile_size = LEVEL_PREVIEW_SIZE / max_dim;
uint32_t tile_halfsize = tile_size >> 1;
tile_halfsize = (tile_halfsize == 0)? 1 : tile_halfsize;
const uint32_t lvl_width = level.width*tile_size-1;
const uint32_t lvl_height = level.height*tile_size-1;
const uint32_t x_offset = (LEVEL_PREVIEW_SIZE - lvl_width) / 2;
const uint32_t y_offset = (LEVEL_PREVIEW_SIZE - lvl_height) / 2;
const Color danger_col = {239,79,81,255};
BeginTextureMode(data->preview);
ClearBackground((Color){255,255,255,0});
DrawRectangle(
x_offset, y_offset, lvl_width, lvl_height,
(Color){64,64,64,255}
);
for (uint32_t i = 0; i < n_tiles; ++i)
{
uint32_t pos_x = tile_size * (i % level.width) + x_offset;
uint32_t pos_y = tile_size * (i / level.width) + y_offset;
if (level.tiles[i].tile_type >= 8 && level.tiles[i].tile_type < 20)
{
uint32_t tmp_idx = level.tiles[i].tile_type - 8;
uint32_t item_type = tmp_idx % 6;
Color col = (tmp_idx > 5)? (Color){110,110,110,255} : (Color){160,117,48,255};
DrawRectangle(pos_x, pos_y, tile_size, tile_size, col);
switch (item_type)
{
case 1:
DrawLine(pos_x, pos_y + tile_halfsize, pos_x + tile_halfsize, pos_y + tile_halfsize, danger_col);
break;
case 2:
DrawLine(pos_x + tile_halfsize, pos_y + tile_halfsize, pos_x + tile_size, pos_y + tile_halfsize, danger_col);
break;
case 3:
DrawLine(pos_x + tile_halfsize, pos_y, pos_x + tile_halfsize, pos_y + tile_halfsize, danger_col);
break;
case 4:
DrawLine(pos_x + tile_halfsize, pos_y + tile_halfsize, pos_x + tile_halfsize, pos_y + tile_size, danger_col);
break;
case 5:
DrawLine(pos_x, pos_y, pos_x + tile_size, pos_y + tile_size, danger_col);
DrawLine(pos_x, pos_y + tile_size, pos_x + tile_size, pos_y, danger_col);
break;
}
DrawRectangleLines(pos_x, pos_y, tile_size, tile_size, (Color){0,0,0,64});
}
else
{
switch(level.tiles[i].tile_type) {
case SOLID_TILE:
DrawRectangle(pos_x, pos_y, tile_size, tile_size, BLACK);
break;
case ONEWAY_TILE:
DrawRectangle(pos_x, pos_y, tile_size, tile_halfsize, (Color){128,64,0,255});
break;
case LADDER:
DrawRectangleLines(pos_x, pos_y, tile_size, tile_size, (Color){214,141,64,255});
break;
case 4:
case 5:
case 6:
case 7:
// Copied from level generation
// Priority: Down, Up, Left, Right
if (i + level.width < n_tiles && level.tiles[i + level.width].tile_type == SOLID_TILE)
{
DrawRectangle(pos_x, pos_y + tile_halfsize , tile_size, tile_halfsize, danger_col);
}
else if (i >= level.width && level.tiles[i - level.width].tile_type == SOLID_TILE)
{
DrawRectangle(pos_x, pos_y, tile_size, tile_halfsize, danger_col);
}
else if (i % level.width != 0 && level.tiles[i - 1].tile_type == SOLID_TILE)
{
DrawRectangle(pos_x, pos_y, tile_halfsize, tile_size, danger_col);
}
else if ((i + 1) % level.width != 0 && level.tiles[i + 1].tile_type == SOLID_TILE)
{
DrawRectangle(pos_x + tile_halfsize, pos_y, tile_halfsize, tile_size, danger_col);
}
else
{
DrawRectangle(pos_x, pos_y + tile_halfsize , tile_size, tile_halfsize, danger_col);
}
break;
case 20:
DrawCircle(pos_x + tile_halfsize, pos_y + tile_halfsize, tile_halfsize, (Color){12,12,12,255});
break;
case 22:
DrawRectangle(pos_x, pos_y, tile_size, tile_size, (Color){255,0,255,255});
break;
case 23:
DrawRectangle(pos_x, pos_y, tile_size, tile_size, (Color){255,255,0,255});
break;
case 24:
DrawRectangle(pos_x, pos_y, tile_size, tile_size, (Color){0,255,0,255});
break;
case 25:
DrawCircle(pos_x + tile_halfsize, pos_y + tile_halfsize, tile_halfsize-1, danger_col);
break;
}
}
}
EndTextureMode();
data->update_preview = false;
}
static void level_select_do_action(Scene_t* scene, ActionType_t action, bool pressed)
{
LevelSelectSceneData_t* data = &(CONTAINER_OF(scene, LevelSelectScene_t, scene)->data);
@ -37,6 +166,7 @@ static void level_select_do_action(Scene_t* scene, ActionType_t action, bool pre
{
data->scroll_area.curr_selection--;
vert_scrollarea_refocus(&data->scroll_area);
data->update_preview = true;
}
}
break;
@ -47,6 +177,7 @@ static void level_select_do_action(Scene_t* scene, ActionType_t action, bool pre
{
data->scroll_area.curr_selection++;
vert_scrollarea_refocus(&data->scroll_area);
data->update_preview = true;
}
}
break;
@ -108,6 +239,8 @@ static void level_select_do_action(Scene_t* scene, ActionType_t action, bool pre
void init_level_select_scene(LevelSelectScene_t* scene)
{
init_scene(&scene->scene, &level_select_do_action, 0);
scene->data.preview = LoadRenderTexture(LEVEL_PREVIEW_SIZE, LEVEL_PREVIEW_SIZE);
scene->data.update_preview = true;
add_scene_layer(
&scene->scene, scene->scene.engine->intended_window_size.x,
scene->scene.engine->intended_window_size.y,
@ -150,6 +283,7 @@ void init_level_select_scene(LevelSelectScene_t* scene)
}
ScrollAreaRenderEnd();
sc_array_add(&scene->scene.systems, &level_preview_render_func);
sc_array_add(&scene->scene.systems, &level_select_render_func);
sc_map_put_64(&scene->scene.action_map, KEY_UP, ACTION_UP);
sc_map_put_64(&scene->scene.action_map, KEY_DOWN, ACTION_DOWN);
@ -159,7 +293,7 @@ void init_level_select_scene(LevelSelectScene_t* scene)
}
void free_level_select_scene(LevelSelectScene_t* scene)
{
UnloadRenderTexture(scene->data.preview);
vert_scrollarea_free(&scene->data.scroll_area);
free_scene(&scene->scene);
}

View File

@ -80,6 +80,11 @@ static void player_sfx_func(GameEngine_t* engine, Entity_t* ent) {
play_sfx(engine, PLAYER_STEP_SFX);
}
}
CTransform_t* p_ct = get_component(ent, CTRANSFORM_COMP_T);
if (p_spr->current_idx == SPR_PLAYER_LADDER && Vector2LengthSqr(p_ct->velocity) > 0.1) {
play_sfx_pitched(engine, PLAYER_CLIMB_SFX, p_spr->current_frame % 2 == 0 ? 1.0f : 0.75f );
}
}
Entity_t* create_player(EntityManager_t* ent_manager)

View File

@ -43,6 +43,8 @@ typedef struct LevelCamera {
float c; // damping factor
float k; // spring constant
float range_limit;
int offset_dir;
float delay;
}LevelCamera_t;
typedef enum LevelSceneState {
@ -123,7 +125,9 @@ typedef struct MenuScene {
typedef struct LevelSelectSceneData {
VertScrollArea_t scroll_area;
RenderTexture2D preview;
LevelPack_t* level_pack;
bool update_preview;
} LevelSelectSceneData_t;
typedef struct LevelSelectScene {

View File

@ -242,7 +242,6 @@ bool load_level_tilemap(LevelScene_t* scene, unsigned int level_num)
}
}
break;
default:
break;
}
@ -288,6 +287,7 @@ bool load_level_tilemap(LevelScene_t* scene, unsigned int level_num)
}
}
scene->data.camera.mode = CAMERA_FOLLOW_PLAYER;
return true;
}