Add collision

This commit is contained in:
Alex Clink 2021-11-14 22:50:45 -05:00
parent 86f413a2af
commit fb844057b0
4 changed files with 119 additions and 5 deletions

View file

@ -3,10 +3,13 @@ class Asteroid < VectorSprite
def initialize def initialize
super super
@frame = VectorSprite.generate_circle(15, size: 10.0, jitter: 3.0) @frame = [] of Vector2
end end
def draw(renderer) def draw(renderer)
# renderer.draw_color = SDL::Color[0, 100, 100, 255]
# draw_radius(renderer)
frame = project_points(points: @frame, scale: Vector2.new(@size, @size)) frame = project_points(points: @frame, scale: Vector2.new(@size, @size))
renderer.draw_color = SDL::Color[128, 128, 128, 255] renderer.draw_color = SDL::Color[128, 128, 128, 255]
draw_frame(renderer, frame) draw_frame(renderer, frame)

View file

@ -35,12 +35,16 @@ class Asteroids < Game
ship.position = Vector2.new(x: width / 2.0, y: height / 2.0) ship.position = Vector2.new(x: width / 2.0, y: height / 2.0)
end end
4.times do 8.times do
@asteroids << Asteroid.build do |a| @asteroids << Asteroid.build do |a|
a.position = Vector2.new(x: rand(0.0..width.to_f), y: rand(0.0..height.to_f)) a.position = Vector2.new(x: rand(0.0..width.to_f), y: rand(0.0..height.to_f))
a.velocity = Vector2.new(x: rand(-20.0..20.0), y: rand(-20.0..20.0)) v_max = 30.0
a.velocity = Vector2.new(x: rand(-v_max..v_max), y: rand(-v_max..v_max))
a.rotation_speed = rand(-5.0..5.0) a.rotation_speed = rand(-5.0..5.0)
a.size = rand(0.5..4.0)
size = rand(5.0..30.0)
a.mass = size
a.frame = VectorSprite.generate_circle(size.to_i, size: size, jitter: 3.0)
end end
end end
@ -85,6 +89,44 @@ class Asteroids < Game
a.update(dt) a.update(dt)
a.position = wrap(a.position) a.position = wrap(a.position)
end end
collission_pairs = [] of Tuple(Asteroid, Asteroid)
@asteroids.each do |a|
@asteroids.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)
puts "#{a} collided with #{b}"
end
end
end
@bullets.each do |bullet|
@asteroids.each do |asteroid|
if bullet.collides_with?(asteroid)
if asteroid.mass > 3.0
2.times do
@asteroids << Asteroid.build do |a|
a.position = asteroid.position + Vector2.new(rand(-1.0..1.0), rand(-1.0..1.0))
v_max = 30.0
a.velocity = Vector2.new(x: rand(-v_max..v_max), y: rand(-v_max..v_max))
a.rotation_speed = rand(-5.0..5.0)
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)
end
end
end
@asteroids.delete(asteroid)
@bullets.delete(bullet)
end
end
end
end end
def draw def draw

View file

@ -11,4 +11,8 @@ class Bullet < Sprite
renderer.draw_color = SDL::Color[brightness, brightness, 0] renderer.draw_color = SDL::Color[brightness, brightness, 0]
renderer.draw_point(@position.x.to_i, @position.y.to_i) renderer.draw_point(@position.x.to_i, @position.y.to_i)
end end
def collides_with?(other : VectorSprite)
@position.distance(other.position) < other.average_radius
end
end end

View file

@ -1,6 +1,8 @@
module LxGame module LxGame
abstract class VectorSprite < Sprite abstract class VectorSprite < Sprite
getter frame = [] of Vector2 property mass : Float64 = 10.0
property frame = [] of Vector2
@average_radius : Float64? = nil
def self.generate_circle(num_points : Int, size = 1.0, jitter = 0.0) : Array(Vector2) def self.generate_circle(num_points : Int, size = 1.0, jitter = 0.0) : Array(Vector2)
0.upto(num_points).map do |n| 0.upto(num_points).map do |n|
@ -36,6 +38,18 @@ module LxGame
end end
end end
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 update(dt : Float64) def update(dt : Float64)
update_position(dt) update_position(dt)
end end
@ -45,5 +59,56 @@ module LxGame
draw_line(renderer, frame[n], frame[(n + 1) % frame.size]) draw_line(renderer, frame[n], frame[(n + 1) % frame.size])
end end
end end
def draw_radius(renderer : SDL::Renderer, points = 30)
circle = self.class.generate_circle(points, average_radius).map do |point|
point + @position
end
draw_frame(renderer, frame: circle)
end
def distance_between(other)
@position.distance(other.position)
end
def collides_with?(other : VectorSprite)
distance_between(other) < average_radius + other.average_radius
end
# Move objects so that they don't overlap
def offset_position(other : VectorSprite)
distance = distance_between(other)
overlap = distance - average_radius - other.average_radius
offset = ((@position - other.position) * (overlap / 2)) / distance
@position -= offset
other.position += offset
end
def resolve_collision(other : VectorSprite)
offset_position(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
@velocity = (tangental_vec * dp_tangent_a) + (normal_vec * ma)
other.velocity = (tangental_vec * dp_tangent_b) + (normal_vec * mb)
end
end end
end end