Skip to content

Instantly share code, notes, and snippets.

@amirrajan
Created December 28, 2021 05:08
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save amirrajan/0619c931632f6e96335fdbf3eccec6d1 to your computer and use it in GitHub Desktop.
Save amirrajan/0619c931632f6e96335fdbf3eccec6d1 to your computer and use it in GitHub Desktop.
Spirit of Akina built with DragonRuby Game Toolkit
# Copyright 2021 Scratchwork Development LLC. All rights reserved.
PI = 3.1415926
class Game
attr_gtk
def tick
defaults
render
input
calc
end
def defaults
defaults_fiddle
defaults_game
end
def defaults_fiddle
state.sprite.width = 19 * 1.5
state.sprite.height = 10 * 1.5
state.momentum_toward_normal = 1.04
state.momentum_away_from_normal = 0.96
state.momentum_decay_out_of_drift = 0.98
state.momentum_decay_in_drift = 0.99
state.drift_minimum = 0.1
state.drift_maximum = 0.5
state.steering_wheel_range = (PI.fdiv 4).round 4
state.steering_wheel_delta_in_drift = 3300.0
state.steering_wheel_delta_out_of_drift = 4800.0
state.steering_wheel_delta_drifting = (PI.fdiv state.steering_wheel_delta_in_drift).round 4
state.steering_wheel_delta_not_drifting = (PI.fdiv state.steering_wheel_delta_out_of_drift).round 4
state.steering_wheel_reset_perc = 0.5
state.camera.scale = 1.5
end
def defaults_game
return if state.tick_count != 0
load_map!
reset_game!
end
def render
outputs.background_color = [0, 0, 0]
render_won
render_game
render_instructions
end
def render_won
return if !state.won
outputs.labels << { x: 640, y: 380, text: "you win!", size_enum: 5, alignment_enum: 1, r: 255, g: 255, b: 255 }
outputs.labels << { x: 640, y: 340, text: "#{'%.2f' % (state.clock.fdiv 60)}s", size_enum: 5, r: 255, g: 255, b: 255, alignment_enum: 1 }
outputs.labels << { x: 640, y: 300, text: "(press 'r' to go again)", size_enum: 5, r: 255, g: 255, b: 255, alignment_enum: 1 }
end
def render_god_mode
return if state.won
return if state.god_mode != :enabled
outputs[:scene].borders << { x: inputs.mouse.x - 30, y: inputs.mouse.y - 30, w: 60, h: 60, g: 255 }
outputs[:scene].borders << state.track_rects.map { |t| relative_to_car t.merge(g: 255) }
outputs[:scene].borders << (relative_to_car car_collision_rect)
end
def render_game
return if state.won
outputs[:scene].w = 2560
outputs[:scene].h = 1440
outputs[:scene].background_color = [0, 0, 0, 0]
outputs[:scene].primitives << sprites_map_viewport
outputs[:scene].primitives << state.smoke.map { |smoke| relative_to_car smoke }
outputs[:scene].primitives << sprites_car.merge(x: sprites_car.x + 640, y: sprites_car.y + 360)
outputs[:scene].primitives << state.deaths.flat_map do |stones|
stones.map { |stone| relative_to_car stone }
end
if state.god_mode == :enabled
outputs.sprites << { x: 0,
y: 0,
w: 2560,
h: 1440,
path: :scene, blendmode_enum: 0 }
else
outputs.sprites << { x: 0 + sprites_scene_offset_x,
y: 0 + sprites_scene_offset_y,
w: 2560 * state.camera.scale,
h: 1440 * state.camera.scale,
path: :scene, blendmode_enum: 0 }
end
end
def render_instructions
outputs.labels << { x: 10,
y: 30,
text: "controls - turn: left/right arrow keys, drift: spacebar",
size_enum: 1,
alignment_enum: 0,
r: 255,
g: 255,
b: 255 }
end
def input
input_game
input_god_mode
end
def input_game
reset_game! if inputs.keyboard.key_down.r
return if state.clock < 30
turn_magnitude = 1.0
if inputs.controller_one.left_analog_x_perc.abs > 0
turn_magnitude = inputs.controller_one.left_analog_x_perc * 2.0
if turn_magnitude > 1.0
turn_magnitude = 1.0
elsif turn_magnitude < -1.0
turn_magnitude = -1.0
end
end
drift_down = !!inputs.keyboard.space ||
!!inputs.controller_one.r1 ||
!!inputs.controller_one.r2
left_down = !!inputs.left || !!inputs.keyboard.j
right_down = !!inputs.right || !!inputs.keyboard.k
state.turn_magnitude = turn_magnitude
if left_finger
if left_finger.x < 320
outputs.borders << { x: 0, y: 0, w: 320, h: 720, r: 255, g: 255, b: 255, a: 128 }
left_down = true
right_down = false
elsif left_finger.x > 320
outputs.borders << { x: 320, y: 0, w: 320, h: 720, r: 255, g: 255, b: 255, a: 128 }
left_down = false
right_down = true
end
end
if right_finger
outputs.borders << { x: 640, y: 0, w: 640, h: 720, r: 255, g: 255, b: 255, a: 128 }
drift_down = true
end
if left_down
state.steering = :left
elsif right_down
state.steering = :right
else
state.steering = :released
end
if drift_down
state.drift_mode = :on
else
state.drift_mode = :off
end
end
def input_god_mode
input_god_mode_enabled
input_god_mode_toggle
end
def input_god_mode_enabled
return if state.god_mode != :enabled
input_god_mode_enabled_mouse
input_god_mode_enabled_keyboard
end
def input_god_mode_enabled_mouse
return if !inputs.mouse.click
if inputs.keyboard.x
to_delete = state.collision_points.find do |r|
inputs.mouse.inside_rect? (relative_to_car x: r.x - 30, y: r.y - 30, w: 60, h: 60)
end
if to_delete
state.collision_points.reject! { |p| p.x == to_delete.x && p.y == to_delete.y }
state.track_rects = state.collision_points.map { |p| { x: p.x - 30, y: p.y - 30, w: 60, h: 60 } }
save_map!
end
else
point = [inputs.mouse.x + state.x - 640, inputs.mouse.y + state.y - 360]
state.collision_points << point
state.track_rects = state.collision_points.map { |p| { x: p.x - 30, y: p.y - 30, w: 60, h: 60 } }
state.x = point.x
state.y = point.y
save_map!
end
end
def input_god_mode_enabled_keyboard
load_map! if inputs.keyboard.key_down.e
if inputs.keyboard.space
state.angle_r += inputs.left_right * 0.01
else
state.y += inputs.keyboard.up_down * 5
state.x += inputs.keyboard.left_right * 5
end
end
def input_god_mode_toggle
return if !inputs.keyboard.key_down.g
if state.god_mode == :disabled
state.god_mode = :enabled
else
state.god_mode = :disabled
end
end
def calc
return if state.god_mode == :enabled
return if state.won
state.clock += 1
calc_camera
calc_steering
calc_physics
calc_smoke
calc_car
calc_prism_stones
calc_game_over
end
def calc_camera
state.camera.target_center_x = -((state.angle_r.to_degrees + 90).vector_x * 340)
state.camera.target_center_y = ((state.angle_r.to_degrees + 90).vector_y * 260)
state.camera.center_x += (state.camera.target_center_x - state.camera.center_x) * 0.01
state.camera.center_y += (state.camera.target_center_y - state.camera.center_y) * 0.01
end
def calc_steering
case state.steering
when :right
if state.steer_angle_r > state.steering_wheel_range * -1.0
state.steer_angle_r = state.steer_angle_r - steering_wheel_delta * state.turn_magnitude.abs
end
when :left
if state.steer_angle_r < state.steering_wheel_range
state.steer_angle_r = state.steer_angle_r + steering_wheel_delta * state.turn_magnitude.abs
end
when :released
if state.steer_angle_r < 0
state.steer_angle_r = state.steer_angle_r + steering_wheel_delta * state.steering_wheel_reset_perc
elsif state.steer_angle_r > 0
state.steer_angle_r = state.steer_angle_r - steering_wheel_delta * state.steering_wheel_reset_perc
end
end
state.steer_angle_r = state.steer_angle_r.round(4).to_f
end
def calc_physics
if state.drift_mode == :on
if state.steer_angle_r > 0
state.drift_percentage_right *= state.momentum_toward_normal
state.drift_percentage_left *= state.momentum_away_from_normal
elsif state.steer_angle_r < 0
state.drift_percentage_right *= state.momentum_away_from_normal
state.drift_percentage_left *= state.momentum_toward_normal
else
state.drift_percentage_right *= state.momentum_decay_out_of_drift
state.drift_percentage_left *= state.momentum_decay_out_of_drift
end
else
state.drift_percentage_right *= state.momentum_decay_out_of_drift
state.drift_percentage_left *= state.momentum_decay_out_of_drift
end
state.drift_percentage_right = state.drift_minimum if state.drift_mode == :on && state.drift_percentage_right < state.drift_minimum
state.drift_percentage_left = state.drift_minimum if state.drift_mode == :on && state.drift_percentage_left < state.drift_minimum
state.drift_percentage_right = state.drift_maximum if state.drift_percentage_right > state.drift_maximum
state.drift_percentage_left = state.drift_maximum if state.drift_percentage_left > state.drift_maximum
state.drift_percentage_right = state.drift_percentage_right.round(4)
state.drift_percentage_left = state.drift_percentage_left.round(4)
end
def calc_smoke
state.smoke.each do |p|
p.w += 2
p.h += 2
p.x -= 1
p.y -= 1
p.x -= p.dx
p.y -= p.dy
p.a -= 2
end
state.smoke.reject! { |p| p.a <= 0 }
return if state.drift_mode == :off
return if !state.tick_count.zmod?(5)
state.smoke << sprites_smoke_new
end
def calc_car
velocity_x = state.speed * Math.sin(state.angle_r + state.steer_angle_r) * (1.0 - state.drift_percentage_right - state.drift_percentage_left)
velocity_y = state.speed * Math.cos(state.angle_r + state.steer_angle_r) * (1.0 - state.drift_percentage_right - state.drift_percentage_left)
normal_x_right = state.speed * Math.sin((PI / 2.0 ) - state.angle_r) * state.drift_percentage_right
normal_y_right = state.speed * Math.cos((PI / 2.0 ) - state.angle_r) * state.drift_percentage_right * -1.0
normal_x_left = state.speed * Math.sin((PI / 2.0 ) - state.angle_r) * state.drift_percentage_left * -1.0
normal_y_left = state.speed * Math.cos((PI / 2.0 ) - state.angle_r) * state.drift_percentage_left
state.x += velocity_x + normal_x_right + normal_x_left
state.y += velocity_y + normal_y_right + normal_y_left
state.angle_r -= state.steer_angle_r
end
def calc_prism_stones
state.deaths.each do |stones|
stones.each do |stone|
stone.w ||= 8
stone.h ||= 8
stone.x ||= stone.original_x
stone.y ||= stone.original_y
stone.path ||= "sprites/#{stone.type}-stone.png"
stone.a = ((stone.t - stone.current_t).fdiv stone.t) * 255
stone.lifetime ||= 300 * 60
stone.lifetime -= 1
if !stone.still
if stone.current_t == stone.t
stone.x = stone.original_x
stone.y = stone.original_y
stone.current_t = 0
else
stone.y += stone.dy
stone.x += stone.dx
stone.current_t += 1
end
end
end
end
end
def calc_game_over
if state.track_rects.length > 1 && (state.track_rects.last.intersect_rect? car_collision_rect)
state.won = true
elsif !state.track_rects.any? { |c| c.intersect_rect? car_collision_rect }
state.deaths << new_prism_stones if state.x != 155 && state.y != 1258
reset_game!
end
end
def reset_game!
state.deaths ||= []
state.deaths.reject! { |d| d.any? { |s| s.lifetime && s.lifetime <= 0 } }
state.god_mode = :disabled
state.x = 155
state.y = 730
state.speed = 1
state.angle_r = 0
state.steering = :released
state.steer_angle_r = 0.0
state.drift_percentage_right = 0.0
state.drift_percentage_left = 0.0
state.drift_mode = :off
state.speed = 4.0
state.clock = 0
state.won = false
state.drift_percentage_left = 0
state.drift_percentage_right = 0
state.drift_sound_debounce = 0
state.car_after_images = []
state.smoke = []
state.camera.target_center_x = 0
state.camera.target_center_y = 0
state.camera.center_x = 0
state.camera.center_y = 0
end
def steering_wheel_delta
return state.steering_wheel_delta_drifting if state.drift_mode == :on
return state.steering_wheel_delta_not_drifting
end
def sprites_car
{
x: -state.sprite.width.half,
y: -state.sprite.height.half,
w: state.sprite.width,
h: state.sprite.height,
path: 'sprites/86.png',
angle: 90 + (state.angle_r.to_degrees * -1),
rotation_anchor_x: 0.7,
rotation_anchor_y: 0.5,
}
end
def sprites_car_after_image
sprites_car.merge(x: state.x - state.sprite.width.half,
y: state.y - state.sprite.height.half,
angle: 90 + (state.angle_r.to_degrees * -1),
a: 200)
end
def sprites_smoke_new
angle = 90 + (state.angle_r.to_degrees * -1)
sprites_car.merge x: state.x - state.sprite.width.half,
y: state.y - state.sprite.height.half,
dx: angle.vector_x * 0.5,
dy: angle.vector_y * 0.5,
angle: angle,
a: 255,
path: ["sprites/smoke-1.png", "sprites/smoke-2.png", "sprites/smoke-3.png"].sample
end
def car_collision_rect
{
x: state.x - 6,
y: state.y - 6,
w: 12,
h: 12,
r: 255
}
end
def sprites_map
{
x: 0,
y: 0,
w: 6400,
h: 6400,
path: 'sprites/map.png'
}
end
def load_map!
state.collision_points = (gtk.deserialize_state 'data/map.txt').collision_points if gtk.read_file 'data/map.txt'
state.track_rects = state.collision_points.map { |p| { x: p.x - 30, y: p.y - 30, w: 60, h: 60 } }
end
def save_map!
gtk.serialize_state 'data/map.txt', state
end
def relative_to_car point
return nil if !point.x || !point.y
point.merge x: point.x - state.x + 640,
y: point.y - state.y + 360
end
def new_prism_stones
c = [:blue, :red, :yellow, :teal, :green, :white].sample
[
{ original_x: state.x, original_y: state.y, w: 8, h: 8,
type: c, current_t: 0, t: 120, dy: 0, dx: 0, still: true },
{ original_x: state.x - 1, original_y: state.y,
type: c, current_t: 0, t: 120, dy: 0.25, dx: 0 },
{ original_x: state.x + 1, original_y: state.y,
type: c, current_t: 0, t: 137, dy: 0.2, dx: 0 }
]
end
def sprites_scene_offset_x
-((1280 * state.camera.scale) - 1280).half - state.camera.center_x
end
def sprites_scene_offset_y
-(( 720 * state.camera.scale) - 720).half - state.camera.center_y
end
def sprites_map_viewport
x = 0
y = 0
source_x = state.x - 640
source_y = state.y - 360
if state.x < 640
source_x = 0
x = 640 - state.x
end
if state.y < 720
source_y = 0
y = 360 - state.y
end
{
x: x,
y: y,
w: 2560,
h: 1440,
source_x: source_x,
source_y: source_y,
source_w: 2560,
source_h: 1440,
path: 'sprites/map.png'
}
end
def left_finger
if inputs.finger_one && inputs.finger_one.x < 640
return inputs.finger_one
elsif inputs.finger_two && inputs.finger_two.x < 640
return inputs.finger_two
else
return nil
end
end
def right_finger
if inputs.finger_one && inputs.finger_one.x > 640
return inputs.finger_one
elsif inputs.finger_two && inputs.finger_two.x > 640
return inputs.finger_two
else
return nil
end
end
end
def tick args
$game ||= Game.new
$game.args = args
$game.tick
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment