ray 0.0.0.pre2 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. data/.gitignore +1 -0
  2. data/.rspec +3 -0
  3. data/README.md +62 -0
  4. data/Rakefile +33 -23
  5. data/VERSION +1 -1
  6. data/ext/audio.c +473 -0
  7. data/ext/color.c +4 -4
  8. data/ext/event.c +25 -3
  9. data/ext/extconf.rb +35 -22
  10. data/ext/font.c +287 -0
  11. data/ext/image.c +682 -33
  12. data/ext/joystick.c +9 -9
  13. data/ext/ray.c +166 -55
  14. data/ext/ray.h +120 -9
  15. data/ext/ray_osx.m +161 -0
  16. data/ext/rect.c +31 -4
  17. data/lib/ray/audio.rb +52 -0
  18. data/lib/ray/color.rb +16 -0
  19. data/lib/ray/dsl.rb +1 -3
  20. data/lib/ray/dsl/event.rb +1 -39
  21. data/lib/ray/dsl/event_listener.rb +38 -0
  22. data/lib/ray/dsl/event_runner.rb +3 -1
  23. data/lib/ray/dsl/event_translator.rb +74 -8
  24. data/lib/ray/dsl/handler.rb +3 -33
  25. data/lib/ray/dsl/matcher.rb +129 -23
  26. data/lib/ray/font.rb +108 -0
  27. data/lib/ray/font_set.rb +37 -0
  28. data/lib/ray/game.rb +171 -34
  29. data/lib/ray/helper.rb +43 -5
  30. data/lib/ray/image.rb +90 -3
  31. data/lib/ray/image_set.rb +35 -0
  32. data/lib/ray/joystick.rb +30 -0
  33. data/lib/ray/music_set.rb +35 -0
  34. data/lib/ray/ray.rb +17 -9
  35. data/lib/ray/rect.rb +51 -0
  36. data/lib/ray/resource_set.rb +92 -0
  37. data/lib/ray/scene.rb +220 -51
  38. data/lib/ray/sound_set.rb +35 -0
  39. data/lib/ray/sprite.rb +184 -0
  40. data/psp/ext.c +4 -0
  41. data/samples/hello_world/hello.rb +35 -0
  42. data/samples/hello_world/hello_dsl.rb +24 -0
  43. data/samples/pong/pong.rb +128 -0
  44. data/samples/sokoban/level_1 +7 -0
  45. data/samples/sokoban/sokoban.rb +370 -0
  46. data/spec/ray/audio_spec.rb +146 -0
  47. data/spec/ray/color_spec.rb +13 -0
  48. data/spec/ray/event_spec.rb +57 -168
  49. data/spec/ray/font_spec.rb +93 -0
  50. data/spec/ray/image_set_spec.rb +48 -0
  51. data/spec/ray/image_spec.rb +130 -44
  52. data/spec/ray/joystick_spec.rb +13 -9
  53. data/spec/ray/matcher_spec.rb +32 -55
  54. data/spec/ray/ray_spec.rb +33 -31
  55. data/spec/ray/rect_spec.rb +80 -0
  56. data/spec/ray/resource_set_spec.rb +105 -0
  57. data/spec/ray/sprite_spec.rb +163 -0
  58. data/spec/res/VeraMono.ttf +0 -0
  59. data/spec/res/aqua2.bmp +0 -0
  60. data/spec/res/pop.wav +0 -0
  61. data/spec/spec.opts +4 -0
  62. data/spec/spec_helper.rb +8 -0
  63. data/yard_ext.rb +91 -0
  64. metadata +104 -38
  65. data/bin/ray +0 -5
  66. data/bin/ray_irb +0 -4
  67. data/ext/SDLMain.h +0 -17
  68. data/ext/SDLMain.m +0 -381
  69. data/lib/ray/config.rb +0 -84
  70. data/lib/ray/dsl/converter.rb +0 -65
  71. data/lib/ray/dsl/listener.rb +0 -30
  72. data/lib/ray/dsl/type.rb +0 -58
  73. data/spec/ray/config_spec.rb +0 -90
  74. data/spec/ray/conversion_spec.rb +0 -43
  75. data/spec/ray/type_spec.rb +0 -17
  76. data/spec_runner.rb +0 -27
data/lib/ray/scene.rb CHANGED
@@ -1,102 +1,271 @@
1
1
  module Ray
2
+ # Scenes contain the main logic of a game.
3
+ #
4
+ # You can define a new scene using a block, which will be called every time
5
+ # your scene is about to be used. However, you may also want to subclass
6
+ # Ray::Scene. When doing this, you'll probably want to override the register
7
+ # method:
8
+ # def register
9
+ # on :some_event do some_stuff end
10
+ # end
11
+ #
12
+ # Another method is called before register: setup. Putting code in register or
13
+ # in setup doesn't matter, but setting the scene up inside register method
14
+ # seems (and, indeed, is) inappropriate. You can override it:
15
+ # def setup
16
+ # @sprite = sprite("image.png")
17
+ # end
18
+ #
19
+ # You can indicate how your scene should be rendered there:
20
+ # render do |win|
21
+ # # Do drawing here
22
+ # end
23
+ #
24
+ # Or you can override render:
25
+ # def render(win)
26
+ # # Do drawing here
27
+ # end
28
+ #
29
+ # Notice win is not filled with an empty color when render is called, i.e.
30
+ # it still contains the frame which appears to the user.
31
+ #
32
+ # Also, scenes are rendered lazily: only once when the scene is created,
33
+ # and then every time need_render! is called.
34
+ #
35
+ # Once your scene is loaded, you'll probably want to clean it up (set some
36
+ # instance variables to nil so they can be garbaged collected for instance).
37
+ # You can do that by passing a block to clean_up:
38
+ # clean_up do
39
+ # @some_big_resource = nil
40
+ # end
41
+ #
42
+ # Or by overriding it:
43
+ # def clean_up
44
+ # @some_big_resource = nil
45
+ # end
46
+ #
47
+ # == Managing the stack of scenes
48
+ # exit is called when you want to stop running the scene, but not to remove
49
+ # the last scene from the stack. It is useful if you want to push a new
50
+ # scene. Hence Ray::Scene#push_scene will call exit.
51
+ #
52
+ # exit! (or pop_scene), on the other hand, is used to go back to the previous
53
+ # scene in the hierarchy
54
+ #
55
+ # == Sending informations to a scene
56
+ # Scenes may need some arguments to work. You can pass those in push_scene:
57
+ # push_scene(:polygon, 6, Ray::Color.red)
58
+ # Then you can use them with scene_arguments:
59
+ # scene :polygon do
60
+ # sides, color = scene_arguments
61
+ # # ...
62
+ # end
63
+ #
64
+ # They are also passed to #setup:
65
+ # def setup(sides, color)
66
+ # # ...
67
+ # end
68
+ #
69
+ # == Limiting the loop rate
70
+ # You can prevent a scene from always running by using #loops_per_second=:
71
+ # self.loops_per_second = 30 # will sleep some time after each loop
72
+ #
73
+ # @see Ray::DSL::EventTranslator
2
74
  class Scene
3
75
  include Ray::Helper
4
76
 
5
77
  class << self
6
- # If you want to subclass Scene, this is the method you need to call.
7
- # It will return a subclass, responding to bind, which allows you to
8
- # register it.
9
- #
10
- # @example
11
- # Klass = Scene.create(:foo) do ... end
12
- # Klass.bind(game)
13
- # game.push_scene(:foo)
14
- def create(scene_name, &block)
15
- klass = Class.new(self)
16
- klass.instance_variable_set("@block", block)
17
-
18
- (class << klass; self; end).class_eval do
19
- define_method(:bind) do |game|
20
- game.scene(scene_name, self, &@block)
21
- end
22
-
23
- define_method(:inspect) { "Scene:#{scene_name}" }
24
- define_method(:block) { @block }
25
- end
26
-
27
- klass.class_eval do
28
- define_method(:initialize) do
29
- super(&self.class.block)
30
- end
31
- end
78
+ # Registers a scene to a game object, used for subclasses.
79
+ def bind(game)
80
+ game.scene(scene_name, self)
81
+ end
32
82
 
33
- return klass
83
+ # @overload scene_name
84
+ # @return [Symbol] the name of the scene
85
+ # @overload scene_name(value)
86
+ # Sets the name of the scene
87
+ def scene_name(val = nil)
88
+ @scene_name = val || @scene_name
34
89
  end
35
90
  end
36
91
 
37
- # Creates a new scene. block will be instance evaluated
38
- # every time the current scene changes.
92
+ scene_name :scene
93
+
94
+ # Creates a new scene. block will be instance evaluated when
95
+ # this scene becomes the current one.
39
96
  def initialize(&block)
40
- @exit = false
41
- @block = block
97
+ @scene_register_block = block
42
98
  end
43
99
 
44
100
  def register_events
45
- instance_eval(&@block)
101
+ @scene_held_keys = []
102
+
103
+ on :key_press do |key, mod|
104
+ @scene_held_keys << key
105
+ end
106
+
107
+ on :key_release do |key, mod|
108
+ @scene_held_keys.reject! { |i| i == key }
109
+ end
110
+
111
+ if @scene_register_block
112
+ instance_eval(&@scene_register_block)
113
+ else
114
+ register
115
+ end
116
+
117
+ @scene_exit = false
118
+ end
119
+
120
+ # Override this method in subclasses to setup the initial state
121
+ # of your scene.
122
+ def setup(*args)
123
+ end
124
+
125
+ # Override this method in subclasses to register your own events
126
+ def register
127
+ end
128
+
129
+ # @param [Symbol, Integer] val A symbol to find the key (its name)
130
+ # or an integer (Ray::Event::KEY_*)
131
+ #
132
+ # @return [true, false] True if the user is holding key.
133
+ def holding?(val)
134
+ if val.is_a? Symbol
135
+ val = key(val)
136
+ @scene_held_keys.any? { |o| val === o }
137
+ elsif val.is_a? DSL::Matcher
138
+ @scene_held_keys.any? { |o| val === o }
139
+ else
140
+ @scene_held_keys.include? val
141
+ end
46
142
  end
47
143
 
48
144
  # Runs until you exit the scene.
49
145
  # This will also raise events if the mouse moves, ... allowing you
50
146
  # to directly listen to a such event.
51
147
  def run
52
- until @exit
53
- ev = DSL::EventTranslator.translate_event(Ray::Event.new)
54
- raise_event(*ev) if ev
148
+ until @scene_exit
149
+ loop_start = Time.now
55
150
 
56
- @always.call if @always
151
+ DSL::EventTranslator.translate_event(Ray::Event.new).each do |args|
152
+ raise_event(*args)
153
+ end
154
+
155
+ @scene_always_block.call if @scene_always_block
57
156
 
58
157
  listener_runner.run
59
158
 
60
- if @need_render
61
- @need_render = false
159
+ if @scene_need_render
160
+ @scene_need_render = false
161
+
162
+ render(@scene_window)
163
+ @scene_window.flip
164
+ end
165
+
166
+ if @scene_loops_per_second
167
+ ellapsed_time = Time.now - loop_start
168
+ time_per_loop = 1.0 / @scene_loops_per_second
62
169
 
63
- @render.call(@window)
64
- @window.flip
170
+ sleep(time_per_loop - ellapsed_time) if ellapsed_time < time_per_loop
65
171
  end
66
172
  end
173
+
174
+ clean_up
67
175
  end
68
176
 
69
- # Exits the scene, but does not pops the scene (the next scene
70
- # will be the same one)
177
+ # Exits the scene, but does not pop the scene.
178
+ #
179
+ # You may want to call this if you pushed a new scene, to switch to
180
+ # the new scene.
71
181
  def exit
72
- @exit = true
182
+ @scene_exit = true
73
183
  end
74
184
 
75
- # Exits the scene and pops it.
185
+ # Exits the scene and pops it (may not work as expected if the current
186
+ # scene is not the last one)
76
187
  def exit!
77
188
  exit
78
189
  game.pop_scene
79
190
  end
80
191
 
81
- # Register a block to be excuted as often as possible.
192
+ # Registers a block to be excuted as often as possible.
82
193
  def always(&block)
83
- @always = block
194
+ @scene_always_block = block
84
195
  end
85
196
 
86
197
  # Marks the scene should be redrawn.
87
198
  def need_render!
88
- @need_render = true
199
+ @scene_need_render = true
89
200
  end
90
201
 
91
202
  # Registers the block to draw the scene.
92
203
  #
204
+ # Does nothing if no block is given, this method being called if you
205
+ # didn't register any render block. You can thus override it in subclasses
206
+ # instead of providing a block to it.
207
+ #
93
208
  # @yield [window] Block to render this scene.
94
209
  # @yieldparam [Ray::Image] window The window you should draw on
95
- def render(&block)
96
- @render = block
210
+ def render(win = nil, &block)
211
+ if block_given?
212
+ @scene_render_block = block
213
+ else
214
+ @scene_render_block.call(win) if @scene_render_block
215
+ end
216
+ end
217
+
218
+ # Pushes a scene in the stack, and exits that one
219
+ def push_scene(scene, *args)
220
+ game.push_scene(scene, *args)
221
+ exit
222
+ end
223
+
224
+ # @see Ray::Game#resize_window
225
+ def resize_window(w, h)
226
+ game.resize_window(w, h)
227
+ end
228
+
229
+ # Cleans the scene or registers a block to clean it.
230
+ def clean_up(&block)
231
+ if block_given?
232
+ @scene_clean_block = block
233
+ else
234
+ @scene_clean_block.call if @scene_clean_block
235
+ end
236
+ end
237
+
238
+ def inspect
239
+ "#<#{self.class} game=#{self.game.inspect}>"
240
+ end
241
+
242
+ alias :pop_scene :exit!
243
+
244
+ def game
245
+ @scene_game
246
+ end
247
+
248
+ def game=(val)
249
+ @scene_game = val
250
+ end
251
+
252
+ def window
253
+ @scene_window
254
+ end
255
+
256
+ def window=(val)
257
+ @scene_window = val
258
+ end
259
+
260
+ def loops_per_second
261
+ @scene_loops_per_second
262
+ end
263
+
264
+ def loops_per_second=(val)
265
+ @scene_loops_per_second = val
97
266
  end
98
267
 
99
- attr_accessor :game
100
- attr_accessor :window
268
+ # The arguments passed to the scene with push_scene
269
+ attr_accessor :scene_arguments
101
270
  end
102
271
  end
@@ -0,0 +1,35 @@
1
+ module Ray
2
+ module SoundSet
3
+ extend Ray::ResourceSet
4
+
5
+ class << self
6
+ def missing_pattern(string)
7
+ Ray::Sound[string]
8
+ end
9
+
10
+ def select!(&block)
11
+ super(&block)
12
+ Ray::Sound.select!(&block)
13
+ end
14
+ end
15
+ end
16
+
17
+ # Creates a new sound set.
18
+ #
19
+ # @param [Regexp] regex Regular expression used to match file
20
+ # @yield [*args] Block returning the sound
21
+ #
22
+ # @yieldparam args Regex captures
23
+ def self.sound_set(regex, &block)
24
+ Ray::SoundSet.add_set(regex, &block)
25
+ end
26
+ end
27
+
28
+ begin
29
+ require 'open-uri'
30
+
31
+ Ray.sound_set(/^(http|ftp):\/\/(\S+)$/) do |protocol, address|
32
+ open("#{protocol}://#{address}") { |io| Ray::Sound.new(io) }
33
+ end
34
+ rescue LoadError
35
+ end
data/lib/ray/sprite.rb ADDED
@@ -0,0 +1,184 @@
1
+ module Ray
2
+ class Sprite
3
+ # Creates a sprite.
4
+ #
5
+ # @param [String, Ray::Image] The image this object will wrap;
6
+ # @option opts [Ray::Rect, Array<Integer>] :at Position of the sprite
7
+ # (defaults to 0,0)
8
+ # @option opts [Ray::Rect, Array<Integer>] :rect Rect of the image which will
9
+ # be drawn (defaults to the
10
+ # whole image)
11
+ # @option opts [Float] :angle The angle which will be used to draw the image
12
+ # in degrees. Defaults to 0.
13
+ # @option opts [Float] :zoom The zoom level which will be used to draw the image.
14
+ # Defaults to 1.
15
+ def initialize(image, opts = {})
16
+ opts = {
17
+ :rect => Ray::Rect.new(0, 0, 0, 0),
18
+ :at => [0, 0],
19
+ :angle => 0.0,
20
+ :zoom => 1
21
+ }.merge(opts)
22
+
23
+ @from_rect = opts[:rect].to_rect
24
+
25
+ rect = opts[:at].to_rect
26
+ @x, @y = rect.x, rect.y
27
+
28
+ self.angle = opts[:angle]
29
+ self.zoom = opts[:zoom]
30
+
31
+ @image = image.to_image
32
+ end
33
+
34
+ # Sets the size of the sprite sheet. For instance,
35
+ # sprite.sheet_size = [3, 4]
36
+ # would mean there are 4 rows and 3 columns in the sprite (and each cell
37
+ # has the same size).
38
+ def sheet_size=(ary)
39
+ w, h = ary
40
+
41
+ @uses_sprite_sheet = true
42
+
43
+ @sprite_sheet_w = w
44
+ @sprite_sheet_h = h
45
+
46
+ self.sheet_pos = [0, 0]
47
+ end
48
+
49
+ # Sets which cell of the sprite sheet should be displayed.
50
+ # sprite.sheet_pos = [0, 1] # Uses the first cell of the second line.
51
+ def sheet_pos=(ary)
52
+ x, y = ary
53
+
54
+ self.from_rect = Ray::Rect.new(x * sprite_width, y * sprite_height,
55
+ sprite_width, sprite_height)
56
+
57
+ @sheet_pos_x = x
58
+ @sheet_pos_y = y
59
+ end
60
+
61
+ # Returns the position of the cell which is being used.
62
+ def sheet_pos
63
+ [@sheet_pos_x, @sheet_pos_y]
64
+ end
65
+
66
+ # @return [Integer, nil] The width of each cell in the sprite sheet
67
+ def sprite_width
68
+ if uses_sprite_sheet?
69
+ @image.w / @sprite_sheet_w
70
+ end
71
+ end
72
+
73
+ # @return [Integer, nil] The height of each cell in the sprite sheet
74
+ def sprite_height
75
+ if uses_sprite_sheet?
76
+ @image.h / @sprite_sheet_h
77
+ end
78
+ end
79
+
80
+ # Disables the sprite sheet
81
+ def disable_sprite_sheet
82
+ self.from_rect = Ray::Rect.new(0, 0, @image.w, @image.h)
83
+
84
+ @sprite_sheet_w = nil
85
+ @sprite_sheet_h = nil
86
+
87
+ @uses_sprite_sheet = false
88
+ end
89
+
90
+ def uses_sprite_sheet?
91
+ @uses_sprite_sheet
92
+ end
93
+
94
+ # Draws the sprite on an image.
95
+ # @param [Ray::Image] screen The image on which the sprite will be drawn
96
+ def draw_on(screen)
97
+ @image.blit(:rect => @from_rect, :on => screen, :at => [@x, @y],
98
+ :angle => @angle, :zoom => @zoom)
99
+ end
100
+
101
+ # Draws the sprite on the screen or on another image
102
+ # @option opts [Ray::Image] :on The image we should draw on. Defaults to
103
+ # Ray.screen.
104
+ def draw(opts = {})
105
+ draw_on(opts[:on] || Ray.screen)
106
+ end
107
+
108
+ # @return [true, false] True if the sprite is located at (x, y)
109
+ def is_at?(x, y)
110
+ @x == x && @y == y
111
+ end
112
+
113
+ # @return [Ray::Rect] The rect where this sprite will be drawn.
114
+ def rect
115
+ Ray::Rect.new(@x, @y, @from_rect.w == 0 ? @image.w : @from_rect.w,
116
+ @from_rect.h == 0 ? @image.h : @from_rect.h)
117
+ end
118
+
119
+ # @param [Ray::Rect, #rect] An object with which the receiver may collide
120
+ # @return [true, false]
121
+ def collide?(obj)
122
+ rect.collide?(obj.to_rect)
123
+ end
124
+
125
+ # @param [Ray::Rect, #rect] (See #collide?)
126
+ # @return [true, false]
127
+ def inside?(obj)
128
+ rect.inside?(obj.to_rect)
129
+ end
130
+
131
+ # @param [Ray::Rect, #rect] (See #collide?)
132
+ # @return [true, false]
133
+ def outside?(obj)
134
+ rect.outside?(obj.to_rect)
135
+ end
136
+
137
+ def to_rect
138
+ rect
139
+ end
140
+
141
+ # @return [Ray::Image]
142
+ attr_reader :image
143
+
144
+ # @return [Integer] position of the sprite
145
+ attr_accessor :x, :y
146
+
147
+ # @return [Ray::Rect] the part of the image which will be drawn. An empty
148
+ # rect means the whole image.
149
+ attr_accessor :from_rect
150
+
151
+ # @return [Ray::Rect] the position of the sprite
152
+ def pos
153
+ [x, y]
154
+ end
155
+
156
+ # Sets the position of the sprite
157
+ def pos=(ary)
158
+ rect = ary.to_rect
159
+
160
+ self.x = rect.x
161
+ self.y = rect.y
162
+ end
163
+
164
+ # @return [Float] The angle used when the image is drawn.
165
+ def angle
166
+ @angle ? @angle : 0
167
+ end
168
+
169
+ # Sets the angle.
170
+ def angle=(val)
171
+ @angle = (val % 360).zero? ? nil : val
172
+ end
173
+
174
+ # @return [Float] the zoom applied to the image when it is drawn.
175
+ def zoom
176
+ @zoom ? @zoom : 1
177
+ end
178
+
179
+ # Sets the zoom level.
180
+ def zoom=(val)
181
+ @zoom = val == 1 ? nil : val
182
+ end
183
+ end
184
+ end