mirror of
https://github.com/SleepingInsomniac/asteroids
synced 2024-12-26 21:59:30 +01:00
Split LxGame into separate shard PixelFaucet
This commit is contained in:
parent
3319e18747
commit
fdad655892
18 changed files with 57 additions and 511 deletions
|
@ -4,6 +4,10 @@ shards:
|
||||||
git: https://github.com/unn4m3d/crystaledge.git
|
git: https://github.com/unn4m3d/crystaledge.git
|
||||||
version: 0.2.6
|
version: 0.2.6
|
||||||
|
|
||||||
|
pixelfaucet:
|
||||||
|
git: https://github.com/sleepinginsomniac/pixelfaucet.git
|
||||||
|
version: 0.0.1+git.commit.5b285d55403dafd318c19b7d4ef9069b67c19625
|
||||||
|
|
||||||
sdl:
|
sdl:
|
||||||
git: https://github.com/ysbaddaden/sdl.cr.git
|
git: https://github.com/ysbaddaden/sdl.cr.git
|
||||||
version: 0.1.0+git.commit.0ded44711246feb3aa3ba28fea249c9b03be061b
|
version: 0.1.0+git.commit.0ded44711246feb3aa3ba28fea249c9b03be061b
|
||||||
|
|
|
@ -13,8 +13,6 @@ crystal: 1.2.1
|
||||||
license: MIT
|
license: MIT
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
sdl:
|
pixelfaucet:
|
||||||
github: ysbaddaden/sdl.cr
|
github: SleepingInsomniac/pixelfaucet
|
||||||
crystaledge:
|
# version: 0.0.1
|
||||||
github: unn4m3d/crystaledge
|
|
||||||
version: 0.2.6
|
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
require "./lx_game/sprite/circle_collision"
|
require "pixelfaucet/sprite"
|
||||||
require "./lx_game/sprite/vector_sprite"
|
require "pixelfaucet/sprite/circle_collision"
|
||||||
|
require "pixelfaucet/sprite/vector_sprite"
|
||||||
|
|
||||||
class Asteroid < Sprite
|
class Asteroid < PF::Sprite
|
||||||
include LxGame::CircleCollision
|
include PF::CircleCollision
|
||||||
include LxGame::VectorSprite
|
include PF::VectorSprite
|
||||||
|
|
||||||
property size : Float64 = 1.0
|
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
|
def initialize
|
||||||
super
|
super
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
require "sdl"
|
require "sdl"
|
||||||
require "crystaledge"
|
require "crystaledge"
|
||||||
include CrystalEdge
|
include CrystalEdge
|
||||||
require "./lx_game/*"
|
require "pixelfaucet/game"
|
||||||
include LxGame
|
|
||||||
require "./ship"
|
require "./ship"
|
||||||
require "./asteroid"
|
require "./asteroid"
|
||||||
require "./bullet"
|
require "./bullet"
|
||||||
require "./explosion"
|
require "./explosion"
|
||||||
|
|
||||||
class Asteroids < Game
|
class Asteroids < PF::Game
|
||||||
@ship : Ship
|
@ship : Ship
|
||||||
@asteroids = [] of Asteroid
|
@asteroids = [] of Asteroid
|
||||||
@bullets = [] of Bullet
|
@bullets = [] of Bullet
|
||||||
@explosions = [] of Explosion
|
@explosions = [] of Explosion
|
||||||
@controller : Controller(LibSDL::Keycode)
|
@controller : PF::Controller(LibSDL::Keycode)
|
||||||
@asteroid_count = 3
|
@asteroid_count = 3
|
||||||
@restart_timer = 0.0
|
@restart_timer = 0.0
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ class Asteroids < Game
|
||||||
|
|
||||||
setup_round
|
setup_round
|
||||||
|
|
||||||
@controller = Controller(LibSDL::Keycode).new({
|
@controller = PF::Controller(LibSDL::Keycode).new({
|
||||||
LibSDL::Keycode::UP => "Thrust",
|
LibSDL::Keycode::UP => "Thrust",
|
||||||
LibSDL::Keycode::RIGHT => "Rotate Right",
|
LibSDL::Keycode::RIGHT => "Rotate Right",
|
||||||
LibSDL::Keycode::LEFT => "Rotate Left",
|
LibSDL::Keycode::LEFT => "Rotate Left",
|
||||||
|
@ -35,7 +35,7 @@ class Asteroids < Game
|
||||||
end
|
end
|
||||||
|
|
||||||
# override to wrap the coordinates
|
# 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
|
x = x % @width
|
||||||
y = y % @height
|
y = y % @height
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ class Asteroids < Game
|
||||||
|
|
||||||
size = rand(20.0..35.0)
|
size = rand(20.0..35.0)
|
||||||
a.mass = size
|
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
|
end
|
||||||
end
|
end
|
||||||
|
@ -145,7 +145,7 @@ class Asteroids < Game
|
||||||
size = asteroid.average_radius / 2
|
size = asteroid.average_radius / 2
|
||||||
a.mass = size
|
a.mass = size
|
||||||
points = size < 6 ? 6 : size.to_i
|
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
|
end
|
||||||
end
|
end
|
||||||
|
@ -187,14 +187,14 @@ class Asteroids < Game
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def draw(engine)
|
def draw
|
||||||
engine.clear
|
clear
|
||||||
@ship.draw(engine)
|
@ship.draw(self)
|
||||||
@bullets.each { |b| b.draw(engine) }
|
@bullets.each { |b| b.draw(self) }
|
||||||
@asteroids.each { |a| a.draw(engine) }
|
@asteroids.each { |a| a.draw(self) }
|
||||||
@explosions.each { |e| e.draw(engine) }
|
@explosions.each { |e| e.draw(self) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
game = Asteroids.new(500, 400, 2)
|
game = Asteroids.new(600, 400, 2)
|
||||||
game.run!
|
game.run!
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
require "./lx_game/sprite/age"
|
require "pixelfaucet/sprite"
|
||||||
|
require "pixelfaucet/sprite/age"
|
||||||
|
|
||||||
class Bullet < Sprite
|
class Bullet < PF::Sprite
|
||||||
include LxGame::SpriteAge
|
include PF::SpriteAge
|
||||||
|
|
||||||
@lifespan = 4.0
|
@lifespan = 2.5
|
||||||
|
|
||||||
def update(dt)
|
def update(dt)
|
||||||
update_age(dt)
|
update_age(dt)
|
||||||
|
@ -13,11 +14,11 @@ class Bullet < Sprite
|
||||||
|
|
||||||
def draw(engine)
|
def draw(engine)
|
||||||
brightness = (((4.0 - self.age) / 4.0) * 255).to_u8
|
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)
|
engine.draw_point(@position.x.to_i, @position.y.to_i, color)
|
||||||
end
|
end
|
||||||
|
|
||||||
def collides_with?(other : VectorSprite)
|
def collides_with?(other : PF::VectorSprite)
|
||||||
@position.distance(other.position) < other.radius
|
@position.distance(other.position) < other.radius
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
require "./lx_game/sprite/age"
|
require "pixelfaucet/emitter"
|
||||||
|
require "pixelfaucet/sprite/age"
|
||||||
|
|
||||||
class Explosion < LxGame::Emitter
|
class Explosion < PF::Emitter
|
||||||
include LxGame::SpriteAge
|
include PF::SpriteAge
|
||||||
|
|
||||||
def update(dt)
|
def update(dt)
|
||||||
update_age(dt)
|
update_age(dt)
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -1,7 +0,0 @@
|
||||||
module SDL
|
|
||||||
class Surface
|
|
||||||
def pixels
|
|
||||||
surface.pixels
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
30
src/ship.cr
30
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
|
class Ship < PF::Sprite
|
||||||
include LxGame::VectorSprite
|
include PF::VectorSprite
|
||||||
|
|
||||||
@fire_cooldown : Float64 = 0.0
|
@fire_cooldown : Float64 = 0.0
|
||||||
@fire_rate : Float64 = 0.2
|
@fire_rate : Float64 = 0.2
|
||||||
@emitter : Emitter
|
@fire_speed = 125.0
|
||||||
@l_emitter : Emitter
|
@fire_recoil = 3.0
|
||||||
@r_emitter : Emitter
|
@emitter : PF::Emitter
|
||||||
|
@l_emitter : PF::Emitter
|
||||||
|
@r_emitter : PF::Emitter
|
||||||
@projected_points : Array(Vector2)? = nil
|
@projected_points : Array(Vector2)? = nil
|
||||||
property blew_up : Bool = false
|
property blew_up : Bool = false
|
||||||
@color = Pixel.new
|
@color = PF::Pixel.new
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
super
|
super
|
||||||
|
@ -24,7 +28,7 @@ class Ship < Sprite
|
||||||
Vector2.new(-3.0, -3.0),
|
Vector2.new(-3.0, -3.0),
|
||||||
]
|
]
|
||||||
|
|
||||||
@emitter = Emitter.build do |e|
|
@emitter = PF::Emitter.build do |e|
|
||||||
e.position = @position
|
e.position = @position
|
||||||
e.emit_freq = 0.01
|
e.emit_freq = 0.01
|
||||||
e.emit_angle = 0.5
|
e.emit_angle = 0.5
|
||||||
|
@ -32,7 +36,7 @@ class Ship < Sprite
|
||||||
e.max_age = 0.25
|
e.max_age = 0.25
|
||||||
end
|
end
|
||||||
|
|
||||||
@l_emitter = Emitter.build do |e|
|
@l_emitter = PF::Emitter.build do |e|
|
||||||
e.position = @position
|
e.position = @position
|
||||||
e.emit_freq = 0.01
|
e.emit_freq = 0.01
|
||||||
e.emit_angle = 0.3
|
e.emit_angle = 0.3
|
||||||
|
@ -40,7 +44,7 @@ class Ship < Sprite
|
||||||
e.max_age = 0.25
|
e.max_age = 0.25
|
||||||
end
|
end
|
||||||
|
|
||||||
@r_emitter = Emitter.build do |e|
|
@r_emitter = PF::Emitter.build do |e|
|
||||||
e.position = @position
|
e.position = @position
|
||||||
e.emit_freq = 0.01
|
e.emit_freq = 0.01
|
||||||
e.emit_angle = 0.3
|
e.emit_angle = 0.3
|
||||||
|
@ -55,11 +59,11 @@ class Ship < Sprite
|
||||||
|
|
||||||
def fire
|
def fire
|
||||||
@fire_cooldown = @fire_rate
|
@fire_cooldown = @fire_rate
|
||||||
@velocity.x -= Math.cos(@rotation) * 3.0
|
@velocity.x -= Math.cos(@rotation) * @fire_recoil
|
||||||
@velocity.y -= Math.sin(@rotation) * 3.0
|
@velocity.y -= Math.sin(@rotation) * @fire_recoil
|
||||||
Bullet.build do |bullet|
|
Bullet.build do |bullet|
|
||||||
bullet.position = project_points([@frame[0]]).first
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue