215 lines
6.3 KiB
Odin
215 lines
6.3 KiB
Odin
package game
|
|
|
|
import "core:strings"
|
|
import "core:math/rand"
|
|
import rl "vendor:raylib"
|
|
|
|
VIEWPORT_W :: 640
|
|
VIEWPORT_H :: 360
|
|
WINDOW_TITLE :: "Pong"
|
|
|
|
Resolution :: struct {
|
|
width: i32,
|
|
height: i32,
|
|
label: cstring,
|
|
}
|
|
|
|
// Preset window dimensions
|
|
RESOLUTIONS := [?]Resolution{
|
|
{1280, 720, "1280x720"},
|
|
{1920, 1080, "1920x1080"},
|
|
{2560, 1440, "2560x1440"},
|
|
{3840, 2160, "3840x2160"},
|
|
}
|
|
|
|
Game :: struct {
|
|
viewport: rl.RenderTexture2D,
|
|
bg_texture: rl.Texture2D,
|
|
crt_shader: rl.Shader,
|
|
scene_stack: [dynamic]Scene,
|
|
should_close: bool,
|
|
audio: Audio_Manager,
|
|
|
|
// Camera state for screen effects / screen shake
|
|
camera: rl.Camera2D,
|
|
shake_intensity: f32,
|
|
shake_timer: f32,
|
|
|
|
// resolution state
|
|
window_w: i32,
|
|
window_h: i32,
|
|
res_index: int
|
|
}
|
|
|
|
// AO: lets me load this at compile time!
|
|
gamepad_compat := #load("../gamecontrollerdb.txt", string)
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// Game Lifecycle
|
|
// ------------------------------------------------------------------------------
|
|
game_init :: proc(game: ^Game) {
|
|
rl.SetConfigFlags({.WINDOW_ALWAYS_RUN, .MSAA_4X_HINT})
|
|
game.res_index = 2
|
|
game.window_w = RESOLUTIONS[game.res_index].width
|
|
game.window_h = RESOLUTIONS[game.res_index].height
|
|
|
|
rl.InitWindow(game.window_w, game.window_h, WINDOW_TITLE)
|
|
rl.SetTargetFPS(60)
|
|
rl.SetExitKey(.KEY_NULL)
|
|
|
|
gamepad_cstr := strings.clone_to_cstring(gamepad_compat)
|
|
defer delete(gamepad_cstr)
|
|
rl.SetGamepadMappings(gamepad_cstr)
|
|
|
|
// init any global managers
|
|
audio_init(&game.audio)
|
|
|
|
// allocate some event capacity to stop early dynamic reallocations
|
|
GLOBAL_EVENTS = make([dynamic]Event, 0, 16)
|
|
|
|
game.scene_stack = make([dynamic]Scene, 0, 8)
|
|
game.viewport = rl.LoadRenderTexture(VIEWPORT_W, VIEWPORT_H)
|
|
|
|
// Look into texture filters
|
|
rl.SetTextureFilter(game.viewport.texture, .BILINEAR)
|
|
|
|
// Generate soft CRT background radial texture once on startup
|
|
bg_center := rl.Color{22, 28, 36, 255}
|
|
bg_corner := rl.Color{6, 8, 12, 255}
|
|
bg_img := rl.GenImageGradientRadial(VIEWPORT_W, VIEWPORT_H, 0.3, bg_center, bg_corner)
|
|
game.bg_texture = rl.LoadTextureFromImage(bg_img)
|
|
rl.UnloadImage(bg_img) // Free CPU memory
|
|
|
|
game.crt_shader = rl.LoadShader(nil, "assets/shaders/crt.fs")
|
|
|
|
// Setup 2D camera matching the 640x360 viewport center
|
|
game.camera = rl.Camera2D{
|
|
target = rl.Vector2{VIEWPORT_W / 2.0, VIEWPORT_H / 2.0},
|
|
offset = rl.Vector2{VIEWPORT_W / 2.0, VIEWPORT_H / 2.0},
|
|
rotation = 0.0,
|
|
zoom = 1.0,
|
|
}
|
|
|
|
game.should_close = false
|
|
set_root_scene(game, make_main_menu())
|
|
}
|
|
|
|
game_update :: proc(game: ^Game) {
|
|
dt := rl.GetFrameTime()
|
|
input := poll_input()
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Update Camera Shake
|
|
// -------------------------------------------------------------------------
|
|
base_center := rl.Vector2{VIEWPORT_W / 2.0, VIEWPORT_H / 2.0}
|
|
|
|
if game.shake_timer > 0 {
|
|
game.shake_timer -= dt
|
|
|
|
// Generate random offset within current intensity bounds
|
|
offset_x := (f32(rand.float32()) * 2.0 - 1.0) * game.shake_intensity
|
|
offset_y := (f32(rand.float32()) * 2.0 - 1.0) * game.shake_intensity
|
|
|
|
game.camera.target = base_center + rl.Vector2{offset_x, offset_y}
|
|
|
|
if game.shake_timer <= 0 {
|
|
game.shake_intensity = 0.0
|
|
game.camera.target = base_center
|
|
}
|
|
} else {
|
|
game.camera.target = base_center
|
|
}
|
|
|
|
active_scene := get_active_scene(game)
|
|
if active_scene != nil {
|
|
scene_update(active_scene, game, input, dt)
|
|
}
|
|
|
|
// background music buffer streaming
|
|
audio_update(&game.audio, dt)
|
|
|
|
// flush any events triggered in this loop
|
|
flush_events()
|
|
}
|
|
|
|
game_render :: proc(game: ^Game) {
|
|
rl.BeginDrawing()
|
|
rl.ClearBackground(rl.BLACK)
|
|
|
|
rl.BeginTextureMode(game.viewport)
|
|
|
|
rl.DrawTexture(game.bg_texture, 0, 0, rl.WHITE)
|
|
|
|
// World/Scene objects rendered through 2D camera space
|
|
rl.BeginMode2D(game.camera)
|
|
// Render all active items on the stack from bottom to top
|
|
for &scene in game.scene_stack {
|
|
scene_render(&scene)
|
|
}
|
|
rl.EndMode2D()
|
|
|
|
rl.EndTextureMode()
|
|
|
|
render_game_to_window(game)
|
|
|
|
rl.DrawFPS(10, game.window_h - 20)
|
|
rl.EndDrawing()
|
|
}
|
|
|
|
|
|
game_quit :: proc(game: ^Game) {
|
|
audio_manager_quit(&game.audio)
|
|
scene_manager_quit(game)
|
|
event_manager_quit(game)
|
|
|
|
rl.UnloadShader(game.crt_shader)
|
|
rl.UnloadTexture(game.bg_texture)
|
|
rl.UnloadRenderTexture(game.viewport)
|
|
rl.CloseWindow()
|
|
}
|
|
|
|
game_should_run :: proc(game: Game) -> bool {
|
|
return !game.should_close && !rl.WindowShouldClose()
|
|
}
|
|
|
|
game_trigger_shake :: proc(game: ^Game, intensity: f32, duration: f32 = 0.15) {
|
|
game.shake_intensity = max(game.shake_intensity, intensity)
|
|
game.shake_timer = duration
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// Window Upscale Render
|
|
// ------------------------------------------------------------------------------
|
|
game_set_resolution :: proc(game: ^Game, res_index: int) {
|
|
game.res_index = clamp(res_index, 0, len(RESOLUTIONS) - 1)
|
|
|
|
target_w := RESOLUTIONS[game.res_index].width
|
|
target_h := RESOLUTIONS[game.res_index].height
|
|
|
|
monitor := rl.GetCurrentMonitor()
|
|
screen_w := rl.GetMonitorWidth(monitor)
|
|
screen_h := rl.GetMonitorHeight(monitor)
|
|
|
|
// Clamp window size so it doesn't exceed physical screen limits
|
|
game.window_w = min(target_w, screen_w)
|
|
game.window_h = min(target_h, screen_h)
|
|
|
|
rl.SetWindowSize(game.window_w, game.window_h)
|
|
}
|
|
|
|
render_game_to_window :: proc(game: ^Game) {
|
|
texture := game.viewport.texture
|
|
|
|
rl.BeginShaderMode(game.crt_shader)
|
|
rl.DrawTexturePro(
|
|
texture,
|
|
// src: inverted y coordinate flips opengl texture buffer correctly
|
|
rl.Rectangle{0, 0, f32(texture.width), -f32(texture.height)},
|
|
// dst: scales viewport canvas to fill window resolution
|
|
rl.Rectangle{0, 0, f32(game.window_w), f32(game.window_h)},
|
|
rl.Vector2{},
|
|
0,
|
|
rl.WHITE,
|
|
)
|
|
rl.EndShaderMode()
|
|
} |