Created
December 28, 2021 05:08
-
-
Save amirrajan/0619c931632f6e96335fdbf3eccec6d1 to your computer and use it in GitHub Desktop.
Spirit of Akina built with DragonRuby Game Toolkit
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
# 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