module main
import vendor.raylib rl
let draw_texture_in_rectangle(
texture: *rl.Texture2D,
destination: rl.Rectangle,
flip_vertical: bool,
) {
let source = rl.Rectangle.{
x = 0.0,
y = if flip_vertical { texture.height.(f32) } else { 0.0 },
width = texture.width.(f32),
height = texture.height.(f32) * if flip_vertical { -1.0 } else { 1.0 },
}
rl.draw_texture_pro(
texture.*,
source,
destination,
.{ x = 0.0, y = 0.0, },
0.0,
rl.White,
)
}
let current_bird_texture(
state: *GameState,
textures: *GameAssets,
): *rl.Texture2D {
if state.bird_velocity_y > 0.25 {
return textures.bird_upflap.&
}
if state.bird_velocity_y < -0.25 {
return textures.bird_downflap.&
}
return textures.bird_midflap.&
}
let render_background(state: *GameState, textures: *GameAssets) {
let texture = textures.background.&
let screen_width = rl.get_screen_width().(f32)
let screen_height = rl.get_screen_height().(f32)
let tile_width = screen_height * texture.width.(f32) / texture.height.(f32)
let scroll_offset = state.background_scroll.(f32) * tile_width
for let mut x = -scroll_offset; x < screen_width; x += tile_width {
draw_texture_in_rectangle(
texture,
.{
x = x,
y = 0.0,
width = tile_width,
height = screen_height,
},
false,
)
}
}
let render_base(state: *GameState, textures: *GameAssets) {
let texture = textures.base.&
let rectangle = base_rectangle()
let tile_width =
rectangle.height * texture.width.(f32) / texture.height.(f32)
let scroll_offset = state.base_scroll.(f32) * tile_width
for let mut x = -scroll_offset; x < rectangle.width; x += tile_width {
draw_texture_in_rectangle(
texture,
.{
x = x,
y = rectangle.y,
width = tile_width,
height = rectangle.height,
},
false,
)
}
}
let render_collision_rectangle(rectangle: rl.Rectangle) {
rl.draw_rectangle_rec(
rectangle,
.{ r = 255, g = 0, b = 0, a = 64, },
)
rl.draw_rectangle_lines_ex(
rectangle,
2.0,
.{ r = 255, g = 0, b = 0, a = 255, },
)
}
let render_collision_boxes(state: *GameState) {
if !state.show_collision_boxes {
return
}
render_collision_rectangle(bird_rectangle(state))
render_collision_rectangle(base_rectangle())
for pipe in state.pipes {
let rectangles = pipe_rectangles(pipe.&)
render_collision_rectangle(rectangles.top)
render_collision_rectangle(rectangles.bottom)
}
}
let render_bird(state: *GameState, textures: *GameAssets) {
let texture = current_bird_texture(state, textures)
let bird = bird_rectangle(state)
let source = rl.Rectangle.{
x = 0.0,
y = 0.0,
width = texture.width.(f32),
height = texture.height.(f32),
}
let destination = rl.Rectangle.{
x = bird.x + bird.width / 2.0,
y = bird.y + bird.height / 2.0,
width = bird.width,
height = bird.height,
}
let origin = rl.Vector2.{
x = destination.width / 2.0,
y = destination.height / 2.0,
}
let rotation = clamp(
-state.bird_velocity_y * 20.0,
-25.0,
75.0,
).(f32)
rl.draw_texture_pro(
texture.*,
source,
destination,
origin,
rotation,
rl.White,
)
}
let draw_pipe_body_tile(
texture: *rl.Texture2D,
destination: rl.Rectangle,
source_height: f32,
flip_vertical: bool,
) {
let source = rl.Rectangle.{
x = 0.0,
y = if flip_vertical { source_height } else { 0.0 },
width = texture.width.(f32),
height = source_height * if flip_vertical { -1.0 } else { 1.0 },
}
rl.draw_texture_pro(
texture.*,
source,
destination,
.{ x = 0.0, y = 0.0, },
0.0,
rl.White,
)
}
let render_pipe_half(
rectangle: rl.Rectangle,
textures: *GameAssets,
flip_vertical: bool,
) {
let body = textures.pipe.&
let head = textures.pipe_head.&
let scale = rectangle.width / body.width.(f32)
let body_tile_height = body.height.(f32) * scale
let head_height = head.height.(f32) * scale
let mut head_y = rectangle.y
if flip_vertical {
head_y = rectangle.y + rectangle.height - head_height
let mut body_bottom = head_y
for body_bottom > rectangle.y {
let remaining_height = body_bottom - rectangle.y
let mut tile_height = body_tile_height
if tile_height > remaining_height {
tile_height = remaining_height
}
draw_pipe_body_tile(
body,
.{
x = rectangle.x,
y = body_bottom - tile_height,
width = rectangle.width,
height = tile_height,
},
tile_height / scale,
true,
)
body_bottom = body_bottom - tile_height
}
} else {
let mut body_top = rectangle.y + head_height
let body_bottom = rectangle.y + rectangle.height
for body_top < body_bottom {
let remaining_height = body_bottom - body_top
let mut tile_height = body_tile_height
if tile_height > remaining_height {
tile_height = remaining_height
}
draw_pipe_body_tile(
body,
.{
x = rectangle.x,
y = body_top,
width = rectangle.width,
height = tile_height,
},
tile_height / scale,
false,
)
body_top = body_top + tile_height
}
}
draw_texture_in_rectangle(
head,
.{
x = rectangle.x,
y = head_y,
width = rectangle.width,
height = head_height,
},
flip_vertical,
)
}
let render_pipe(pipe: *Pipe, textures: *GameAssets) {
let rectangles = pipe_rectangles(pipe)
let screen_width = rl.get_screen_width().(f32)
if rectangles.top.x >= screen_width ||
rectangles.top.x + rectangles.top.width <= 0.0 {
return
}
// Flip the upper pipe so its opening points downwards.
render_pipe_half(rectangles.top, textures, true)
render_pipe_half(rectangles.bottom, textures, false)
}
let render_pipes(state: *GameState, textures: *GameAssets) {
for pipe in state.pipes {
render_pipe(pipe.&, textures)
}
}
let draw_text_with_shadow(
text: cstr,
x, y, font_size: i32,
color: rl.Color,
) {
let shadow_offset = scale_with_window_height(2)
rl.draw_text(
text,
x + shadow_offset,
y + shadow_offset,
font_size,
rl.Black,
)
rl.draw_text(text, x, y, font_size, color)
}
let render_overlay(
title: cstr,
title_color: rl.Color,
instruction: cstr,
second_instruction: cstr,
opacity: u8,
) {
let screen_width = rl.get_screen_width()
let screen_height = rl.get_screen_height()
rl.draw_rectangle(
0,
0,
screen_width,
screen_height,
.{ r = 0, g = 0, b = 0, a = opacity, },
)
let title_font_size = scale_with_window_height(overlay_title_font_size)
let instruction_font_size =
scale_with_window_height(overlay_instruction_font_size)
let title_y_offset = scale_with_window_height(overlay_title_y_offset)
let instruction_y_offset =
scale_with_window_height(overlay_instruction_y_offset)
let title_width = rl.measure_text(title, title_font_size)
draw_text_with_shadow(
title,
(screen_width - title_width) / 2,
screen_height / 2 - title_y_offset,
title_font_size,
title_color,
)
if instruction != nil {
let instruction_width = rl.measure_text(instruction, instruction_font_size)
draw_text_with_shadow(
instruction,
(screen_width - instruction_width) / 2,
screen_height / 2 + instruction_y_offset,
instruction_font_size,
rl.White,
)
}
if second_instruction != nil {
let second_y_offset =
scale_with_window_height(overlay_second_instruction_y_offset)
let second_width =
rl.measure_text(second_instruction, instruction_font_size)
draw_text_with_shadow(
second_instruction,
(screen_width - second_width) / 2,
screen_height / 2 + second_y_offset,
instruction_font_size,
rl.White,
)
}
}
let render_defeat(state: *GameState) {
if state.lost {
render_overlay(
defeat_title,
rl.Red,
nil,
nil,
160,
)
}
}
let render_pause(state: *GameState) {
if state.paused {
render_overlay(
pause_title,
rl.White,
nil,
nil,
160,
)
}
}
let render_begin(state: *GameState) {
if !state.started {
render_overlay(
begin_title,
rl.Yellow,
begin_instruction,
nil,
128,
)
}
}
let render_points(state: *GameState) {
let text = rl.text_format("%u", state.points)
let font_size = scale_with_window_height(32)
let shadow_offset = scale_with_window_height(2)
let text_width = rl.measure_text(text, font_size)
let x = (rl.get_screen_width() - text_width) / 2
let y = (rl.get_screen_height().(f64) * 0.10).(i32)
rl.draw_text(
text,
x + shadow_offset,
y + shadow_offset,
font_size,
rl.Black,
)
rl.draw_text(text, x, y, font_size, rl.White)
}
let render(state: *GameState, textures: *GameAssets) {
render_background(state, textures)
render_pipes(state, textures)
render_base(state, textures)
render_bird(state, textures)
when ReleaseMode == .Debug {
render_collision_boxes(state)
}
render_defeat(state)
render_pause(state)
render_begin(state)
render_points(state)
}