17. Camera Bounds and Improved Jump
[[programvideogames]]Welcome back, everyone. In today's lecture, we're going to make some significant improvements to our game's feel and functionality. We'll be implementing a camera system, adding smoother jumping mechanics, and making a few other important tweaks.
Let's start with our level data. We're expanding our LDtk_Level struct to include world coordinates and dimensions:
LDtk_Level :: struct {
// ... existing fields ...
worldX, worldY: f32,
pxWid, pxHei: f32,
}
This will allow us to properly position our level in the world and set up our camera boundaries.
Speaking of the camera, we're adding some new fields to our Game_State:
Game_State :: struct {
// ... existing fields ...
level_min, level_max: Vec2,
jump_timer: f32,
coyote_timer: f32,
}
The level_min and level_max will help us constrain our camera, while jump_timer and coyote_timer are for our improved jumping mechanics.
We're also adjusting our window and rendering setup:
RENDER_WIDTH :: 640
RENDER_HEIGHT :: 360
ZOOM :: WINDOW_WIDTH / RENDER_WIDTH
This gives us more control over our game's visual scale.
Now, let's implement our camera system. In the main game loop, we're adding this block:
// Update Camera
{
render_half_size := Vec2{RENDER_WIDTH, RENDER_HEIGHT} / 2
gs.camera.target = {player.x, player.y} - render_half_size
if gs.camera.target.x < gs.level_min.x {
gs.camera.target.x = gs.level_min.x
}
if gs.camera.target.y < gs.level_min.y {
gs.camera.target.y = gs.level_min.y
}
if gs.camera.target.x + RENDER_WIDTH > gs.level_max.x {
gs.camera.target.x = gs.level_max.x - RENDER_WIDTH
}
if gs.camera.target.y + RENDER_HEIGHT > gs.level_max.y {
gs.camera.target.y = gs.level_max.y - RENDER_HEIGHT
}
}
This centers the camera on the player and ensures it doesn't go beyond the level boundaries.
Moving on to our jumping mechanics, we're introducing two new constants:
JUMP_TIME :: 0.2
COYOTE_TIME :: 0.15
These will allow for more forgiving and responsive jumps. In our player_update function, we're now decrementing these timers:
gs.jump_timer -= dt
gs.coyote_timer -= dt
We've also made some changes to our jump logic:
try_jump :: proc(gs: ^Game_State, player: ^Entity) {
if rl.IsKeyPressed(.SPACE) {
gs.jump_timer = JUMP_TIME
}
if .Grounded in player.flags {
gs.coyote_timer = COYOTE_TIME
}
if (.Grounded in player.flags || gs.coyote_timer > 0) && gs.jump_timer > 0 {
// ... perform jump ...
}
}
This allows for a small window of time where the player can still jump after leaving a platform, and a brief period where pressing jump slightly before landing will still trigger a jump.
We've also added variable jump height:
if rl.IsKeyReleased(.SPACE) {
player.vel.y *= 0.5
}
This cuts the player's upward velocity if they release the jump button, allowing for more precise jumps.
Lastly, we've made a small but important change to our rendering order. We're now calling rl.EndMode2D() before drawing the FPS counter, ensuring it's always visible on the screen regardless of camera position.
These changes should make our game feel much more responsive and polished.