mygame/lib/mygame.rb
require 'sdl'
module MyGame
module Key
include SDL::Key
end
class Wave
@@wave = {}
def initialize(fname, ch = :auto, loop = 1)
@ch = ch
@loop = loop
@fname = fname
self.class.load(fname)
end
def self.load(fname)
@@wave[fname] = SDL::Mixer::Wave.load(fname)
end
def self.clear_cache
@@wave = {}
end
def self._loops(loop)
if loop.nil?
0
elsif loop <= 0
-1
else
loop - 1
end
end
def self._ch(ch)
if ch.nil? or ch == :auto
-1
else
ch
end
end
def self._play(ch, wave, loop)
SDL::Mixer.play_channel(_ch(ch), wave, _loops(loop))
end
def self.play(fname, ch = :auto, loop = 1)
_play(ch, load(fname), loop)
end
def play(ch = @ch, loop = @loop)
self.class._play(ch, @@wave[@fname], loop)
end
end
class Music < Wave
def self.load(fname)
@@wave[fname] = SDL::Mixer::Music.load(fname)
end
def _play(ch, wave, loop)
SDL::Mixer.play_channel(ch, wave, loop)
end
end
class DrawPrim
attr_accessor :screen
attr_accessor :x, :y, :w, :h
attr_accessor :alpha, :hide
def self.draw(*args)
new(*args).draw
end
def initialize(*argv)
@screen = @@screen
@hide = false
@alpha = 255
end
end
class Image < DrawPrim
@@image_buf = {}
attr_accessor :angle, :scale
attr_reader :image
def cx; x + w / 2; end
def cy; y + h / 2; end
def hit?(target)
target.x >= x and target.x < x + w and
target.y >= y and target.y < y + h
end
def initialize(fname = nil, x = 0, y = 0)
super
@tx = @ty = nil unless defined?(@tx) or defined?(@ty)
load(fname) if fname
@x, @y = x, y
@ox = @oy = 0
@angle = 0
@scale = 1
@anim_labels = {}
@anim_active = nil
@anim_counter = 0
end
def update_anim
if anim = @anim_labels[@anim_active]
size = anim[:patten].size
idx = anim[:patten][@anim_counter / anim[:time] % size]
num = @image.w / @w
@ox = idx % num * @w
@oy = idx / num * @h
@anim_counter += 1
end
end
def run
update_anim
end
def set_anim(label, time, patten=nil, loop=nil)
@anim_labels[label] = {
:time => [time, 1].max,
:patten => patten,
:loop => loop,
}
end
def start_anim(label, restart = false)
return if @anim_active == label and !restart
@anim_active = label
@anim_counter = 0
end
def stop_anim(label)
@anim_active = nil
end
def load(fname)
unless @image = @@image_buf[fname]
@image = SDL::Surface.load(fname).display_format
set_color_key(@tx, @ty) if @tx and @ty
@@image_buf[fname] = @image
end
@w, @h = @image.w, @image.h
@image
end
def add_load(fname)
return load(fname) unless @image
old_image = @image
new_image = load(fname)
p w = [old_image.w, new_image.w].max
p h = old_image.h + new_image.h
@image = SDL::Surface.new(SDL::SWSURFACE, w, h, 32, *mask_rgba)
SDL.blit_surface old_image, 0, 0, 0, 0, @image, 0, 0
SDL.blit_surface new_image, 0, 0, 0, 0, @image, 0, old_image.h
@image = @image.display_format
@w, @h = @image.w, @image.h
@image
end
def set_color_key(x = 0, y = 0)
@image.set_color_key SDL::SRCCOLORKEY, @image.getPixel(x, y)
@image = @image.display_format
end
def draw(x = @x, y = @y)
return if hide or @image.nil?
@image.set_alpha(SDL::SRCALPHA, alpha) if alpha < 255
if scale == 1 and angle == 0
SDL.blit_surface @image, @ox, @oy, @w, @h, screen, x, y
else
SDL.transform_blit(@image, screen, @angle, @scale, @scale, @w/2, @h/2, x, y, 0)
end
end
def self.clear_cache
@@image_buf = {}
end
end
class TImage < Image
def initialize(fname = nil, x = 0, y = 0, tx = 0, ty = 0)
@tx, @ty = tx, tx
super fname, x, y
end
end
require 'kconv'
require 'rbconfig'
class Font < DrawPrim
def self.setup_default_setting(ttf = nil, size = nil)
datadir = Config::CONFIG["datadir"]
mygame_datadir = File.join(datadir, 'mygame')
@@default_ttf_path = ttf || File.join(mygame_datadir, 'M+2VM+IPAG-circle.ttf')
@@default_size = size || 16
end
setup_default_setting
def self.default_size
@@default_size
end
def self.default_size=(size)
@@default_size = size
end
def self.default_ttf_path
@@default_ttf_path
end
def self.default_ttf_path=(size)
@@default_ttf_path = size
end
attr_accessor :added_width
attr_reader :str
def initialize(_str = '', x = 0, y = 0, color = [255, 255, 255],
size=self.class.default_size, ttf_path=self.class.default_ttf_path)
super
@x = x
@y = y
@color = color
@size = size
@ttf_path = ttf_path
@font = open_tff(@ttf_path, @size)
@font.style = SDL::TTF::STYLE_NORMAL
@disp_length = nil
@last_str = nil
self.str = _str
end
def open_tff(ttf_path, size)
SDL::TTF.open(ttf_path, size)
end
def hit?(target)
target.x >= x and target.x < x + w and
target.y >= y and target.y < y + h
end
%w(color shadow_color size ttf_path).each do |e|
attr_reader e
eval "def #{e}=_#{e}
return if _#{e} == @last_#{e}
@last_#{e} = _#{e}
@#{e} = _#{e}
@font = open_tff(@ttf_path, @size)
create_surface
end"
end
def str=(_str)
_str = _str.to_s
return if _str == @last_str
@last_str = _str
@str = Kconv.toutf8(_str)
create_surface
end
def create_surface
@w, @h = @font.text_size(@str)
@dx, @dy = if @shadow_color
[1 + @size / 24, 1 + @size / 24]
else
[0, 0]
end
@surface = SDL::Surface.new(SDL::SWSURFACE, w + @dx, h + @dy, 32, *mask_rgba)
if @shadow_color
@font.drawSolidUTF8(@surface, @str, @dx, @dy, *@shadow_color)
@font.drawSolidUTF8(@surface, @str, 0, @dy, *@shadow_color)
end
@font.drawSolidUTF8(@surface, @str, 0, 0, *@color)
@surface.set_color_key SDL::SRCCOLORKEY, @surface.getPixel(0, 0)
@surface = @surface.display_format
end
def start_effect(w)
@added_width = w
@disp_length = 0
end
def disp_length_max?
@disp_length.nil? or @disp_length >= str.length
end
def run
@disp_length += @added_width unless disp_length_max?
end
def draw(x = @x, y = @y)
return if hide or @surface.nil?
if disp_length_max?
disp_w = 0
disp_h = 0
else
disp_w = @disp_length / 2 * size
disp_h = h + @dy
end
SDL.blit_surface @surface, 0, 0, disp_w, disp_h, screen, x, y
end
end
class SFont < Font
def initialize *args
@shadow_color = [32, 32, 32]
super
end
end
class Square < DrawPrim
attr_accessor :alpha, :hide
def initialize(*args)
@args = args
@alpha = args[5] || 255
@fill = false
end
def draw(x = @args[0], y = @args[1])
return if hide
if alpha < 255
@args[5] = alpha
args = [x, y, *@args[2, 4]]
@fill ? @@screen.draw_filled_rect_alpha(*args) : @@screen.draw_rect_alpha(*args)
else
args = [x, y, *@args[2, 3]]
@fill ? @@screen.fill_rect(*args) : @@screen.draw_rect(*args)
end
end
end
class FillSquare < Square
def initialize(*args)
super
@fill = true
end
end
def init(flags = SDL::INIT_AUDIO | SDL::INIT_VIDEO)
raise if SDL.inited_system(flags) > 0
init_events
SDL.init flags
SDL::Mixer.open(22050 * 4) if flags & SDL::INIT_AUDIO
SDL::Mixer.allocate_channels(16)
SDL::TTF.init
@@background_color = [0, 0, 0]
@@press_last_key ||= {}
end
def quit
SDL.quit
end
def create_screen(screenw = 640, screenh = 480, bpp = 16, flags = SDL::SWSURFACE)
screen = SDL.set_video_mode(screenw, screenh, bpp, flags)
def screen.update x=0, y=0, w=0, h=0
self.update_rect x, y, w, h
end
@@screen = screen
end
def init_game(*args)
init
create_screen(*args)
end
def main_loop(fps = 60)
@@ran_loop = true
@@real_fps = 0
do_wait = true
@@count = 0
@@tm_start = @@ticks = SDL.get_ticks
until @@loop_end
poll_event
SDL::Key.scan
if block_given?
screen.fillRect 0, 0, screen.w, screen.h, background_color if background_color
yield screen
end
sync(fps) if do_wait
screen.flip
end
end
def poll_event
while event = SDL::Event2.poll
event.class.name =~ /\w+\z/
name = $&.gsub(/([a-z])([A-Z])/) { "#{$1}_#{$2.downcase}" }.downcase
(@@events[name.to_sym] || {}).each {|key, block| block.call(event) }
end
end
def sync(fps)
diff = @@ticks + (1000 / fps) - SDL.get_ticks
SDL.delay(diff) if diff > 0
@@ticks = SDL.get_ticks
@@count += 1
if @@count >= 30
@@count = 0
@@real_fps = 30 * 1000 / (@@ticks - @@tm_start)
@@tm_start = @@ticks
end
end
def real_fps
@@real_fps
end
def ran_loop?
@@ran_loop
end
def screen
@@screen
end
def background_color
@@background_color
end
def background_color=(color)
@@background_color = color
end
def add_event(event, key=nil, &block)
@@events[event] || raise("unknown event type `#{event}'")
@@events[event][key || block.object_id] = block
end
def remove_event(event, key=nil)
if key
@@events[event].delete(key)
else
@@events[event].each {|key, | @@events[event].delete(key) }
end
end
def init_events
%w(active key_down key_up mouse_motion mouse_button_down mouse_button_up
joy_axis joy_ball joy_hat joy_button_up joy_button_down
quit sys_wm video_resize).each {|e| @@events[e.to_sym] = {} }
add_event(:quit, :close) { @@loop_end = true }
add_event(:key_down, :close) {|e| @@loop_end = true if e.sym == Key::ESCAPE }
end
def press_key?(key)
SDL::Key.press?(key)
end
def press_new_key?(key)
flag = @@press_last_key[key] == false && SDL::Key.press?(key)
@@press_last_key[key] = SDL::Key.press?(key)
flag
end
def mask_rgba
masks = [0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000]
masks.reverse! if big_endian = ([1].pack("N") == [1].pack("L"))
masks
end
@@screen = nil
@@ran_loop = false
@@loop_end = false
@@events = {}
module_function :real_fps
module_function :init
module_function :quit
module_function :create_screen
module_function :init_game
module_function :main_loop
module_function :ran_loop?
module_function :screen
module_function :background_color
module_function :background_color=
module_function :add_event
module_function :remove_event
module_function :init_events
end