diff options
| author | Amir Rajan <[email protected]> | 2020-09-11 02:02:01 -0500 |
|---|---|---|
| committer | Amir Rajan <[email protected]> | 2020-09-11 02:02:57 -0500 |
| commit | 33ec37b141e896b47ed642923fd33b0c658ae9fb (patch) | |
| tree | a40d3e5d41beeb06508200078f6f26b0ee57d6a4 /samples/99_genre_platformer/the_little_probe/app/main.rb | |
| parent | 958cf43779d2bf528869e80511c4c4f2a433b2db (diff) | |
| download | dragonruby-game-toolkit-contrib-33ec37b141e896b47ed642923fd33b0c658ae9fb.tar.gz dragonruby-game-toolkit-contrib-33ec37b141e896b47ed642923fd33b0c658ae9fb.zip | |
synced samples
Diffstat (limited to 'samples/99_genre_platformer/the_little_probe/app/main.rb')
| -rw-r--r-- | samples/99_genre_platformer/the_little_probe/app/main.rb | 887 |
1 files changed, 887 insertions, 0 deletions
diff --git a/samples/99_genre_platformer/the_little_probe/app/main.rb b/samples/99_genre_platformer/the_little_probe/app/main.rb new file mode 100644 index 0000000..1c90218 --- /dev/null +++ b/samples/99_genre_platformer/the_little_probe/app/main.rb @@ -0,0 +1,887 @@ +class FallingCircle + attr_gtk + + def tick + fiddle + defaults + render + input + calc + end + + def fiddle + state.gravity = -0.02 + circle.radius = 15 + circle.elasticity = 0.4 + camera.follow_speed = 0.4 * 0.4 + end + + def render + render_stage_editor + render_debug + render_game + end + + def defaults + if state.tick_count == 0 + outputs.sounds << "sounds/bg.ogg" + end + + state.storyline ||= [ + { text: "<- -> to aim, hold space to charge", distance_gate: 0 }, + { text: "the little probe - by @amirrajan, made with DragonRuby Game Toolkit", distance_gate: 0 }, + { text: "mission control, this is sasha. landing on europa successful.", distance_gate: 0 }, + { text: "operation \"find earth 2.0\", initiated at 8-29-2036 14:00.", distance_gate: 0 }, + { text: "jupiter's sure is beautiful...", distance_gate: 4000 }, + { text: "hmm, it seems there's some kind of anomoly in the sky", distance_gate: 7000 }, + { text: "dancing lights, i'll call them whisps.", distance_gate: 8000 }, + { text: "#todo... look i ran out of time -_-", distance_gate: 9000 }, + { text: "there's never enough time", distance_gate: 9000 }, + { text: "the game jam was fun though ^_^", distance_gate: 10000 }, + ] + + load_level force: args.state.tick_count == 0 + state.line_mode ||= :terrain + + state.sound_index ||= 1 + circle.potential_lift ||= 0 + circle.angle ||= 90 + circle.check_point_at ||= -1000 + circle.game_over_at ||= -1000 + circle.x ||= -485 + circle.y ||= 12226 + circle.check_point_x ||= circle.x + circle.check_point_y ||= circle.y + circle.dy ||= 0 + circle.dx ||= 0 + circle.previous_dy ||= 0 + circle.previous_dx ||= 0 + circle.angle ||= 0 + circle.after_images ||= [] + circle.terrains_to_monitor ||= {} + circle.impact_history ||= [] + + camera.x ||= 0 + camera.y ||= 0 + camera.target_x ||= 0 + camera.target_y ||= 0 + state.snaps ||= { } + state.snap_number = 10 + + args.state.storyline_x ||= -1000 + args.state.storyline_y ||= -1000 + end + + def render_game + outputs.background_color = [0, 0, 0] + outputs.sprites << [-circle.x + 1100, + -circle.y - 100, + 2416 * 4, + 3574 * 4, + 'sprites/jupiter.png'] + outputs.sprites << [-circle.x, + -circle.y, + 2416 * 4, + 3574 * 4, + 'sprites/level.png'] + outputs.sprites << state.whisp_queue + render_aiming_retical + render_circle + render_notification + end + + def render_notification + toast_length = 500 + if circle.game_over_at.elapsed_time < toast_length + label_text = "..." + elsif circle.check_point_at.elapsed_time > toast_length + args.state.current_storyline = nil + return + end + if circle.check_point_at && + circle.check_point_at.elapsed_time == 1 && + !args.state.current_storyline + if args.state.storyline.length > 0 && args.state.distance_traveled > args.state.storyline[0][:distance_gate] + args.state.current_storyline = args.state.storyline.shift[:text] + args.state.distance_traveled ||= 0 + args.state.storyline_x = circle.x + args.state.storyline_y = circle.y + end + return unless args.state.current_storyline + end + label_text = args.state.current_storyline + return unless label_text + x = circle.x + camera.x + y = circle.y + camera.y - 40 + w = 900 + h = 30 + outputs.primitives << [x - w.idiv(2), y - h, w, h, 255, 255, 255, 255].solid + outputs.primitives << [x - w.idiv(2), y - h, w, h, 0, 0, 0, 255].border + outputs.labels << [x, y - 4, label_text, 1, 1, 0, 0, 0, 255] + end + + def render_aiming_retical + outputs.sprites << [state.camera.x + circle.x + circle.angle.vector_x(circle.potential_lift * 10) - 5, + state.camera.y + circle.y + circle.angle.vector_y(circle.potential_lift * 10) - 5, + 10, 10, 'sprites/circle-orange.png'] + outputs.sprites << [state.camera.x + circle.x + circle.angle.vector_x(circle.radius * 3) - 5, + state.camera.y + circle.y + circle.angle.vector_y(circle.radius * 3) - 5, + 10, 10, 'sprites/circle-orange.png', 0, 128] + if rand > 0.9 + outputs.sprites << [state.camera.x + circle.x + circle.angle.vector_x(circle.radius * 3) - 5, + state.camera.y + circle.y + circle.angle.vector_y(circle.radius * 3) - 5, + 10, 10, 'sprites/circle-white.png', 0, 128] + end + end + + def render_circle + outputs.sprites << circle.after_images.map do |ai| + ai.merge(x: ai.x + state.camera.x - circle.radius, + y: ai.y + state.camera.y - circle.radius, + w: circle.radius * 2, + h: circle.radius * 2, + path: 'sprites/circle-white.png') + end + + outputs.sprites << [(circle.x - circle.radius) + state.camera.x, + (circle.y - circle.radius) + state.camera.y, + circle.radius * 2, + circle.radius * 2, + 'sprites/probe.png'] + end + + def render_debug + return unless state.debug_mode + + outputs.labels << [10, 30, state.line_mode, 0, 0, 0, 0, 0] + outputs.labels << [12, 32, state.line_mode, 0, 0, 255, 255, 255] + + args.outputs.lines << trajectory(circle).line.to_hash.tap do |h| + h[:x] += state.camera.x + h[:y] += state.camera.y + h[:x2] += state.camera.x + h[:y2] += state.camera.y + end + + outputs.primitives << state.terrain.find_all do |t| + circle.x.between?(t.x - 640, t.x2 + 640) || circle.y.between?(t.y - 360, t.y2 + 360) + end.map do |t| + [ + t.line.associate(r: 0, g: 255, b: 0) do |h| + h.x += state.camera.x + h.y += state.camera.y + h.x2 += state.camera.x + h.y2 += state.camera.y + if circle.rect.intersect_rect? t[:rect] + h[:r] = 255 + h[:g] = 0 + end + h + end, + t[:rect].border.associate(r: 255, g: 0, b: 0) do |h| + h.x += state.camera.x + h.y += state.camera.y + h.b = 255 if line_near_rect? circle.rect, t + h + end + ] + end + + outputs.primitives << state.lava.find_all do |t| + circle.x.between?(t.x - 640, t.x2 + 640) || circle.y.between?(t.y - 360, t.y2 + 360) + end.map do |t| + [ + t.line.associate(r: 0, g: 0, b: 255) do |h| + h.x += state.camera.x + h.y += state.camera.y + h.x2 += state.camera.x + h.y2 += state.camera.y + if circle.rect.intersect_rect? t[:rect] + h[:r] = 255 + h[:b] = 0 + end + h + end, + t[:rect].border.associate(r: 255, g: 0, b: 0) do |h| + h.x += state.camera.x + h.y += state.camera.y + h.b = 255 if line_near_rect? circle.rect, t + h + end + ] + end + + if state.god_mode + border = circle.rect.merge(x: circle.rect.x + state.camera.x, + y: circle.rect.y + state.camera.y, + g: 255) + else + border = circle.rect.merge(x: circle.rect.x + state.camera.x, + y: circle.rect.y + state.camera.y, + b: 255) + end + + outputs.borders << border + + overlapping ||= {} + + circle.impact_history.each do |h| + label_mod = 300 + x = (h[:body][:x].-(150).idiv(label_mod)) * label_mod + camera.x + y = (h[:body][:y].+(150).idiv(label_mod)) * label_mod + camera.y + 10.times do + if overlapping[x] && overlapping[x][y] + y -= 52 + else + break + end + end + + overlapping[x] ||= {} + overlapping[x][y] ||= true + outputs.primitives << [x, y - 25, 300, 50, 0, 0, 0, 128].solid + outputs.labels << [x + 10, y + 24, "dy: %.2f" % h[:body][:new_dy], -2, 0, 255, 255, 255] + outputs.labels << [x + 10, y + 9, "dx: %.2f" % h[:body][:new_dx], -2, 0, 255, 255, 255] + outputs.labels << [x + 10, y - 5, " ?: #{h[:body][:new_reason]}", -2, 0, 255, 255, 255] + + outputs.labels << [x + 100, y + 24, "angle: %.2f" % h[:impact][:angle], -2, 0, 255, 255, 255] + outputs.labels << [x + 100, y + 9, "m(l): %.2f" % h[:terrain][:slope], -2, 0, 255, 255, 255] + outputs.labels << [x + 100, y - 5, "m(c): %.2f" % h[:body][:slope], -2, 0, 255, 255, 255] + + outputs.labels << [x + 200, y + 24, "ray: #{h[:impact][:ray]}", -2, 0, 255, 255, 255] + outputs.labels << [x + 200, y + 9, "nxt: #{h[:impact][:ray_next]}", -2, 0, 255, 255, 255] + outputs.labels << [x + 200, y - 5, "typ: #{h[:impact][:type]}", -2, 0, 255, 255, 255] + end + + if circle.floor + outputs.labels << [circle.x + camera.x + 30, circle.y + camera.y + 100, "point: #{circle.floor_point.slice(:x, :y).values}", -2, 0] + outputs.labels << [circle.x + camera.x + 31, circle.y + camera.y + 101, "point: #{circle.floor_point.slice(:x, :y).values}", -2, 0, 255, 255, 255] + outputs.labels << [circle.x + camera.x + 30, circle.y + camera.y + 85, "circle: #{circle.hash.slice(:x, :y).values}", -2, 0] + outputs.labels << [circle.x + camera.x + 31, circle.y + camera.y + 86, "circle: #{circle.hash.slice(:x, :y).values}", -2, 0, 255, 255, 255] + outputs.labels << [circle.x + camera.x + 30, circle.y + camera.y + 70, "rel: #{circle.floor_relative_x} #{circle.floor_relative_y}", -2, 0] + outputs.labels << [circle.x + camera.x + 31, circle.y + camera.y + 71, "rel: #{circle.floor_relative_x} #{circle.floor_relative_y}", -2, 0, 255, 255, 255] + end + end + + def render_stage_editor + return unless state.god_mode + return unless state.point_one + args.lines << [state.point_one, inputs.mouse.point, 0, 255, 255] + end + + def trajectory body + [body.x + body.dx, + body.y + body.dy, + body.x + body.dx * 1000, + body.y + body.dy * 1000, + 0, 255, 255] + end + + def lengthen_line line, num + line = normalize_line(line) + slope = geometry.line_slope(line, replace_infinity: 10).abs + if slope < 2 + [line.x - num, line.y, line.x2 + num, line.y2].line.to_hash + else + [line.x, line.y, line.x2, line.y2].line.to_hash + end + end + + def normalize_line line + if line.x > line.x2 + x = line.x2 + y = line.y2 + x2 = line.x + y2 = line.y + else + x = line.x + y = line.y + x2 = line.x2 + y2 = line.y2 + end + [x, y, x2, y2] + end + + def rect_for_line line + if line.x > line.x2 + x = line.x2 + y = line.y2 + x2 = line.x + y2 = line.y + else + x = line.x + y = line.y + x2 = line.x2 + y2 = line.y2 + end + + w = x2 - x + h = y2 - y + + if h < 0 + y += h + h = h.abs + end + + if w < circle.radius + x -= circle.radius + w = circle.radius * 2 + end + + if h < circle.radius + y -= circle.radius + h = circle.radius * 2 + end + + { x: x, y: y, w: w, h: h } + end + + def snap_to_grid x, y, snaps + snap_number = 10 + x = x.to_i + y = y.to_i + + x_floor = x.idiv(snap_number) * snap_number + x_mod = x % snap_number + x_ceil = (x.idiv(snap_number) + 1) * snap_number + + y_floor = y.idiv(snap_number) * snap_number + y_mod = y % snap_number + y_ceil = (y.idiv(snap_number) + 1) * snap_number + + if snaps[x_floor] + x_result = x_floor + elsif snaps[x_ceil] + x_result = x_ceil + elsif x_mod < snap_number.idiv(2) + x_result = x_floor + else + x_result = x_ceil + end + + snaps[x_result] ||= {} + + if snaps[x_result][y_floor] + y_result = y_floor + elsif snaps[x_result][y_ceil] + y_result = y_ceil + elsif y_mod < snap_number.idiv(2) + y_result = y_floor + else + y_result = y_ceil + end + + snaps[x_result][y_result] = true + return [x_result, y_result] + + end + + def snap_line line + x, y, x2, y2 = line + end + + def string_to_line s + x, y, x2, y2 = s.split(',').map(&:to_f) + + if x > x2 + x2, x = x, x2 + y2, y = y, y2 + end + + x, y = snap_to_grid x, y, state.snaps + x2, y2 = snap_to_grid x2, y2, state.snaps + [x, y, x2, y2].line.to_hash + end + + def load_lines file + data = gtk.read_file(file) || "" + data.each_line + .reject { |l| l.strip.length == 0 } + .map { |l| string_to_line l } + .map { |h| h.merge(rect: rect_for_line(h)) } + end + + def load_terrain + load_lines 'level.txt' + end + + def load_lava + load_lines 'level_lava.txt' + end + + def load_level force: false + if force + state.snaps = {} + state.terrain = load_terrain + state.lava = load_lava + else + state.terrain ||= load_terrain + state.lava ||= load_lava + end + end + + def save_lines lines, file + s = lines.map do |l| + "#{l.x1},#{l.y1},#{l.x2},#{l.y2}" + end.join("\n") + gtk.write_file(file, s) + end + + def save_level + save_lines(state.terrain, 'level.txt') + save_lines(state.lava, 'level_lava.txt') + load_level force: true + end + + def line_near_rect? rect, terrain + geometry.intersect_rect?(rect, terrain[:rect]) + end + + def point_within_line? point, line + return false if !point + return false if !line + return true + end + + def calc_impacts x, dx, y, dy, radius + results = { } + results[:x] = x + results[:y] = y + results[:dx] = x + results[:dy] = y + results[:point] = { x: x, y: y } + results[:rect] = { x: x - radius, y: y - radius, w: radius * 2, h: radius * 2 } + results[:trajectory] = trajectory(results) + results[:impacts] = terrain.find_all { |t| line_near_rect? results[:rect], t }.map do |t| + { + terrain: t, + point: geometry.line_intersect(results[:trajectory], t), + type: :terrain + } + end.reject { |t| !point_within_line? t[:point], t[:terrain] } + + results[:impacts] += lava.find_all { |t| line_near_rect? results[:rect], t }.map do |t| + { + terrain: t, + point: geometry.line_intersect(results[:trajectory], t), + type: :lava + } + end.reject { |t| !point_within_line? t[:point], t[:terrain] } + + results + end + + def calc_potential_impacts + impact_results = calc_impacts circle.x, circle.dx, circle.y, circle.dy, circle.radius + circle.rect = impact_results[:rect] + circle.trajectory = impact_results[:trajectory] + circle.impacts = impact_results[:impacts] + end + + def calc_terrains_to_monitor + circle.impact = nil + circle.impacts.each do |i| + circle.terrains_to_monitor[i[:terrain]] ||= { + ray_start: geometry.ray_test(circle, i[:terrain]), + } + + circle.terrains_to_monitor[i[:terrain]][:ray_current] = geometry.ray_test(circle, i[:terrain]) + if circle.terrains_to_monitor[i[:terrain]][:ray_start] != circle.terrains_to_monitor[i[:terrain]][:ray_current] + if circle.x.between?(i[:terrain].x, i[:terrain].x2) || circle.y.between?(i[:terrain].y, i[:terrain].y2) + circle.impact = i + circle.ray_current = circle.terrains_to_monitor[i[:terrain]][:ray_current] + end + end + end + end + + def impact_result body, impact + infinity_alias = 1000 + r = { + body: {}, + terrain: {}, + impact: {} + } + + r[:body][:line] = body.trajectory.dup + r[:body][:slope] = geometry.line_slope(body.trajectory, replace_infinity: infinity_alias) + r[:body][:slope_sign] = r[:body][:slope].sign + r[:body][:x] = body.x + r[:body][:y] = body.y + r[:body][:dy] = body.dy + r[:body][:dx] = body.dx + + r[:terrain][:line] = impact[:terrain].dup + r[:terrain][:slope] = geometry.line_slope(impact[:terrain], replace_infinity: infinity_alias) + r[:terrain][:slope_sign] = r[:terrain][:slope].sign + + r[:impact][:angle] = geometry.angle_between_lines(body.trajectory, impact[:terrain], replace_infinity: infinity_alias) + r[:impact][:point] = { x: impact[:point].x, y: impact[:point].y } + r[:impact][:same_slope_sign] = r[:body][:slope_sign] == r[:terrain][:slope_sign] + r[:impact][:ray] = body.ray_current + r[:body][:new_on_floor] = body.on_floor + r[:body][:new_floor] = r[:terrain][:line] + + if r[:impact][:angle].abs < 90 && r[:terrain][:slope].abs < 3 + play_sound + r[:body][:new_dy] = r[:body][:dy] * circle.elasticity * -1 + r[:body][:new_dx] = r[:body][:dx] * circle.elasticity + r[:impact][:type] = :horizontal + r[:body][:new_reason] = "-" + elsif r[:impact][:angle].abs < 90 && r[:terrain][:slope].abs > 3 + play_sound + r[:body][:new_dy] = r[:body][:dy] * 1.1 + r[:body][:new_dx] = r[:body][:dx] * -circle.elasticity + r[:impact][:type] = :vertical + r[:body][:new_reason] = "|" + else + play_sound + r[:body][:new_dx] = r[:body][:dx] * -circle.elasticity + r[:body][:new_dy] = r[:body][:dy] * -circle.elasticity + r[:impact][:type] = :slanted + r[:body][:new_reason] = "/" + end + + r[:impact][:energy] = r[:body][:new_dx].abs + r[:body][:new_dy].abs + + if r[:impact][:energy] <= 0.3 && r[:terrain][:slope].abs < 4 + r[:body][:new_dx] = 0 + r[:body][:new_dy] = 0 + r[:impact][:energy] = 0 + r[:body][:new_on_floor] = true + r[:body][:new_floor] = r[:terrain][:line] + r[:body][:new_reason] = "0" + end + + r[:impact][:ray_next] = geometry.ray_test({ x: r[:body][:x] - (r[:body][:dx] * 1.1) + r[:body][:new_dx], + y: r[:body][:y] - (r[:body][:dy] * 1.1) + r[:body][:new_dy] + state.gravity }, + r[:terrain][:line]) + + if r[:impact][:ray_next] == r[:impact][:ray] + r[:body][:new_dx] *= -1 + r[:body][:new_dy] *= -1 + r[:body][:new_reason] = "clip" + end + + r + end + + def game_over! + circle.x = circle.check_point_x + circle.y = circle.check_point_y + circle.dx = 0 + circle.dy = 0 + circle.game_over_at = state.tick_count + end + + def not_game_over! + impact_history_entry = impact_result circle, circle.impact + circle.impact_history << impact_history_entry + circle.x -= circle.dx * 1.1 + circle.y -= circle.dy * 1.1 + circle.dx = impact_history_entry[:body][:new_dx] + circle.dy = impact_history_entry[:body][:new_dy] + circle.on_floor = impact_history_entry[:body][:new_on_floor] + + if circle.on_floor + circle.check_point_at = state.tick_count + circle.check_point_x = circle.x + circle.check_point_y = circle.y + end + + circle.previous_floor = circle.floor || {} + circle.floor = impact_history_entry[:body][:new_floor] || {} + circle.floor_point = impact_history_entry[:impact][:point] + if circle.floor.slice(:x, :y, :x2, :y2) != circle.previous_floor.slice(:x, :y, :x2, :y2) + new_relative_x = if circle.dx > 0 + :right + elsif circle.dx < 0 + :left + else + nil + end + + new_relative_y = if circle.dy > 0 + :above + elsif circle.dy < 0 + :below + else + nil + end + + circle.floor_relative_x = new_relative_x + circle.floor_relative_y = new_relative_y + end + + circle.impact = nil + circle.terrains_to_monitor.clear + end + + def calc_physics + if args.state.god_mode + calc_potential_impacts + calc_terrains_to_monitor + return + end + + if circle.y < -700 + game_over + return + end + + return if state.game_over + return if circle.on_floor + circle.previous_dy = circle.dy + circle.previous_dx = circle.dx + circle.x += circle.dx + circle.y += circle.dy + args.state.distance_traveled ||= 0 + args.state.distance_traveled += circle.dx.abs + circle.dy.abs + circle.dy += state.gravity + calc_potential_impacts + calc_terrains_to_monitor + return unless circle.impact + if circle.impact && circle.impact[:type] == :lava + game_over! + else + not_game_over! + end + end + + def input_god_mode + state.debug_mode = !state.debug_mode if inputs.keyboard.key_down.forward_slash + + # toggle god mode + if inputs.keyboard.key_down.g + state.god_mode = !state.god_mode + state.potential_lift = 0 + circle.floor = nil + circle.floor_point = nil + circle.floor_relative_x = nil + circle.floor_relative_y = nil + circle.impact = nil + circle.terrains_to_monitor.clear + return + end + + return unless state.god_mode + + circle.x = circle.x.to_i + circle.y = circle.y.to_i + + # move god circle + if inputs.keyboard.left || inputs.keyboard.a + circle.x -= 20 + elsif inputs.keyboard.right || inputs.keyboard.d || inputs.keyboard.f + circle.x += 20 + end + + if inputs.keyboard.up || inputs.keyboard.w + circle.y += 20 + elsif inputs.keyboard.down || inputs.keyboard.s + circle.y -= 20 + end + + # delete terrain + if inputs.keyboard.key_down.x + calc_terrains_to_monitor + state.terrain = state.terrain.reject do |t| + t[:rect].intersect_rect? circle.rect + end + + state.lava = state.lava.reject do |t| + t[:rect].intersect_rect? circle.rect + end + + calc_potential_impacts + save_level + end + + # change terrain type + if inputs.keyboard.key_down.l + if state.line_mode == :terrain + state.line_mode = :lava + else + state.line_mode = :terrain + end + end + + if inputs.mouse.click && !state.point_one + state.point_one = inputs.mouse.click.point + elsif inputs.mouse.click && state.point_one + l = [*state.point_one, *inputs.mouse.click.point] + l = [l.x - state.camera.x, + l.y - state.camera.y, + l.x2 - state.camera.x, + l.y2 - state.camera.y].line.to_hash + l[:rect] = rect_for_line l + if state.line_mode == :terrain + state.terrain << l + else + state.lava << l + end + save_level + next_x = inputs.mouse.click.point.x - 640 + next_y = inputs.mouse.click.point.y - 360 + circle.x += next_x + circle.y += next_y + state.point_one = nil + elsif inputs.keyboard.one + state.point_one = [circle.x + camera.x, circle.y+ camera.y] + end + + # cancel chain lines + if inputs.keyboard.key_down.nine || inputs.keyboard.key_down.escape || inputs.keyboard.key_up.six || inputs.keyboard.key_up.one + state.point_one = nil + end + end + + def play_sound + return if state.sound_debounce > 0 + state.sound_debounce = 5 + outputs.sounds << "sounds/03#{"%02d" % state.sound_index}.wav" + state.sound_index += 1 + if state.sound_index > 21 + state.sound_index = 1 + end + end + + def input_game + if inputs.keyboard.down || inputs.keyboard.space + circle.potential_lift += 0.03 + circle.potential_lift = circle.potential_lift.lesser(10) + elsif inputs.keyboard.key_up.down || inputs.keyboard.key_up.space + play_sound + circle.dy += circle.angle.vector_y circle.potential_lift + circle.dx += circle.angle.vector_x circle.potential_lift + + if circle.on_floor + if circle.floor_relative_y == :above + circle.y += circle.potential_lift.abs * 2 + elsif circle.floor_relative_y == :below + circle.y -= circle.potential_lift.abs * 2 + end + end + + circle.on_floor = false + circle.potential_lift = 0 + circle.terrains_to_monitor.clear + circle.impact_history.clear + circle.impact = nil + calc_physics + end + + # aim probe + if inputs.keyboard.right || inputs.keyboard.a + circle.angle -= 2 + elsif inputs.keyboard.left || inputs.keyboard.d + circle.angle += 2 + end + end + + def input + input_god_mode + input_game + end + + def calc_camera + state.camera.target_x = 640 - circle.x + state.camera.target_y = 360 - circle.y + xdiff = state.camera.target_x - state.camera.x + ydiff = state.camera.target_y - state.camera.y + state.camera.x += xdiff * camera.follow_speed + state.camera.y += ydiff * camera.follow_speed + end + + def calc + state.sound_debounce ||= 0 + state.sound_debounce -= 1 + state.sound_debounce = 0 if state.sound_debounce < 0 + if state.god_mode + circle.dy *= 0.1 + circle.dx *= 0.1 + end + calc_camera + state.whisp_queue ||= [] + if state.tick_count.mod_zero?(4) + state.whisp_queue << { + x: -300, + y: 1400 * rand, + speed: 2.randomize(:ratio) + 3, + w: 20, + h: 20, path: 'sprites/whisp.png', + a: 0, + created_at: state.tick_count, + angle: 0, + r: 100, + g: 128 + 128 * rand, + b: 128 + 128 * rand + } + end + + state.whisp_queue.each do |w| + w.x += w[:speed] * 2 + w.x -= circle.dx * 0.3 + w.y -= w[:speed] + w.y -= circle.dy * 0.3 + w.angle += w[:speed] + w.a = w[:created_at].ease(30) * 255 + end + + state.whisp_queue = state.whisp_queue.reject { |w| w[:x] > 1280 } + + if state.tick_count.mod_zero?(2) && (circle.dx != 0 || circle.dy != 0) + circle.after_images << { + x: circle.x, + y: circle.y, + w: circle.radius, + h: circle.radius, + a: 255, + created_at: state.tick_count + } + end + + circle.after_images.each do |ai| + ai.a = ai[:created_at].ease(10, :flip) * 255 + end + + circle.after_images = circle.after_images.reject { |ai| ai[:created_at].elapsed_time > 10 } + calc_physics + end + + def circle + state.circle + end + + def camera + state.camera + end + + def terrain + state.terrain + end + + def lava + state.lava + end +end + +# $gtk.reset + +def tick args + args.outputs.background_color = [0, 0, 0] + if args.inputs.keyboard.r + args.gtk.reset + return + end + # uncomment the line below to slow down the game so you + # can see each tick as it passes + # args.gtk.slowmo! 30 + $game ||= FallingCircle.new + $game.args = args + $game.tick +end + +def reset + $game = nil +end |
