diff --git a/app/scenes/_list.rb b/app/scenes/_list.rb index 05b860c..15b67d2 100644 --- a/app/scenes/_list.rb +++ b/app/scenes/_list.rb @@ -5,7 +5,7 @@ module Scene DEFAULT: MainMenu, main_menu: MainMenu, settings: SettingsMenu, - #paused: PauseMenu, - #cube_tube: CubeTube + paused: PauseMenu, + cube_tube: CubeTubeGame } end diff --git a/app/scenes/cube_tube.rb b/app/scenes/cube_tube.rb index cc0ae9b..4e0fa3a 100644 --- a/app/scenes/cube_tube.rb +++ b/app/scenes/cube_tube.rb @@ -1,22 +1,12 @@ # frozen_string_literal: true -# Extension to the scene class for the cube tube scene -module Scene - class << self - def tick_cube_tube(args) - # call the gameplay scene tick method (handles pause menu, etc) - tick_gameplay(args) - args.state.game ||= CubeTubeGame.new args - args.state.game.tick - end - end -end +# this is the main gameplay! +class CubeTubeGame < GameplayScene + def initialize(args, opts = {}) + super -# -class CubeTubeGame - def initialize args @args = args - + @blocksize = 30 @grid_w = 10 @grid_h = 20 @@ -59,10 +49,371 @@ class CubeTubeGame @current_music = :music1 - reset_game + reset(args) end - def reset_game + def render_background + # draw a solid black background + @args.outputs.solids << [ + 0, + 0, + 1280, + 1280, + 0, 0, 0 + ] + + @bg_x += (@level + 1) * 2 unless @gameover + @bg_x %= @bg_w if @bg_x >= @bg_w + + Sprite.for(:tunnel).render(@args, { x: @bg_x, y: 0, w: @bg_w, h: 720 }) + Sprite.for(:tunnel).render(@args, { x: @bg_x - @bg_w, y: 0, w: @bg_w, h: 720 }) + + Sprite.for(:train).render(@args, { x: 0, y: @grid_x - 64 }) + end + + def render_foreground + Sprite.for(:train_fore).render(@args, { x: 0, y: @grid_x - 64 }) + end + + # x and y are positions in the grid, not pixels + def render_block(x, y, color) + @sprite_index[color].render( + @args, + { + x: (1280 - @grid_y) - (y * @blocksize) - 6, + y: @grid_x + (x * @blocksize), + w: @blocksize + 6, + h: @blocksize + } + ) + end + + def render_grid + (0..@grid_w - 1).each do |x| + (0..@grid_h - 1).each do |y| + if @grid[x][y] != 0 && (!@lines_to_clear.include?(y) || (@line_clear_timer % 14) < 7) + render_block(x, y, @grid[x][y]) + end + end + end + end + + def render_piece(piece, piece_x, piece_y) + (0..piece.length - 1).each do |x| + (0..piece[x].length - 1).each do |y| + render_block(piece_x + x, piece_y + y, piece[x][y]) if piece[x][y] != 0 + end + end + end + + def render_current_piece + render_piece(@current_piece, @current_piece_x, @current_piece_y) if @line_clear_timer <= 0 + end + + def render_next_piece + screen_x = @grid_y + 400 + screen_y = @grid_x + 80 + screen_w = 250 + screen_h = 200 + + Sprite.for(:screen).render(@args, { x: screen_x, y: screen_y + 10, w: screen_w, h: screen_h + 10 }) + + next_piece = @line_clear_timer <= 0 ? @next_piece : @current_piece + centerx = (@next_piece_box[2] - next_piece.length) / 2 + centery = (@next_piece_box[3] - next_piece[0].length) / 2 + + render_piece next_piece, @next_piece_box[0] + centerx, @next_piece_box[1] + centery + + @args.outputs.labels << [screen_x + 33, screen_y + screen_h - 8, 'Next piece', 8, 255, 255, 255, 255 ] + + screen_s = + case (@args.state.tick_count % 32) + when 0..7 + :screen_s1 + when 8..15 + :screen_s2 + when 16..23 + :screen_s3 + when 24..31 + :screen_s4 + end + + Sprite.for(screen_s).render(@args, { x: screen_x, y: screen_y + 10, w: screen_w, h: screen_h + 10 }) + end + + def render_score + @args.outputs.labels << [200, 600, "Lines: #{@lines}", 10, 255, 255, 255, 255] + @args.outputs.labels << [400, 600, "Level: #{@level}", 10, 255, 255, 255, 255] + end + + def render_gameover + @args.outputs.solids << [0, 245, 1280, 200, 0, 0, 0, 255] + @args.outputs.labels << [200, 450, 'GAME OVER', 100, 255, 255, 255, 255] + end + + def render + render_background + render_next_piece + render_current_piece + render_grid + render_foreground + render_score + render_gameover if @showgameover + end + + def current_piece_colliding + (0..@current_piece.length - 1).each do |x| + (0..@current_piece[x].length - 1).each do |y| + next if @current_piece[x][y].zero? + if (@current_piece_y + y >= @grid_h) || + ((@current_piece_y + y) >= 0 && @grid[@current_piece_x + x][@current_piece_y + y] != 0) + return true + end + end + end + false + end + + def get_speed + case @level + when 0 then 53 + when 1 then 49 + when 2 then 45 + when 3 then 41 + when 4 then 37 + when 5 then 33 + when 6 then 28 + when 7 then 22 + when 8 then 17 + when 9 then 11 + when 10 then 10 + when 11 then 9 + when 12 then 8 + when 13 then 7 + when 14..15 then 6 + when 16..17 then 5 + when 18..19 then 4 + else 3 + end + end + + def change_music + @current_music = @current_music == :music1 ? :music2 : :music1 + Music.queue_up(@current_music) + end + + def line_full?(row) + (0..@grid_w - 1).each do |x| + return false if @grid[x][row].zero? + end + true + end + + def plant_current_piece + (0..@current_piece.length - 1).each do |x| + (0..@current_piece[x].length - 1).each do |y| + next if @current_piece[x][y].zero? + + @grid[@current_piece_x + x][@current_piece_y + y] = @current_piece[x][y] + end + end + + @lines_to_clear = [] + + # see if any rows need to be cleared out + (0..@grid_h - 1).each do |y| + next unless line_full?(y) + + # no empty space in the row + @lines_to_clear.push y + @lines += 1 + @line_clear_timer = 70 + if (@lines % 10).floor.zero? + @level += 1 + change_music + end + end + + select_next_piece + if @lines_to_clear.empty? + Sound.play(@args, :drop) + if current_piece_colliding + @gameover = true + Music.stop(@args) + end + else + Sound.play(@args, :clear) + @current_speed = get_speed + end + + @next_move = @current_speed + 2 + end + + def select_next_piece + @current_piece = @next_piece + + @current_piece_x = 4 + @current_piece_y = -1 + + r = (rand 7) + 1 + @next_piece = + case r + when 1 then [[r, 0], [r, r], [0, r]] + when 2 then [[0, r], [r, r], [r, 0]] + when 3 then [[r, r, r], [r, 0, 0]] + when 4 then [[r, r], [r, r] ] + when 5 then [[r], [r], [r], [r]] + when 6 then [[r, 0], [r, r], [r, 0]] + when 7 then [[r, 0, 0], [r, r, r]] + end + select_next_piece if @current_piece.nil? + end + + def rotate_current_piece_left + Sound.play(@args, :rotate) + @current_piece = @current_piece.transpose.map(&:reverse) + @current_piece_x = @grid_w - @current_piece.length if (@current_piece_x + @current_piece.length) >= @grid_w + end + + def rotate_current_piece_right + Sound.play(@args, :rotate) + @current_piece = @current_piece.transpose.map(&:reverse) + @current_piece = @current_piece.transpose.map(&:reverse) + @current_piece = @current_piece.transpose.map(&:reverse) + end + + def fill_grid + b = false + (0..@grid_h - 1).each do |y| + (0..@grid_w - 1).each do |x| + if @grid[x][y].zero? + @grid[x][y] = (rand 7) + 1 + b = true + end + end + return nil if b + end + @showgameover = true + end + + def restart_game + reset(@args) + end + + def move_current_piece_up + if (@current_piece_x + @current_piece.length) < @grid_w + @current_piece_x += 1 + if current_piece_colliding + Sound.play(@args, :move_deny) + @current_piece_x -= 1 + else + Sound.play(@args, :move) + end + else + Sound.play(@args, :move_deny) + end + end + + def move_current_piece_down + if @current_piece_x.positive? + @current_piece_x -= 1 + if current_piece_colliding + Sound.play(@args, :move_deny) + @current_piece_x += 1 + else + Sound.play(@args, :move) + end + else + Sound.play(@args, :move_deny) + end + end + + def iterate_line_clear + return false unless @line_clear_timer.positive? + + @line_clear_timer -= 1 + return true unless @line_clear_timer.zero? + + @lines_to_clear.each do |y| + y.downto(1).each do |i| + (0..@grid_w - 1).each do |j| + @grid[j][i] = @grid[j][i - 1] + end + end + (0..@grid_w - 1).each do |i| + @grid[i][0] = 0 + end + end + Sound.play(@args, :drop) + @lines_to_clear = [] + false + end + + def iterate_input + restart_game if Input.pressed?(@args, :primary) && @gameover && @showgameover + return if @gameover + + move_current_piece_down if Input.pressed?(@args, :down) + move_current_piece_up if Input.pressed?(@args, :up) + @next_move -= @current_speed / 3 if Input.pressed_or_held?(@args, :left) + rotate_current_piece_left if Input.pressed?(@args, :rotate_left) + rotate_current_piece_right if Input.pressed?(@args, :rotate_right) + end + + # train bounce effect + def iterate_train_bounce + # TODO: time it better to the music + case @args.state.tick_count % (@current_speed * 6) + when 0..3, (@current_speed * 2)..((@current_speed * 2) + 3) + @grid_x = @start_grid_x + 3 + else + @grid_x = @start_grid_x + end + end + + def iterate_movement + @next_move -= 1 + return unless @next_move <= 0 + + @next_move = @current_speed + @current_piece_y += 1 + + return unless current_piece_colliding + + @current_piece_y -= 1 + plant_current_piece + end + + def iterate + # input first + iterate_input + + fill_grid if @gameover && !@showgameover + # skip the rest if it's game over + return if @gameover + + # resume music if it's paused + Music.resume(@args) if !Music.stopped(@args) && Music.paused(@args) + + iterate_train_bounce + + # if we're currently animating a line clear, then skip the movement logic + return if iterate_line_clear + + iterate_movement + end + + # called every tick of the game loop + def tick(args) + iterate + render + super + end + + # custom logic to reset this scene + def reset(args) + super + @lines = 0 @level = 0 @current_speed = get_speed @@ -88,393 +439,7 @@ class CubeTubeGame end @current_music = :music1 - Music.play(@args, @current_music) + Music.play(args, @current_music) Music.set_volume(args, args.state.setting.music ? 0.8 : 0.0) end - - def render_grid_border x, y, w, h, color - - for i in x..(x+w)-1 do - render_block i, y, color - render_block i, (y + h - 1), color - end - - for i in y..(y+h)-1 do - render_block x, i, color - render_block (x + w - 1), i, color - end - end - - def render_background - # draw a solid black background - @args.outputs.solids << [ - 0, - 0, - 1280, - 1280, - 0, 0, 0 - ] - - @bg_x += (@level+1)*2 unless @gameover - if(@bg_x >= @bg_w) - @bg_x %= @bg_w - end - - Sprite.for(:tunnel).render(@args, { x: @bg_x, y: 0, w: @bg_w, h: 720 }) - Sprite.for(:tunnel).render(@args, { x: @bg_x - @bg_w, y: 0, w: @bg_w, h: 720 }) - - Sprite.for(:train).render(@args, { x: 0, y: @grid_x - 64 }) - end - - def render_foreground - Sprite.for(:train_fore).render(@args, { x: 0, y: @grid_x - 64 }) - end - - # x and y are positions in the grid, not pixels - def render_block x, y, color - @sprite_index[color].render( - @args, - { - x: (1280 - @grid_y) - (y * @blocksize) - 6, - y: @grid_x + (x * @blocksize), - w: @blocksize + 6, - h: @blocksize - } - ) - end - - def render_grid - - #render_grid_border -1, -1, @grid_w + 2, @grid_h + 2, 8 - - for x in 0..@grid_w-1 do - for y in 0..@grid_h-1 do - render_block x, y, @grid[x][y] if @grid[x][y] != 0 && (!@lines_to_clear.include?(y) || (@line_clear_timer % 14) < 7) - end - end - - end - - def render_piece piece, piece_x, piece_y - - for x in 0..piece.length-1 do - for y in 0..piece[x].length-1 do - render_block piece_x + x, piece_y + y, piece[x][y] if piece[x][y] != 0 - end - end - end - - def render_current_piece - render_piece @current_piece, @current_piece_x, @current_piece_y if @line_clear_timer <= 0 - end - - def render_next_piece - - screen_x = @grid_y + 400 - screen_y = @grid_x + 80 - screen_w = 250 - screen_h = 200 - - Sprite.for(:screen).render(@args, { x: screen_x, y: screen_y + 10, w: screen_w, h: screen_h + 10 }) - - next_piece = @line_clear_timer <= 0 ? @next_piece : @current_piece - # render_grid_border *@next_piece_box, 8 - centerx = (@next_piece_box[2] - next_piece.length) / 2 - centery = (@next_piece_box[3] - next_piece[0].length) / 2 - - render_piece next_piece, @next_piece_box[0] + centerx, @next_piece_box[1] + centery - - @args.outputs.labels << [screen_x + 33, screen_y + screen_h - 8, "Next piece", 8, 255, 255, 255, 255 ] - - screen_s = case (@args.state.tick_count % 32) - when 0..7 - :screen_s1 - when 8..15 - :screen_s2 - when 16..23 - :screen_s3 - when 24..31 - :screen_s4 - end - - Sprite.for(screen_s).render(@args, { x: screen_x, y: screen_y + 10, w: screen_w, h: screen_h + 10 }) - end - - def render_score - @args.outputs.labels << [ 200, 600, "Lines: #{@lines}", 10, 255, 255, 255, 255 ] - @args.outputs.labels << [ 400, 600, "Level: #{@level}", 10, 255, 255, 255, 255 ] - @args.outputs.labels << [ 400, 563, "(Speed: #{(1/(@current_speed/60)).round 2} L/s)", 0.1, 255, 255, 255, 255 ] - end - - def render_gameover - @args.outputs.solids << [ 0, 245, 1280, 200, 0, 0, 0, 255] - @args.outputs.labels << [ 200, 450, "GAME OVER", 100, 255, 255, 255, 255 ] - end - - def render - render_background - render_next_piece - render_current_piece - render_grid - render_foreground - render_score - render_gameover if @showgameover - end - - def current_piece_colliding - for x in 0..@current_piece.length-1 do - for y in 0..@current_piece[x].length-1 do - if (@current_piece[x][y] != 0) && ((@current_piece_y + y >= @grid_h) || ((@current_piece_y + y) >= 0 && @grid[@current_piece_x + x][@current_piece_y + y] != 0 )) - return true - end - end - end - return false - end - - def get_speed - return case @level - when 0 then 53 - when 1 then 49 - when 2 then 45 - when 3 then 41 - when 4 then 37 - when 5 then 33 - when 6 then 28 - when 7 then 22 - when 8 then 17 - when 9 then 11 - when 10 then 10 - when 11 then 9 - when 12 then 8 - when 13 then 7 - when 14 then 6 - when 15 then 6 - when 16 then 5 - when 17 then 5 - when 18 then 4 - when 19 then 4 - when 20 then 3 - else 3 - end - end - - def change_music - if @current_music == :music1 - @current_music = :music2 - else - @current_music = :music1 - end - queue = Music.queue - Music.queue_up(@current_music) - end - - def plant_current_piece - rows_to_check = [] - - for x in 0..@current_piece.length-1 do - for y in 0..@current_piece[x].length-1 do - if @current_piece[x][y] != 0 - col = @current_piece_x + x - row = @current_piece_y + y - @grid[col][row] = @current_piece[x][y] - rows_to_check << row if !rows_to_check.include? row - end - end - end - - @lines_to_clear = [] - - # see if any rows need to be cleared out - for y in 0..@grid_h-1 - full = true - for x in 0..@grid_w-1 - if @grid[x][y] == 0 - full = false - break - end - end - - if full # no empty space in the row - @lines_to_clear.push y - @lines += 1 - @line_clear_timer = 70 - if (@lines%10).floor == 0 - @level += 1 - change_music - end - end - end - - select_next_piece - if @lines_to_clear.empty? - Sound.play(@args, :drop) - if current_piece_colliding - @gameover = true - Music.stop(@args) - end - else - Sound.play(@args, :clear) - @current_speed = get_speed - end - - @next_move = @current_speed + 2 - end - - def select_next_piece - - @current_piece = @next_piece - - @current_piece_x = 4 - @current_piece_y = -1 - - r = (rand 7) + 1 - @next_piece = case r - when 1 then [ [r, 0], [r, r], [0, r]] - when 2 then [ [0, r], [r, r], [r, 0]] - when 3 then [ [r, r, r], [r, 0, 0]] - when 4 then [ [r, r], [r, r] ] - when 5 then [ [r], [r], [r], [r]] - when 6 then [ [r, 0], [r, r], [r, 0]] - when 7 then [ [r, 0, 0], [r, r, r]] - end - select_next_piece if @current_piece == nil - end - - def rotate_current_piece_left - Sound.play(@args, :rotate) - @current_piece = @current_piece.transpose.map(&:reverse) - if(@current_piece_x + @current_piece.length) >= @grid_w - @current_piece_x = @grid_w - @current_piece.length - end - end - - def rotate_current_piece_right - Sound.play(@args, :rotate) - @current_piece = @current_piece.transpose.map(&:reverse) - @current_piece = @current_piece.transpose.map(&:reverse) - @current_piece = @current_piece.transpose.map(&:reverse) - end - - def fill_grid - b = false - for y in 0..@grid_h-1 do - for x in 0..@grid_w-1 do - if @grid[x][y] == 0 - @grid[x][y] = (rand 7) + 1 - b = true - end - end - return if b - end - @showgameover = true - end - - def restart_game - reset_game - end - - def iterate - - # check input first - k = @args.inputs.keyboard - c = @args.inputs.controller_one - - if @gameover - if @showgameover - if k.key_down.space || k.key_down.enter || c.key_down.start - restart_game - end - else - fill_grid - end - return - end - - unless Music.stopped(@args) - Music.resume(@args) if Music.paused(@args) - end - - if @line_clear_timer.positive? - @line_clear_timer -= 1 - if @line_clear_timer.zero? - for y in @lines_to_clear - for i in y.downto(1) do - for j in 0..@grid_w-1 - @grid[j][i] = @grid[j][i-1] - end - end - for i in 0..@grid_w-1 - @grid[i][0] = 0 - end - end - Sound.play(@args, :drop) - @lines_to_clear = [] - end - return - end - - if k.key_down.down || k.key_down.s || c.key_down.down - if @current_piece_x > 0 - @current_piece_x -= 1 - if current_piece_colliding - Sound.play(@args, :move_deny) - @current_piece_x += 1 - else - Sound.play(@args, :move) - end - else - Sound.play(@args, :move_deny) - end - end - if k.key_down.up || k.key_down.w || c.key_down.up - if (@current_piece_x + @current_piece.length) < @grid_w - @current_piece_x += 1 - if current_piece_colliding - Sound.play(@args, :move_deny) - @current_piece_x -= 1 - else - Sound.play(@args, :move) - end - else - Sound.play(@args, :move_deny) - end - end - if k.key_down.left || k.key_held.left || k.key_down.a || k.key_held.a || c.key_down.left || c.key_held.left - @next_move -= @current_speed / 3 - end - if k.key_down.plus || k.key_down.equal_sign - @level += 1 - @current_speed = get_speed - end - - if k.key_down.q || c.key_down.a - rotate_current_piece_left - end - if k.key_down.e || c.key_down.b - rotate_current_piece_right - end - - case @args.state.tick_count % (@current_speed * 6) - when 0..3, (@current_speed * 2)..((@current_speed * 2) + 3) - @grid_x = @start_grid_x + 3 - else - @grid_x = @start_grid_x - end - - @next_move -= 1 - if @next_move <= 0 - @next_move = @current_speed - @current_piece_y += 1 - if current_piece_colliding - @current_piece_y -= 1 - plant_current_piece - end - end - end - - def tick - iterate - render - end -end \ No newline at end of file +end diff --git a/app/scenes/gameplay.rb b/app/scenes/gameplay.rb index 49b408b..e7c1aed 100644 --- a/app/scenes/gameplay.rb +++ b/app/scenes/gameplay.rb @@ -1,54 +1,51 @@ -module Scene - class << self - # This is your main entrypoint into the actual fun part of your game! - def tick_gameplay(args) - labels = [] - sprites = [] +# frozen_string_literal: true - # focus tracking - if !args.state.has_focus && args.inputs.keyboard.has_focus - args.state.has_focus = true - elsif args.state.has_focus && !args.inputs.keyboard.has_focus - args.state.has_focus = false - end +# This is a base class for the main gameplay, handling things like pausing +class GameplayScene < SceneInstance + def initialize(_args, opts = {}) + super + end - # auto-pause & input-based pause - if !args.state.has_focus || Input.pressed?(args, :pause) - return pause(args) - end + # called every tick of the game loop + def tick(args) + super - tick_pause_button(args, sprites) if mobile? - - draw_bg(args, BLACK) - - # labels << label("GAMEPLAY", x: 40, y: args.grid.top - 40, size: SIZE_LG, font: FONT_BOLD) - args.outputs.labels << labels - args.outputs.sprites << sprites + # focus tracking + if !args.state.has_focus && args.inputs.keyboard.has_focus + args.state.has_focus = true + elsif args.state.has_focus && !args.inputs.keyboard.has_focus + args.state.has_focus = false end - def pause(args) - Sound.play(args, :select) - return Scene.push(args, :paused, reset: true) - end + # auto-pause & input-based pause + return pause(args) if !args.state.has_focus || Input.pressed?(args, :pause) - def tick_pause_button(args, sprites) - pause_button = { - x: 72.from_right, - y: 72.from_top, - w: 52, - h: 52, - path: Sprite.for(:pause), - } - pause_rect = pause_button.dup - pause_padding = 12 - pause_rect.x -= pause_padding - pause_rect.y -= pause_padding - pause_rect.w += pause_padding * 2 - pause_rect.h += pause_padding * 2 - if args.inputs.mouse.down && args.inputs.mouse.inside_rect?(pause_rect) - return pause(args) - end - sprites << pause_button - end + tick_pause_button(args) if mobile? + + draw_bg(args, BLACK) + end + + def pause(args) + Sound.play(args, :select) + Scene.push(args, :paused, reset: true) + end + + def tick_pause_button(args) + pause_button = { + x: 72.from_right, + y: 72.from_top, + w: 52, + h: 52, + path: Sprite.for(:pause) + } + pause_rect = pause_button.dup + pause_padding = 12 + pause_rect.x -= pause_padding + pause_rect.y -= pause_padding + pause_rect.w += pause_padding * 2 + pause_rect.h += pause_padding * 2 + return pause(args) if args.inputs.mouse.down && args.inputs.mouse.inside_rect?(pause_rect) + + args.outputs.sprites << pause_button end end diff --git a/app/scenes/menu.rb b/app/scenes/menu.rb index 64921d0..e0a6fc3 100644 --- a/app/scenes/menu.rb +++ b/app/scenes/menu.rb @@ -55,7 +55,7 @@ class MenuScene < SceneInstance button_border = { w: 340, h: 80, x: l.x - 170, y: l.y - 55 }.merge(WHITE) (args.outputs.borders << button_border) if mobile? if args.inputs.mouse.up && args.inputs.mouse.inside_rect?(button_border) - o = options.find { |o| o[:key] == l[:key] } + o = @menu_options.find { |o| o[:key] == l[:key] } Sound.play(args, :menu) o[:on_select].call(args) if o end @@ -105,6 +105,11 @@ class MenuScene < SceneInstance @menu_options[@menu_state.current_option_i][:on_select].call(args) Sound.play(args, :select) end + + if Input.pressed?(args, :secondary) && !Scene.stack(args).empty? + Sound.play(args, :select) + Scene.pop(args) + end end # custom logic to reset this scene diff --git a/app/scenes/paused.rb b/app/scenes/paused.rb index 3eedb62..33967ff 100644 --- a/app/scenes/paused.rb +++ b/app/scenes/paused.rb @@ -1,42 +1,46 @@ -module Scene - class << self - # scene reached from gameplay when the player needs a break - def tick_paused(args) - draw_bg(args, DARK_YELLOW) +# frozen_string_literal: true - options = [ - { - key: :resume, - on_select: -> (args) { Scene.pop(args) } - }, - { - key: :settings, - on_select: -> (args) { Scene.push(args, :settings, reset: true) } - }, - { - key: :return_to_main_menu, - on_select: -> (args) { Scene.switch(args, :main_menu) } - }, - ] +# This is the pause menu, triggered by a button press when the player wants a +# break from gameplay. +class PauseMenu < MenuScene + def initialize(args, opts = {}) + menu_options = [ + { + key: :resume, + on_select: ->(args) { Scene.pop(args) } + }, + { + key: :settings, + on_select: ->(args) { Scene.push(args, :settings, reset: true) } + }, + { + key: :return_to_main_menu, + on_select: ->(args) { Scene.switch(args, :main_menu) } + } + ] - if args.gtk.platform?(:desktop) - options << { - key: :quit, - on_select: -> (args) { args.gtk.request_quit } - } - end - - Menu.tick(args, :paused, options) - - Music.pause(args) unless Music.stopped(args) - - if Input.pressed?(args, :secondary) - Sound.play(args, :select) - options.find { |o| o[:key] == :resume }[:on_select].call(args) - - end - - args.outputs.labels << label(:paused, x: args.grid.w / 2, y: args.grid.top - 200, align: ALIGN_CENTER, size: SIZE_LG, font: FONT_BOLD) + if args.gtk.platform?(:desktop) + menu_options << { + key: :quit, + on_select: ->(args) { args.gtk.request_quit } + } end + + super args, opts, menu_options + end + + # called every tick of the game loop + def tick(args) + super + Music.pause(args) unless Music.stopped(args) || Music.paused(args) + + args.outputs.labels << label( + :paused, + x: args.grid.w / 2, + y: args.grid.top - 200, + align: ALIGN_CENTER, + size: SIZE_LG, + font: FONT_BOLD + ) end end diff --git a/app/scenes/settings.rb b/app/scenes/settings.rb index 8a522d0..4ea08f6 100644 --- a/app/scenes/settings.rb +++ b/app/scenes/settings.rb @@ -55,11 +55,6 @@ class SettingsMenu < MenuScene # actual menu logic is handled by the MenuScene super class super - if Input.pressed?(args, :secondary) - Sound.play(args, :select) - @menu_options.find { |o| o[:key] == :back }[:on_select].call(args) - end - args.outputs.labels << label( :settings, x: args.grid.w / 2, diff --git a/app/util/scene.rb b/app/util/scene.rb index c9967ae..f56656c 100644 --- a/app/util/scene.rb +++ b/app/util/scene.rb @@ -19,10 +19,16 @@ module Scene args.state.scene_stack ||= [] # if we're here /not/ from push or pop, clear the scene stack args.state.scene_stack.clear unless push_or_pop - + # if `scene` is not a `SceneInstance`, it's probably a symbol representing # the scene we're switching to, so go get it. the_scene = scene.is_a?(SceneInstance) ? scene : SCENES[scene].new(args) + puts '---' + puts 'switching to' + puts scene unless scene.is_a?(SceneInstance) + puts SCENES[scene] unless scene.is_a?(SceneInstance) + puts the_scene + puts '---' # if the stack is empty (e.g. we just cleared it), then push this scene args.state.scene_stack.push(the_scene) if args.state.scene_stack.empty? @@ -53,5 +59,9 @@ module Scene args.state.scene_stack ||= [] SCENES[:DEFAULT].new(args) end + + def stack(args) + args.state.scene_stack ||= [] + end end end