Created
January 16, 2024 07:31
-
-
Save amirrajan/c7ee0c74177e8e050669ec7415ff32f9 to your computer and use it in GitHub Desktop.
DragonRuby Game Toolkit - Bullet Hell
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
class ShopScene | |
attr_gtk | |
def activate | |
state.module_selected = nil | |
state.available_modules = state.modules.shuffle.take(3) | |
state.available_module_1 = state.available_modules[0] | |
state.available_module_2 = state.available_modules[1] | |
state.available_module_3 = state.available_modules[2] | |
end | |
def tick | |
if state.scene_at == state.tick_count - 1 | |
activate | |
end | |
state.next_wave_button = layout.rect(row: 0, col: 22, w: 2, h: 1) | |
state.module_1_button = layout.rect(row: 10, col: 0, w: 8, h: 2) | |
state.module_2_button = layout.rect(row: 10, col: 8, w: 8, h: 2) | |
state.module_3_button = layout.rect(row: 10, col: 16, w: 8, h: 2) | |
calc | |
render | |
end | |
def increase_difficulty_and_start_level | |
state.next_scene = :level | |
state.enemies_spawned = 0 | |
state.enemies = [] | |
state.level += 1 | |
state.enemy_spawn_rate = (state.enemy_spawn_rate * 0.95).to_i | |
state.enemy_min_health = (state.enemy_min_health * 1.1).to_i + 1 | |
state.enemy_health_range = state.enemy_min_health * 2 | |
state.enemies_to_spawn = (state.enemies_to_spawn * 1.1).to_i + 2 | |
state.enemy_dy *= 1.05 | |
end | |
def calc | |
if state.module_selected | |
if inputs.mouse.click && inputs.mouse.click.point.inside_rect?(state.next_wave_button) | |
increase_difficulty_and_start_level | |
end | |
else | |
if inputs.mouse.click && inputs.mouse.click.point.inside_rect?(state.module_1_button) | |
perform_upgrade state.available_module_1 | |
state.available_module_1 = nil | |
state.module_selected = true | |
elsif inputs.mouse.click && inputs.mouse.click.point.inside_rect?(state.module_2_button) | |
perform_upgrade state.available_module_2 | |
state.available_module_2 = nil | |
state.module_selected = true | |
elsif inputs.mouse.click && inputs.mouse.click.point.inside_rect?(state.module_3_button) | |
perform_upgrade state.available_module_3 | |
state.available_module_3 = nil | |
state.module_selected = true | |
end | |
end | |
end | |
def perform_upgrade module_name | |
return if state.module_selected | |
if module_name == :bullet_damage | |
state.bullet_damage += 1 | |
elsif module_name == :blaster_rate | |
state.blaster_rate = (state.blaster_rate * 0.85).to_i | |
elsif module_name == :blaster_spread | |
state.blaster_spread += 2 | |
else | |
raise "perform_upgade: Unknown module: #{module_name}" | |
end | |
end | |
def render | |
outputs.primitives << layout.debug_primitives.map { |p| p.merge a: 80 } | |
outputs.labels << layout.rect(row: 0, col: 11, w: 2, h: 1) | |
.center | |
.merge(text: "Assembly Points: #{state.assembly_points}", | |
anchor_x: 0.5, anchor_y: 0.5) | |
if state.module_selected | |
outputs.primitives << button_prefab(state.next_wave_button, "Next Wave", a: 255) | |
end | |
a = if state.module_selected | |
80 | |
else | |
255 | |
end | |
outputs.primitives << button_prefab(state.module_1_button, state.available_module_1, a: a) | |
outputs.primitives << button_prefab(state.module_2_button, state.available_module_2, a: a) | |
outputs.primitives << button_prefab(state.module_3_button, state.available_module_3, a: a) | |
end | |
def button_prefab rect, text, a: 255 | |
return nil if !text | |
[ | |
rect.merge(path: :solid, r: 0, g: 0, b: 0, a: a), | |
geometry.center(rect).merge(text: text, anchor_x: 0.5, anchor_y: 0.5, r: 255, g: 255, b: 255, size_px: rect.h.idiv(4)) | |
] | |
end | |
end | |
class LevelScene | |
attr_gtk | |
def tick | |
if inputs.keyboard.key_down.g | |
state.enemies_spawned = state.enemies_to_spawn - 1 | |
state.enemies = [] | |
end | |
calc | |
render | |
end | |
def calc | |
calc_bullets | |
calc_enemies | |
calc_bullet_hits | |
calc_enemy_push_back | |
calc_deaths | |
end | |
def calc_deaths | |
state.enemies.reject! { |e| e.hp <= 0 } | |
state.bullets.reject! { |b| b.dead_at } | |
end | |
def enemy_prefab enemy | |
b = (enemy.hp / (state.enemy_min_health + state.enemy_health_range)) * 255 | |
[ | |
enemy.merge(path: :solid, r: 128, g: 0, b: b), | |
geometry.center(enemy).merge(text: enemy.hp, anchor_x: 0.5, anchor_y: 0.5, r: 255, g: 255, b: 255, size_px: enemy.h * 0.5) | |
] | |
end | |
def render | |
outputs.background_color = [0, 0, 0] | |
outputs.primitives << { x: 30, y: 30.from_top, text: "Wave: #{state.level}", r: 255, g: 255, b: 255 } | |
level_completion_perc = (state.enemies_spawned - state.enemies.length).fdiv(state.enemies_to_spawn) | |
outputs.primitives << { x: 30, y: 60.from_top, text: "#{(level_completion_perc * 100).to_i}% (#{state.enemies_to_spawn} #{state.enemy_dy})", r: 255, g: 255, b: 255 } | |
outputs.primitives << { x: 30, y: 90.from_top, text: "#{$gtk.current_framerate.to_sf}", r: 255, g: 255, b: 255 } | |
outputs.sprites << state.bullets.map do |b| | |
b.merge w: 10, h: 10, path: :solid, r: 0, g: 255, b: 255 | |
end | |
outputs.primitives << state.enemies.map { |e| enemy_prefab e } | |
outputs.primitives << state.missles.map { |m| missile_prefab m } | |
end | |
def calc_bullets | |
if state.tick_count.zmod? state.blaster_rate | |
bullet_count = state.blaster_spread | |
min_degrees = state.blaster_spread.idiv(2) * -2 | |
bullet_count.times do |i| | |
degree_offset = min_degrees + (i * 2) | |
state.bullets << { x: 640, | |
y: 0, | |
dy: (attack_angle + degree_offset).vector_y * state.bullet_speed, | |
dx: (attack_angle + degree_offset).vector_x * state.bullet_speed } | |
end | |
end | |
state.bullets.each do |b| | |
b.x += b.dx | |
b.y += b.dy | |
end | |
state.bullets.reject! { |b| b.y > 720 || b.x > 1280 || b.x < 0 } | |
end | |
def calc_enemies | |
if state.tick_count.zmod?(state.enemy_spawn_rate) && state.enemies_spawned < state.enemies_to_spawn | |
state.enemies_spawned += 1 | |
x = rand(1280 - 96) + 48 | |
y = 720 | |
hp = state.enemy_min_health + rand(state.enemy_health_range) | |
state.enemies << { x: x, | |
y: y, | |
w: 48, | |
h: 48, | |
push_back_x: 0, | |
push_back_y: 0, | |
spawn_at: state.tick_count, | |
dy: state.enemy_dy, | |
start_hp: hp, | |
hp: hp } | |
end | |
state.enemies.each do |e| | |
if e.y + e.h > 720 | |
e.y -= (((e.y + e.h) - 720) / e.h) * 10 | |
end | |
e.y += e.dy | |
if e.x < 0 && e.push_back_x < 0 | |
e.push_back_x = e.push_back_x.abs | |
elsif (e.x + e.w) > 1280 && e.push_back_x > 0 | |
e.push_back_x = e.push_back_x.abs * -1 | |
end | |
e.x += e.push_back_x | |
e.y += e.push_back_y | |
e.push_back_x *= 0.9 | |
e.push_back_y *= 0.9 | |
end | |
state.enemies.reject! { |e| e.y < 0 } | |
if state.enemies.empty? && state.enemies_spawned >= state.enemies_to_spawn | |
state.next_scene = :shop | |
state.bullets.clear | |
end | |
end | |
def calc_bullet_hits | |
state.bullets.each do |b| | |
state.enemies.each do |e| | |
if geometry.intersect_rect? b.merge(w: 4, h: 4, anchor_x: 0.5, anchor_x: 0.5), e | |
e.hp -= state.bullet_damage | |
push_back_angle = geometry.angle b, geometry.center(e) | |
push_back_x = push_back_angle.vector_x * state.bullet_damage * 0.1 | |
push_back_y = push_back_angle.vector_y * state.bullet_damage * 0.1 | |
e.push_back_x += push_back_x | |
e.push_back_y += push_back_y | |
e.hit_at = state.tick_count | |
b.dead_at = state.tick_count | |
end | |
end | |
end | |
end | |
def calc_enemy_push_back | |
state.enemies.sort_by { |e| -e.y }.each do |e| | |
has_pushed_back = false | |
other_enemies = geometry.find_all_intersect_rect e, state.enemies | |
other_enemies.each do |e2| | |
next if e == e2 | |
push_back_angle = geometry.angle geometry.center(e), geometry.center(e2) | |
e2.push_back_x += (e.push_back_x).fdiv(other_enemies.length) * 0.7 | |
e2.push_back_y += (e.push_back_y).fdiv(other_enemies.length) * 0.7 | |
has_pushed_back = true | |
end | |
if has_pushed_back | |
e.push_back_x *= 0.2 | |
e.push_back_y *= 0.2 | |
end | |
end | |
end | |
def attack_angle | |
geometry.angle state.turret_position, inputs.mouse | |
end | |
end | |
class Game | |
attr_gtk | |
def initialize | |
@level_scene = LevelScene.new | |
@shop_scene = ShopScene.new | |
end | |
def tick | |
defaults | |
current_scene.args = args | |
current_scene.tick | |
if state.next_scene | |
state.scene = state.next_scene | |
state.scene_at = state.tick_count | |
state.next_scene = nil | |
end | |
end | |
def current_scene | |
if state.scene == :level | |
@level_scene | |
elsif state.scene == :shop | |
@shop_scene | |
end | |
end | |
def defaults | |
state.shield ||= 10 | |
state.assembly_points ||= 4 | |
state.scene ||= :level | |
state.bullets ||= [] | |
state.enemies ||= [] | |
state.missles ||= [] | |
state.bullet_speed ||= 5 | |
state.turret_position ||= { x: 640, y: 0 } | |
state.modules ||= [ | |
:blaster_spread, | |
:bullet_damage, | |
:blaster_rate, | |
] | |
state.blaster_spread ||= 1 | |
state.blaster_rate ||= 60 | |
state.level ||= 1 | |
state.bullet_damage ||= 1 | |
state.enemy_spawn_rate ||= 120 | |
state.enemy_min_health ||= 1 | |
state.enemy_health_range ||= 2 | |
state.enemies_to_spawn ||= 5 | |
state.enemies_spawned ||= 0 | |
state.enemy_dy ||= -0.2 | |
end | |
end | |
def tick args | |
$game ||= Game.new | |
$game.args = args | |
$game.tick | |
end | |
def reset | |
$game = nil | |
end | |
# $gtk.reset |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment