gamebox 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. data/.gitignore +4 -0
  2. data/Gemfile +9 -0
  3. data/History.txt +5 -0
  4. data/README.txt +4 -3
  5. data/Rakefile +12 -4
  6. data/TODO.txt +1 -5
  7. data/VERSION +1 -1
  8. data/docs/getting_started.rdoc +2 -2
  9. data/gamebox.gemspec +26 -10
  10. data/lib/gamebox/actor.rb +10 -3
  11. data/lib/gamebox/actor_factory.rb +2 -2
  12. data/lib/gamebox/actor_view.rb +14 -11
  13. data/lib/gamebox/actors/collidable_debugger.rb +35 -0
  14. data/lib/gamebox/actors/curtain.rb +2 -2
  15. data/lib/gamebox/actors/logo.rb +0 -6
  16. data/lib/gamebox/actors/score.rb +2 -5
  17. data/lib/gamebox/actors/spatial_debugger.rb +47 -0
  18. data/lib/gamebox/actors/svg_actor.rb +4 -6
  19. data/lib/gamebox/arbiter.rb +108 -15
  20. data/lib/gamebox/behavior.rb +29 -1
  21. data/lib/gamebox/behaviors/animated.rb +14 -23
  22. data/lib/gamebox/behaviors/audible.rb +1 -12
  23. data/lib/gamebox/behaviors/collidable.rb +29 -22
  24. data/lib/gamebox/behaviors/collidable/aabb_collidable.rb +61 -0
  25. data/lib/gamebox/behaviors/collidable/circle_collidable.rb +17 -0
  26. data/lib/gamebox/behaviors/collidable/collidable_shape.rb +35 -0
  27. data/lib/gamebox/behaviors/collidable/polygon_collidable.rb +85 -0
  28. data/lib/gamebox/behaviors/graphical.rb +13 -10
  29. data/lib/gamebox/behaviors/layered.rb +6 -20
  30. data/lib/gamebox/behaviors/physical.rb +116 -93
  31. data/lib/gamebox/class_finder.rb +6 -2
  32. data/lib/gamebox/config_manager.rb +24 -4
  33. data/lib/gamebox/data/config/objects.yml +5 -3
  34. data/lib/gamebox/ftor.rb +372 -0
  35. data/lib/gamebox/gamebox_application.rb +2 -8
  36. data/lib/gamebox/hooked_gosu_window.rb +30 -0
  37. data/lib/gamebox/input_manager.rb +78 -79
  38. data/lib/gamebox/lib/code_statistics.rb +1 -1
  39. data/lib/gamebox/lib/numbers_ext.rb +1 -1
  40. data/lib/gamebox/lib/rect.rb +612 -0
  41. data/lib/gamebox/physical_stage.rb +12 -2
  42. data/lib/gamebox/physics.rb +14 -3
  43. data/lib/gamebox/resource_manager.rb +28 -65
  44. data/lib/gamebox/sound_manager.rb +7 -13
  45. data/lib/gamebox/spatial_hash.rb +60 -14
  46. data/lib/gamebox/spatial_stagehand.rb +19 -0
  47. data/lib/gamebox/stage.rb +16 -1
  48. data/lib/gamebox/stage_manager.rb +1 -1
  49. data/lib/gamebox/svg_document.rb +1 -0
  50. data/lib/gamebox/tasks/gamebox_tasks.rb +23 -11
  51. data/lib/gamebox/templates/template_app/.gitignore +1 -0
  52. data/lib/gamebox/templates/template_app/Gemfile +1 -1
  53. data/lib/gamebox/templates/template_app/Rakefile +6 -21
  54. data/lib/gamebox/templates/template_app/config/environment.rb +14 -0
  55. data/lib/gamebox/templates/template_app/config/game.yml +2 -1
  56. data/lib/gamebox/templates/template_app/script/generate +0 -3
  57. data/lib/gamebox/templates/template_app/src/demo_stage.rb +1 -11
  58. data/lib/gamebox/templates/template_app/src/game.rb +0 -1
  59. data/lib/gamebox/templates/template_app/src/my_actor.rb +2 -2
  60. data/lib/gamebox/version.rb +1 -1
  61. data/lib/gamebox/views/graphical_actor_view.rb +15 -9
  62. data/lib/gamebox/wrapped_screen.rb +114 -7
  63. data/load_paths.rb +20 -0
  64. data/script/perf_spatial_hash.rb +73 -0
  65. data/spec/actor_view_spec.rb +1 -1
  66. data/spec/arbiter_spec.rb +264 -0
  67. data/spec/behavior_spec.rb +19 -2
  68. data/spec/collidable_spec.rb +105 -5
  69. data/spec/helper.rb +1 -1
  70. data/spec/label_spec.rb +1 -1
  71. data/spec/resource_manager_spec.rb +1 -1
  72. data/spec/spatial_hash_spec.rb +1 -1
  73. metadata +52 -10
  74. data/lib/gamebox/lib/surface_ext.rb +0 -76
@@ -1,12 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
  $: << "#{File.dirname(__FILE__)}/../config"
3
3
 
4
- require 'rubygems'
5
- require 'rubygame'
6
- include Rubygame
7
- include Rubygame::Events
8
- require 'event_compat'
9
- require 'surface_ext'
4
+ require 'gosu'
5
+ include Gosu
10
6
 
11
7
  require "environment"
12
8
 
@@ -42,7 +38,6 @@ class GameboxApp
42
38
  end
43
39
 
44
40
  def setup
45
- Rubygame.init
46
41
  @game = @context[:game]
47
42
 
48
43
  @config_manager = @context[:config_manager]
@@ -65,7 +60,6 @@ class GameboxApp
65
60
  end
66
61
 
67
62
  def shutdown
68
- Rubygame.quit
69
63
  end
70
64
 
71
65
  def debug_eval(eval_str)
@@ -0,0 +1,30 @@
1
+ require 'publisher'
2
+
3
+ class HookedGosuWindow < Window
4
+ extend Publisher
5
+ can_fire :update, :draw, :button_down, :button_up
6
+
7
+ def initialize(width, height, fullscreen)
8
+ super(width, height, fullscreen)
9
+ end
10
+
11
+ def update
12
+ millis = Gosu::milliseconds
13
+ @last_millis ||= millis
14
+ fire :update, (millis - @last_millis)
15
+ @last_millis = millis
16
+ end
17
+
18
+ def draw
19
+ fire :draw
20
+ end
21
+
22
+ # in gosu this captures mouse and keyboard events
23
+ def button_down(id)
24
+ fire :button_down, id
25
+ end
26
+
27
+ def button_up(id)
28
+ fire :button_up, id
29
+ end
30
+ end
@@ -1,6 +1,7 @@
1
1
  require 'publisher'
2
+ require 'hooked_gosu_window'
2
3
 
3
- # InputManager handles the pumping of SDL for events and distributing of said events.
4
+ # InputManager handles the pumping for events and distributing of said events.
4
5
  # You can gain access to these events by registering for all events,
5
6
  # or just the ones you care about.
6
7
  # All events:
@@ -15,109 +16,107 @@ require 'publisher'
15
16
  #
16
17
  # Don't forget to unreg for these things between stages,
17
18
  # since the InputManager is shared across stages.
18
- class InputManager
19
+ class InputManager
19
20
  extend Publisher
20
21
  can_fire :key_up, :event_received
21
22
 
22
-
23
- # lookup map for mouse button clicks
24
- MOUSE_BUTTON_LOOKUP = {
25
- MOUSE_LEFT => :left,
26
- MOUSE_MIDDLE => :middle,
27
- MOUSE_RIGHT => :right,
28
- }
29
-
30
- constructor :config_manager
23
+ attr_accessor :window
24
+ constructor :config_manager, :wrapped_screen
31
25
 
32
26
  # Sets up the clock and main event loop. You should never call this method,
33
27
  # as this class should be initialized by diy.
34
28
  def setup
35
- @queue = EventQueue.new
36
- @queue.enable_new_style_events
37
- @queue.ignore = [
38
- InputFocusGained,
39
- InputFocusLost,
40
- MouseFocusGained,
41
- MouseFocusLost,
42
- WindowMinimized,
43
- WindowUnminimized,
44
- JoystickAxisMoved,
45
- JoystickBallMoved,
46
- JoystickButtonPressed,
47
- JoystickButtonReleased,
48
- JoystickHatMoved,
49
- WindowResized
50
- ]
51
-
52
- @clock = Clock.new do |c|
53
- c.target_framerate = 40
54
- if c.respond_to? :calibrate
55
- c.calibrate
56
- c.granularity = 2 if c.granularity < 2
57
- end
58
- end
29
+ @window = @wrapped_screen.screen
59
30
 
60
- @auto_quit = @config_manager[:auto_quit]
31
+ @auto_quit = instance_eval(@config_manager[:auto_quit])
61
32
 
62
33
  @hooks = {}
63
34
  @non_id_hooks = {}
64
35
  end
65
-
66
- # Sets the target framerate for the game.
67
- # This setting controls how lock Clock#tick will delay.
68
- def framerate=(frame_rate)
69
- @clock.target_framerate = frame_rate
70
- end
71
-
72
- # Returns the target framerate.
73
- def framerate
74
- @clock.target_framerate
75
- end
76
36
 
77
- # Returns the current framerate.
78
- def current_framerate
79
- @clock.framerate
80
- end
81
-
82
- # This is where the queue gets pumped. This gets called from your game application.
37
+ # This gets called from game app and sets up all the
38
+ # events. (also shows the window)
83
39
  def main_loop(game)
84
- catch(:rubygame_quit) do
85
- loop do
86
- # add magic hooks
87
- @queue.each do |event|
88
- _handle_event(event)
89
- end
90
40
 
91
- game.update @clock.tick
41
+ @window.when :button_up do |button_id|
42
+ _handle_event button_id, :up
43
+ end
44
+ @window.when :button_down do |button_id|
45
+ _handle_event button_id, :down
46
+ end
47
+ @window.when :update do |millis|
48
+
49
+ @last_mouse_x ||= @window.mouse_x
50
+ @last_mouse_y ||= @window.mouse_y
51
+
52
+ x_diff = @last_mouse_x - @window.mouse_x
53
+ y_diff = @last_mouse_y - @window.mouse_y
54
+
55
+ unless x_diff < 0.1 && x_diff > -0.1 && y_diff < 0.1 && y_diff > -0.1
56
+ _handle_event nil, :motion
57
+
58
+ @last_mouse_x = @window.mouse_x
59
+ @last_mouse_y = @window.mouse_y
92
60
  end
61
+
62
+ game.update millis
63
+ end
64
+ @window.when :draw do
65
+ game.draw
93
66
  end
67
+
68
+ @window.show
94
69
  end
95
70
 
96
- def _handle_event(event) #:nodoc:
97
- case event
98
- when KeyPressed
99
- case event.key
100
- when @auto_quit
101
- throw :rubygame_quit
71
+ def _handle_event(gosu_id, action) #:nodoc:
72
+ @window.close if gosu_id == @auto_quit
73
+ event_data = nil
74
+
75
+ if gosu_id.nil?
76
+ event_type = :mouse_motion
77
+ callback_key = :mouse_motion
78
+ event_data = [@window.mouse_x, @window.mouse_y]
79
+ elsif gosu_id >= MsRangeBegin && gosu_id <= MsRangeEnd
80
+ event_type = :mouse
81
+ if action == :up
82
+ callback_key = :mouse_up
83
+ else
84
+ callback_key = :mouse_down
85
+ end
86
+ elsif gosu_id >= KbRangeBegin && gosu_id <= KbRangeEnd
87
+ event_type = :keyboard
88
+ if action == :up
89
+ callback_key = :keyboard_up
90
+ else
91
+ callback_key = :keyboard_down
92
+ end
93
+ elsif gosu_id >= GpRangeBegin && gosu_id <= GpRangeEnd
94
+ event_type = :game_pad
95
+ if action == :up
96
+ callback_key = :game_pad_up
97
+ else
98
+ callback_key = :game_pad_down
102
99
  end
103
- when QuitRequested
104
- throw :rubygame_quit
105
100
  end
101
+
102
+ event = {
103
+ :type => event_type,
104
+ :id => gosu_id,
105
+ :action => action,
106
+ :callback_key => event_type,
107
+ :data => event_data
108
+ }
109
+
106
110
  fire :event_received, event
107
111
 
108
112
  # fix for pause bug?
109
113
  @hooks ||= {}
110
114
  @non_id_hooks ||= {}
111
115
 
112
- event_hooks = @hooks[event.class]
113
- id = event.key if event.respond_to? :key
114
-
115
- if event.respond_to? :button
116
- id ||= (MOUSE_BUTTON_LOOKUP[event.button] or event.button)
117
- end
116
+ event_hooks = @hooks[callback_key]
118
117
 
119
- unless id.nil?
120
- event_action_hooks = event_hooks[id] if event_hooks
118
+ unless gosu_id.nil?
119
+ event_action_hooks = event_hooks[gosu_id] if event_hooks
121
120
  if event_action_hooks
122
121
  for callback in event_action_hooks
123
122
  callback.call event
@@ -125,7 +124,7 @@ class InputManager
125
124
  end
126
125
  end
127
126
 
128
- non_id_event_hooks = @non_id_hooks[event.class]
127
+ non_id_event_hooks = @non_id_hooks[callback_key]
129
128
  if non_id_event_hooks
130
129
  for callback in non_id_event_hooks
131
130
  callback.call event
@@ -209,10 +208,10 @@ class InputManager
209
208
 
210
209
  # autohook a boolean to be set to true while a key is pressed
211
210
  def while_key_pressed(key, target, accessor)
212
- _register_hook target, KeyPressed, key do
211
+ _register_hook target, :keyboard_down, key do
213
212
  target.send "#{accessor}=", true
214
213
  end
215
- _register_hook target, KeyReleased, key do
214
+ _register_hook target, :keyboard_up, key do
216
215
  target.send "#{accessor}=", false
217
216
  end
218
217
  end
@@ -1,6 +1,6 @@
1
1
  class CodeStatistics #:nodoc:
2
2
 
3
- TEST_TYPES = %w(Units Functionals Unit\ tests Functional\ tests Integration\ tests)
3
+ TEST_TYPES = %w(Units Functionals Unit\ tests Functional\ tests Integration\ tests Specs)
4
4
 
5
5
  def initialize(*pairs)
6
6
  @pairs = pairs
@@ -1,3 +1,3 @@
1
1
  class Float
2
- INFINITY = 1.0/0.0
2
+ INFINITY = 1.0/0.0
3
3
  end
@@ -0,0 +1,612 @@
1
+ #--
2
+ # Rubygame -- Ruby code and bindings to SDL to facilitate game creation
3
+ # Copyright (C) 2004-2007 John Croisant
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
+ #++
19
+
20
+ #--
21
+ # Table of Contents:
22
+ #
23
+ # class Rect
24
+ # GENERAL:
25
+ # initialize
26
+ # new_from_object
27
+ # to_s
28
+ # to_a, to_ary
29
+ # []
30
+ # ATTRIBUTES:
31
+ # x, y, w, h [<- accessors]
32
+ # width, height, size
33
+ # left, top, right, bottom
34
+ # center, centerx, centery
35
+ # topleft, topright
36
+ # bottomleft, bottomright
37
+ # midleft, midtop, midright, midbottom
38
+ # UTILITY METHODS:
39
+ # clamp, clamp!
40
+ # clip, clip!
41
+ # collide_hash, collide_hash_all
42
+ # collide_array, collide_array_all
43
+ # collide_point?
44
+ # collide_rect?
45
+ # contain?
46
+ # inflate, inflate!
47
+ # move, move!
48
+ # normalize, normalize!
49
+ # union, union!
50
+ # union_all, union_all!
51
+ #
52
+ # class Surface
53
+ # make_rect
54
+ #
55
+ #++
56
+
57
+ #module Rubygame
58
+
59
+ # A Rect is a representation of a rectangle, with four core attributes
60
+ # (x offset, y offset, width, and height) and a variety of functions
61
+ # for manipulating and accessing these attributes.
62
+ #
63
+ # Like all coordinates in Rubygame (and its base library, SDL), x and y
64
+ # offsets are measured from the top-left corner of the screen, with greater
65
+ # y offsets being lower. Thus, specifying the x and y offsets of the Rect
66
+ # is equivalent to setting the location of its top-left corner.
67
+ #
68
+ # In Rubygame, Rects are used for collision detection and describing
69
+ # the area of a Surface to operate on.
70
+ class Rect < Array
71
+
72
+ #--
73
+ # GENERAL
74
+ #++
75
+
76
+ # Create a new Rect, attempting to extract its own information from
77
+ # the given arguments. The arguments must fall into one of these cases:
78
+ #
79
+ # - 4 integers +(x, y, w, h)+.
80
+ # - 1 Rect or Array containing 4 integers +([x, y, w, h])+.
81
+ # - 2 Arrays containing 2 integers each +([x,y], [w,h])+.
82
+ # - 1 object with a +rect+ attribute which is a valid Rect object.
83
+ #
84
+ # All rect core attributes (x,y,w,h) must be integers.
85
+ #
86
+ def initialize(*argv)
87
+ case argv.length
88
+ when 1
89
+ if argv[0].kind_of? Array; super(argv[0])
90
+ elsif argv[0].respond_to? :rect; super(argv[0])
91
+ end
92
+ when 2
93
+ super(argv[0].concat(argv[1]))
94
+ when 4
95
+ super(argv)
96
+ end
97
+ return self
98
+ end
99
+
100
+ # Extract or generate a Rect from the given object, if possible, using the
101
+ # following process:
102
+ #
103
+ # 1. If it's a Rect already, return a duplicate Rect.
104
+ # 2. Elsif it's an Array with at least 4 values, make a Rect from it.
105
+ # 3. Elsif it has a +rect+ attribute., perform (1) and (2) on that.
106
+ # 4. Otherwise, raise TypeError.
107
+ #
108
+ # See also Surface#make_rect()
109
+ def Rect.new_from_object(object)
110
+ case(object)
111
+ when Rect
112
+ return object.dup
113
+ when Array
114
+ if object.length >= 4
115
+ return Rect.new(object)
116
+ else
117
+ raise(ArgumentError,"Array does not have enough indices to be made into a Rect (%d for 4)."%object.length )
118
+ end
119
+ else
120
+ begin
121
+ case(object.rect)
122
+ when Rect
123
+ return object.rect.dup
124
+ when Array
125
+ if object.rect.length >= 4
126
+ return Rect.new(object.rect)
127
+ else
128
+ raise(ArgumentError,"Array does not have enough indices to be made into a Rect (%d for 4)."%object.rect.length )
129
+ end
130
+ end # case object.rect
131
+ rescue NoMethodError # if no rect.rect
132
+ raise(TypeError,"Object must be a Rect or Array [x,y,w,h], or have an attribute called 'rect'. (Got %s instance.)"%object.class)
133
+ end
134
+ end # case object
135
+ end
136
+
137
+
138
+ # Print the Rect in the form "+#<Rect [x,y,w,h]>+"
139
+ def to_s; "#<Rect [%s,%s,%s,%s]>"%self; end
140
+
141
+ # Print the Rect in the form "+#<Rect:id [x,y,w,h]>+"
142
+ def inspect; "#<Rect:#{self.object_id} [%s,%s,%s,%s]>"%self; end
143
+
144
+
145
+ # Returns an SDL::Rect version of this Rect. Float values are
146
+ # rounded to the nearest integer.
147
+ #
148
+ def to_sdl # :nodoc:
149
+ SDL::Rect.new( self.collect{|n| n.round } )
150
+ end
151
+
152
+
153
+ #--
154
+ # ATTRIBUTES
155
+ #++
156
+
157
+ # Returns self.at(0)
158
+ def x; return self.at(0); end
159
+ # Sets self[0] to +val+
160
+ def x=(val); self[0] = val; end
161
+
162
+ alias left x
163
+ alias left= x=;
164
+ alias l x
165
+ alias l= x=;
166
+
167
+ # Returns self.at(1)
168
+ def y; return self.at(1); end
169
+ # Sets self[1] to +val+
170
+ def y=(val); self[1] = val; end
171
+
172
+ alias top y
173
+ alias top= y=;
174
+ alias t y
175
+ alias t= y=;
176
+
177
+ # Returns self.at(2)
178
+ def w; return self.at(2); end
179
+ # Sets self[2] to +val+
180
+ def w=(val); self[2] = val; end
181
+
182
+ alias width w
183
+ alias width= w=;
184
+
185
+ # Returns self.at(3)
186
+ def h; return self.at(3); end
187
+ # Sets self[3] to +val+
188
+ def h=(val); self[3] = val; end
189
+
190
+ alias height h
191
+ alias height= h=;
192
+
193
+ # Return the width and height of the Rect.
194
+ def size; return self[2,2]; end
195
+
196
+ # Set the width and height of the Rect.
197
+ def size=(size)
198
+ raise ArgumentError, "Rect#size= takes an Array of form [width, height]." if size.size != 2
199
+ self[2,2] = size
200
+ size
201
+ end
202
+
203
+ # Return the x coordinate of the right side of the Rect.
204
+ def right; return self.at(0)+self.at(2); end
205
+
206
+ # Set the x coordinate of the right side of the Rect by translating the
207
+ # Rect (adjusting the x offset).
208
+ def right=(r); self[0] = r - self.at(2); return r; end
209
+
210
+ alias r right
211
+ alias r= right=;
212
+
213
+ # Return the y coordinate of the bottom side of the Rect.
214
+ def bottom; return self.at(1)+self.at(3); end
215
+
216
+ # Set the y coordinate of the bottom side of the Rect by translating the
217
+ # Rect (adjusting the y offset).
218
+ def bottom=(b); self[1] = b - self.at(3); return b; end
219
+
220
+ alias b bottom
221
+ alias b= bottom=;
222
+
223
+ # Return the x and y coordinates of the center of the Rect.
224
+ def center; return self.centerx, self.centery; end
225
+
226
+ # Set the x and y coordinates of the center of the Rect by translating the
227
+ # Rect (adjusting the x and y offsets).
228
+ def center=(center)
229
+ raise ArgumentError, "Rect#center= takes an Array of the form [x,y]." if center.size != 2
230
+ self.centerx, self.centery = center
231
+ center
232
+ end
233
+ alias c center
234
+ alias c= center=;
235
+
236
+ # Return the x coordinate of the center of the Rect
237
+ def centerx; return self.at(0)+(self.at(2).div(2)); end
238
+
239
+ # Set the x coordinate of the center of the Rect by translating the
240
+ # Rect (adjusting the x offset).
241
+ def centerx=(x); self[0] = x - (self.at(2).div(2)); return x; end
242
+
243
+ alias cx centerx
244
+ alias cx= centerx=;
245
+
246
+ # Return the y coordinate of the center of the Rect
247
+ def centery; return self.at(1)+(self.at(3).div(2)); end
248
+
249
+ # Set the y coordinate of the center of the Rect by translating the
250
+ # Rect (adjusting the y offset).
251
+ def centery=(y); self[1] = y- (self.at(3).div(2)); return y; end
252
+
253
+ alias cy centery
254
+ alias cy= centery=;
255
+
256
+ # Return the x and y coordinates of the top-left corner of the Rect
257
+ def topleft; return self[0,2].to_a; end
258
+
259
+ # Set the x and y coordinates of the top-left corner of the Rect by
260
+ # translating the Rect (adjusting the x and y offsets).
261
+ def topleft=(topleft)
262
+ raise ArgumentError, "Rect#topright= takes an Array of form [x, y]." if topleft.size != 2
263
+ self[0,2] = topleft
264
+ return topleft
265
+ end
266
+
267
+ alias tl topleft
268
+ alias tl= topleft=;
269
+
270
+ # Return the x and y coordinates of the top-right corner of the Rect
271
+ def topright; return self.right, self.at(1); end
272
+
273
+ # Set the x and y coordinates of the top-right corner of the Rect by
274
+ # translating the Rect (adjusting the x and y offsets).
275
+ def topright=(topright)
276
+ raise ArgumentError, "Rect#topright= takes an Array of form [x, y]." if topright.size != 2
277
+ self.right, self[1] = topright
278
+ return topright
279
+ end
280
+
281
+ alias tr topright
282
+ alias tr= topright=;
283
+
284
+ # Return the x and y coordinates of the bottom-left corner of the Rect
285
+ def bottomleft; return self.at(0), self.bottom; end
286
+
287
+ # Set the x and y coordinates of the bottom-left corner of the Rect by
288
+ # translating the Rect (adjusting the x and y offsets).
289
+ def bottomleft=(bottomleft)
290
+ raise ArgumentError, "Rect#bottomleft= takes an Array of form [x, y]." if bottomleft.size != 2
291
+ self[0], self.bottom = bottomleft
292
+ return bottomleft
293
+ end
294
+
295
+ alias bl bottomleft
296
+ alias bl= bottomleft=;
297
+
298
+ # Return the x and y coordinates of the bottom-right corner of the Rect
299
+ def bottomright; return self.right, self.bottom; end
300
+
301
+ # Set the x and y coordinates of the bottom-right corner of the Rect by
302
+ # translating the Rect (adjusting the x and y offsets).
303
+ def bottomright=(bottomright)
304
+ raise ArgumentError, "Rect#bottomright= takes an Array of form [x, y]." if bottomright.size != 2
305
+ self.right, self.bottom = bottomright
306
+ return bottomright
307
+ end
308
+
309
+ alias br bottomright
310
+ alias br= bottomright=;
311
+
312
+ # Return the x and y coordinates of the midpoint on the left side of the
313
+ # Rect.
314
+ def midleft; return self.at(0), self.centery; end
315
+
316
+ # Set the x and y coordinates of the midpoint on the left side of the Rect
317
+ # by translating the Rect (adjusting the x and y offsets).
318
+ def midleft=(midleft)
319
+ raise ArgumentError, "Rect#midleft= takes an Array of form [x, y]." if midleft.size != 2
320
+ self[0], self.centery = midleft
321
+ return midleft
322
+ end
323
+
324
+ alias ml midleft
325
+ alias ml= midleft=;
326
+
327
+ # Return the x and y coordinates of the midpoint on the left side of the
328
+ # Rect.
329
+ def midtop; return self.centerx, self.at(1); end
330
+
331
+ # Set the x and y coordinates of the midpoint on the top side of the Rect
332
+ # by translating the Rect (adjusting the x and y offsets).
333
+ def midtop=(midtop)
334
+ raise ArgumentError, "Rect#midtop= takes an Array of form [x, y]." if midtop.size != 2
335
+ self.centerx, self[1] = midtop
336
+ return midtop
337
+ end
338
+
339
+ alias mt midtop
340
+ alias mt= midtop=;
341
+
342
+ # Return the x and y coordinates of the midpoint on the left side of the
343
+ # Rect.
344
+ def midright; return self.right, self.centery; end
345
+
346
+ # Set the x and y coordinates of the midpoint on the right side of the Rect
347
+ # by translating the Rect (adjusting the x and y offsets).
348
+ def midright=(midright)
349
+ raise ArgumentError, "Rect#midright= takes an Array of form [x, y]." if midright.size != 2
350
+ self.right, self.centery = midright
351
+ return midright
352
+ end
353
+
354
+ alias mr midright
355
+ alias mr= midright=;
356
+
357
+ # Return the x and y coordinates of the midpoint on the left side of the
358
+ # Rect.
359
+ def midbottom; return self.centerx, self.bottom; end
360
+
361
+ # Set the x and y coordinates of the midpoint on the bottom side of the
362
+ # Rect by translating the Rect (adjusting the x and y offsets).
363
+ def midbottom=(midbottom)
364
+ raise ArgumentError, "Rect#midbottom= takes an Array of form [x, y]." if midbottom.size != 2
365
+ self.centerx, self.bottom = midbottom
366
+ return midbottom
367
+ end
368
+
369
+ alias mb midbottom
370
+ alias mb= midbottom=;
371
+
372
+ #--
373
+ # UTILITY METHODS
374
+ #++
375
+
376
+
377
+ # As #clamp!, but the original caller is not changed.
378
+ def clamp(rect)
379
+ self.dup.clamp!(rect)
380
+ end
381
+
382
+ # Translate the calling Rect to be entirely inside the given Rect. If
383
+ # the caller is too large along either axis to fit in the given rect,
384
+ # it is centered with respect to the given rect, along that axis.
385
+ def clamp!(rect)
386
+ nself = self.normalize
387
+ rect = Rect.new_from_object(rect)
388
+ #If self is inside given, there is no need to move self
389
+ unless rect.contain?(nself)
390
+
391
+ #If self is too wide:
392
+ if nself.at(2) >= rect.at(2)
393
+ self[0] = rect.centerx - nself.at(2).div(2)
394
+ #Else self is not too wide
395
+ else
396
+ #If self is to the left of arg
397
+ if nself.at(0) < rect.at(0)
398
+ self[0] = rect.at(0)
399
+ #If self is to the right of arg
400
+ elsif nself.right > rect.right
401
+ self[0] = rect.right - nself.at(2)
402
+ #Otherwise, leave x alone
403
+ end
404
+ end
405
+
406
+ #If self is too tall:
407
+ if nself.at(3) >= rect.at(3)
408
+ self[1] = rect.centery - nself.at(3).div(2)
409
+ #Else self is not too tall
410
+ else
411
+ #If self is above arg
412
+ if nself.at(1) < rect.at(1)
413
+ self[1] = rect.at(1)
414
+ #If self below arg
415
+ elsif nself.bottom > rect.bottom
416
+ self[1] = rect.bottom - nself.at(3)
417
+ #Otherwise, leave y alone
418
+ end
419
+ end
420
+ end
421
+ return self
422
+ end
423
+
424
+ # As #clip!, but the original caller is not changed.
425
+ def clip(rect)
426
+ self.dup.clip!(rect)
427
+ end
428
+
429
+ # Crop the calling Rect to be entirely inside the given Rect. If the
430
+ # caller does not intersect the given Rect at all, its width and height
431
+ # are set to zero, but its x and y offsets are not changed.
432
+ #
433
+ # As a side effect, the Rect is normalized.
434
+ def clip!(rect)
435
+ nself = self.normalize
436
+ other = Rect.new_from_object(rect).normalize!
437
+ if self.collide_rect?(other)
438
+ self[0] = [nself.at(0), other.at(0)].max
439
+ self[1] = [nself.at(1), other.at(1)].max
440
+ self[2] = [nself.right, other.right].min - self.at(0)
441
+ self[3] = [nself.bottom, other.bottom].min - self.at(1)
442
+ else #if they do not intersect at all:
443
+ self[0], self[1] = nself.topleft
444
+ self[2], self[3] = 0, 0
445
+ end
446
+ return self
447
+ end
448
+
449
+ # Iterate through all key/value pairs in the given hash table, and
450
+ # return the first pair whose value is a Rect that collides with the
451
+ # caller.
452
+ #
453
+ # Because a hash table is unordered, you should not expect any
454
+ # particular Rect to be returned first.
455
+ def collide_hash(hash_rects)
456
+ hash_rects.each { |key,value|
457
+ if value.collide_rect?+(self); return [key,value]; end
458
+ }
459
+ return nil
460
+ end
461
+
462
+ # Iterate through all key/value pairs in the given hash table, and
463
+ # return an Array of every pair whose value is a Rect that collides
464
+ # the caller.
465
+ #
466
+ # Because a hash table is unordered, you should not expect the returned
467
+ # pairs to be in any particular order.
468
+ def collide_hash_all(hash_rects)
469
+ hash_rects.select { |key,value|
470
+ value.collide_rect?+(self)
471
+ }
472
+ end
473
+
474
+ # Iterate through all elements in the given Array, and return
475
+ # the *index* of the first element which is a Rect that collides with
476
+ # the caller.
477
+ def collide_array(array_rects)
478
+ for i in (0...(array_rects.length))
479
+ if array_rects[i].collide_rect?(self)
480
+ return i
481
+ end
482
+ end
483
+ return nil
484
+ end
485
+
486
+ # Iterate through all elements in the given Array, and return
487
+ # an Array containing the *indices* of every element that is a Rect
488
+ # that collides with the caller.
489
+ def collide_array_all(array_rects)
490
+ indexes = []
491
+ for i in (0...(array_rects.length))
492
+ if array_rects[i].collide_rect?(self)
493
+ indexes += [i]
494
+ end
495
+ end
496
+ return indexes
497
+ end
498
+
499
+ # True if the point is inside (including on the border) of the caller.
500
+ # If you have Array of coordinates, you can use collide_point?(*coords).
501
+ def collide_point?(x,y)
502
+ nself = normalize()
503
+ x.between?(nself.left,nself.right) && y.between?(nself.top,nself.bottom)
504
+ end
505
+
506
+ # True if the caller and the given Rect overlap (or touch) at all.
507
+ def collide_rect?(rect)
508
+ nself = self.normalize
509
+ rect = Rect.new_from_object(rect).normalize!
510
+ return ((nself.l >= rect.l && nself.l <= rect.r) or (rect.l >= nself.l && rect.l <= nself.r)) &&
511
+ ((nself.t >= rect.t && nself.t <= rect.b) or (rect.t >= nself.t && rect.t <= nself.b))
512
+ end
513
+
514
+ # True if the given Rect is totally within the caller. Borders may
515
+ # overlap.
516
+ def contain?(rect)
517
+ nself = self.normalize
518
+ rect = Rect.new_from_object(rect).normalize!
519
+ return (nself.left <= rect.left and rect.right <= nself.right and
520
+ nself.top <= rect.top and rect.bottom <= nself.bottom)
521
+ end
522
+
523
+ # As #inflate!, but the original caller is not changed.
524
+ def inflate(x,y)
525
+ return self.class.new(self.at(0) - x.div(2),
526
+ self.at(1) - y.div(2),
527
+ self.at(2) + x,
528
+ self.at(3) + y)
529
+ end
530
+
531
+ # Increase the Rect's size is the x and y directions, while keeping the
532
+ # same center point. For best results, expand by an even number.
533
+ # X and y inflation can be given as an Array or as separate values.
534
+ def inflate!(x,y)
535
+ self[0] -= x.div(2)
536
+ self[1] -= y.div(2)
537
+ self[2] += x
538
+ self[3] += y
539
+ return self
540
+ end
541
+
542
+ # As #move!, but the original caller is not changed.
543
+ def move(x,y)
544
+ self.dup.move!(x,y)
545
+ end
546
+
547
+ # Translate the Rect by the given amounts in the x and y directions.
548
+ # Positive values are rightward for x and downward for y.
549
+ # X and y movement can be given as an Array or as separate values.
550
+ def move!(x,y)
551
+ self[0]+=x; self[1]+=y
552
+ return self
553
+ end
554
+
555
+ # As #normalize!, but the original caller is not changed.
556
+ def normalize
557
+ self.dup.normalize!()
558
+ end
559
+
560
+ # Fix Rects that have negative width or height, without changing the
561
+ # area it represents. Has no effect on Rects with non-negative width
562
+ # and height. Some Rect methods will automatically normalize the Rect.
563
+ def normalize!
564
+ if self.at(2) < 0
565
+ self[0], self[2] = self.at(0)+self.at(2), -self.at(2)
566
+ end
567
+ if self.at(3) < 0
568
+ self[1], self[3] = self.at(1)+self.at(3), -self.at(3)
569
+ end
570
+ self
571
+ end
572
+
573
+ # As #union!, but the original caller is not changed.
574
+ def union(rect)
575
+ self.dup.union!(rect)
576
+ end
577
+
578
+ # Expand the caller to also cover the given Rect. The Rect is still a
579
+ # rectangle, so it may also cover areas that neither of the original
580
+ # Rects did, for example areas between the two Rects.
581
+ def union!(rect)
582
+ self.normalize!
583
+ rleft, rtop = self.topleft
584
+ rright, rbottom = self.bottomright
585
+ r2 = Rect.new_from_object(rect).normalize!
586
+
587
+ rleft = [rleft, r2.left].min
588
+ rtop = [rtop, r2.top].min
589
+ rright = [rright, r2.right].max
590
+ rbottom = [rbottom, r2.bottom].max
591
+
592
+ self[0,4] = rleft, rtop, rright - rleft, rbottom - rtop
593
+ return self
594
+ end
595
+
596
+ # As #union_all!, but the original caller is not changed.
597
+ def union_all(array_rects)
598
+ self.dup.union_all!(array_rects)
599
+ end
600
+
601
+ # Expand the caller to cover all of the given Rects. See also #union!
602
+ def union_all!(array_rects)
603
+ array_rects.each do |r|
604
+ self.union!(r)
605
+ end
606
+ return self
607
+ end
608
+
609
+
610
+ end # class Rect
611
+
612
+ #end # module Rubygame