Last active
July 28, 2022 13:50
-
-
Save amirrajan/68396c1555a8083dc91afeb06565348d to your computer and use it in GitHub Desktop.
Broad Sword of Justice in DragonRuby Game Toolkit https://amirrajan.itch.io/broad-sword-of-justice
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 Game | |
attr_gtk | |
def reset_anvils | |
state.anvil_phase = :idle | |
state.anvil_timer = 3.seconds | |
reset_anvil_1 | |
reset_anvil_2 | |
reset_anvil_3 | |
end | |
def reset_anvil_1 | |
state.anvil.x = -130 | |
state.anvil.y = 0 | |
state.anvil.w = 130 | |
state.anvil.h = 130 | |
state.anvil.dy = 26 | |
state.anvil.dx = 13 | |
end | |
def reset_anvil_2 | |
state.anvil_2.x = -450 | |
state.anvil_2.y = 0 | |
state.anvil_2.w = 130 | |
state.anvil_2.h = 130 | |
state.anvil_2.dy = 26 | |
state.anvil_2.dx = 14 | |
end | |
def reset_anvil_3 | |
state.anvil_3.x = 1165 | |
state.anvil_3.y = 800 | |
state.anvil_3.w = 130 | |
state.anvil_3.h = 130 | |
state.anvil_3.dy = 0 | |
state.anvil_3.dx = 0 | |
end | |
def reset_game | |
return if !state.game_over && state.initialized | |
audio[:charging].playtime = 0 | |
audio[:charging].paused = true | |
state.clock = 0 | |
state.floor = 128 | |
state.gravity = -0.5 | |
state.white_flash = false | |
state.white_flash_at = nil | |
reset_anvils | |
state.player.x = 100 | |
state.player.y = 800 | |
state.player.dy = 0 | |
state.player.action = :falling | |
state.player.facing = :right | |
state.player.charging_at = nil | |
state.player.attack_at = nil | |
state.boss.x = 1150 | |
state.boss.y = 128 | |
state.boss.action = :standing | |
state.boss.action_at = state.clock | |
state.boss.phase = :ready | |
state.boss.phase_at = :ready | |
state.boss.facing = :left | |
state.camera.trauma = 0 | |
state.camera.x_offset = 0 | |
state.camera.y_offset = 0 | |
state.camera.angle = 0 | |
state.bullets = [] | |
state.initialized = true | |
state.game_over = false | |
state.game_over_at = nil | |
end | |
def tick | |
state.clock ||= -1 | |
if !state.initialized | |
audio["cheer_#{Kernel.global_tick_count}"] = { | |
input: 'sounds/cheer.ogg', | |
pause: false, | |
repeat: false, | |
gain: 0.2 | |
} | |
end | |
defaults | |
input | |
calc | |
render | |
state.clock += 1 | |
end | |
def player_charge_percentage | |
return 0 if !player_charging? | |
return state.player.charging_at.elapsed_time(state.clock).fdiv(320) | |
end | |
def player_on_ground? | |
state.player.action == :standing || state.player.action == :walking | |
end | |
def player_in_air? | |
state.player.action == :jumping || state.player.action == :falling | |
end | |
def player_can_charge? | |
state.player.action == :standing || state.player.action == :walking | |
end | |
def player_charging? | |
state.player.action == :charging | |
end | |
def player_attacking? | |
state.player.action == :attacking | |
end | |
def player_hurt_box | |
{ x: state.player.x - 32, y: state.player.y - 32, w: 64, h: 64 } | |
end | |
def boss_hurt_box | |
{ x: boss.x - 32, y: boss.y - 32, w: 64, h: 64 } | |
end | |
def player_hit_box | |
if state.player.facing == :right | |
{ x: state.player.x + 32, y: state.player.y - 32, w: 100, h: 100 } | |
else | |
{ x: state.player.x - 32 - 100, y: state.player.y - 32, w: 100, h: 100 } | |
end | |
end | |
def render_player | |
path = "sprites/player/player-idle.png" | |
flip_horizontally = false | |
if state.player.action == :walking | |
index = 0.frame_index count: 2, | |
hold_for: 8, | |
repeat: true | |
path = "sprites/player/player-walk-#{index}.png" | |
elsif player_in_air? | |
path = "sprites/player/player-jump.png" | |
elsif player_charging? | |
index = state.player.charging_at.frame_index count: 9, | |
hold_for: 35, | |
repeat: true, | |
tick_count_override: state.clock | |
path = "sprites/player/player-charge-#{index}.png" | |
elsif state.player.action == :attacking | |
index = state.player.attack_at.frame_index count: 4, | |
hold_for: 4, | |
repeat: false, | |
tick_count_override: state.clock | |
index ||= 3 | |
path = "sprites/player/player-attack-#{index}.png" | |
end | |
if state.player.facing == :left | |
flip_horizontally = true | |
end | |
outputs[:scene].sprites << { x: state.player.x - 128, | |
y: state.player.y - 128, | |
w: 256, | |
h: 256, | |
flip_horizontally: flip_horizontally, | |
path: path } | |
# outputs.debug << player_hurt_box.to_border(r: 255, g: 255, b: 255) | |
# outputs.debug << player_hit_box.to_border(r: 255, g: 255, b: 255) | |
# outputs.debug << { x: 30, y: 30.from_top, text: "#{state.player.action} #{state.player.attack_at || -1} #{player_charge_percentage.to_sf} #{state.camera.trauma.to_sf}", r: 255, g: 255, b: 255 } | |
end | |
def render_boss | |
path = "sprites/boss/boss-idle.png" | |
flip_horizontally = false | |
flip_horizontally = true if state.boss.facing == :left | |
if boss.action == :shooting | |
index = boss.action_at.frame_index count: 4, | |
hold_for: 2, | |
repeat: false, | |
tick_count_override: state.clock | |
path = if !index | |
"sprites/boss/boss-idle.png" | |
else | |
"sprites/boss/boss-attack-straight-#{index}.png" | |
end | |
end | |
if boss.phase == :dead | |
path = "sprites/boss/boss-dead.png" | |
end | |
outputs[:scene].sprites << { x: state.boss.x - 128, | |
y: state.boss.y - 128, | |
w: 256, | |
h: 256, | |
flip_horizontally: flip_horizontally, | |
path: path } | |
# outputs.debug << { x: 30.from_right, | |
# y: 30.from_top, | |
# text: "#{boss.action} #{boss.phase}", | |
# r: 255, | |
# g: 255, | |
# b: 255, | |
# alignment_enum: 2 } | |
# outputs.debug << boss_hurt_box.to_border(r: 255, g: 255, b: 255) | |
# outputs.debug << { x: boss_gun_barrel_point.x - 5, | |
# y: boss_gun_barrel_point.y - 5, | |
# w: 10, | |
# h: 10, r: 255, g: 255, b: 255 }.to_border | |
end | |
def defaults | |
audio[:background_music] ||= { | |
input: 'sounds/background-music.ogg', | |
looping: true, | |
paused: false, | |
gain: 0.15 | |
} | |
audio[:charging] ||= { | |
input: 'sounds/charge-sword.ogg', | |
looping: false, | |
paused: true, | |
gain: 0.1, | |
playtime: 0.0 | |
} | |
audio[:attacking] ||= { | |
input: 'sounds/swing-sword.ogg', | |
looping: false, | |
paused: true, | |
gain: 1.0, | |
playtime: 0.0 | |
} | |
reset_game | |
end | |
def input | |
if !player_charging? && !player_attacking? | |
if inputs.keyboard.right && state.player.x < 1280 - 32 | |
state.player.action = :walking if state.player.action == :standing | |
state.player.facing = :right | |
state.player.x += 4 | |
elsif inputs.keyboard.left && state.player.x > 32 | |
state.player.action = :walking if state.player.action == :standing | |
state.player.facing = :left | |
state.player.x -= 4 | |
else | |
state.player.action = :standing if state.player.action == :walking | |
end | |
end | |
if inputs.keyboard.space && player_can_charge? | |
state.player.action = :charging | |
if !state.player.charging_at | |
audio[:charging].playtime = 0 | |
audio[:charging].paused = false | |
state.player.charging_at = args.state.clock | |
end | |
end | |
if inputs.keyboard.key_up.space | |
if player_charging? | |
state.player.action = :standing | |
state.player.charging_at = nil | |
audio[:charging].playtime = 0 | |
audio[:charging].paused = true | |
end | |
end | |
if inputs.keyboard.key_down.up && (player_on_ground? || state.player.action == :falling) | |
if (state.player.y - state.floor).abs < 10 | |
state.player.action = :jumping | |
state.player.dy = 15 | |
end | |
end | |
if boss.phase == :dead && (state.start_win_music_at.elapsed_time(state.clock) + 600) < state.clock | |
if inputs.keyboard.key_down.space && boss.phase_at.elapsed_time(state.clock) > 400 | |
state.game_over = true | |
if audio[:win_music] | |
audio[:win_music].playtime = 0 | |
audio[:win_music].paused = true | |
end | |
if audio[:background_music] | |
audio[:background_music].playtime = 0 | |
audio[:background_music].paused = false | |
end | |
audio["cheer_#{Kernel.global_tick_count}"] = { | |
input: 'sounds/cheer.ogg', | |
pause: false, | |
repeat: false, | |
gain: 0.2 | |
} | |
end | |
end | |
end | |
def calc | |
calc_player | |
calc_boss | |
calc_anvil | |
calc_game_over | |
calc_camera | |
end | |
def player_die! | |
state.game_over = true | |
audio[:death] = { | |
input: 'sounds/death.ogg', | |
repeat: false, | |
gain: 0.2 | |
} | |
end | |
def calc_game_over | |
bullet = state.bullets.find { |b| b.intersect_rect? player_hurt_box } | |
if bullet | |
player_die! | |
end | |
if player_hurt_box.intersect_rect? state.anvil | |
player_die! | |
end | |
if player_hurt_box.intersect_rect? state.anvil_2 | |
player_die! | |
end | |
if player_hurt_box.intersect_rect? state.anvil_3 | |
player_die! | |
end | |
end | |
def render | |
outputs.background_color = [0, 0, 0] | |
outputs[:scene].w = 1280 | |
outputs[:scene].h = 720 | |
outputs[:scene].sprites << { x: 0, y: 0, w: 1280, h: 720, path: 'sprites/stage-background.png' } | |
render_boss | |
render_player | |
if boss.phase != :dead | |
outputs[:scene].sprites << { x: state.anvil.x, y: state.anvil.y, w: state.anvil.w, h: state.anvil.h, path: 'sprites/anvil.png', angle: (state.clock % 360) * -10 } | |
outputs[:scene].sprites << { x: state.anvil_2.x, y: state.anvil_2.y, w: state.anvil_2.w, h: state.anvil_2.h, path: 'sprites/anvil.png', angle: (state.clock % 360) * -10 } | |
outputs[:scene].sprites << { x: state.anvil_3.x, y: state.anvil_3.y, w: state.anvil_3.w, h: state.anvil_3.h, path: 'sprites/anvil.png', angle: (state.clock % 360) * -10 } | |
end | |
outputs[:scene].sprites << state.bullets.map { |s| s.merge(path: "sprites/boss/bullet.png") } | |
if state.white_flash && state.white_flash_at.elapsed_time(state.clock) > 0 | |
a = 255 - state.white_flash_at.elapsed_time(state.clock) | |
outputs[:scene].sprites << { x: 0, y: 0, w: 1280, h: 720, a: a } | |
end | |
outputs.sprites << { x: 0 - state.camera.x_offset, | |
y: 0 - state.camera.x_offset, | |
w: 1280, | |
h: 720, | |
angle: state.camera.angle, | |
path: :scene } | |
if boss.phase == :dead && state.start_win_music_at | |
if state.start_win_music_at == state.clock | |
audio[:win_music] = { | |
input: 'sounds/win-music.ogg', | |
looping: true, | |
paused: false, | |
gain: 0.1 | |
} | |
end | |
if state.start_win_music_at < state.clock | |
a = state.start_win_music_at.elapsed_time(state.clock).clamp(0, 255) | |
outputs.sprites << { x: 0, y: 0, w: 1280, h: 720, path: 'sprites/title.png', a: a } | |
outputs.labels << { x: 640, y: 360, text: "press space to play again", a: a, r: 255, g: 255, b: 255, size_enum: 2, alignment_enum: 1 } | |
end | |
end | |
end | |
def calc_player | |
if state.player.dy < 0 | |
state.player.action = :falling | |
end | |
if (state.player.y + state.player.dy) < state.floor | |
state.player.y = state.floor | |
state.player.dy = 0 | |
state.player.action = :standing if player_in_air? | |
end | |
if player_in_air? | |
state.player.y += state.player.dy | |
state.player.dy += state.gravity | |
end | |
if player_charging? && state.player.charging_at.elapsed_time(state.clock) > 320 | |
state.player.action = :attacking | |
state.player.charging_at = nil | |
if !state.player.attack_at | |
audio[:attacking].playtime = 0 | |
audio[:attacking].paused = false | |
if player_hit_box.intersect_rect? boss_hurt_box | |
audio[:background_music].paused = true | |
state.camera.trauma = 0 | |
state.camera.x_offset = 0 | |
state.camera.y_offset = 0 | |
state.player.attack_at ||= state.clock | |
boss.phase = :dead | |
boss.phase_at = state.clock | |
state.bullets = [] | |
win! | |
end | |
end | |
end | |
end | |
def calc_anvil | |
return if boss.phase == :dead | |
if player_hurt_box.x > boss_hurt_box.x | |
state.anvil_phase = :queued | |
elsif state.anvil_phase == :idle | |
reset_anvils | |
end | |
if state.anvil_phase == :queued | |
state.anvil_timer -= 1 | |
end | |
if state.anvil_timer < 0 | |
state.anvil_timer -= 1 | |
state.anvil_phase = :in_flight | |
state.anvil.dy += state.gravity | |
state.anvil.y += state.anvil.dy | |
state.anvil.x += state.anvil.dx | |
if state.anvil_timer < -3.seconds | |
state.anvil_2.dy += state.gravity | |
state.anvil_2.y += state.anvil_2.dy | |
state.anvil_2.x += state.anvil_2.dx | |
end | |
if state.anvil_timer < -11.seconds | |
state.anvil_3.dy += state.gravity | |
state.anvil_3.y += state.anvil_3.dy | |
state.anvil_3.x += state.anvil_3.dx | |
end | |
end | |
if state.anvil_timer < 0 && state.anvil.x > 1280 && state.anvil_2.x > 1280 | |
if player_hurt_box.x < boss_hurt_box.x | |
reset_anvils | |
elsif state.anvil_3.y < -130 | |
reset_anvils | |
end | |
end | |
end | |
def win! | |
state.white_flash = true | |
state.white_flash_at = state.clock + 7 | |
state.start_win_music_at = state.clock + 7 + 255 | |
end | |
def calc_boss | |
if boss.phase == :ready | |
if boss.action_at.elapsed_time(state.clock) > 120 | |
boss.phase = :phase_1 | |
boss.phase_at = state.clock | |
end | |
elsif boss.phase == :phase_1 | |
if boss.phase_at.elapsed_time(state.clock) == 1 | |
boss_shoot_bullet! | |
elsif boss.phase_at.elapsed_time(state.clock) == 11 | |
boss_shoot_bullet! | |
elsif boss.phase_at.elapsed_time(state.clock) == 21 | |
boss_shoot_bullet! | |
elsif boss.phase_at.elapsed_time(state.clock) == 61 | |
boss_shoot_bullet! | |
elsif boss.phase_at.elapsed_time(state.clock) == 71 | |
boss_shoot_bullet! | |
elsif boss.phase_at.elapsed_time(state.clock) == 81 | |
boss_shoot_bullet! | |
elsif boss.phase_at.elapsed_time(state.clock) >= 400 | |
boss.phase = :phase_2 | |
boss.phase_at = state.clock | |
end | |
elsif boss.phase == :phase_2 | |
if boss.phase_at.elapsed_time(state.clock) == 1 | |
boss_shoot_bullet! | |
elsif boss.phase_at.elapsed_time(state.clock) == 11 | |
boss_shoot_bullet! | |
elsif boss.phase_at.elapsed_time(state.clock) == 21 | |
boss_shoot_bullet_45! | |
elsif boss.phase_at.elapsed_time(state.clock) == 61 | |
boss_shoot_bullet! | |
elsif boss.phase_at.elapsed_time(state.clock) == 71 | |
boss_shoot_bullet! | |
elsif boss.phase_at.elapsed_time(state.clock) == 81 | |
boss_shoot_bullet_45! | |
elsif boss.phase_at.elapsed_time(state.clock) >= 81 | |
boss.phase = :phase_3 | |
boss.phase_at = state.clock | |
end | |
elsif boss.phase == :phase_3 | |
boss.action = :tired | |
boss.action_at = state.clock | |
if boss.phase_at.elapsed_time(state.clock) > 430 | |
boss.phase = :phase_1 | |
boss.phase_at = state.clock | |
boss.action = :idle | |
boss.action_at = state.clock | |
end | |
end | |
if boss.action == :shooting && boss.action_at.elapsed_time(state.clock) > 8 | |
boss.action = :idle | |
end | |
state.bullets.each do |b| | |
b.x += b.dx | |
b.y += b.dy | |
end | |
state.bullets.reject! { |b| b.x < -100 } | |
end | |
def boss_shoot_bullet! | |
bullet_speed = 10 | |
boss.action = :shooting | |
boss.action_at = state.clock | |
dx = 1 | |
dy = 0 | |
flip_horizontally = false | |
if boss.facing == :left | |
dx = -1 | |
flip_horizontally = true | |
end | |
state.bullets << { x: boss_gun_barrel_point.x, | |
y: boss_gun_barrel_point.y, | |
w: 16, | |
h: 7, | |
flip_horizontally: flip_horizontally, | |
dy: 0, | |
dx: bullet_speed * dx } | |
audio["bullet_#{Kernel.global_tick_count}"] = { | |
input: 'sounds/gunshot.ogg', | |
pause: false, | |
repeat: false, | |
gain: 0.2 | |
} | |
end | |
def boss_shoot_bullet_45! | |
bullet_speed = 10 | |
boss.action = :shooting | |
boss.action_at = state.clock | |
dx = 1 | |
dy = 0.8 | |
flip_horizontally = false | |
if boss.facing == :left | |
dx = -1 | |
flip_horizontally = true | |
end | |
state.bullets << { x: boss_gun_barrel_point.x, | |
y: boss_gun_barrel_point.y, | |
w: 16, | |
h: 7, | |
flip_horizontally: flip_horizontally, | |
dx: bullet_speed * dx, | |
dy: bullet_speed * dy } | |
audio["bullet_#{Kernel.global_tick_count}"] = { | |
input: 'sounds/gunshot.ogg', | |
pause: false, | |
repeat: false, | |
gain: 0.2 | |
} | |
end | |
def boss_gun_barrel_point | |
if boss.facing == :left | |
{ x: boss.x - 32, y: boss.y + 8 } | |
else | |
{ x: boss.x, y: boss.y + 8 } | |
end | |
end | |
def boss | |
state.boss | |
end | |
def calc_camera | |
if player_charging? | |
if player_charge_percentage < 0.2 | |
state.camera.trauma += 0.005 | |
elsif player_charge_percentage < 0.5 | |
state.camera.trauma += 0.01 | |
elsif player_charge_percentage < 0.8 | |
state.camera.trauma += 0.02 | |
else | |
state.camera.trauma += 0.04 | |
end | |
end | |
next_camera_angle = 180.0 / 20.0 * state.camera.trauma**2 | |
next_offset = 100.0 * state.camera.trauma**2 | |
state.camera.angle = state.camera.angle > 0 ? | |
next_camera_angle * -1 : | |
next_camera_angle | |
state.camera.x_offset = next_offset.randomize(:sign, :ratio) | |
state.camera.y_offset = next_offset.randomize(:sign, :ratio) | |
state.camera.trauma *= 0.95 | |
end | |
end | |
def tick args | |
if !args.inputs.keyboard.has_focus && args.gtk.production && args.state.tick_count != 0 | |
args.outputs.background_color = [0, 0, 0] | |
args.outputs.labels << { x: 640, y: 360, text: "Game Paused (click to resume).", alignment_enum: 1, r: 255, g: 255, b: 255 } | |
else | |
$game ||= Game.new | |
$game.args = args | |
$game.tick | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment