Created
June 22, 2025 01:54
-
-
Save naranyala/f5559b63e232423ccd6ccfe7ad40dcc8 to your computer and use it in GitHub Desktop.
endless jump game (vertical) made it with odin+raylib
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import "core:math" | |
import "core:fmt" | |
import rl "vendor:raylib" | |
Platform :: struct { | |
pos: rl.Vector2, | |
size: rl.Vector2, | |
} | |
main :: proc() { | |
rl.InitWindow(800, 600, "Endless Vertical Jump Game") | |
defer rl.CloseWindow() | |
rl.SetTargetFPS(60) | |
player_pos := rl.Vector2{400, 500} | |
player_vel := rl.Vector2{0, 0} | |
player_size := rl.Vector2{20, 20} | |
gravity: f32 = 1200 | |
move_speed: f32 = 2800 // Fast horizontal movement | |
jump_speed: f32 = 700 // High jump | |
is_grounded := false | |
score: i32 = 0 | |
camera_offset: f32 = 0 | |
game_over := false | |
highest_y: f32 = 500 // Track highest point reached | |
// Platform generation | |
platforms := make([dynamic]Platform) | |
defer delete(platforms) | |
// Initial platforms - vertical progression | |
append(&platforms, Platform{{350, 500}, {200, 20}}) // Starting platform | |
append(&platforms, Platform{{200, 400}, {150, 20}}) // Left side | |
append(&platforms, Platform{{450, 320}, {150, 20}}) // Right side | |
append(&platforms, Platform{{100, 240}, {150, 20}}) // Left side | |
append(&platforms, Platform{{550, 160}, {150, 20}}) // Right side | |
append(&platforms, Platform{{300, 80}, {200, 20}}) // Center top | |
platform_spawn_y: f32 = 0 // Spawn platforms going upward (negative Y) | |
for !rl.WindowShouldClose() { | |
if game_over { | |
// Game over screen | |
rl.BeginDrawing() | |
defer rl.EndDrawing() | |
rl.ClearBackground(rl.BLACK) | |
rl.DrawText("GAME OVER!", 250, 250, 50, rl.RED) | |
score_text := fmt.ctprintf("Final Score: %d", score) | |
rl.DrawText(score_text, 300, 320, 30, rl.WHITE) | |
height_text := fmt.ctprintf("Max Height: %.0fm", abs(highest_y) / 20) | |
rl.DrawText(height_text, 280, 360, 25, rl.YELLOW) | |
rl.DrawText("Press R to Restart", 280, 400, 25, rl.WHITE) | |
if rl.IsKeyPressed(rl.KeyboardKey.R) { | |
// Reset game | |
player_pos = {400, 500} | |
player_vel = {0, 0} | |
score = 0 | |
camera_offset = 0 | |
highest_y = 500 | |
game_over = false | |
is_grounded = false | |
// Reset platforms | |
clear(&platforms) | |
append(&platforms, Platform{{350, 500}, {200, 20}}) | |
append(&platforms, Platform{{200, 400}, {150, 20}}) | |
append(&platforms, Platform{{450, 320}, {150, 20}}) | |
append(&platforms, Platform{{100, 240}, {150, 20}}) | |
append(&platforms, Platform{{550, 160}, {150, 20}}) | |
append(&platforms, Platform{{300, 80}, {200, 20}}) | |
platform_spawn_y = 0 | |
} | |
continue | |
} | |
dt := rl.GetFrameTime() | |
// Apply gravity | |
player_vel.y += gravity * dt | |
// Horizontal movement input | |
player_vel.x *= 0.9 | |
if rl.IsKeyDown(rl.KeyboardKey.A) || rl.IsKeyDown(rl.KeyboardKey.LEFT) { | |
player_vel.x -= move_speed * dt | |
} | |
if rl.IsKeyDown(rl.KeyboardKey.D) || rl.IsKeyDown(rl.KeyboardKey.RIGHT) { | |
player_vel.x += move_speed * dt | |
} | |
// Jump input (only when grounded) | |
if (rl.IsKeyPressed(rl.KeyboardKey.J) || rl.IsKeyPressed(rl.KeyboardKey.SPACE)) && is_grounded { | |
player_vel.y = -jump_speed | |
} | |
// Update position | |
player_pos += player_vel * dt | |
// Track highest point reached | |
if player_pos.y < highest_y { | |
highest_y = player_pos.y | |
} | |
// Camera follows player vertically (inverted for upward movement) | |
target_camera := -(player_pos.y - 300) // Keep player in lower part of screen | |
camera_offset += (target_camera - camera_offset) * 5 * dt | |
// Keep player within horizontal screen bounds | |
if player_pos.x < 0 do player_pos.x = 0 | |
if player_pos.x + player_size.x > 800 do player_pos.x = 800 - player_size.x | |
// Platform collision | |
is_grounded = false | |
for &platform in platforms { | |
// Check if player is landing on platform from above | |
if player_pos.x + player_size.x > platform.pos.x && | |
player_pos.x < platform.pos.x + platform.size.x && | |
player_pos.y + player_size.y > platform.pos.y && | |
player_pos.y < platform.pos.y + platform.size.y && | |
player_vel.y > 0 { // Only land when falling down | |
player_pos.y = platform.pos.y - player_size.y | |
player_vel.y = 0 | |
is_grounded = true | |
break | |
} | |
} | |
// Generate new platforms as player climbs higher | |
if platform_spawn_y - player_pos.y > -200 { // Generate platforms above player | |
// Create platforms in a zig-zag pattern for vertical climbing | |
platform_x: f32 | |
platform_width: f32 = f32(rl.GetRandomValue(120, 180)) | |
// Alternate between left and right sides with some randomness | |
if rl.GetRandomValue(0, 1) == 0 { | |
platform_x = f32(rl.GetRandomValue(50, 300)) // Left side | |
} else { | |
platform_x = f32(rl.GetRandomValue(450, 700)) // Right side | |
} | |
// Ensure platform fits on screen | |
if platform_x + platform_width > 800 { | |
platform_x = 800 - platform_width | |
} | |
append(&platforms, Platform{{platform_x, platform_spawn_y}, {platform_width, 20}}) | |
// Space platforms vertically for jumpable gaps | |
// With jump_speed=700 and gravity=1200, max jump height ≈ 204 pixels | |
platform_spawn_y -= f32(rl.GetRandomValue(120, 180)) // Vertical spacing | |
} | |
// Remove platforms that are too far below to save memory | |
for i := len(platforms) - 1; i >= 0; i -= 1 { | |
if platforms[i].pos.y > player_pos.y + 800 { // Remove platforms far below | |
ordered_remove(&platforms, i) | |
} | |
} | |
// Score increases as player climbs higher (negative Y = higher up) | |
new_score := i32(max(0, abs(highest_y - 500) / 20)) // 20 pixels = 1 point | |
if new_score > score { | |
score = new_score | |
} | |
// Game over if player falls too far below starting point | |
if player_pos.y > 800 { | |
game_over = true | |
} | |
// Render | |
rl.BeginDrawing() | |
defer rl.EndDrawing() | |
// Background gradient effect - darker as you go higher | |
bg_color := rl.Color{ | |
u8(max(0, 20 + i32(player_pos.y) / 10)), | |
u8(max(0, 30 + i32(player_pos.y) / 15)), | |
u8(max(10, 60 + i32(player_pos.y) / 8)), | |
255 | |
} | |
rl.ClearBackground(bg_color) | |
// Apply camera offset for vertical movement | |
rl.BeginMode2D({{0, camera_offset}, {0, 0}, 0, 1}) | |
// Draw platforms | |
for platform in platforms { | |
rl.DrawRectangleV(platform.pos, platform.size, rl.BROWN) | |
rl.DrawRectangleLinesEx({platform.pos.x, platform.pos.y, platform.size.x, platform.size.y}, 2, rl.ORANGE) | |
} | |
// Draw player (green when grounded, red when airborne) | |
player_color := is_grounded ? rl.GREEN : rl.RED | |
rl.DrawRectangleV(player_pos, player_size, player_color) | |
rl.DrawRectangleLinesEx({player_pos.x, player_pos.y, player_size.x, player_size.y}, 2, rl.WHITE) | |
// Draw height markers every 100 pixels | |
for h := f32(0); h > player_pos.y - 600; h -= 100 { | |
height_m := abs(h - 500) / 20 | |
marker_text := fmt.ctprintf("%.0fm", height_m) | |
rl.DrawText(marker_text, 10, i32(h), 20, rl.LIGHTGRAY) | |
rl.DrawLine(0, i32(h), 50, i32(h), rl.LIGHTGRAY) | |
} | |
rl.EndMode2D() | |
// Draw UI (fixed to screen) | |
score_text := fmt.ctprintf("Score: %d", score) | |
rl.DrawText(score_text, 10, 10, 30, rl.WHITE) | |
height_text := fmt.ctprintf("Height: %.0fm", abs(highest_y - 500) / 20) | |
rl.DrawText(height_text, 10, 50, 25, rl.YELLOW) | |
rl.DrawText("A/D: Move | Space/J: Jump | Climb as high as you can!", 10, 550, 18, rl.WHITE) | |
status_text := is_grounded ? "GROUNDED" : "AIRBORNE" | |
status_color := is_grounded ? rl.GREEN : rl.RED | |
rl.DrawText(cstring(raw_data(status_text)), 600, 10, 20, status_color) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment