Compare commits

...

4 Commits

Author SHA1 Message Date
En Yi acb1b7858c Add lookahead feature into main game 2024-08-17 14:59:17 +08:00
En Yi 90bbad09ae Add free-movement to camera in editor scene 2024-08-17 14:49:51 +08:00
En Yi 5d8385f9ff Refactor camera update system 2024-08-17 14:10:59 +08:00
En Yi 9fdb8296ff Add key to toggle player movement
This key will be used for looking ahead
2024-08-17 13:58:06 +08:00
9 changed files with 235 additions and 159 deletions

View File

@ -91,6 +91,7 @@ typedef struct _CPlayerState_t {
uint8_t jump_pressed; uint8_t jump_pressed;
uint8_t is_crouch; uint8_t is_crouch;
bool ladder_state; bool ladder_state;
bool locked; // Whether to respond to inputs
} CPlayerState_t; } CPlayerState_t;
typedef enum ContainerItem { typedef enum ContainerItem {

View File

@ -23,5 +23,6 @@ typedef enum ActionType
ACTION_SPAWN_TILE, ACTION_SPAWN_TILE,
ACTION_REMOVE_TILE, ACTION_REMOVE_TILE,
ACTION_SWITCH_TILESET, ACTION_SWITCH_TILESET,
ACTION_LOOKAHEAD,
}ActionType_t; }ActionType_t;
#endif // __ACTIONS_H #endif // __ACTIONS_H

View File

@ -9,6 +9,7 @@ add_library(lib_scenes STATIC
game_scene.c game_scene.c
game_systems.c game_systems.c
scene_systems.c scene_systems.c
camera_systems.c
) )
target_include_directories(lib_scenes target_include_directories(lib_scenes
PUBLIC PUBLIC

View File

@ -0,0 +1,102 @@
#include "game_systems.h"
#include "ent_impl.h"
#include "raymath.h"
void camera_update_system(Scene_t* scene)
{
LevelSceneData_t* data = &CONTAINER_OF(scene, LevelScene_t, scene)->data;
Entity_t* p_player;
const int width = data->game_rec.width;
const int height =data->game_rec.height;
data->camera.cam.offset = (Vector2){ width/2.0f, height/2.0f };
if (data->camera.mode == CAMERA_FOLLOW_PLAYER)
{
Vector2 target_vel = {0};
sc_map_foreach_value(&scene->ent_manager.entities_map[PLAYER_ENT_TAG], p_player)
{
CTransform_t* p_ctransform = get_component(p_player, CTRANSFORM_COMP_T);
data->camera.target_pos.x = p_player->position.x;
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 (p_movement->ground_state == 0b01
|| (p_movement->water_state & 1)
|| (p_pstate->ladder_state & 1)
)
{
data->camera.base_y = p_player->position.y;
}
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.x = Clamp(data->camera.target_pos.x, data->game_rec.width / 2,
fmax(data->tilemap.width * data->tilemap.tile_size, data->game_rec.width) - data->game_rec.width / 2);
data->camera.target_pos.y = Clamp(data->camera.target_pos.y, data->game_rec.height / 2,
fmax(data->tilemap.height * data->tilemap.tile_size, data->game_rec.width) - data->game_rec.height / 2);
// Mass-Spring damper update in x direction
float x = data->camera.target_pos.x - data->camera.cam.target.x;
float v = data->camera.current_vel.x - target_vel.x;
float F = data->camera.k * x - data->camera.c * v;
// Kinematics update
const float dt = scene->delta_time;
float a_dt = F * dt / data->camera.mass;
data->camera.cam.target.x += (data->camera.current_vel.x + a_dt * 0.5) * dt;
data->camera.current_vel.x += a_dt;
// Simple lerp for y direction
float dy = (data->camera.target_pos.y - data->camera.cam.target.y);
data->camera.cam.target.y += dy * 0.1f;
}
else
{
//sc_map_foreach_value(&scene->ent_manager.entities_map[PLAYER_ENT_TAG], p_player)
//{
// break;
//}
}
// Bound camera within level limits
Vector2 max = GetWorldToScreen2D(
(Vector2){
fmax(data->tilemap.width * data->tilemap.tile_size, data->game_rec.width),
fmax(data->tilemap.height * data->tilemap.tile_size, data->game_rec.height)
},
data->camera.cam
);
Vector2 min = GetWorldToScreen2D((Vector2){0, 0}, data->camera.cam);
if (max.x < width)
{
//data->camera.cam.offset.x = width - (max.x - width/2.0f);
data->camera.cam.target.x = fmax(data->tilemap.width * data->tilemap.tile_size, data->game_rec.width) - (width >> 1);
data->camera.current_vel.x = 0;
}
if (min.x > 0)
{
//data->camera.cam.offset.x = (width >> 1) - min.x;
data->camera.cam.target.x = (width >> 1);
data->camera.current_vel.x = 0;
}
if (max.y < height)
{
//data->camera.cam.offset.y = height - (max.y - height/2.0f);
data->camera.cam.target.y = fmax(data->tilemap.height * data->tilemap.tile_size, data->game_rec.height) - (height >> 1);
data->camera.current_vel.y = 0;
}
if (min.y > 0)
{
//data->camera.cam.offset.y = (height >> 1) - min.y;
data->camera.cam.target.y = (height >> 1);
data->camera.current_vel.y = 0;
}
}

View File

@ -842,15 +842,19 @@ static void level_do_action(Scene_t* scene, ActionType_t action, bool pressed)
{ {
case ACTION_UP: case ACTION_UP:
p_playerstate->player_dir.y = (pressed)? -1 : 0; p_playerstate->player_dir.y = (pressed)? -1 : 0;
if (data->camera.mode == CAMERA_RANGED_MOVEMENT) data->camera.cam.target.y -= 20;
break; break;
case ACTION_DOWN: case ACTION_DOWN:
p_playerstate->player_dir.y = (pressed)? 1 : 0; p_playerstate->player_dir.y = (pressed)? 1 : 0;
if (data->camera.mode == CAMERA_RANGED_MOVEMENT) data->camera.cam.target.y += 20;
break; break;
case ACTION_LEFT: case ACTION_LEFT:
p_playerstate->player_dir.x = (pressed)? -1 : 0; p_playerstate->player_dir.x = (pressed)? -1 : 0;
if (data->camera.mode == CAMERA_RANGED_MOVEMENT) data->camera.cam.target.x -= 20;
break; break;
case ACTION_RIGHT: case ACTION_RIGHT:
p_playerstate->player_dir.x = (pressed)? 1 : 0; p_playerstate->player_dir.x = (pressed)? 1 : 0;
if (data->camera.mode == CAMERA_RANGED_MOVEMENT) data->camera.cam.target.x += 20;
break; break;
case ACTION_JUMP: case ACTION_JUMP:
p_playerstate->jump_pressed = pressed; p_playerstate->jump_pressed = pressed;
@ -1022,7 +1026,6 @@ static void level_do_action(Scene_t* scene, ActionType_t action, bool pressed)
case ACTION_REMOVE_TILE: case ACTION_REMOVE_TILE:
toggle_block_system(scene, action, pressed); toggle_block_system(scene, action, pressed);
update_entity_manager(&scene->ent_manager); update_entity_manager(&scene->ent_manager);
default:
break; break;
case ACTION_SWITCH_TILESET: case ACTION_SWITCH_TILESET:
if (!pressed) if (!pressed)
@ -1033,6 +1036,12 @@ static void level_do_action(Scene_t* scene, ActionType_t action, bool pressed)
data->solid_tile_sprites = get_sprite(&scene->engine->assets, SOLID_TILE_SELECTIONS[data->selected_solid_tilemap]); data->solid_tile_sprites = get_sprite(&scene->engine->assets, SOLID_TILE_SELECTIONS[data->selected_solid_tilemap]);
} }
break; break;
case ACTION_LOOKAHEAD:
p_playerstate->locked = pressed;
data->camera.mode = pressed ? CAMERA_RANGED_MOVEMENT : CAMERA_FOLLOW_PLAYER;
break;
default:
break;
} }
} }
} }
@ -1321,10 +1330,11 @@ void init_sandbox_scene(LevelScene_t* scene)
sc_map_put_64(&scene->scene.action_map, KEY_T, ACTION_CRATE_ACTIVATION); sc_map_put_64(&scene->scene.action_map, KEY_T, ACTION_CRATE_ACTIVATION);
sc_map_put_64(&scene->scene.action_map, KEY_L, ACTION_EXIT); sc_map_put_64(&scene->scene.action_map, KEY_L, ACTION_EXIT);
sc_map_put_64(&scene->scene.action_map, KEY_R, ACTION_RESTART); sc_map_put_64(&scene->scene.action_map, KEY_R, ACTION_RESTART);
sc_map_put_64(&scene->scene.action_map, KEY_B, ACTION_TOGGLE_GRID);
sc_map_put_64(&scene->scene.action_map, KEY_Z, ACTION_SWITCH_TILESET); sc_map_put_64(&scene->scene.action_map, KEY_Z, ACTION_SWITCH_TILESET);
sc_map_put_64(&scene->scene.action_map, KEY_V, ACTION_SET_SPAWNPOINT); sc_map_put_64(&scene->scene.action_map, KEY_X, ACTION_TOGGLE_GRID);
sc_map_put_64(&scene->scene.action_map, KEY_C, ACTION_SET_SPAWNPOINT);
sc_map_put_64(&scene->scene.action_map, KEY_U, ACTION_TOGGLE_TIMESLOW); sc_map_put_64(&scene->scene.action_map, KEY_U, ACTION_TOGGLE_TIMESLOW);
sc_map_put_64(&scene->scene.action_map, KEY_V, ACTION_LOOKAHEAD);
sc_map_put_64(&scene->scene.action_map, MOUSE_LEFT_BUTTON, ACTION_SPAWN_TILE); sc_map_put_64(&scene->scene.action_map, MOUSE_LEFT_BUTTON, ACTION_SPAWN_TILE);
sc_map_put_64(&scene->scene.action_map, MOUSE_RIGHT_BUTTON, ACTION_REMOVE_TILE); sc_map_put_64(&scene->scene.action_map, MOUSE_RIGHT_BUTTON, ACTION_REMOVE_TILE);

View File

@ -56,6 +56,7 @@ static void level_scene_render_func(Scene_t* scene)
static void level_do_action(Scene_t* scene, ActionType_t action, bool pressed) static void level_do_action(Scene_t* scene, ActionType_t action, bool pressed)
{ {
LevelSceneData_t* data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data);
CPlayerState_t* p_playerstate; CPlayerState_t* p_playerstate;
sc_map_foreach_value(&scene->ent_manager.component_map[CPLAYERSTATE_T], p_playerstate) sc_map_foreach_value(&scene->ent_manager.component_map[CPLAYERSTATE_T], p_playerstate)
{ {
@ -63,19 +64,27 @@ static void level_do_action(Scene_t* scene, ActionType_t action, bool pressed)
{ {
case ACTION_UP: case ACTION_UP:
p_playerstate->player_dir.y = (pressed)? -1 : 0; p_playerstate->player_dir.y = (pressed)? -1 : 0;
if (data->camera.mode == CAMERA_RANGED_MOVEMENT) data->camera.cam.target.y -= 20;
break; break;
case ACTION_DOWN: case ACTION_DOWN:
p_playerstate->player_dir.y = (pressed)? 1 : 0; p_playerstate->player_dir.y = (pressed)? 1 : 0;
if (data->camera.mode == CAMERA_RANGED_MOVEMENT) data->camera.cam.target.y += 20;
break; break;
case ACTION_LEFT: case ACTION_LEFT:
p_playerstate->player_dir.x = (pressed)? -1 : 0; p_playerstate->player_dir.x = (pressed)? -1 : 0;
if (data->camera.mode == CAMERA_RANGED_MOVEMENT) data->camera.cam.target.x -= 20;
break; break;
case ACTION_RIGHT: case ACTION_RIGHT:
p_playerstate->player_dir.x = (pressed)? 1 : 0; p_playerstate->player_dir.x = (pressed)? 1 : 0;
if (data->camera.mode == CAMERA_RANGED_MOVEMENT) data->camera.cam.target.x += 20;
break; break;
case ACTION_JUMP: case ACTION_JUMP:
p_playerstate->jump_pressed = pressed; p_playerstate->jump_pressed = pressed;
break; break;
case ACTION_LOOKAHEAD:
p_playerstate->locked = pressed;
data->camera.mode = pressed ? CAMERA_RANGED_MOVEMENT : CAMERA_FOLLOW_PLAYER;
break;
default: default:
break; break;
} }
@ -488,6 +497,7 @@ void init_game_scene(LevelScene_t* scene)
sc_map_put_64(&scene->scene.action_map, KEY_R, ACTION_RESTART); sc_map_put_64(&scene->scene.action_map, KEY_R, ACTION_RESTART);
sc_map_put_64(&scene->scene.action_map, KEY_RIGHT_BRACKET, ACTION_NEXTLEVEL); sc_map_put_64(&scene->scene.action_map, KEY_RIGHT_BRACKET, ACTION_NEXTLEVEL);
sc_map_put_64(&scene->scene.action_map, KEY_LEFT_BRACKET, ACTION_PREVLEVEL); sc_map_put_64(&scene->scene.action_map, KEY_LEFT_BRACKET, ACTION_PREVLEVEL);
sc_map_put_64(&scene->scene.action_map, KEY_Z, ACTION_LOOKAHEAD);
} }

View File

@ -297,6 +297,9 @@ void player_movement_input_system(Scene_t* scene)
// Ladder handling // Ladder handling
if (!p_pstate->ladder_state) if (!p_pstate->ladder_state)
{
// Transit into ladder state if possible
if (!p_pstate->locked)
{ {
if (p_pstate->player_dir.y < 0) if (p_pstate->player_dir.y < 0)
{ {
@ -338,6 +341,7 @@ void player_movement_input_system(Scene_t* scene)
} }
} }
} }
}
else else
{ {
unsigned int tile_x = (p_player->position.x + p_bbox->half_size.x) / TILE_SIZE; unsigned int tile_x = (p_player->position.x + p_bbox->half_size.x) / TILE_SIZE;
@ -353,10 +357,11 @@ void player_movement_input_system(Scene_t* scene)
p_pstate->ladder_state |= tilemap.tiles[tile_idx].tile_type == LADDER; p_pstate->ladder_state |= tilemap.tiles[tile_idx].tile_type == LADDER;
} }
} }
if (p_pstate->ladder_state) if (p_pstate->ladder_state)
{ {
p_ctransform->velocity.y = p_pstate->player_dir.y * 150; p_ctransform->velocity.y = p_pstate->player_dir.y * 150 * (p_pstate->locked ? 0 : 1);
p_ctransform->velocity.x = p_pstate->player_dir.x * 40; p_ctransform->velocity.x = p_pstate->player_dir.x * 40 * (p_pstate->locked ? 0 : 1);
if (p_pstate->player_dir.y != 0) if (p_pstate->player_dir.y != 0)
{ {
p_player->position.x = tile_x * TILE_SIZE + 1; p_player->position.x = tile_x * TILE_SIZE + 1;
@ -365,40 +370,13 @@ void player_movement_input_system(Scene_t* scene)
} }
bool in_water = (p_mstate->water_state & 1); bool in_water = (p_mstate->water_state & 1);
p_pstate->is_crouch |= (p_pstate->player_dir.y > 0)? 0b10 : 0; if (p_pstate->locked)
if (!in_water)
{ {
p_pstate->is_crouch |= (p_pstate->is_crouch & 1)? 0b10 : 0;
p_pstate->player_dir.y = 0;
p_ctransform->accel = Vector2Scale(Vector2Normalize(p_pstate->player_dir), MOVE_ACCEL);
} }
else else
{ {
// Although this can be achieved via higher friction, i'll explain away as the player is not p_pstate->is_crouch |= (p_pstate->player_dir.y > 0)? 0b10 : 0;
// good with swimming, resulting in lower movement acceleration
p_ctransform->accel = Vector2Scale(Vector2Normalize(p_pstate->player_dir), MOVE_ACCEL/1.2);
}
if (p_pstate->is_crouch & 1)
{
p_ctransform->accel = Vector2Scale(p_ctransform->accel, 0.5);
}
// Short Hop
// Jumped check is needed to make sure it is applied on jumps, not generally
// One issue caused is lower velocity in water
if (p_cjump->jumped)
{
if (!p_pstate->jump_pressed)
{
p_cjump->jump_released = true;
if (!p_cjump->short_hop && p_ctransform->velocity.y < 0)
{
p_cjump->short_hop = true;
p_ctransform->velocity.y /= 2;
}
}
}
Vector2 point_to_check = Vector2Add( Vector2 point_to_check = Vector2Add(
p_player->position, p_player->position,
(Vector2){0, p_bbox->size.y - PLAYER_HEIGHT} (Vector2){0, p_bbox->size.y - PLAYER_HEIGHT}
@ -411,11 +389,47 @@ void player_movement_input_system(Scene_t* scene)
p_pstate->is_crouch |= 0b10; p_pstate->is_crouch |= 0b10;
} }
if (!(p_mstate->ground_state & 1)) p_pstate->is_crouch &= 0b01; if (!(p_mstate->ground_state & 1)) p_pstate->is_crouch &= 0b01;
p_pstate->is_crouch >>= 1; }
if (!in_water)
{
p_pstate->player_dir.y = 0;
}
if (!p_pstate->locked)
{
// 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 / (in_water ? 1.2f : 1.0f));
if (p_pstate->is_crouch & 1)
{
p_ctransform->accel = Vector2Scale(p_ctransform->accel, 0.5);
}
}
// Short Hop
// Jumped check is needed to make sure it is applied on jumps, not generally
// One issue caused is lower velocity in water
if (p_cjump->jumped)
{
if (!p_pstate->jump_pressed || p_pstate->locked)
{
p_cjump->jump_released = true;
if (!p_cjump->short_hop && p_ctransform->velocity.y < 0)
{
p_cjump->short_hop = true;
p_ctransform->velocity.y /= 2;
}
}
}
// Jumps is possible as long as you have a jump // Jumps is possible as long as you have a jump
// Check if possible to jump when jump is pressed // Check if possible to jump when jump is pressed
if (!p_pstate->locked)
{
if (p_cjump->jump_released && p_pstate->jump_pressed && p_cjump->jumps > 0 && p_cjump->jump_ready) if (p_cjump->jump_released && p_pstate->jump_pressed && p_cjump->jumps > 0 && p_cjump->jump_ready)
{ {
play_sfx(scene->engine, PLAYER_JMP_SFX); play_sfx(scene->engine, PLAYER_JMP_SFX);
@ -451,6 +465,10 @@ void player_movement_input_system(Scene_t* scene)
play_sfx_pitched(scene->engine, PLAYER_LAND_SFX, 0.8f + rand() * 0.4f / (float)RAND_MAX); play_sfx_pitched(scene->engine, PLAYER_LAND_SFX, 0.8f + rand() * 0.4f / (float)RAND_MAX);
} }
} }
// Post movement state update
p_pstate->is_crouch >>= 1;
}
} }
void player_bbox_update_system(Scene_t* scene) void player_bbox_update_system(Scene_t* scene)
@ -1989,81 +2007,6 @@ void sprite_animation_system(Scene_t* scene)
} }
} }
void camera_update_system(Scene_t* scene)
{
LevelSceneData_t* data = &CONTAINER_OF(scene, LevelScene_t, scene)->data;
Entity_t* p_player;
const int width = data->game_rec.width;
const int height =data->game_rec.height;
data->camera.cam.offset = (Vector2){ width/2.0f, height/2.0f };
Vector2 target_vel = {0};
sc_map_foreach_value(&scene->ent_manager.entities_map[PLAYER_ENT_TAG], p_player)
{
CTransform_t* p_ctransform = get_component(p_player, CTRANSFORM_COMP_T);
data->camera.target_pos.x = p_player->position.x;
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 (p_movement->ground_state == 0b01
|| (p_movement->water_state & 1)
|| (p_pstate->ladder_state & 1)
)
{
data->camera.base_y = p_player->position.y;
}
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.x = Clamp(data->camera.target_pos.x, data->game_rec.width / 2,
fmax(data->tilemap.width * TILE_SIZE, data->game_rec.width) - data->game_rec.width / 2);
data->camera.target_pos.y = Clamp(data->camera.target_pos.y, data->game_rec.height / 2,
fmax(data->tilemap.height * TILE_SIZE, data->game_rec.width) - data->game_rec.height / 2);
// Mass-Spring damper update in x direction
float x = data->camera.target_pos.x - data->camera.cam.target.x;
float v = data->camera.current_vel.x - target_vel.x;
float F = data->camera.k * x - data->camera.c * v;
// Kinematics update
const float dt = scene->delta_time;
float a_dt = F * dt / data->camera.mass;
data->camera.cam.target.x += (data->camera.current_vel.x + a_dt * 0.5) * dt;
data->camera.current_vel.x += a_dt;
// Simple lerp for y direction
float dy = (data->camera.target_pos.y - data->camera.cam.target.y);
data->camera.cam.target.y += dy * 0.1f;
Vector2 max = GetWorldToScreen2D(
(Vector2){
fmax(data->tilemap.width * TILE_SIZE, data->game_rec.width),
fmax(data->tilemap.height * TILE_SIZE, data->game_rec.height)
},
data->camera.cam
);
Vector2 min = GetWorldToScreen2D((Vector2){0, 0}, data->camera.cam);
if (max.x < width)
{
data->camera.cam.offset.x = width - (max.x - width/2.0f);
data->camera.cam.target.x = fmax(data->tilemap.width * TILE_SIZE, data->game_rec.width) - width / 2;
data->camera.current_vel.x = 0;
}
if (min.x > 0)
{
data->camera.cam.offset.x = width/2.0f - min.x;
data->camera.cam.target.x = width / 2;
data->camera.current_vel.x = 0;
}
}
void level_end_detection_system(Scene_t* scene) void level_end_detection_system(Scene_t* scene)
{ {
LevelScene_t* lvl_scene = CONTAINER_OF(scene, LevelScene_t, scene); LevelScene_t* lvl_scene = CONTAINER_OF(scene, LevelScene_t, scene);

View File

@ -27,8 +27,14 @@ typedef struct CoinCounter
uint16_t total; uint16_t total;
}CoinCounter_t; }CoinCounter_t;
typedef enum CameraMode {
CAMERA_FOLLOW_PLAYER = 0,
CAMERA_RANGED_MOVEMENT,
} CameraMode_t;
typedef struct LevelCamera { typedef struct LevelCamera {
Camera2D cam; Camera2D cam;
CameraMode_t mode;
Vector2 target_pos; Vector2 target_pos;
float base_y; float base_y;
//Vector2 prev_pos; //Vector2 prev_pos;
@ -36,6 +42,7 @@ typedef struct LevelCamera {
float mass; float mass;
float c; // damping factor float c; // damping factor
float k; // spring constant float k; // spring constant
float range_limit;
}LevelCamera_t; }LevelCamera_t;
typedef struct LevelSceneData { typedef struct LevelSceneData {

View File

@ -12,6 +12,7 @@ void init_level_scene_data(LevelSceneData_t* data, uint32_t max_tiles, Tile_t* t
data->camera.mass = 0.2f; data->camera.mass = 0.2f;
data->camera.k = 6.2f; data->camera.k = 6.2f;
data->camera.c = 2.2f; data->camera.c = 2.2f;
data->camera.range_limit = 200.0f;
data->tilemap.max_tiles = max_tiles; data->tilemap.max_tiles = max_tiles;
if (tiles != NULL) if (tiles != NULL)