diff --git a/shard.lock b/shard.lock index 1031c8b..dafb81d 100644 --- a/shard.lock +++ b/shard.lock @@ -4,6 +4,10 @@ shards: git: https://github.com/unn4m3d/crystaledge.git version: 0.2.6 + pixelfaucet: + git: https://github.com/sleepinginsomniac/pixelfaucet.git + version: 0.0.1+git.commit.5b285d55403dafd318c19b7d4ef9069b67c19625 + sdl: git: https://github.com/ysbaddaden/sdl.cr.git version: 0.1.0+git.commit.0ded44711246feb3aa3ba28fea249c9b03be061b diff --git a/shard.yml b/shard.yml index 3067272..e241a78 100644 --- a/shard.yml +++ b/shard.yml @@ -13,8 +13,6 @@ crystal: 1.2.1 license: MIT dependencies: - sdl: - github: ysbaddaden/sdl.cr - crystaledge: - github: unn4m3d/crystaledge - version: 0.2.6 + pixelfaucet: + github: SleepingInsomniac/pixelfaucet + # version: 0.0.1 diff --git a/src/asteroid.cr b/src/asteroid.cr index 3739e79..9a82304 100644 --- a/src/asteroid.cr +++ b/src/asteroid.cr @@ -1,12 +1,13 @@ -require "./lx_game/sprite/circle_collision" -require "./lx_game/sprite/vector_sprite" +require "pixelfaucet/sprite" +require "pixelfaucet/sprite/circle_collision" +require "pixelfaucet/sprite/vector_sprite" -class Asteroid < Sprite - include LxGame::CircleCollision - include LxGame::VectorSprite +class Asteroid < PF::Sprite + include PF::CircleCollision + include PF::VectorSprite property size : Float64 = 1.0 - property color : Pixel = Pixel.new(128, 128, 128, 255) + property color = PF::Pixel.new(128, 128, 128, 255) def initialize super diff --git a/src/asteroids.cr b/src/asteroids.cr index 38efe67..72f7fcb 100644 --- a/src/asteroids.cr +++ b/src/asteroids.cr @@ -1,19 +1,19 @@ require "sdl" require "crystaledge" include CrystalEdge -require "./lx_game/*" -include LxGame +require "pixelfaucet/game" + require "./ship" require "./asteroid" require "./bullet" require "./explosion" -class Asteroids < Game +class Asteroids < PF::Game @ship : Ship @asteroids = [] of Asteroid @bullets = [] of Bullet @explosions = [] of Explosion - @controller : Controller(LibSDL::Keycode) + @controller : PF::Controller(LibSDL::Keycode) @asteroid_count = 3 @restart_timer = 0.0 @@ -26,7 +26,7 @@ class Asteroids < Game setup_round - @controller = Controller(LibSDL::Keycode).new({ + @controller = PF::Controller(LibSDL::Keycode).new({ LibSDL::Keycode::UP => "Thrust", LibSDL::Keycode::RIGHT => "Rotate Right", LibSDL::Keycode::LEFT => "Rotate Left", @@ -35,7 +35,7 @@ class Asteroids < Game end # override to wrap the coordinates - def draw_point(x : Int32, y : Int32, pixel : Pixel, surface = @screen) + def draw_point(x : Int32, y : Int32, pixel : PF::Pixel, surface = @screen) x = x % @width y = y % @height @@ -61,7 +61,7 @@ class Asteroids < Game size = rand(20.0..35.0) a.mass = size - a.frame = VectorSprite.generate_circle(size.to_i, size: size, jitter: 3.0) + a.frame = PF::VectorSprite.generate_circle(size.to_i, size: size, jitter: 3.0) end end end @@ -145,7 +145,7 @@ class Asteroids < Game size = asteroid.average_radius / 2 a.mass = size points = size < 6 ? 6 : size.to_i - a.frame = VectorSprite.generate_circle(points, size: size, jitter: 3.0) + a.frame = PF::VectorSprite.generate_circle(points, size: size, jitter: 3.0) end end end @@ -187,14 +187,14 @@ class Asteroids < Game end end - def draw(engine) - engine.clear - @ship.draw(engine) - @bullets.each { |b| b.draw(engine) } - @asteroids.each { |a| a.draw(engine) } - @explosions.each { |e| e.draw(engine) } + def draw + clear + @ship.draw(self) + @bullets.each { |b| b.draw(self) } + @asteroids.each { |a| a.draw(self) } + @explosions.each { |e| e.draw(self) } end end -game = Asteroids.new(500, 400, 2) +game = Asteroids.new(600, 400, 2) game.run! diff --git a/src/bullet.cr b/src/bullet.cr index 384cb5c..206ab1c 100644 --- a/src/bullet.cr +++ b/src/bullet.cr @@ -1,9 +1,10 @@ -require "./lx_game/sprite/age" +require "pixelfaucet/sprite" +require "pixelfaucet/sprite/age" -class Bullet < Sprite - include LxGame::SpriteAge +class Bullet < PF::Sprite + include PF::SpriteAge - @lifespan = 4.0 + @lifespan = 2.5 def update(dt) update_age(dt) @@ -13,11 +14,11 @@ class Bullet < Sprite def draw(engine) brightness = (((4.0 - self.age) / 4.0) * 255).to_u8 - color = Pixel.new(r: brightness, g: brightness, b: 0_u8) + color = PF::Pixel.new(r: brightness, g: brightness, b: 0_u8) engine.draw_point(@position.x.to_i, @position.y.to_i, color) end - def collides_with?(other : VectorSprite) + def collides_with?(other : PF::VectorSprite) @position.distance(other.position) < other.radius end end diff --git a/src/explosion.cr b/src/explosion.cr index e174395..f88c57c 100644 --- a/src/explosion.cr +++ b/src/explosion.cr @@ -1,7 +1,8 @@ -require "./lx_game/sprite/age" +require "pixelfaucet/emitter" +require "pixelfaucet/sprite/age" -class Explosion < LxGame::Emitter - include LxGame::SpriteAge +class Explosion < PF::Emitter + include PF::SpriteAge def update(dt) update_age(dt) diff --git a/src/lx_game/controller.cr b/src/lx_game/controller.cr deleted file mode 100644 index edf080f..0000000 --- a/src/lx_game/controller.cr +++ /dev/null @@ -1,31 +0,0 @@ -module LxGame - # Handle button to action mapping in a dynamic way - class Controller(T) - def initialize(@mapping : Hash(T, String)) - @keysdown = {} of String => Bool - - @mapping.values.each do |key| - @keysdown[key] = false - end - end - - def registered?(button) - @mapping.keys.includes?(button) - end - - def press(button) - return nil unless registered?(button) - @keysdown[@mapping[button]] = true - end - - def release(button) - return nil unless registered?(button) - @keysdown[@mapping[button]] = false - end - - # Returns duration of time pressed or false if not pressed - def action?(name) - @keysdown[name] - end - end -end diff --git a/src/lx_game/emitter.cr b/src/lx_game/emitter.cr deleted file mode 100644 index 5c6d055..0000000 --- a/src/lx_game/emitter.cr +++ /dev/null @@ -1,55 +0,0 @@ -require "./sprite" -require "./particle" - -module LxGame - class Emitter < Sprite - property emitting : Bool = true - property particles = [] of Particle - property max_age : Float64 = 1.0 - property emit_freq : Float64 = 0.05 - property strength : Float64 = 50.0 - getter last_emitted : Float64 = 0.0 - property emit_angle : Float64 = 2 * Math::PI - property size : Float64 = 0.0 - - # property color : Pixel = Pixel.new - - def generate_particle - Particle.build do |particle| - particle.position = @position - - if @size > 0.0 - particle.position.x += rand(-@size..@size) - particle.position.y += rand(-@size..@size) - end - - direction = rand((@rotation - @emit_angle)..(@rotation + @emit_angle)) - particle.velocity = @velocity + Vector2.new(Math.cos(direction), Math.sin(direction)) * @strength - particle.lifespan = @max_age - end - end - - def update(dt : Float64) - update_position(dt) - - @last_emitted += dt - - if @emitting && @last_emitted >= @emit_freq - particle_count, remaining = @last_emitted.divmod(@emit_freq) - particle_count.to_i.times do - @particles << generate_particle - end - @last_emitted = remaining - end - - @particles.each { |particle| particle.update(dt) } - @particles.reject!(&.dead?) - end - - def draw(engine) - @particles.each do |particle| - particle.draw(engine) - end - end - end -end diff --git a/src/lx_game/game.cr b/src/lx_game/game.cr deleted file mode 100644 index 8f09e9e..0000000 --- a/src/lx_game/game.cr +++ /dev/null @@ -1,121 +0,0 @@ -require "./lib_sdl" -require "./pixel" - -module LxGame - abstract class Game - FPS_INTERVAL = 1.0 - - property width : Int32 - property height : Int32 - property scale : Int32 - property title : String - - @fps_lasttime : Float64 = Time.monotonic.total_milliseconds # the last recorded time. - @fps_current : UInt32 = 0 # the current FPS. - @fps_frames : UInt32 = 0 # frames passed since the last recorded fps. - @last_time : Float64 = Time.monotonic.total_milliseconds - - def initialize(@width, @height, @scale = 1, @title = self.class.name) - SDL.init(SDL::Init::VIDEO) - @window = SDL::Window.new(@title, @width * @scale, @height * @scale) - @renderer = SDL::Renderer.new(@window, flags: SDL::Renderer::Flags::PRESENTVSYNC) # , flags: SDL::Renderer::Flags::SOFTWARE) - @renderer.scale = {@scale, @scale} - @screen = SDL::Surface.new(LibSDL.create_rgb_surface( - flags: 0, width: @width, height: @height, depth: 32, - r_mask: 0xFF000000, g_mask: 0x00FF0000, b_mask: 0x0000FF00, a_mask: 0x000000FF - )) - end - - abstract def update(dt : Float64) - abstract def draw(engine : Game) - - def elapsed_time - Time.monotonic.total_milliseconds - end - - def clear(r = 0, g = 0, b = 0) - @screen.fill(0, 0, 0) - end - - def draw_point(x : Int32, y : Int32, pixel : Pixel, surface = @screen) - target = surface.pixels + (y * surface.pitch) + (x * 4) - target.as(Pointer(UInt32)).value = pixel.format(surface.format) - end - - # Draw a line using Bresenham’s Algorithm - def draw_line(p1 : Vector2, p2 : Vector2, pixel : Pixel, surface = @screen) - return draw_line(p2, p1, pixel, surface) if p1.x > p2.x - x1, y1, x2, y2 = p1.x.to_i, p1.y.to_i, p2.x.to_i, p2.y.to_i - - dx = (x2 - x1).abs - dy = -(y2 - y1).abs - - sx = x1 < x2 ? 1 : -1 - sy = y1 < y2 ? 1 : -1 - - d = dx + dy - x, y = x1, y1 - - loop do - draw_point(x, y, pixel, surface) - break if x == x2 && y == y2 - - d2 = d + d - - if d2 >= dy - d += dy - x += sx - end - - if d2 <= dx - d += dx - y += sy - end - end - end - - private def engine_update - @fps_frames += 1 - et = elapsed_time - - if @fps_lasttime < et - FPS_INTERVAL * 1000 - @fps_lasttime = et - @fps_current = @fps_frames - @fps_frames = 0 - @window.title = String.build { |io| io << @title << " - " << @fps_current << " fps" } - end - - update((et - @last_time) / 1000.0) - @last_time = et - end - - private def engine_draw - @screen.lock do - draw(self) - end - - @renderer.copy(SDL::Texture.from(@screen, @renderer)) - @renderer.present - end - - def run! - loop do - case event = SDL::Event.poll - when SDL::Event::Keyboard - if event.keydown? - @controller.press(event.sym) - elsif event.keyup? - @controller.release(event.sym) - end - when SDL::Event::Quit - break - end - - engine_update - engine_draw - end - ensure - SDL.quit - end - end -end diff --git a/src/lx_game/graphics.cr b/src/lx_game/graphics.cr deleted file mode 100644 index b33d517..0000000 --- a/src/lx_game/graphics.cr +++ /dev/null @@ -1,43 +0,0 @@ -module LxGame - # Draw a line using Bresenham’s Algorithm - def draw_line(renderer : SDL::Renderer, p1 : Vector2, p2 : Vector2, draw_points = false, point_color = SDL::Color[255, 0, 0, 255]) - return draw_line(renderer, p2, p1) if p1.x > p2.x - x1, y1, x2, y2 = p1.x.to_i, p1.y.to_i, p2.x.to_i, p2.y.to_i - - dx = (x2 - x1).abs - dy = -(y2 - y1).abs - - sx = x1 < x2 ? 1 : -1 - sy = y1 < y2 ? 1 : -1 - - d = dx + dy - x, y = x1, y1 - - loop do - draw_point(renderer, x, y) - break if x == x2 && y == y2 - - d2 = d + d - - if d2 >= dy - d += dy - x += sx - end - - if d2 <= dx - d += dx - y += sy - end - end - - if draw_points - renderer.draw_color = point_color - draw_point(renderer, x1, y1) - draw_point(renderer, x2, y2) - end - end - - def draw_point(renderer, x1, y1) - renderer.draw_point(x1, y1) - end -end diff --git a/src/lx_game/lib_sdl.cr b/src/lx_game/lib_sdl.cr deleted file mode 100644 index 35156b3..0000000 --- a/src/lx_game/lib_sdl.cr +++ /dev/null @@ -1,7 +0,0 @@ -module SDL - class Surface - def pixels - surface.pixels - end - end -end diff --git a/src/lx_game/particle.cr b/src/lx_game/particle.cr deleted file mode 100644 index 1008cd4..0000000 --- a/src/lx_game/particle.cr +++ /dev/null @@ -1,21 +0,0 @@ -require "./sprite" -require "./sprite/age" - -module LxGame - class Particle < Sprite - include SpriteAge - - def update(dt : Float64) - update_age(dt) - return if dead? - update_position(dt) - end - - def draw(engine) - return if dead? - brightness = ((((@lifespan - @age) / @lifespan) * 255) / 2).to_u8 - color = Pixel.new(r: brightness, g: brightness, b: brightness) - engine.draw_point(@position.x.to_i, @position.y.to_i, color) - end - end -end diff --git a/src/lx_game/pixel.cr b/src/lx_game/pixel.cr deleted file mode 100644 index ed2b3c1..0000000 --- a/src/lx_game/pixel.cr +++ /dev/null @@ -1,16 +0,0 @@ -module LxGame - struct Pixel - def self.random - new(rand(0_u8..0xFF_u8), rand(0_u8..0xFF_u8), rand(0_u8..0xFF_u8), 0xFF_u8) - end - - property r, g, b, a - - def initialize(@r : UInt8 = 255, @g : UInt8 = 255, @b : UInt8 = 255, @a : UInt8 = 255) - end - - def format(format) - LibSDL.map_rgba(format, @r, @g, @b, @a) - end - end -end diff --git a/src/lx_game/sprite.cr b/src/lx_game/sprite.cr deleted file mode 100644 index 9c47009..0000000 --- a/src/lx_game/sprite.cr +++ /dev/null @@ -1,36 +0,0 @@ -module LxGame - abstract class Sprite - def self.build - sprite = new - yield sprite - sprite - end - - property position : Vector2 - property velocity : Vector2 - property scale : Vector2 - property rotation : Float64 - property rotation_speed : Float64 - property mass : Float64 = 10.0 - - def initialize - @position = Vector2.new(0.0, 0.0) - @velocity = Vector2.new(0.0, 0.0) - @scale = Vector2.new(1.0, 1.0) - @rotation = 0.0 - @rotation_speed = 0.0 - end - - def update_position(dt : Float64) - @rotation += @rotation_speed * dt - @position += @velocity * dt - end - - def distance_between(other) - self.position.distance(other.position) - end - - abstract def update(dt : Float64) - abstract def draw(engine : Game) - end -end diff --git a/src/lx_game/sprite/age.cr b/src/lx_game/sprite/age.cr deleted file mode 100644 index 352d158..0000000 --- a/src/lx_game/sprite/age.cr +++ /dev/null @@ -1,14 +0,0 @@ -module LxGame - module SpriteAge - property lifespan : Float64 = Float64::INFINITY - property age : Float64 = 0.0 - - def dead? - self.age >= self.lifespan - end - - def update_age(dt : Float64) - self.age += dt - end - end -end diff --git a/src/lx_game/sprite/circle_collision.cr b/src/lx_game/sprite/circle_collision.cr deleted file mode 100644 index e712369..0000000 --- a/src/lx_game/sprite/circle_collision.cr +++ /dev/null @@ -1,48 +0,0 @@ -module LxGame - module CircleCollision - property radius : Float64 = 1.0 - - # Check if two circles are colliding - def collides_with?(other : Sprite) - distance_between(other) < radius + other.radius - end - - # Move objects so that they don't overlap - def offset_collision(other : Sprite) - distance = distance_between(other) - overlap = distance - radius - other.radius - offset = ((position - other.position) * (overlap / 2)) / distance - - self.position -= offset - other.position += offset - end - - # Resolve a collision by offsetting the two positions - # and transfering the momentum - def resolve_collision(other : VectorSprite) - offset_collision(other) - distance = distance_between(other) - - # Calculate the new velocities - normal_vec = (position - other.position) / distance - tangental_vec = Vector2.new(-normal_vec.y, normal_vec.x) - - # Dot product of velocity with the tangent - # (the direction in which to bounce towards) - dp_tangent_a = velocity.dot(tangental_vec) - dp_tangent_b = other.velocity.dot(tangental_vec) - - # Dot product of the normal - dp_normal_a = velocity.dot(normal_vec) - dp_normal_b = other.velocity.dot(normal_vec) - - # conservation of momentum - ma = (dp_normal_a * (mass - other.mass) + 2.0 * other.mass * dp_normal_b) / (mass + other.mass) - mb = (dp_normal_b * (other.mass - mass) + 2.0 * mass * dp_normal_a) / (mass + other.mass) - - # Set the new velocities - self.velocity = (tangental_vec * dp_tangent_a) + (normal_vec * ma) - other.velocity = (tangental_vec * dp_tangent_b) + (normal_vec * mb) - end - end -end diff --git a/src/lx_game/sprite/vector_sprite.cr b/src/lx_game/sprite/vector_sprite.cr deleted file mode 100644 index 3add4f8..0000000 --- a/src/lx_game/sprite/vector_sprite.cr +++ /dev/null @@ -1,71 +0,0 @@ -module LxGame - module VectorSprite - def self.generate_circle(num_points : Int, size = 1.0, jitter = 0.0) : Array(Vector2) - 0.upto(num_points).map do |n| - angle = (2 * Math::PI) * (n / num_points) - - x = size + rand(-jitter..jitter) - - rc = Math.cos(angle) - rs = Math.sin(angle) - Vector2.new(0.0 * rc - x * rs, x * rc + 0.0 * rs) - end.to_a - end - - property frame = [] of Vector2 - @average_radius : Float64? = nil - - def project_points(points : Array(Vector2), rotation = self.rotation, translate : Vector2? = nil, scale : Vector2? = nil) - rc = Math.cos(rotation) - rs = Math.sin(rotation) - - translation = - if t = translate - self.position + t - else - self.position - end - - points.map do |point| - rotated = Vector2.new(point.x * rc - point.y * rs, point.y * rc + point.x * rs) - - scale.try do |scale| - rotated = rotated * scale - end - - translation + rotated - end - end - - # Calculated as the average R for all points in the frame - def radius - average_radius - end - - # Calculated as the average R for all points in the frame - def average_radius - @average_radius ||= begin - # calculate length from center for all points - lengths = frame.map do |vec| - Math.sqrt(vec.x ** 2 + vec.y ** 2) - end - - # get the average of the lengths - lengths.reduce { |t, p| t + p } / frame.size.to_f - end - end - - def draw_frame(engine : Game, frame = @frame, color : Pixel = Pixel.new) - 0.upto(frame.size - 1) do |n| - engine.draw_line(frame[n], frame[(n + 1) % frame.size], color) - end - end - - def draw_radius(engine : Game, points = 30, color : Pixel = Pixel.new) - circle = self.class.generate_circle(points, average_radius).map do |point| - point + @position - end - draw_frame(engine, frame: circle, color: color) - end - end -end diff --git a/src/ship.cr b/src/ship.cr index c071c75..b436ac0 100644 --- a/src/ship.cr +++ b/src/ship.cr @@ -1,16 +1,20 @@ -require "./lx_game/sprite/vector_sprite" +require "pixelfaucet/sprite" +require "pixelfaucet/sprite/vector_sprite" +require "pixelfaucet/emitter" -class Ship < Sprite - include LxGame::VectorSprite +class Ship < PF::Sprite + include PF::VectorSprite @fire_cooldown : Float64 = 0.0 @fire_rate : Float64 = 0.2 - @emitter : Emitter - @l_emitter : Emitter - @r_emitter : Emitter + @fire_speed = 125.0 + @fire_recoil = 3.0 + @emitter : PF::Emitter + @l_emitter : PF::Emitter + @r_emitter : PF::Emitter @projected_points : Array(Vector2)? = nil property blew_up : Bool = false - @color = Pixel.new + @color = PF::Pixel.new def initialize super @@ -24,7 +28,7 @@ class Ship < Sprite Vector2.new(-3.0, -3.0), ] - @emitter = Emitter.build do |e| + @emitter = PF::Emitter.build do |e| e.position = @position e.emit_freq = 0.01 e.emit_angle = 0.5 @@ -32,7 +36,7 @@ class Ship < Sprite e.max_age = 0.25 end - @l_emitter = Emitter.build do |e| + @l_emitter = PF::Emitter.build do |e| e.position = @position e.emit_freq = 0.01 e.emit_angle = 0.3 @@ -40,7 +44,7 @@ class Ship < Sprite e.max_age = 0.25 end - @r_emitter = Emitter.build do |e| + @r_emitter = PF::Emitter.build do |e| e.position = @position e.emit_freq = 0.01 e.emit_angle = 0.3 @@ -55,11 +59,11 @@ class Ship < Sprite def fire @fire_cooldown = @fire_rate - @velocity.x -= Math.cos(@rotation) * 3.0 - @velocity.y -= Math.sin(@rotation) * 3.0 + @velocity.x -= Math.cos(@rotation) * @fire_recoil + @velocity.y -= Math.sin(@rotation) * @fire_recoil Bullet.build do |bullet| bullet.position = project_points([@frame[0]]).first - bullet.velocity = @velocity + Vector2.new(Math.cos(@rotation), Math.sin(@rotation)) * 100.0 + bullet.velocity = @velocity + Vector2.new(Math.cos(@rotation), Math.sin(@rotation)) * @fire_speed end end