gamebox 0.1.1 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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