SFML/ruby/test/fps.rb

314 lines
7.8 KiB
Ruby
Raw Normal View History

require 'RubySFML'
include SFML
require 'gl'
include Gl
require 'glu'
include Glu
class Array
def magnitude
return Math::sqrt(self[0]**2 + self[1]**2 + self[2]**2)
end
def normalize
mag = self.magnitude
return [self[0]/mag, self[1]/mag, self[2]/mag]
end
def normalize!
mag = self.magnitude
self[0] /= mag
self[1] /= mag
self[2] /= mag
end
def vec(pos)
return [pos[0]-self[0], pos[1]-self[1], pos[2]-self[2]]
end
def dot(vec)
return self[0]*vec[0] + self[1]*vec[1] + self[2]*vec[2]
end
def cross(vec)
return [
self[1]*vec[2] - self[2]*vec[1],
self[2]*vec[0] - self[0]*vec[2],
self[0]*vec[1] - self[1]*vec[0]
]
end
end
class Player
attr_accessor :pos, :view, :score
def initialize(x, y)
@score = 0
@pos = [x+0.5, 10, y+0.5]
@view = [0, 0, -1]
end
def turn_left(time)
angle = -2.0 * time
@view[0], @view[2] = @view[0]*Math::cos(angle) - @view[2]*Math::sin(angle), @view[0]*Math::sin(angle) + @view[2]*Math::cos(angle)
@view.normalize!
end
def turn_right(time)
angle = 2.0 * time
@view[0], @view[2] = @view[0]*Math::cos(angle) - @view[2]*Math::sin(angle), @view[0]*Math::sin(angle) + @view[2]*Math::cos(angle)
@view.normalize!
end
def move_right(time)
angle = 0.5*Math::PI
right = [@view[0]*Math::cos(angle) - @view[2]*Math::sin(angle), 0, @view[0]*Math::sin(angle) + @view[2]*Math::cos(angle)]
d = 5*time
@pos = [@pos[0]+right[0]*d, @pos[1], @pos[2]+right[2]*d]
end
def move_left(time)
angle = 0.5*Math::PI
right = [@view[0]*Math::cos(angle) - @view[2]*Math::sin(angle), 0, @view[0]*Math::sin(angle) + @view[2]*Math::cos(angle)]
d = 5*time
@pos = [@pos[0]-right[0]*d, @pos[1], @pos[2]-right[2]*d]
end
def move_forward(time)
d = 5*time
@pos = [@pos[0]+@view[0]*d, @pos[1], @pos[2]+@view[2]*d]
end
def move_backward(time)
d = 5*time
@pos = [@pos[0]-@view[0]*d, @pos[1], @pos[2]-@view[2]*d]
end
def init_view
glLoadIdentity()
gluLookAt(@pos[0], @pos[1], @pos[2], @pos[0]+@view[0], @pos[1]+@view[1], @pos[2]+@view[2], 0, 1, 0)
end
def update(win, map)
pos = @pos
input, time = win.input, win.frameTime
turn_left(time) if input.isKeyDown(Key::Left)
turn_right(time) if input.isKeyDown(Key::Right)
move_forward(time) if input.isKeyDown(Key::W)
move_backward(time) if input.isKeyDown(Key::S)
move_left(time) if input.isKeyDown(Key::A)
move_right(time) if input.isKeyDown(Key::D)
@pos = pos if map.solid?(@pos[0], @pos[2])
end
end
class Block
attr_accessor :pos
def initialize(x, y)
@pos = [x.to_i+0.5, 0, y.to_i+0.5]
end
def draw()
glPushMatrix()
glTranslatef(@pos[0], @pos[1], @pos[2])
glColor4f(1, 0, 0, 1)
glBegin(GL_QUADS)
# Facing +z
glNormal3d(0, 0, 1)
glVertex3f(0.5, 0.5, 0.5)
glVertex3f(-0.5, 0.5, 0.5)
glVertex3f(-0.5, -0.5, 0.5)
glVertex3f(0.5, -0.5, 0.5)
# Facing -z
glNormal3d(0, 0, -1)
glVertex3f(-0.5, 0.5, -0.5)
glVertex3f(0.5, 0.5, -0.5)
glVertex3f(0.5, -0.5, -0.5)
glVertex3f(-0.5, -0.5, -0.5)
# Facing +x
glNormal3d(1, 0, 0)
glVertex3f(0.5, 0.5, 0.5)
glVertex3f(0.5, -0.5, 0.5)
glVertex3f(0.5, -0.5, -0.5)
glVertex3f(0.5, 0.5, -0.5)
# Facing -x
glNormal3d(-1, 0, 0)
glVertex3f(-0.5, 0.5, 0.5)
glVertex3f(-0.5, 0.5, -0.5)
glVertex3f(-0.5, -0.5, -0.5)
glVertex3f(-0.5, -0.5, 0.5)
glEnd()
glPopMatrix()
end
end
class Billboard
attr_accessor :pos
def initialize(x, y, size=0.25)
@size = size
@pos = [x.to_i+0.5, 0, y.to_i+0.5]
end
def draw(camera)
normal = @pos.vec(camera.pos).normalize
up = [0, 1, 0]
right = up.cross(normal).normalize
glPushMatrix()
glTranslatef(@pos[0], @pos[1], @pos[2])
glColor4f(1, 1, 1, 1)
glBegin(GL_QUADS)
# Facing camera
glNormal3d(normal[0], normal[1], normal[2])
glTexCoord2f(1, 0)
glVertex3f((right[0]+up[0])*@size, (right[1]+up[1])*@size, (right[2]+up[2])*@size)
glTexCoord2f(0, 0)
glVertex3f((-right[0]+up[0])*@size, (-right[1]+up[1])*@size, (-right[2]+up[2])*@size)
glTexCoord2f(0, 1)
glVertex3f(-(right[0]+up[0])*@size, -(right[1]+up[1])*@size, -(right[2]+up[2])*@size)
glTexCoord2f(1, 1)
glVertex3f((right[0]-up[0])*@size, (right[1]-up[1])*@size, (right[2]-up[2])*@size)
glEnd()
glPopMatrix()
end
end
# Map class holds and draws tiles and gems.
class Map
attr_reader :width, :height, :blocks, :gems
def initialize(filename, player)
@start = Time.now
@sb = SoundBuffer.new("media/Beep.wav")
@sound = Sound.new(@sb)
@gem_image = Image.new("media/Gem.png")
@exit_image = Image.new("media/Star.png")
@exit_image.resize(25, 25, Color.Black)
@exit = nil
@blocks = []
@gems = []
lines = File.readlines(filename).map { |line| line.chop }
@height = lines.size
@width = lines[0].size
@tiles = Array.new(@width) do |x|
Array.new(@height) do |y|
case lines[y][x, 1]
when '"', '#' then @blocks << Block.new(x, y); @blocks[-1]
when 'S' then player.pos = [x+0.5, 0, y+0.5]; nil
when 'E' then @exit = Billboard.new(x, y); nil
when 'x' then @gems << Billboard.new(x, y); nil
else nil
end
end
end
end
def draw(player)
@blocks.each {|b| b.draw()}
glEnable(GL_TEXTURE_2D)
@gem_image.bind
@gems.each {|g| g.draw(player)}
@exit_image.bind
@exit.draw(player)
glDisable(GL_TEXTURE_2D)
end
def exit?(player)
player.pos[0].to_i == @exit.pos[0].to_i and player.pos[2].to_i == @exit.pos[2].to_i
end
# Solid at a given pixel position?
def solid?(x, y)
y < 0 or x < 0 or x >= @width or y >= @width or @tiles[x.to_i][y.to_i]
end
def collect_gems(player)
@gems.reject! {|g|
if player.pos.vec(g.pos).magnitude <= 0.5
@sound.play
player.score += 10
true
else
false
end
}
end
end
$clock = Clock.new
mode = VideoMode.new(640, 480, 32)
win = RenderWindow.new(mode, "RubySFML Test", 0)
win.showMouseCursor(false)
win.useVerticalSync(true)
sky_image = Image.new("media/Space.png")
player = Player.new(0, 5)
levels = Dir["media/Level*.txt"].sort
level = 1
level_start = Time.now
map = Map.new(levels.shift, player)
# Simple game loop
done = false
while !done
if map.exit?(player)
if levels.empty?
game_over = true
else
level += 1
level_start = Time.now
map = Map.new(levels.shift, player)
end
end
while e = win.getEvent()
done = true if e.type == Event::Closed or
(e.type == Event::KeyReleased and e.code == Key::Escape)
end
time = win.frameTime
player.update(win, map)
#win.beginOpenGL()
glEnable(GL_DEPTH_TEST)
glEnable(GL_CULL_FACE)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glMatrixMode(GL_PROJECTION)
glPushMatrix()
glLoadIdentity()
gluPerspective(45.0, 640.0/480.0, 0.1, 100.0)
glMatrixMode(GL_MODELVIEW)
glPushMatrix()
player.init_view()
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
glEnable(GL_COLOR_MATERIAL)
glEnable(GL_LIGHTING)
glEnable(GL_LIGHT0)
glEnable(GL_NORMALIZE)
glDisable(GL_TEXTURE_2D)
glLightfv(GL_LIGHT0, GL_POSITION, player.pos + [1])
glLightfv(GL_LIGHT0, GL_DIFFUSE, [1, 1, 1, 1])
glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.25)
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, [0, 0, 0, 1])
map.collect_gems(player)
map.draw(player)
glPopMatrix()
glMatrixMode(GL_PROJECTION)
glPopMatrix()
#win.endOpenGL()
text = Text.new("Score: #{player.score}", "", 20)
win.draw(text)
if game_over
text = Text.new("Game Over!", "", 60)
text.left, text.top = 200, 200
win.draw(text)
elsif Time.now - level_start < 3
text = Text.new("Level #{level}!", "", 60)
text.left, text.top = 200, 200
win.draw(text)
text = Text.new("(Collect the gems and find the exit)", "", 25)
text.left, text.top = 125, 260
win.draw(text)
end
win.display()
sleep(0.01)
end