Split LxGame into separate shard PixelFaucet

This commit is contained in:
Alex Clink 2021-11-25 20:28:11 -05:00
parent 3319e18747
commit fdad655892
18 changed files with 57 additions and 511 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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!

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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 Bresenhams 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

View file

@ -1,43 +0,0 @@
module LxGame
# Draw a line using Bresenhams 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

View file

@ -1,7 +0,0 @@
module SDL
class Surface
def pixels
surface.pixels
end
end
end

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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