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