Last active
January 14, 2021 15:11
-
-
Save icculus/a2a3957405d894e348f40370704c6990 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This is the complete source code to Flappy Dragon: | |
# https://dragonruby.itch.io/flappydragon | |
# | |
# You can tinker with this by getting a copy of DragonRuby Game Toolkit: | |
# https://dragonruby.org/ | |
# | |
# Amir Rajan wrote this game, catch him at https://twitter.com/amirrajan ! | |
class FlappyDragon | |
attr_accessor :grid, :inputs, :game, :outputs | |
def tick | |
defaults | |
render | |
calc | |
process_inputs | |
end | |
def defaults | |
game.flap_power = 11 | |
game.gravity = 0.9 | |
game.ceiling = 600 | |
game.ceiling_flap_power = 6 | |
game.wall_countdown_length = 100 | |
game.wall_gap_size = 100 | |
game.wall_countdown ||= 0 | |
game.hi_score ||= 0 | |
game.score ||= 0 | |
game.walls ||= [] | |
game.x ||= 50 | |
game.y ||= 500 | |
game.dy ||= 0 | |
game.scene ||= :menu | |
game.scene_at ||= 0 | |
game.difficulty ||= :normal | |
game.new_difficulty ||= :normal | |
game.countdown ||= 4.seconds | |
game.flash_at ||= 0 | |
end | |
def render | |
outputs.sounds << "sounds/flappy-song.ogg" if game.tick_count == 1 | |
render_score | |
render_menu | |
render_game | |
end | |
def render_score | |
outputs.primitives << [:labels, 10, 710, "HI SCORE: #{game.hi_score}", large_white_typeset] | |
outputs.primitives << [:labels, 10, 680, "SCORE: #{game.score}", large_white_typeset] | |
outputs.primitives << [:labels, 10, 650, "DIFFICULTY: #{game.difficulty.upcase}", large_white_typeset] | |
end | |
def render_menu | |
return unless game.scene == :menu | |
render_overlay | |
outputs.labels << [285, 700, "Flappy Dragon", 50, 0, 255, 255, 255] | |
outputs.labels << [330, 500, "Instructions: Press Spacebar to flap. Don't die.", 4, 0, 255, 255, 255] | |
outputs.labels << [450, 380, "[Tab] Difficulty: #{game.new_difficulty.capitalize}", 4, 0, 255, 255, 255] | |
outputs.labels << [450, 350, "[Enter] Start at New Difficulty ", 4, 0, 255, 255, 255] | |
outputs.labels << [450, 320, "[Escape] Cancel/Resume ", 4, 0, 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", 255, 255, 255] | |
end | |
def render_overlay | |
outputs.primitives << [:solids, grid.rect, 0, 0, 0, 230] | |
end | |
def render_game | |
render_game_over | |
render_background | |
render_walls | |
render_dragon | |
render_flash | |
end | |
def render_game_over | |
return unless game.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 = game.tick_count | |
scroll_point_at = game.scene_at if game.scene == :menu | |
scroll_point_at = game.death_at if game.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 | |
update_wall_prefabs | |
outputs.sprites += game.walls.map(&:sprites) | |
end | |
def render_dragon | |
game.show_death = true if game.countdown == 3.seconds | |
render_debug_hitbox false | |
if game.show_death == false || !game.death_at | |
animation_index = frame_for_animation game.flapped_at, 2, 6 | |
game.flapped_at = false if animation_index == 5 | |
sprite_name = "sprites/dragon_fly#{animation_index + 1}.png" | |
game.dragon_sprite = [game.x, game.y, 100, 80, sprite_name, game.dy * 1.2] | |
else | |
sprite_name = "sprites/dragon_die.png" | |
game.dragon_sprite = [game.x, game.y, 100, 80, sprite_name, game.dy * 1.2] | |
sprite_changed_elapsed = elapsed_time(game.death_at) - 1.seconds | |
game.dragon_sprite.angle += (sprite_changed_elapsed ** 1.3) * game.death_fall_direction * -1 | |
game.dragon_sprite.x += (sprite_changed_elapsed ** 1.2) * game.death_fall_direction | |
game.dragon_sprite.y += (sprite_changed_elapsed * 14 - sprite_changed_elapsed ** 1.6) | |
end | |
outputs.sprites << game.dragon_sprite | |
end | |
def render_debug_hitbox show | |
return unless show | |
outputs.borders << game.dragon_sprite.rect if game.dragon_sprite | |
end | |
def render_flash | |
return unless game.flash_at | |
flash_length = 20 | |
outputs.primitives << [:solids, | |
grid.rect, | |
white, | |
255 * game.tick_count.perc_to_zero(game.flash_at, flash_length)] | |
game.flash_at = 0 if elapsed_time(game.flash_at) > flash_length | |
end | |
def calc | |
return unless game.scene == :game | |
calc_countdown | |
game.countdown -= 1 and return if game.countdown > 0 | |
calc_walls | |
calc_flap | |
calc_game_over | |
end | |
def calc_countdown | |
reset_game if game.countdown == 1 | |
end | |
def calc_walls | |
game.walls.each { |w| w.x -= 8 } | |
walls_count_before_removal = game.walls.length | |
game.walls.reject! { |w| w.x < -100 } | |
game.score += 1 if game.walls.count < walls_count_before_removal | |
game.wall_countdown -= 1 and return if game.wall_countdown > 0 | |
game.walls << game.new_entity(:wall) do |w| | |
w.x = grid.right | |
w.opening = grid.top | |
.randomize(:ratio) | |
.greater(200) | |
.lesser(520) | |
w.bottom_height = w.opening - game.wall_gap_size | |
w.top_y = w.opening + game.wall_gap_size | |
end | |
game.wall_countdown = game.wall_countdown_length | |
end | |
def calc_flap | |
game.y += game.dy | |
game.dy = game.dy.lesser game.flap_power | |
game.dy -= game.gravity | |
return if game.y < game.ceiling | |
game.y = game.ceiling | |
game.dy = game.dy.lesser game.ceiling_flap_power | |
end | |
def calc_game_over | |
return unless game_over? | |
game.death_at = game.tick_count | |
game.death_from = game.walls.first | |
game.death_fall_direction = -1 | |
game.death_fall_direction = 1 if game.x > game.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 game.scene == :menu | |
if inputs.keyboard.key_down.tab | |
case game.new_difficulty | |
when :easy | |
game.new_difficulty = :normal | |
when :normal | |
game.new_difficulty = :hard | |
when :hard | |
game.new_difficulty = :flappy | |
when :flappy | |
game.new_difficulty = :easy | |
end | |
end | |
if inputs.keyboard.key_down.enter | |
game.difficulty = game.new_difficulty | |
change_to_scene :game | |
reset_game false | |
game.hi_score = 0 | |
begin_countdown | |
end | |
if inputs.keyboard.key_down.escape | |
game.new_difficulty = game.difficulty | |
change_to_scene :game | |
end | |
end | |
def process_inputs_game | |
return unless game.scene == :game | |
if inputs.keyboard.key_down.escape || inputs.keyboard.key_down.enter | |
change_to_scene :menu | |
elsif inputs.keyboard.key_down.space && game.countdown == 0 | |
game.dy = 0 | |
game.dy += game.flap_power | |
game.flapped_at = game.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 update_wall_prefabs | |
game.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 | |
end | |
def elapsed_time at | |
game.tick_count - at | |
end | |
def frame_for_animation started_at, hold_time, number_of_key_frames | |
return 0 unless started_at | |
elapsed_time(started_at).idiv(hold_time) % number_of_key_frames | |
end | |
def white | |
[255, 255, 255] | |
end | |
def large_white_typeset | |
[5, 0, 255, 255, 255] | |
end | |
def at_beginning? | |
game.walls.count == 0 | |
end | |
def anchor_center | |
[0.5, 0.5] | |
end | |
def dragon_collision_box | |
game.dragon_sprite | |
.scale_rect(1.0 - collision_forgiveness, | |
anchor_center) | |
.rect_shift_right(10) | |
.rect_shift_up(game.dy * 2) | |
end | |
def game_over? | |
return true if game.y <= 0.-(500 * collision_forgiveness) && !at_beginning? | |
game.walls | |
.flat_map { |w| w.sprites } | |
.any? do |s| | |
s.intersects_rect?(dragon_collision_box) | |
end | |
end | |
def collision_forgiveness | |
case game.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 | |
game.countdown ||= -1 | |
return "" if game.countdown == 0 | |
return "GO!" if game.countdown.idiv(60) == 0 | |
return "GAME OVER" if game.death_at | |
return "READY?" | |
end | |
def begin_countdown | |
game.countdown = 4.seconds | |
end | |
def score_text | |
return "" unless game.countdown > 1.seconds | |
return "" unless game.death_at | |
return "SCORE: 0 (LOL)" if game.score == 0 | |
return "HI SCORE: #{game.score}" if game.score == game.hi_score | |
return "SCORE: #{game.score}" | |
end | |
def reset_game set_flash = true | |
game.flash_at = game.tick_count if set_flash | |
game.walls = [] | |
game.y = 500 | |
game.dy = 0 | |
game.hi_score = game.hi_score.greater(game.score) | |
game.score = 0 | |
game.wall_countdown = game.wall_countdown_length.fdiv(2) | |
game.show_death = false | |
game.death_at = nil | |
end | |
def change_to_scene scene | |
game.scene = scene | |
game.scene_at = game.tick_count | |
inputs.keyboard.clear | |
end | |
end | |
$flappy_dragon = FlappyDragon.new | |
def tick args | |
$flappy_dragon.grid = args.grid | |
$flappy_dragon.inputs = args.inputs | |
$flappy_dragon.game = args.game | |
$flappy_dragon.outputs = args.outputs | |
$flappy_dragon.tick | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment