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) }