mirror of
https://github.com/SleepingInsomniac/pixelfaucet
synced 2025-01-18 22:26:32 +01:00
Refactor, entity / sprite
This commit is contained in:
parent
3e7685c613
commit
ce0f055bf5
18 changed files with 412 additions and 262 deletions
|
@ -1,7 +1,6 @@
|
|||
require "../src/game"
|
||||
require "../src/controller"
|
||||
require "../src/sprite"
|
||||
require "../src/sprite/vector_sprite"
|
||||
require "../src/pixel"
|
||||
require "../src/point"
|
||||
require "../src/pixel_text"
|
||||
|
|
77
examples/balls.cr
Normal file
77
examples/balls.cr
Normal file
|
@ -0,0 +1,77 @@
|
|||
require "../src/game"
|
||||
require "../src/shape"
|
||||
require "../src/entity"
|
||||
require "../src/entity/circle_collision"
|
||||
|
||||
module PF
|
||||
class Ball < Entity
|
||||
include CircleCollision
|
||||
|
||||
getter frame : Array(Point(Float64))
|
||||
|
||||
def initialize(size : Float64)
|
||||
@frame = Shape.circle(size.to_i32, size.to_i32)
|
||||
@mass = size
|
||||
@radius = size
|
||||
end
|
||||
end
|
||||
|
||||
class Balls < Game
|
||||
@balls : Array(Ball) = [] of Ball
|
||||
|
||||
def initialize(*args, **kwargs)
|
||||
super
|
||||
|
||||
15.times do
|
||||
position = Point(Float64).new(rand(0.0_f64..@width.to_f64), rand(0.0_f64..@height.to_f64))
|
||||
ball = Ball.new(rand(10.0..30.0))
|
||||
ball.position = position
|
||||
ball.velocity = Point(Float64).new(rand(-50.0..50.0), rand(-50.0..50.0))
|
||||
|
||||
@balls << ball
|
||||
end
|
||||
end
|
||||
|
||||
# override to wrap the coordinates
|
||||
def draw_point(x : Int32, y : Int32, pixel : PF::Pixel, surface = @screen)
|
||||
x = x % @width
|
||||
y = y % @height
|
||||
|
||||
x = @width + x if x < 0
|
||||
y = @height + y if y < 0
|
||||
|
||||
super(x, y, pixel, surface)
|
||||
end
|
||||
|
||||
def update(dt, event)
|
||||
@balls.each do |b|
|
||||
b.update(dt)
|
||||
b.position = b.position % viewport # wrap coords
|
||||
end
|
||||
|
||||
collission_pairs = [] of Tuple(Ball, Ball)
|
||||
@balls.each do |a|
|
||||
@balls.each do |b|
|
||||
next if a == b
|
||||
next if collission_pairs.includes?({a, b})
|
||||
|
||||
if a.collides_with?(b)
|
||||
collission_pairs << {a, b}
|
||||
a.resolve_collision(b)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def draw
|
||||
clear(10, 10, 30)
|
||||
# @balls.each { |b| draw_circle(b.position.to_i32, b.radius.to_i32) }
|
||||
@balls.each do |ball|
|
||||
fill_shape(Shape.translate(ball.frame, translation: ball.position).map(&.to_i32))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
balls = PF::Balls.new(600, 400, 2)
|
||||
balls.run!
|
113
examples/snow.cr
Normal file
113
examples/snow.cr
Normal file
|
@ -0,0 +1,113 @@
|
|||
require "crystaledge"
|
||||
|
||||
require "../src/game"
|
||||
require "../src/controller"
|
||||
require "../src/sprite"
|
||||
require "../src/pixel"
|
||||
require "../src/point"
|
||||
require "../src/pixel_text"
|
||||
|
||||
class Wind
|
||||
property width : Int32
|
||||
property height : Int32
|
||||
property density : Int32
|
||||
property gusts : Array(Gust) = [] of Gust
|
||||
@step : Float64?
|
||||
|
||||
struct Gust
|
||||
property position : PF::Point(Float64)
|
||||
property strength : PF::Point(Float64)
|
||||
|
||||
def initialize(@position, @strength)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(@width, @height, @density = 20)
|
||||
setup_vectors
|
||||
end
|
||||
|
||||
def step
|
||||
@step ||= (@width / @density)
|
||||
end
|
||||
|
||||
def setup_vectors
|
||||
@gusts = [] of Gust
|
||||
y = step / 2
|
||||
while y < @height
|
||||
x = step / 2
|
||||
while x < @width
|
||||
@gusts << Gust.new(PF::Point(Float64).new(x, y), PF::Point(Float64).new(rand(-1.0..1.0), rand(-1.0..1.0)))
|
||||
x += step
|
||||
end
|
||||
y += step
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Flake
|
||||
property shape : UInt8
|
||||
property position : PF::Point(Float64)
|
||||
property z_pos : Float64
|
||||
property velocity : PF::Point(Float64)
|
||||
|
||||
def initialize(@position, @shape = rand(0_u8..2_u8), @z_pos = rand(0.0..1.0), velocity : PF::Point(Float64)? = nil)
|
||||
@velocity = velocity || PF::Point(Float64).new(rand(-2.0..2.0), rand(0.0..20.0))
|
||||
end
|
||||
|
||||
def update(dt)
|
||||
@velocity.y = @velocity.y + 1.0 * dt
|
||||
@position += @velocity * dt
|
||||
end
|
||||
end
|
||||
|
||||
class Snow < PF::Game
|
||||
@wind : Wind
|
||||
@pixels : Slice(UInt32)
|
||||
@last_flake : Float64 = 0.0
|
||||
@flakes : Array(Flake) = [] of Flake
|
||||
|
||||
def initialize(*args, **kwargs)
|
||||
super
|
||||
|
||||
@wind = Wind.new(@width, @height)
|
||||
@pixels = Slice.new(@screen.pixels.as(Pointer(UInt32)), @width * @height)
|
||||
clear(0, 0, 15)
|
||||
end
|
||||
|
||||
def update(dt, event)
|
||||
@last_flake += dt
|
||||
|
||||
if @last_flake >= 0.025
|
||||
@last_flake = 0.0
|
||||
@flakes << Flake.new(position: PF::Point.new(rand(0.0..@width.to_f64), 0))
|
||||
end
|
||||
|
||||
@flakes.reject! do |flake|
|
||||
@wind.gusts.each do |gust|
|
||||
size = @wind.step / 3
|
||||
if flake.position > gust.position - size && flake.position < gust.position + size
|
||||
flake.velocity = flake.velocity + gust.strength * 3 * dt
|
||||
end
|
||||
end
|
||||
|
||||
flake.update(dt)
|
||||
flake.position.y > @height
|
||||
end
|
||||
end
|
||||
|
||||
def draw
|
||||
clear(0, 0, 15)
|
||||
|
||||
@flakes.each do |flake|
|
||||
color = PF::Pixel.white * flake.z_pos
|
||||
if flake.shape == 0
|
||||
draw_point(flake.position.to_i32, color)
|
||||
else
|
||||
draw_circle(flake.position.to_i32, flake.shape, color)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
engine = Snow.new(1200, 800, 1)
|
||||
engine.run!
|
24
examples/sprite_example.cr
Normal file
24
examples/sprite_example.cr
Normal file
|
@ -0,0 +1,24 @@
|
|||
require "../src/game"
|
||||
require "../src/sprite"
|
||||
|
||||
module PF
|
||||
class SpriteExample < Game
|
||||
@bricks : Sprite
|
||||
|
||||
def initialize(*args, **kwargs)
|
||||
super
|
||||
@bricks = Sprite.new("./assets/pf-font.png")
|
||||
end
|
||||
|
||||
def update(dt, event)
|
||||
end
|
||||
|
||||
def draw
|
||||
clear(255, 255, 255)
|
||||
@bricks.draw(@screen, width // 2 - @bricks.width // 2, height // 2 - @bricks.height // 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
game = PF::SpriteExample.new(200, 200, 2)
|
||||
game.run!
|
|
@ -15,7 +15,7 @@ class TextGame < PF::Game
|
|||
@msg = "Hello, World!"
|
||||
end
|
||||
|
||||
def update(dt)
|
||||
def update(dt, event)
|
||||
@x += @dx * dt
|
||||
@y += @dy * dt
|
||||
|
||||
|
|
|
@ -1,71 +1,60 @@
|
|||
# require "crystaledge"
|
||||
|
||||
require "../src/game"
|
||||
require "../src/controller"
|
||||
require "../src/sprite"
|
||||
require "../src/sprite/vector_sprite"
|
||||
require "../src/entity"
|
||||
require "../src/pixel"
|
||||
require "../src/shape"
|
||||
require "../src/point"
|
||||
|
||||
class Triangle < PF::Sprite
|
||||
include PF::VectorSprite
|
||||
class Triangle < PF::Entity
|
||||
property frame : Array(PF::Point(Float64))
|
||||
|
||||
def initialize(*args, **kwargs)
|
||||
@frame = [] of PF::Point(Float64)
|
||||
end
|
||||
|
||||
def update(dt)
|
||||
end
|
||||
|
||||
def draw(engine)
|
||||
frame = project_points(@frame)
|
||||
engine.fill_triangle(frame[0], frame[1], frame[2], PF::Pixel.yellow)
|
||||
_frame = PF::Shape.rotate(@frame, @rotation)
|
||||
_frame = PF::Shape.translate(_frame, @position)
|
||||
engine.fill_triangle(_frame.map(&.to_i32), PF::Pixel.yellow)
|
||||
end
|
||||
end
|
||||
|
||||
class TriangleThing < PF::Game
|
||||
@tri : Triangle
|
||||
@paused = true
|
||||
@paused = false
|
||||
@controller : PF::Controller(LibSDL::Scancode)
|
||||
|
||||
def initialize(@width, @height, @scale)
|
||||
super(@width, @height, @scale)
|
||||
|
||||
@tri = Triangle.build do |t|
|
||||
t.position = Vector2.new(@width / 2, @height / 2)
|
||||
t.frame = PF::VectorSprite.generate_circle(3, size = @width / 3)
|
||||
end
|
||||
@tri = Triangle.new
|
||||
@tri.position = PF::Point.new(@width / 2, @height / 2)
|
||||
@tri.frame = PF::Shape.circle(3, size = @width / 3)
|
||||
|
||||
@controller = PF::Controller(LibSDL::Scancode).new({
|
||||
LibSDL::Scancode::RIGHT => "Rotate Right",
|
||||
LibSDL::Scancode::LEFT => "Rotate Left",
|
||||
LibSDL::Scancode::SPACE => "Pause",
|
||||
LibSDL::Scancode::A => "Move Left",
|
||||
LibSDL::Scancode::D => "Move Right",
|
||||
LibSDL::Scancode::W => "Move Up",
|
||||
LibSDL::Scancode::S => "Move Down",
|
||||
})
|
||||
end
|
||||
|
||||
def update(dt)
|
||||
def update(dt, event)
|
||||
case event
|
||||
when SDL::Event::Keyboard
|
||||
@controller.press(event.scancode) if event.keydown?
|
||||
@controller.release(event.scancode) if event.keyup?
|
||||
end
|
||||
|
||||
@paused = !@paused if @controller.pressed?("Pause")
|
||||
|
||||
@tri.rotation += 0.5 * dt if @controller.action?("Rotate Right")
|
||||
@tri.rotation -= 0.5 * dt if @controller.action?("Rotate Left")
|
||||
|
||||
if @controller.action?("Move Up")
|
||||
@tri.frame[1] = @tri.frame[1] + Vector2.new(0.0, -10.0) * dt
|
||||
end
|
||||
|
||||
if @controller.action?("Move Down")
|
||||
@tri.frame[1] = @tri.frame[1] + Vector2.new(0.0, 10.0) * dt
|
||||
end
|
||||
|
||||
if @controller.action?("Move Left")
|
||||
@tri.frame[1] = @tri.frame[1] + Vector2.new(-10.0, 0.0) * dt
|
||||
end
|
||||
|
||||
if @controller.action?("Move Right")
|
||||
@tri.frame[1] = @tri.frame[1] + Vector2.new(10.0, 0.0) * dt
|
||||
end
|
||||
@tri.rotation = @tri.rotation + 0.5 * dt if @controller.action?("Rotate Right")
|
||||
@tri.rotation = @tri.rotation - 0.5 * dt if @controller.action?("Rotate Left")
|
||||
|
||||
unless @paused
|
||||
@tri.rotation += 1.0 * dt
|
||||
@tri.rotation = @tri.rotation + 1.0 * dt
|
||||
end
|
||||
|
||||
@tri.update(dt)
|
||||
|
|
26
src/entity.cr
Normal file
26
src/entity.cr
Normal file
|
@ -0,0 +1,26 @@
|
|||
require "./sprite"
|
||||
|
||||
module PF
|
||||
# An entity is an object with a sprite and a physics body
|
||||
class Entity
|
||||
property sprite : Sprite? = nil
|
||||
|
||||
property position : Point(Float64) = Point.new(0.0, 0.0)
|
||||
property velocity : Point(Float64) = Point.new(0.0, 0.0)
|
||||
property rotation : Float64 = 0.0
|
||||
property rotation_speed : Float64 = 0.0
|
||||
property mass : Float64 = 1.0
|
||||
|
||||
def initialize(@sprite = nil)
|
||||
end
|
||||
|
||||
def update(dt : Float64)
|
||||
@rotation += @rotation_speed * dt
|
||||
@position += @velocity * dt
|
||||
end
|
||||
|
||||
def distance(other : Entity)
|
||||
position.distance(other.position)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,33 +1,34 @@
|
|||
module PF
|
||||
module CircleCollision
|
||||
include CrystalEdge
|
||||
require "../entity"
|
||||
|
||||
module PF
|
||||
# This module contains methods to handle circle entity collision
|
||||
module CircleCollision
|
||||
property radius : Float64 = 1.0
|
||||
|
||||
# Check if two circles are colliding
|
||||
def collides_with?(other : Sprite)
|
||||
distance_between(other) < radius + other.radius
|
||||
def collides_with?(other : Entity)
|
||||
distance(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
|
||||
def offset_collision(other : Entity)
|
||||
d = distance(other)
|
||||
overlap = d - radius - other.radius
|
||||
offset = ((position - other.position) * (overlap / 2)) / d
|
||||
|
||||
self.position -= offset
|
||||
other.position += offset
|
||||
self.position = position - offset
|
||||
other.position = other.position + offset
|
||||
end
|
||||
|
||||
# Resolve a collision by offsetting the two positions
|
||||
# and transfering the momentum
|
||||
def resolve_collision(other : VectorSprite)
|
||||
def resolve_collision(other : Entity)
|
||||
offset_collision(other)
|
||||
distance = distance_between(other)
|
||||
d = distance(other)
|
||||
|
||||
# Calculate the new velocities
|
||||
normal_vec = (position - other.position) / distance
|
||||
tangental_vec = Vector2.new(-normal_vec.y, normal_vec.x)
|
||||
normal_vec = (position - other.position) / d
|
||||
tangental_vec = Point(Float64).new(-normal_vec.y, normal_vec.x)
|
||||
|
||||
# Dot product of velocity with the tangent
|
||||
# (the direction in which to bounce towards)
|
|
@ -1,5 +1,5 @@
|
|||
module PF
|
||||
module SpriteAge
|
||||
module EntityAge
|
||||
property lifespan : Float64 = Float64::INFINITY
|
||||
property age : Float64 = 0.0
|
||||
|
43
src/game.cr
43
src/game.cr
|
@ -11,8 +11,9 @@ module PF
|
|||
FPS_INTERVAL = 1.0
|
||||
SHOW_FPS = true
|
||||
|
||||
property width : Int32
|
||||
property height : Int32
|
||||
getter width : Int32
|
||||
getter height : Int32
|
||||
@viewport : Point(Int32)? = nil
|
||||
property scale : Int32
|
||||
property title : String
|
||||
property running = true
|
||||
|
@ -37,6 +38,22 @@ module PF
|
|||
abstract def update(dt : Float64, event : SDL::Event)
|
||||
abstract def draw
|
||||
|
||||
def width=(value : Int32)
|
||||
@viewport = nil
|
||||
@width = value
|
||||
# TODO: Resize window
|
||||
end
|
||||
|
||||
def height=(value : Int32)
|
||||
@viewport = nil
|
||||
@height = value
|
||||
# TODO: Resize window
|
||||
end
|
||||
|
||||
def viewport
|
||||
@viewport ||= Point.new(@width, @height)
|
||||
end
|
||||
|
||||
def elapsed_time
|
||||
Time.monotonic.total_milliseconds
|
||||
end
|
||||
|
@ -61,16 +78,21 @@ module PF
|
|||
end
|
||||
end
|
||||
|
||||
# Draw a single point
|
||||
# ditto
|
||||
def draw_point(vector : Vector2, pixel : Pixel = Pixel.new, surface = @screen)
|
||||
draw_point(vector.x.to_i32, vector.y.to_i32, pixel, surface)
|
||||
end
|
||||
|
||||
# Draw a single point
|
||||
def draw_point(point, pixel : Pixel = Pixel.new, surface = @screen)
|
||||
# ditto
|
||||
def draw_point(point : Point(Int), pixel : Pixel = Pixel.new, surface = @screen)
|
||||
draw_point(point.x, point.y, pixel, surface)
|
||||
end
|
||||
|
||||
# ditto
|
||||
def draw_point(point : Point(Float64), pixel : Pixel = Pixel.new, surface = @screen)
|
||||
draw_point(point.to_i32, pixel, surface)
|
||||
end
|
||||
|
||||
# Draw a line using Bresenham’s Algorithm
|
||||
def draw_line(x1 : Int, y1 : Int, x2 : Int, y2 : Int, pixel : Pixel = Pixel.new, surface = @screen)
|
||||
# The slope for each axis
|
||||
|
@ -106,13 +128,14 @@ module PF
|
|||
end
|
||||
end
|
||||
|
||||
# Draw a line using Bresenham’s Algorithm
|
||||
def draw_line(p1 : Vector2, p2 : Vector2, pixel : Pixel = Pixel.new, surface = @screen)
|
||||
draw_line(p1.x.to_i, p1.y.to_i, p2.x.to_i, p2.y.to_i, pixel, surface)
|
||||
# ditto
|
||||
def draw_line(p1 : Point(Int), p2 : Point(Int), pixel : Pixel = Pixel.new, surface = @screen)
|
||||
draw_line(p1.x, p1.y, p2.x, p2.y, pixel, surface)
|
||||
end
|
||||
|
||||
def draw_line(p1 : Point, p2 : Point, pixel : Pixel = Pixel.new, surface = @screen)
|
||||
draw_line(p1.x, p1.y, p2.x, p2.y, pixel, surface)
|
||||
# ditto
|
||||
def draw_line(p1 : Point(Float), p2 : Point(Float), pixel : Pixel = Pixel.new, surface = @screen)
|
||||
draw_line(p1.to_i32, p2.to_i32, pixel, surface)
|
||||
end
|
||||
|
||||
# Draw the outline of a square rect
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module PF
|
||||
abstract class Game
|
||||
# Fill an abitrary polygon. Expects a clockwise winding of points
|
||||
def fill_shape(*points : Point, color : Pixel = Pixel.new, surface = @screen)
|
||||
def fill_shape(points : Enumerable(Point), color : Pixel = Pixel.new, surface = @screen)
|
||||
return if points.empty?
|
||||
return draw_point(points[0], color, surface) if points.size == 1
|
||||
return draw_line(points[0], points[1], color, surface) if points.size == 2
|
||||
|
@ -61,6 +61,10 @@ module PF
|
|||
end
|
||||
end
|
||||
|
||||
def fill_shape(*points : Point, color : Pixel = Pixel.new, surface = @screen)
|
||||
fill_shape(points, color, surface)
|
||||
end
|
||||
|
||||
def draw_shape(*points : Point, color : Pixel = Pixel.new, surface = @screen)
|
||||
0.upto(points.size - 1) do |n|
|
||||
draw_line(points[n], points[(n + 1) % points.size], color, surface)
|
||||
|
|
|
@ -2,13 +2,6 @@ require "../line"
|
|||
|
||||
module PF
|
||||
abstract class Game
|
||||
def fill_triangle(p1 : Vector2, p2 : Vector2, p3 : Vector2, pixel : Pixel = Pixel.new, surface = @screen)
|
||||
p1 = Point(Int32).new(x: p1.x.to_i, y: p1.y.to_i)
|
||||
p2 = Point(Int32).new(x: p2.x.to_i, y: p2.y.to_i)
|
||||
p3 = Point(Int32).new(x: p3.x.to_i, y: p3.y.to_i)
|
||||
fill_triangle(p1, p2, p3, pixel, surface)
|
||||
end
|
||||
|
||||
def fill_triangle(p1 : PF::Point, p2 : PF::Point, p3 : PF::Point, pixel : Pixel = Pixel.new, surface = @screen)
|
||||
# Sort points from top to bottom
|
||||
p1, p2 = p2, p1 if p2.y < p1.y
|
||||
|
@ -61,76 +54,8 @@ module PF
|
|||
end
|
||||
end
|
||||
|
||||
# Fills a triangle shape by drawing two edges from the top vertex and scanning across left to right
|
||||
def fill_triangle_bresenham(p1 : Point, p2 : Point, p3 : Point, pixel : Pixel = Pixel.new, surface = @screen)
|
||||
# Sort points from top to bottom
|
||||
p1, p2 = p2, p1 if p2.y < p1.y
|
||||
p1, p3 = p3, p1 if p3.y < p1.y
|
||||
p2, p3 = p3, p2 if p3.y < p2.y
|
||||
|
||||
s1 = p2 - p1
|
||||
m1 = s1.y / s1.x
|
||||
|
||||
edge1 = calculate_edge(p1, p2)
|
||||
edge2 = calculate_edge(p1, p3)
|
||||
edge3 = calculate_edge(p2, p3)
|
||||
|
||||
if edge1.size > edge2.size
|
||||
edge2.pop
|
||||
edge2.concat edge3
|
||||
else
|
||||
edge1.pop
|
||||
edge1.concat edge3
|
||||
end
|
||||
|
||||
0.upto(edge1.size - 1) do |line|
|
||||
if edge1[line].x < edge2[line].x
|
||||
edge1[line].x.upto(edge2[line].x) { |x| draw_point(x, edge1[line].y, pixel, surface) }
|
||||
else
|
||||
edge2[line].x.upto(edge1[line].x) { |x| draw_point(x, edge1[line].y, pixel, surface) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Calculate an edge using Bresenham’s Algorithm
|
||||
def calculate_edge(p1 : Point, p2 : Point)
|
||||
# The slope for each axis
|
||||
slope = Point.new((p2.x - p1.x).abs, -(p2.y - p1.y).abs)
|
||||
|
||||
# The step direction in both axis
|
||||
step = Point.new(p1.x < p2.x ? 1 : -1, p1.y < p2.y ? 1 : -1)
|
||||
|
||||
# The final decision accumulation
|
||||
# Initialized to the height of x and y
|
||||
decision = slope.x + slope.y
|
||||
|
||||
edge = [] of Point(Int32)
|
||||
point = p1
|
||||
|
||||
edge << point
|
||||
|
||||
loop do
|
||||
# draw_point(point.x, point.y, Pixel.yellow)
|
||||
# Break if we've reached the ending point
|
||||
break if point == p2
|
||||
|
||||
# Square the decision to avoid floating point calculations
|
||||
decision_squared = decision + decision
|
||||
|
||||
# if decision_squared is greater than
|
||||
if decision_squared >= slope.y
|
||||
decision += slope.y
|
||||
point.x += step.x
|
||||
end
|
||||
|
||||
if decision_squared <= slope.x
|
||||
decision += slope.x
|
||||
point.y += step.y
|
||||
edge << point
|
||||
end
|
||||
end
|
||||
|
||||
edge
|
||||
def fill_triangle(points : Enumerable(PF::Point), pixel : Pixel = Pixel.new, surface = @screen)
|
||||
fill_triangle(points[0], points[1], points[2], pixel, surface)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,28 +1,24 @@
|
|||
require "sdl/image"
|
||||
require "./sprite"
|
||||
|
||||
module PF
|
||||
class PixelText
|
||||
class PixelText < Sprite
|
||||
getter width : Int32
|
||||
getter height : Int32
|
||||
@img : SDL::Surface
|
||||
@chars : String
|
||||
|
||||
def initialize(path : String, @width : Int32 = 7, @height : Int32 = 8, mapping : String? = nil)
|
||||
@img = SDL::IMG.load(path)
|
||||
super(path)
|
||||
@chars = mapping || "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!?().,/\\[]{}$#+-“”‘’'\"@"
|
||||
end
|
||||
|
||||
def convert(surface : SDL::Surface)
|
||||
@img = @img.convert(surface)
|
||||
end
|
||||
|
||||
def color(pixel : Pixel)
|
||||
color_val = pixel.format(@img.format)
|
||||
alpha_mask = @img.format.a_mask
|
||||
color_val = pixel.format(@surface.format)
|
||||
alpha_mask = @surface.format.a_mask
|
||||
|
||||
0.upto(@img.height - 1) do |y|
|
||||
0.upto(@img.width - 1) do |x|
|
||||
loc = pixel_pointer(x, y, @img)
|
||||
0.upto(@surface.height - 1) do |y|
|
||||
0.upto(@surface.width - 1) do |x|
|
||||
loc = pixel_pointer(x, y)
|
||||
|
||||
if loc.value & alpha_mask != 0
|
||||
loc.value = color_val
|
||||
|
@ -47,17 +43,12 @@ module PF
|
|||
char_x *= @width
|
||||
|
||||
unless char == ' '
|
||||
@img.blit(surface, SDL::Rect.new(char_x - 1, char_y, @width, @height), SDL::Rect.new(x + ix * @width, y + iy * @height, @width, @height))
|
||||
@surface.blit(surface, SDL::Rect.new(char_x - 1, char_y, @width, @height), SDL::Rect.new(x + ix * @width, y + iy * @height, @width, @height))
|
||||
end
|
||||
end
|
||||
|
||||
ix += 1
|
||||
end
|
||||
end
|
||||
|
||||
private def pixel_pointer(x : Int32, y : Int32, surface = @img)
|
||||
target = surface.pixels + (y * surface.pitch) + (x * 4)
|
||||
target.as(Pointer(UInt32))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -49,6 +49,14 @@ module PF
|
|||
@x < other.x && @y < other.y
|
||||
end
|
||||
|
||||
def %(other : Point)
|
||||
Point.new(x % other.x, y % other.y)
|
||||
end
|
||||
|
||||
def %(n : Float | Int)
|
||||
Point.new(x % n, y % n)
|
||||
end
|
||||
|
||||
def abs
|
||||
Point.new(x.abs, y.abs)
|
||||
end
|
||||
|
|
44
src/shape.cr
Normal file
44
src/shape.cr
Normal file
|
@ -0,0 +1,44 @@
|
|||
module PF
|
||||
module Shape
|
||||
# Generate an array of points that form a circle
|
||||
def self.circle(num_points : Int, size = 1.0, jitter = 0.0)
|
||||
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)
|
||||
Point.new(0.0 * rc - x * rs, x * rc + 0.0 * rs)
|
||||
end.to_a
|
||||
end
|
||||
|
||||
# Rotate points by *rotation*
|
||||
def self.rotate(points : Enumerable(Point), rotation : Float64)
|
||||
rc = Math.cos(rotation)
|
||||
rs = Math.sin(rotation)
|
||||
|
||||
points.map do |point|
|
||||
Point.new(point.x * rc - point.y * rs, point.y * rc + point.x * rs)
|
||||
end
|
||||
end
|
||||
|
||||
# Translate points by *translation*
|
||||
def self.translate(points : Enumerable(Point), translation : Point)
|
||||
points.map { |p| p + translation }
|
||||
end
|
||||
|
||||
# ditto
|
||||
def self.translate(*points : Point, translation : Point)
|
||||
self.translation(points, translation: translation)
|
||||
end
|
||||
|
||||
# Scale points by a certain *amount*
|
||||
def self.scale(points : Enumerable(Point), amount : Point)
|
||||
points.map { |p| p * amount }
|
||||
end
|
||||
|
||||
# calculate length from center for all points, and then get the average
|
||||
def self.average_radius(points : Enumerable(Point))
|
||||
points.map(&.length).reduce { |t, p| t + p } / frame.size
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,40 +1,39 @@
|
|||
require "crystaledge"
|
||||
require "sdl/image"
|
||||
|
||||
module PF
|
||||
abstract class Sprite
|
||||
include CrystalEdge
|
||||
class Sprite
|
||||
@surface : SDL::Surface
|
||||
|
||||
def self.build
|
||||
sprite = new
|
||||
yield sprite
|
||||
sprite
|
||||
delegate :convert, to: @img
|
||||
|
||||
def initialize(@surface)
|
||||
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
|
||||
def initialize(path : String)
|
||||
@surface = SDL::IMG.load(path)
|
||||
end
|
||||
|
||||
def update_position(dt : Float64)
|
||||
@rotation += @rotation_speed * dt
|
||||
@position += @velocity * dt
|
||||
def width
|
||||
@surface.width
|
||||
end
|
||||
|
||||
def distance_between(other)
|
||||
self.position.distance(other.position)
|
||||
def height
|
||||
@surface.height
|
||||
end
|
||||
|
||||
abstract def update(dt : Float64)
|
||||
abstract def draw(engine : Game)
|
||||
def draw(surface : SDL::Surface, x : Int32, y : Int32)
|
||||
@surface.blit(surface, nil, SDL::Rect.new(x, y, width, height))
|
||||
end
|
||||
|
||||
# Raw access to the pixels as a Slice
|
||||
def pixels
|
||||
Slice.new(@screen.pixels.as(Pointer(UInt32)), width * height)
|
||||
end
|
||||
|
||||
# Get the pointer to a pixel
|
||||
private def pixel_pointer(x : Int32, y : Int32)
|
||||
target = @surface.pixels + (y * @surface.pitch) + (x * 4)
|
||||
target.as(Pointer(UInt32))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
module PF
|
||||
module VectorSprite
|
||||
include CrystalEdge
|
||||
|
||||
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
|
|
@ -1,3 +1,3 @@
|
|||
module PF
|
||||
VERSION = "0.0.2"
|
||||
VERSION = "0.0.3"
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue