Last active
July 30, 2026 14:45
-
-
Save amirrajan/80770d6df09c487e4143c1cb4869e1e1 to your computer and use it in GitHub Desktop.
Celeste Classic in DragonRuby Game Toolkit (WIP)
This file contains hidden or 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 :camera | |
| def initialize world_data | |
| @world_data = world_data | |
| @player = Player.new @world_data.player.ordinal_x, | |
| @world_data.player.ordinal_y | |
| @tiles = @world_data.tiles | |
| @walls = @world_data.walls | |
| @spikes = @world_data.spikes | |
| @camera = { | |
| trauma: 0, | |
| angle: 0, | |
| x_offset: 0, | |
| y_offset: 0 | |
| } | |
| end | |
| def tick inputs | |
| @player.tick inputs, @walls | |
| spike = Geometry.find_intersect_rect @player, @spikes, using: :hurt_box | |
| if spike || @player.y < -8 | |
| kill_and_reset_player | |
| end | |
| tick_camera | |
| end | |
| def kill_and_reset_player | |
| @camera.trauma = 0.2 | |
| @player.reset @world_data.player.ordinal_x, | |
| @world_data.player.ordinal_y | |
| end | |
| def tick_camera | |
| next_camera_angle = 180.0 / 20.0 * @camera.trauma**2 | |
| next_offset = 100.0 * @camera.trauma**2 | |
| @camera.angle = @camera.angle > 0 ? | |
| next_camera_angle * -1 : | |
| next_camera_angle | |
| @camera.x_offset = next_offset.randomize(:sign, :ratio) | |
| @camera.y_offset = next_offset.randomize(:sign, :ratio) | |
| @camera.trauma *= 0.95 | |
| end | |
| def primitives | |
| [ | |
| world_primitives, | |
| @player.primitives | |
| ] | |
| end | |
| def world_primitives | |
| @tiles.map do |h| | |
| color = if h.type == :wall | |
| { r: 30, g: 128, b: 255, a: 255 } | |
| elsif h.type == :spike | |
| { r: 255, g: 128, b: 30, a: 255 } | |
| else | |
| { r: 0, g: 0, b: 0, a: 0 } | |
| end | |
| { | |
| **h.rect, | |
| path: :solid, | |
| **color | |
| } | |
| end | |
| end | |
| end | |
| class Player | |
| attr :x, :y, :w, :h, :dead | |
| def initialize ordinal_x, ordinal_y | |
| reset ordinal_x, ordinal_y | |
| end | |
| def reset ordinal_x, ordinal_y | |
| @ordinal_x = ordinal_x | |
| @ordinal_y = ordinal_y | |
| @max_dx = 1 | |
| @accel = 0.6 | |
| @deccel = 0.6 | |
| @dx = 0 | |
| @dy = 0 | |
| @gravity = 0.105 | |
| @max_dy = 2 | |
| @x = @ordinal_x * 8 | |
| @y = @ordinal_y * 8 | |
| @on_ground = true | |
| @on_ground_grace = 6 | |
| @w = 8 | |
| @h = 8 | |
| end | |
| def hurt_box | |
| { | |
| x: @x, | |
| y: @y, | |
| w: 8, | |
| h: 8, | |
| } | |
| end | |
| def rect | |
| { | |
| x: @x, | |
| y: @y, | |
| w: 8, | |
| h: 8, | |
| } | |
| end | |
| def tick inputs, walls | |
| if inputs.keyboard.key_down.j && @on_ground | |
| @on_ground = false | |
| @dy = 2 | |
| end | |
| if inputs.left_right == 0 | |
| @dx = @dx.lerp(0, @deccel) | |
| else | |
| @dx = @dx.lerp(@max_dx * inputs.left_right, @accel) | |
| end | |
| @x += @dx | |
| collision = Geometry.find_intersect_rect self, walls, using: :rect | |
| if collision | |
| if @dx > 0 | |
| @x = collision.rect.x - @w | |
| elsif @dx < 0 | |
| @x = collision.rect.x + collision.rect.w | |
| end | |
| end | |
| @y += @dy | |
| @dy -= @gravity | |
| collision = Geometry.find_intersect_rect self, walls, using: :rect | |
| if collision | |
| if @dy > 0 | |
| @y = collision.rect.y - @h | |
| elsif @dy < 0 | |
| @y = collision.rect.y + collision.rect.h | |
| end | |
| @on_ground = true | |
| @on_ground_grace = 6 | |
| @dy = 0 | |
| elsif @on_ground | |
| next_player_location = { | |
| rect: { x: @x, y: @y - 4, w: 8, h: 8 } | |
| } | |
| collision = Geometry.find_intersect_rect next_player_location, walls, using: :rect | |
| if !collision | |
| @on_ground_grace -= 1 | |
| if @on_ground_grace < 0 | |
| @on_ground = false | |
| end | |
| end | |
| end | |
| end | |
| def primitives | |
| { | |
| **rect, | |
| path: :solid, | |
| r: 30, g: 255, b: 128 | |
| } | |
| end | |
| end | |
| module Main | |
| def start | |
| @game = Game.new parse_world_data | |
| end | |
| def tick | |
| @game.tick inputs | |
| outputs.background_color = [30, 30, 30] | |
| outputs[:scene].background_color = [30, 30, 30] | |
| outputs[:scene].primitives << @game.primitives | |
| outputs.primitives << { | |
| x: 0 - @game.camera.x_offset, | |
| y: 0 - @game.camera.y_offset, | |
| w: 128, | |
| h: 128, | |
| path: :scene, | |
| angle: @game.camera.angle | |
| } | |
| end | |
| def parse_world_data | |
| tiles = [] | |
| walls = [] | |
| spikes = [] | |
| player = nil | |
| ascii_world = <<~S | |
| 0000000000000 0 | |
| 0000000 0 | |
| 0000 0 | |
| 000 000 | |
| 000 0000 | |
| 000 00 | |
| 00000 0 | |
| 00 0 | |
| 0 0 | |
| 0 000 | |
| 0 00 | |
| 0 00 00 | |
| 0P 00 00 XXX00 | |
| 00000 00XX00000 | |
| 00000XX000000000 | |
| 0000000000000000 | |
| S | |
| symbols = ascii_world.strip | |
| .split("\n") | |
| .map | |
| .with_index do |line, row| | |
| line.chars | |
| .map | |
| .with_index do |char, col| | |
| if char == "0" | |
| :wall | |
| elsif char == "X" | |
| :spike | |
| elsif char == "P" | |
| :player | |
| else | |
| :empty | |
| end | |
| end | |
| end.reverse | |
| symbols.each_with_index do |xs, y| | |
| xs.each_with_index do |t, x| | |
| if t == :player | |
| player = { ordinal_x: x, ordinal_y: y } | |
| elsif t != :empty | |
| entry = { | |
| ordinal_x: x, | |
| ordinal_y: y, | |
| type: t, | |
| rect: { | |
| x: x * 8, | |
| y: y * 8, | |
| w: 8, | |
| h: 8 | |
| }, | |
| hurt_box: { | |
| x: x * 8 + 1, | |
| y: y * 8 + 1, | |
| w: 6, | |
| h: 6 | |
| } | |
| } | |
| tiles << entry | |
| if t == :wall | |
| walls << entry | |
| elsif t == :spike | |
| spikes << entry | |
| end | |
| end | |
| end | |
| end | |
| { | |
| player: player, | |
| tiles: tiles, | |
| walls: walls, | |
| spikes: spikes | |
| } | |
| end | |
| end | |
| DR.reset |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment