Skip to content

Instantly share code, notes, and snippets.

@amirrajan
Last active July 28, 2022 13:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save amirrajan/68396c1555a8083dc91afeb06565348d to your computer and use it in GitHub Desktop.
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
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