summaryrefslogtreecommitdiffhomepage
path: root/samples/99_genre_arcade/flappy_dragon/app/main.rb
diff options
context:
space:
mode:
authorAmir Rajan <[email protected]>2020-09-11 02:02:01 -0500
committerAmir Rajan <[email protected]>2020-09-11 02:02:57 -0500
commit33ec37b141e896b47ed642923fd33b0c658ae9fb (patch)
treea40d3e5d41beeb06508200078f6f26b0ee57d6a4 /samples/99_genre_arcade/flappy_dragon/app/main.rb
parent958cf43779d2bf528869e80511c4c4f2a433b2db (diff)
downloaddragonruby-game-toolkit-contrib-33ec37b141e896b47ed642923fd33b0c658ae9fb.tar.gz
dragonruby-game-toolkit-contrib-33ec37b141e896b47ed642923fd33b0c658ae9fb.zip
synced samples
Diffstat (limited to 'samples/99_genre_arcade/flappy_dragon/app/main.rb')
-rw-r--r--samples/99_genre_arcade/flappy_dragon/app/main.rb360
1 files changed, 360 insertions, 0 deletions
diff --git a/samples/99_genre_arcade/flappy_dragon/app/main.rb b/samples/99_genre_arcade/flappy_dragon/app/main.rb
new file mode 100644
index 0000000..56ce3ec
--- /dev/null
+++ b/samples/99_genre_arcade/flappy_dragon/app/main.rb
@@ -0,0 +1,360 @@
+class FlappyDragon
+ attr_accessor :grid, :inputs, :state, :outputs
+
+ def tick
+ defaults
+ render
+ calc
+ process_inputs
+ end
+
+ def defaults
+ state.flap_power = 11
+ state.gravity = 0.9
+ state.ceiling = 600
+ state.ceiling_flap_power = 6
+ state.wall_countdown_length = 100
+ state.wall_gap_size = 100
+ state.wall_countdown ||= 0
+ state.hi_score ||= 0
+ state.score ||= 0
+ state.walls ||= []
+ state.x ||= 50
+ state.y ||= 500
+ state.dy ||= 0
+ state.scene ||= :menu
+ state.scene_at ||= 0
+ state.difficulty ||= :normal
+ state.new_difficulty ||= :normal
+ state.countdown ||= 4.seconds
+ state.flash_at ||= 0
+ end
+
+ def render
+ outputs.sounds << "sounds/flappy-song.ogg" if state.tick_count == 1
+ render_score
+ render_menu
+ render_game
+ end
+
+ def render_score
+ outputs.primitives << [10, 710, "HI SCORE: #{state.hi_score}", large_white_typeset].label
+ outputs.primitives << [10, 680, "SCORE: #{state.score}", large_white_typeset].label
+ outputs.primitives << [10, 650, "DIFFICULTY: #{state.difficulty.upcase}", large_white_typeset].label
+ end
+
+ def render_menu
+ return unless state.scene == :menu
+ render_overlay
+
+ outputs.labels << [640, 700, "Flappy Dragon", 50, 1, 255, 255, 255]
+ outputs.labels << [640, 500, "Instructions: Press Spacebar to flap. Don't die.", 4, 1, 255, 255, 255]
+ outputs.labels << [430, 430, "[Tab] Change difficulty", 4, 0, 255, 255, 255]
+ outputs.labels << [430, 400, "[Enter] Start at New Difficulty ", 4, 0, 255, 255, 255]
+ outputs.labels << [430, 370, "[Escape] Cancel/Resume ", 4, 0, 255, 255, 255]
+ outputs.labels << [640, 300, "(mouse, touch, and game controllers work, too!) ", 4, 1, 255, 255, 255]
+ outputs.labels << [640, 200, "Difficulty: #{state.new_difficulty.capitalize}", 4, 1, 255, 255, 255]
+
+ outputs.labels << [10, 100, "Code: @amirrajan", 255, 255, 255]
+ outputs.labels << [10, 80, "Art: @mobypixel", 255, 255, 255]
+ outputs.labels << [10, 60, "Music: @mobypixel", 255, 255, 255]
+ outputs.labels << [10, 40, "Engine: DragonRuby GTK", 255, 255, 255]
+ end
+
+ def render_overlay
+ outputs.primitives << [grid.rect.scale_rect(1.1, 0, 0), 0, 0, 0, 230].solid
+ end
+
+ def render_game
+ render_game_over
+ render_background
+ render_walls
+ render_dragon
+ render_flash
+ end
+
+ def render_game_over
+ return unless state.scene == :game
+ outputs.labels << [638, 358, score_text, 20, 1]
+ outputs.labels << [635, 360, score_text, 20, 1, 255, 255, 255]
+ outputs.labels << [638, 428, countdown_text, 20, 1]
+ outputs.labels << [635, 430, countdown_text, 20, 1, 255, 255, 255]
+ end
+
+ def render_background
+ outputs.sprites << [0, 0, 1280, 720, 'sprites/background.png']
+
+ scroll_point_at = state.tick_count
+ scroll_point_at = state.scene_at if state.scene == :menu
+ scroll_point_at = state.death_at if state.countdown > 0
+ scroll_point_at ||= 0
+
+ outputs.sprites << scrolling_background(scroll_point_at, 'sprites/parallax_back.png', 0.25)
+ outputs.sprites << scrolling_background(scroll_point_at, 'sprites/parallax_middle.png', 0.50)
+ outputs.sprites << scrolling_background(scroll_point_at, 'sprites/parallax_front.png', 1.00, -80)
+ end
+
+ def render_walls
+ state.walls.each do |w|
+ w.sprites = [
+ [w.x, w.bottom_height - 720, 100, 720, 'sprites/wall.png', 180],
+ [w.x, w.top_y, 100, 720, 'sprites/wallbottom.png', 0]
+ ]
+ end
+ outputs.sprites << state.walls.map(&:sprites)
+ end
+
+ def render_dragon
+ state.show_death = true if state.countdown == 3.seconds
+
+ render_debug_hitbox false
+
+ if state.show_death == false || !state.death_at
+ animation_index = state.flapped_at.frame_index 6, 2, false if state.flapped_at
+ sprite_name = "sprites/dragon_fly#{animation_index.or(0) + 1}.png"
+ state.dragon_sprite = [state.x, state.y, 100, 80, sprite_name, state.dy * 1.2]
+ else
+ sprite_name = "sprites/dragon_die.png"
+ state.dragon_sprite = [state.x, state.y, 100, 80, sprite_name, state.dy * 1.2]
+ sprite_changed_elapsed = state.death_at.elapsed_time - 1.seconds
+ state.dragon_sprite.angle += (sprite_changed_elapsed ** 1.3) * state.death_fall_direction * -1
+ state.dragon_sprite.x += (sprite_changed_elapsed ** 1.2) * state.death_fall_direction
+ state.dragon_sprite.y += (sprite_changed_elapsed * 14 - sprite_changed_elapsed ** 1.6)
+ end
+
+ outputs.sprites << state.dragon_sprite
+ end
+
+ def render_debug_hitbox show
+ return unless show
+ outputs.borders << [dragon_collision_box.rect, 255, 0, 0] if state.dragon_sprite
+ outputs.borders << state.walls.flat_map do |w|
+ w.sprites.map { |s| [s.rect, 255, 0, 0] }
+ end
+ end
+
+ def render_flash
+ return unless state.flash_at
+
+ outputs.primitives << [grid.rect,
+ white,
+ 255 * state.flash_at.ease(20, :flip)].solid
+
+ state.flash_at = 0 if state.flash_at.elapsed_time > 20
+ end
+
+ def calc
+ return unless state.scene == :game
+ reset_game if state.countdown == 1
+ state.countdown -= 1 and return if state.countdown > 0
+ calc_walls
+ calc_flap
+ calc_game_over
+ end
+
+ def calc_walls
+ state.walls.each { |w| w.x -= 8 }
+
+ walls_count_before_removal = state.walls.length
+
+ state.walls.reject! { |w| w.x < -100 }
+
+ state.score += 1 if state.walls.count < walls_count_before_removal
+
+ state.wall_countdown -= 1 and return if state.wall_countdown > 0
+
+ state.walls << state.new_entity(:wall) do |w|
+ w.x = grid.right
+ w.opening = grid.top
+ .randomize(:ratio)
+ .greater(200)
+ .lesser(520)
+ w.bottom_height = w.opening - state.wall_gap_size
+ w.top_y = w.opening + state.wall_gap_size
+ end
+
+ state.wall_countdown = state.wall_countdown_length
+ end
+
+ def calc_flap
+ state.y += state.dy
+ state.dy = state.dy.lesser state.flap_power
+ state.dy -= state.gravity
+ return if state.y < state.ceiling
+ state.y = state.ceiling
+ state.dy = state.dy.lesser state.ceiling_flap_power
+ end
+
+ def calc_game_over
+ return unless game_over?
+
+ state.death_at = state.tick_count
+ state.death_from = state.walls.first
+ state.death_fall_direction = -1
+ state.death_fall_direction = 1 if state.x > state.death_from.x
+ outputs.sounds << "sounds/hit-sound.wav"
+ begin_countdown
+ end
+
+ def process_inputs
+ process_inputs_menu
+ process_inputs_game
+ end
+
+ def process_inputs_menu
+ return unless state.scene == :menu
+
+ changediff = inputs.keyboard.key_down.tab || inputs.controller_one.key_down.select
+ if inputs.mouse.click
+ p = inputs.mouse.click.point
+ if (p.y >= 165) && (p.y < 200) && (p.x >= 500) && (p.x < 800)
+ changediff = true
+ end
+ end
+
+ if changediff
+ case state.new_difficulty
+ when :easy
+ state.new_difficulty = :normal
+ when :normal
+ state.new_difficulty = :hard
+ when :hard
+ state.new_difficulty = :flappy
+ when :flappy
+ state.new_difficulty = :easy
+ end
+ end
+
+ if inputs.keyboard.key_down.enter || inputs.controller_one.key_down.start || inputs.controller_one.key_down.a
+ state.difficulty = state.new_difficulty
+ change_to_scene :game
+ reset_game false
+ state.hi_score = 0
+ begin_countdown
+ end
+
+ if inputs.keyboard.key_down.escape || (inputs.mouse.click && !changediff) || inputs.controller_one.key_down.b
+ state.new_difficulty = state.difficulty
+ change_to_scene :game
+ end
+ end
+
+ def process_inputs_game
+ return unless state.scene == :game
+
+ clicked_menu = false
+ if inputs.mouse.click
+ p = inputs.mouse.click.point
+ clicked_menu = (p.y >= 620) && (p.x < 275)
+ end
+
+ if clicked_menu || inputs.keyboard.key_down.escape || inputs.keyboard.key_down.enter || inputs.controller_one.key_down.start
+ change_to_scene :menu
+ elsif (inputs.mouse.down || inputs.mouse.click || inputs.keyboard.key_down.space || inputs.controller_one.key_down.a) && state.countdown == 0
+ state.dy = 0
+ state.dy += state.flap_power
+ state.flapped_at = state.tick_count
+ outputs.sounds << "sounds/fly-sound.wav"
+ end
+ end
+
+ def scrolling_background at, path, rate, y = 0
+ [
+ [ 0 - at.*(rate) % 1440, y, 1440, 720, path],
+ [1440 - at.*(rate) % 1440, y, 1440, 720, path]
+ ]
+ end
+
+ def white
+ [255, 255, 255]
+ end
+
+ def large_white_typeset
+ [5, 0, 255, 255, 255]
+ end
+
+ def at_beginning?
+ state.walls.count == 0
+ end
+
+ def dragon_collision_box
+ state.dragon_sprite
+ .scale_rect(1.0 - collision_forgiveness, 0.5, 0.5)
+ .rect_shift_right(10)
+ .rect_shift_up(state.dy * 2)
+ end
+
+ def game_over?
+ return true if state.y <= 0.-(500 * collision_forgiveness) && !at_beginning?
+
+ state.walls
+ .flat_map { |w| w.sprites }
+ .any? do |s|
+ s.intersect_rect?(dragon_collision_box)
+ end
+ end
+
+ def collision_forgiveness
+ case state.difficulty
+ when :easy
+ 0.9
+ when :normal
+ 0.7
+ when :hard
+ 0.5
+ when :flappy
+ 0.3
+ else
+ 0.9
+ end
+ end
+
+ def countdown_text
+ state.countdown ||= -1
+ return "" if state.countdown == 0
+ return "GO!" if state.countdown.idiv(60) == 0
+ return "GAME OVER" if state.death_at
+ return "READY?"
+ end
+
+ def begin_countdown
+ state.countdown = 4.seconds
+ end
+
+ def score_text
+ return "" unless state.countdown > 1.seconds
+ return "" unless state.death_at
+ return "SCORE: 0 (LOL)" if state.score == 0
+ return "HI SCORE: #{state.score}" if state.score == state.hi_score
+ return "SCORE: #{state.score}"
+ end
+
+ def reset_game set_flash = true
+ state.flash_at = state.tick_count if set_flash
+ state.walls = []
+ state.y = 500
+ state.dy = 0
+ state.hi_score = state.hi_score.greater(state.score)
+ state.score = 0
+ state.wall_countdown = state.wall_countdown_length.fdiv(2)
+ state.show_death = false
+ state.death_at = nil
+ end
+
+ def change_to_scene scene
+ state.scene = scene
+ state.scene_at = state.tick_count
+ inputs.keyboard.clear
+ inputs.controller_one.clear
+ end
+end
+
+$flappy_dragon = FlappyDragon.new
+
+def tick args
+ $flappy_dragon.grid = args.grid
+ $flappy_dragon.inputs = args.inputs
+ $flappy_dragon.state = args.state
+ $flappy_dragon.outputs = args.outputs
+ $flappy_dragon.tick
+end