====== CrSFML Pong ====== {{:youtube:crsfml-pong.png?400|}} ===== src/crsfml-pong-take-5.cr ===== require "crsfml/graphics" require "./shapeaux" GAME_WIDTH = 800 GAME_HEIGHT = 600 PADDLE_SPEED = 300.0_f32 BALL_SPEED = 400.0_f32 alias ActionMap = Hash(String, SF::Keyboard::Key) class Paddle < SF::Transformable include SF::Drawable def initialize(position : SF::Vector2f, size : SF::Vector2f, color : SF::Color, speed : Float32, action_map : ActionMap) super() @shape = SF::RectangleShape.new(size) @shape.origin = {size.x / 2, size.y / 2} @shape.fill_color = color @speed = speed @action_map = action_map @input_direction = SF.vector2f 0, 0 self.position = position end def process_input @input_direction.y = 0 if SF::Keyboard.key_pressed? @action_map["up"] @input_direction.y = -1.0 end if SF::Keyboard.key_pressed? @action_map["down"] @input_direction.y = +1.0 end end def update(delta_time : SF::Time) dy = @input_direction.y * @speed * delta_time.as_seconds move 0, dy rect = collision_rect if top(rect) < 0 self.position = {position.x, @shape.origin.y } end if bottom(rect)> GAME_HEIGHT self.position = {position.x, GAME_HEIGHT - rect.height + @shape.origin.y } end end def draw(target : SF::RenderTarget, states : SF::RenderStates) states.transform *= transform target.draw @shape, states end def collision_rect : SF::FloatRect SF::FloatRect.new(position - @shape.origin, @shape.size) end end class Ball < SF::Transformable include SF::Drawable def initialize(position : SF::Vector2f, radius : Float32, color : SF::Color, velocity : SF::Vector2f) super() @shape = SF::CircleShape.new radius @shape.origin = { radius, radius } @shape.fill_color = color @velocity = velocity self.position = position end def update(delta_time : SF::Time, paddles : Array(Paddle)) update_x delta_time, paddles update_y delta_time, paddles end def draw(target : SF::RenderTarget, states : SF::RenderStates) states.transform *= transform target.draw @shape, states end private def update_x(delta_time : SF::Time, paddles : Array(Paddle)) dx = @velocity.x * delta_time.as_seconds move dx, 0 paddles.each do |p| c = collision_circle r = p.collision_rect if circle_rect_collide? c, r if c.center.x > right(r) self.position = {right(r) + @shape.origin.x, position.y} @velocity.x *= -1 elsif c.center.x < left(r) self.position = {left(r) - 2 * c.radius + @shape.origin.x, position.y} @velocity.x *= -1 end end end c = collision_circle if c.center.x - c.radius < 0 @velocity.x = +@velocity.x.abs end if c.center.x + c.radius > GAME_WIDTH @velocity.x = -@velocity.x.abs end end private def update_y(delta_time : SF::Time, paddles : Array(Paddle)) dy = @velocity.y * delta_time.as_seconds move 0, dy paddles.each do |p| c = collision_circle r = p.collision_rect if circle_rect_collide? c, r if c.center.y < top(r) self.position = {position.x, top(r) - 2*c.radius + @shape.origin.y} @velocity.y *= -1 elsif c.center.y > bottom(r) self.position = {position.x, bottom(r) + @shape.origin.y} @velocity.y *= -1 end end end c = collision_circle if c.center.y - c.radius < 0 @velocity.y = +@velocity.y.abs end if c.center.y + c.radius > GAME_HEIGHT @velocity.y = -@velocity.y.abs end end def collision_circle : Circle x = position.x - @shape.origin.x + @shape.radius y = position.y - @shape.origin.y + @shape.radius Circle.new(SF.vector2f(x, y), @shape.radius) end end def process_events(window : SF::Window) while event = window.poll_event if event.is_a? SF::Event::Closed window.close end if event.is_a? SF::Event::KeyPressed if event.code == SF::Keyboard::Escape window.close end end end end def setup_paddles : Array(Paddle) paddles = [] of Paddle paddles << Paddle.new( SF.vector2f(40, GAME_HEIGHT / 2), SF.vector2f(20, 80), SF::Color::Red, PADDLE_SPEED, {"up" => SF::Keyboard::Up, "down" => SF::Keyboard::Down} ) paddles << Paddle.new( SF.vector2f(GAME_WIDTH - 40, GAME_HEIGHT / 2), SF.vector2f(20, 80), SF::Color::Red, PADDLE_SPEED, {"up" => SF::Keyboard::W, "down" => SF::Keyboard::S} ) paddles end def setupo_balls : Array(Ball) balls = [] of Ball n = 100 n.times do |i| sx = rand(0.5 .. 3.0) sy = rand(0.5 .. 3.0) balls << Ball.new( SF.vector2f(GAME_WIDTH / n * (i + 1), GAME_HEIGHT / 2), 15.0, SF::Color.new((255 / n * (i + 1)).to_i, (255 - (255 / n * (i + 1))).to_i, 128), SF.vector2f(BALL_SPEED * sx, BALL_SPEED * sy) ) end balls end ###################################################################### window = SF::RenderWindow.new( SF::VideoMode.new(GAME_WIDTH, GAME_HEIGHT), "Pong take 5" ) paddles = setup_paddles balls = setupo_balls clock = SF::Clock.new elapsed_time = SF::Time::Zero while window.open? # process events and players' inputs process_events window paddles.each { |p| p.process_input } # update paddles.each { |p| p.update elapsed_time } balls.each { |b| b.update elapsed_time, paddles} # render window.clear paddles.each { |p| window.draw p } balls.each { |b| window.draw b } window.display elapsed_time = clock.restart end ===== src/shapeaux.cr ===== require "crsfml/graphics" def left(r : SF::FloatRect) r.left end def right(r : SF::FloatRect) r.left + r.width end def top(r : SF::FloatRect) r.top end def bottom(r : SF::FloatRect) r.top + r.height end struct Circle property center : SF::Vector2f property radius : Float32 def initialize(@center, @radius) end end def circle_rect_collide?(circle : Circle, rect : SF::FloatRect) : Bool c = circle.center t = c t.x = left(rect) if c.x < left(rect) t.x = right(rect) if c.x > right(rect) t.y = top(rect) if c.y < top(rect) t.y = bottom(rect) if c.y > bottom(rect) ct = t - c distance2 = ct.x ** 2 + ct.y ** 2 radius2 = circle.radius ** 2 distance2 < radius2 end ===== shard.yml ===== name: crsfml-pong-take-5 version: 0.1.0 authors: - watercat targets: crsfml-pong-take-5: main: src/crsfml-pong-take-5.cr crystal: 1.7.3 license: WTFPL dependencies: crsfml: github: oprypin/crsfml version: ~> 2.5.2