minigl 1.3.7 → 1.3.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,47 +1,45 @@
1
1
  require_relative 'global'
2
2
 
3
3
  module AGL
4
- # This class provides easy control of a tile map, i.e., a map consisting of
5
- # a grid of equally sized tiles. It also provides viewport control, through
6
- # its camera property and methods.
7
- class Map
8
- # :nodoc:
9
- Sqrt2Div2 = Math.sqrt(2) / 2
10
- # :nodoc:
11
- MinusPiDiv4 = -Math::PI / 4
4
+ # This class provides easy control of a tile map, i.e., a map consisting of
5
+ # a grid of equally sized tiles. It also provides viewport control, through
6
+ # its camera property and methods.
7
+ class Map
8
+ Sqrt2Div2 = Math.sqrt(2) / 2 # :nodoc:
9
+ MinusPiDiv4 = -Math::PI / 4 # :nodoc:
12
10
 
13
- # A Vector where x is the tile width and y is the tile height.
14
- attr_reader :tile_size
11
+ # A Vector where x is the tile width and y is the tile height.
12
+ attr_reader :tile_size
15
13
 
16
- # A Vector where x is the horizontal tile count and y the vertical count.
17
- attr_reader :size
14
+ # A Vector where x is the horizontal tile count and y the vertical count.
15
+ attr_reader :size
18
16
 
19
- # A Rectangle representing the region of the map that is currently
20
- # visible.
21
- attr_reader :cam
17
+ # A Rectangle representing the region of the map that is currently
18
+ # visible.
19
+ attr_reader :cam
22
20
 
23
- # Creates a new map.
24
- #
25
- # Parameters:
26
- # [t_w] The width of the tiles.
27
- # [t_h] The height of the tiles.
28
- # [t_x_count] The horizontal count of tiles in the map.
29
- # [t_y_count] The vertical count of tiles in the map.
30
- # [scr_w] Width of the viewport for the map.
31
- # [scr_h] Height of the viewport for the map.
21
+ # Creates a new map.
22
+ #
23
+ # Parameters:
24
+ # [t_w] The width of the tiles.
25
+ # [t_h] The height of the tiles.
26
+ # [t_x_count] The horizontal count of tiles in the map.
27
+ # [t_y_count] The vertical count of tiles in the map.
28
+ # [scr_w] Width of the viewport for the map.
29
+ # [scr_h] Height of the viewport for the map.
32
30
  # [isometric] Whether to use a isometric map. By default, an ortogonal map
33
31
  # is used.
34
- # [limit_cam] Whether the camera should respect the bounds of the map
35
- # (i.e., when given coordinates that would imply regions
36
- # outside the map to appear in the screen, the camera would
37
- # move to the nearest position where only the map shows up
38
- # in the screen).
39
- def initialize t_w, t_h, t_x_count, t_y_count, scr_w = 800, scr_h = 600, isometric = false, limit_cam = true
40
- @tile_size = Vector.new t_w, t_h
41
- @size = Vector.new t_x_count, t_y_count
42
- @cam = Rectangle.new 0, 0, scr_w, scr_h
43
- @limit_cam = limit_cam
44
- @isometric = isometric
32
+ # [limit_cam] Whether the camera should respect the bounds of the map
33
+ # (i.e., when given coordinates that would imply regions
34
+ # outside the map to appear in the screen, the camera would
35
+ # move to the nearest position where only the map shows up
36
+ # in the screen).
37
+ def initialize t_w, t_h, t_x_count, t_y_count, scr_w = 800, scr_h = 600, isometric = false, limit_cam = true
38
+ @tile_size = Vector.new t_w, t_h
39
+ @size = Vector.new t_x_count, t_y_count
40
+ @cam = Rectangle.new 0, 0, scr_w, scr_h
41
+ @limit_cam = limit_cam
42
+ @isometric = isometric
45
43
  if isometric
46
44
  initialize_isometric
47
45
  elsif limit_cam
@@ -49,116 +47,115 @@ module AGL
49
47
  @max_y = t_y_count * t_h - scr_h
50
48
  end
51
49
  set_camera 0, 0
52
- end
50
+ end
53
51
 
54
- # Returns a Vector with the total size of the map, in pixels (x for the
55
- # width and y for the height).
56
- def get_absolute_size
57
- return Vector.new(@tile_size.x * @size.x, @tile_size.y * @size.y) unless @isometric
58
- avg = (@size.x + @size.y) * 0.5
59
- Vector.new (avg * @tile_size.x).to_i, (avg * @tile_size.y).to_i
60
- end
52
+ # Returns a Vector with the total size of the map, in pixels (x for the
53
+ # width and y for the height).
54
+ def get_absolute_size
55
+ return Vector.new(@tile_size.x * @size.x, @tile_size.y * @size.y) unless @isometric
56
+ avg = (@size.x + @size.y) * 0.5
57
+ Vector.new (avg * @tile_size.x).to_i, (avg * @tile_size.y).to_i
58
+ end
61
59
 
62
- # Returns a Vector with the coordinates of the center of the map.
63
- def get_center
64
- abs_size = get_absolute_size
65
- Vector.new(abs_size.x * 0.5, abs_size.y * 0.5)
66
- end
60
+ # Returns a Vector with the coordinates of the center of the map.
61
+ def get_center
62
+ abs_size = get_absolute_size
63
+ Vector.new(abs_size.x * 0.5, abs_size.y * 0.5)
64
+ end
67
65
 
68
- # Returns the position in the screen corresponding to the given tile
69
- # indices.
70
- #
71
- # Parameters:
72
- # [map_x] The index of the tile in the horizontal direction. It must be in
73
- # the interval <code>0..t_x_count</code>.
74
- # [map_y] The index of the tile in the vertical direction. It must be in
75
- # the interval <code>0..t_y_count</code>.
76
- def get_screen_pos map_x, map_y
77
- return Vector.new(map_x * @tile_size.x - @cam.x, map_y * @tile_size.y - @cam.y) unless @isometric
78
- Vector.new ((map_x - map_y - 1) * @tile_size.x * 0.5) - @cam.x + @x_offset,
79
- ((map_x + map_y) * @tile_size.y * 0.5) - @cam.y
80
- end
66
+ # Returns the position in the screen corresponding to the given tile
67
+ # indices.
68
+ #
69
+ # Parameters:
70
+ # [map_x] The index of the tile in the horizontal direction. It must be in
71
+ # the interval <code>0..t_x_count</code>.
72
+ # [map_y] The index of the tile in the vertical direction. It must be in
73
+ # the interval <code>0..t_y_count</code>.
74
+ def get_screen_pos map_x, map_y
75
+ return Vector.new(map_x * @tile_size.x - @cam.x, map_y * @tile_size.y - @cam.y) unless @isometric
76
+ Vector.new ((map_x - map_y - 1) * @tile_size.x * 0.5) - @cam.x + @x_offset,
77
+ ((map_x + map_y) * @tile_size.y * 0.5) - @cam.y
78
+ end
81
79
 
82
- # Returns the tile in the map that corresponds to the given position in
83
- # the screen, as a Vector, where x is the horizontal index and y the
84
- # vertical index.
85
- #
86
- # Parameters:
87
- # [scr_x] The x-coordinate in the screen.
88
- # [scr_y] The y-coordinate in the screen.
89
- def get_map_pos scr_x, scr_y
90
- return Vector.new((scr_x + @cam.x) / @tile_size.x, (scr_y + @cam.y) / @tile_size.y) unless @isometric
80
+ # Returns the tile in the map that corresponds to the given position in
81
+ # the screen, as a Vector, where x is the horizontal index and y the
82
+ # vertical index.
83
+ #
84
+ # Parameters:
85
+ # [scr_x] The x-coordinate in the screen.
86
+ # [scr_y] The y-coordinate in the screen.
87
+ def get_map_pos scr_x, scr_y
88
+ return Vector.new((scr_x + @cam.x) / @tile_size.x, (scr_y + @cam.y) / @tile_size.y) unless @isometric
91
89
 
92
90
  # Obtém a posição transformada para as coordenadas isométricas
93
- v = get_isometric_position scr_x, scr_y
94
-
95
- # Depois divide pelo tamanho do quadrado para achar a posição da matriz
96
- Vector.new((v.x * @inverse_square_size).to_i, (v.y * @inverse_square_size).to_i)
97
- end
91
+ v = get_isometric_position scr_x, scr_y
98
92
 
99
- # Verifies whether a tile is inside the map.
100
- #
101
- # Parameters:
102
- # [v] A Vector representing the tile, with x as the horizontal index and
103
- # y as the vertical index.
104
- def is_in_map v
105
- v.x >= 0 && v.y >= 0 && v.x < @size.x && v.y < @size.y
106
- end
93
+ # Depois divide pelo tamanho do quadrado para achar a posição da matriz
94
+ Vector.new((v.x * @inverse_square_size).to_i, (v.y * @inverse_square_size).to_i)
95
+ end
107
96
 
108
- # Sets the top left corner of the viewport to the given position of the
109
- # map. Note that this is not the position in the screen.
110
- #
111
- # Parameters:
112
- # [cam_x] The x-coordinate inside the map, in pixels (not a tile index).
113
- # [cam_y] The y-coordinate inside the map, in pixels (not a tile index).
114
- def set_camera cam_x, cam_y
115
- @cam.x = cam_x
116
- @cam.y = cam_y
117
- set_bounds
118
- end
97
+ # Verifies whether a tile is inside the map.
98
+ #
99
+ # Parameters:
100
+ # [v] A Vector representing the tile, with x as the horizontal index and
101
+ # y as the vertical index.
102
+ def is_in_map v
103
+ v.x >= 0 && v.y >= 0 && v.x < @size.x && v.y < @size.y
104
+ end
119
105
 
120
- # Moves the viewport by the given amount of pixels.
121
- #
122
- # Parameters:
123
- # [x] The amount of pixels to move horizontally. Negative values will
124
- # cause the camera to move to the left.
125
- # [y] The amount of pixels to move vertically. Negative values will cause
126
- # the camera to move up.
127
- def move_camera x, y
128
- @cam.x += x
129
- @cam.y += y
130
- set_bounds
131
- end
106
+ # Sets the top left corner of the viewport to the given position of the
107
+ # map. Note that this is not the position in the screen.
108
+ #
109
+ # Parameters:
110
+ # [cam_x] The x-coordinate inside the map, in pixels (not a tile index).
111
+ # [cam_y] The y-coordinate inside the map, in pixels (not a tile index).
112
+ def set_camera cam_x, cam_y
113
+ @cam.x = cam_x
114
+ @cam.y = cam_y
115
+ set_bounds
116
+ end
132
117
 
133
- # Iterates through the currently visible tiles, providing the horizontal
134
- # tile index, the vertical tile index, the x-coordinate (in pixels) and
135
- # the y-coordinate (in pixels), of each tile, in that order, to a given
136
- # block of code.
137
- #
138
- # Example:
139
- #
140
- # map.foreach do |i, j, x, y|
141
- # draw_tile tiles[i][j], x, y
142
- # end
143
- def foreach
144
- for j in @min_vis_y..@max_vis_y
145
- for i in @min_vis_x..@max_vis_x
146
- pos = get_screen_pos i, j
147
- yield i, j, pos.x, pos.y
148
- end
149
- end
150
- end
118
+ # Moves the viewport by the given amount of pixels.
119
+ #
120
+ # Parameters:
121
+ # [x] The amount of pixels to move horizontally. Negative values will
122
+ # cause the camera to move to the left.
123
+ # [y] The amount of pixels to move vertically. Negative values will cause
124
+ # the camera to move up.
125
+ def move_camera x, y
126
+ @cam.x += x
127
+ @cam.y += y
128
+ set_bounds
129
+ end
151
130
 
152
- private
131
+ # Iterates through the currently visible tiles, providing the horizontal
132
+ # tile index, the vertical tile index, the x-coordinate (in pixels) and
133
+ # the y-coordinate (in pixels), of each tile, in that order, to a given
134
+ # block of code.
135
+ #
136
+ # Example:
137
+ #
138
+ # map.foreach do |i, j, x, y|
139
+ # draw_tile tiles[i][j], x, y
140
+ # end
141
+ def foreach
142
+ for j in @min_vis_y..@max_vis_y
143
+ for i in @min_vis_x..@max_vis_x
144
+ pos = get_screen_pos i, j
145
+ yield i, j, pos.x, pos.y
146
+ end
147
+ end
148
+ end
153
149
 
154
- def set_bounds
155
- if @isometric
156
- v1 = get_isometric_position(0, 0)
157
- v2 = get_isometric_position(@cam.w - 1, 0)
158
- v3 = get_isometric_position(@cam.w - 1, @cam.h - 1)
159
- v4 = get_isometric_position(0, @cam.h - 1)
150
+ private
160
151
 
161
- if @limit_cam
152
+ def set_bounds
153
+ if @limit_cam
154
+ if @isometric
155
+ v1 = get_isometric_position(0, 0)
156
+ v2 = get_isometric_position(@cam.w - 1, 0)
157
+ v3 = get_isometric_position(@cam.w - 1, @cam.h - 1)
158
+ v4 = get_isometric_position(0, @cam.h - 1)
162
159
  if v1.x < -@max_offset
163
160
  offset = -(v1.x + @max_offset)
164
161
  @cam.x += offset * Sqrt2Div2
@@ -183,39 +180,40 @@ module AGL
183
180
  @cam.y -= offset * Sqrt2Div2 / @tile_ratio
184
181
  v4.y = @iso_abs_size.y + @max_offset
185
182
  end
186
- end
187
-
188
- @min_vis_x = get_map_pos(0, 0).x
189
- @min_vis_y = get_map_pos(@cam.w - 1, 0).y
190
- @max_vis_x = get_map_pos(@cam.w - 1, @cam.h - 1).x
191
- @max_vis_y = get_map_pos(0, @cam.h - 1).y
192
- else
193
- if @limit_cam
183
+ else
194
184
  @cam.x = 0 if @cam.x < 0
195
185
  @cam.x = @max_x if @cam.x > @max_x
196
186
  @cam.y = 0 if @cam.y < 0
197
187
  @cam.y = @max_y if @cam.y > @max_y
198
188
  end
199
- @min_vis_x = @cam.x / @tile_size.x
200
- @min_vis_y = @cam.y / @tile_size.y
201
- @max_vis_x = (@cam.x + @cam.w - 1) / @tile_size.x
202
- @max_vis_y = (@cam.y + @cam.h - 1) / @tile_size.y
203
- end
189
+ end
190
+
204
191
  @cam.x = @cam.x.round
205
192
  @cam.y = @cam.y.round
193
+ if @isometric
194
+ @min_vis_x = get_map_pos(0, 0).x
195
+ @min_vis_y = get_map_pos(@cam.w - 1, 0).y
196
+ @max_vis_x = get_map_pos(@cam.w - 1, @cam.h - 1).x
197
+ @max_vis_y = get_map_pos(0, @cam.h - 1).y
198
+ else
199
+ @min_vis_x = @cam.x / @tile_size.x
200
+ @min_vis_y = @cam.y / @tile_size.y
201
+ @max_vis_x = (@cam.x + @cam.w - 1) / @tile_size.x
202
+ @max_vis_y = (@cam.y + @cam.h - 1) / @tile_size.y
203
+ end
206
204
 
207
- if @min_vis_y < 0; @min_vis_y = 0
208
- elsif @min_vis_y > @size.y - 1; @min_vis_y = @size.y - 1; end
205
+ if @min_vis_y < 0; @min_vis_y = 0
206
+ elsif @min_vis_y > @size.y - 1; @min_vis_y = @size.y - 1; end
209
207
 
210
- if @max_vis_y < 0; @max_vis_y = 0
211
- elsif @max_vis_y > @size.y - 1; @max_vis_y = @size.y - 1; end
208
+ if @max_vis_y < 0; @max_vis_y = 0
209
+ elsif @max_vis_y > @size.y - 1; @max_vis_y = @size.y - 1; end
212
210
 
213
- if @min_vis_x < 0; @min_vis_x = 0
214
- elsif @min_vis_x > @size.x - 1; @min_vis_x = @size.x - 1; end
211
+ if @min_vis_x < 0; @min_vis_x = 0
212
+ elsif @min_vis_x > @size.x - 1; @min_vis_x = @size.x - 1; end
215
213
 
216
- if @max_vis_x < 0; @max_vis_x = 0
217
- elsif @max_vis_x > @size.x - 1; @max_vis_x = @size.x - 1; end
218
- end
214
+ if @max_vis_x < 0; @max_vis_x = 0
215
+ elsif @max_vis_x > @size.x - 1; @max_vis_x = @size.x - 1; end
216
+ end
219
217
 
220
218
  def initialize_isometric
221
219
  @x_offset = (@size.y * 0.5 * @tile_size.x).round
@@ -254,5 +252,5 @@ module AGL
254
252
  position.x -= @isometric_offset_x; position.y -= @isometric_offset_y
255
253
  position
256
254
  end
257
- end
255
+ end
258
256
  end
@@ -1,471 +1,471 @@
1
1
  require_relative 'global'
2
2
 
3
3
  module AGL
4
- # Represents an object with a rectangular bounding box and the +passable+
5
- # property. It is the simplest structure that can be passed as an element of
6
- # the +obst+ array parameter of the +move+ method.
7
- class Block
8
- # The x-coordinate of the top left corner of the bounding box.
9
- attr_reader :x
10
-
11
- # The y-coordinate of the top left corner of the bounding box.
12
- attr_reader :y
13
-
14
- # The width of the bounding box.
15
- attr_reader :w
16
-
17
- # The height of the bounding box.
18
- attr_reader :h
19
-
20
- # Whether a moving object can pass through this block when coming from
21
- # below. This is a common feature of platforms in platform games.
22
- attr_reader :passable
23
-
24
- # Creates a new block.
25
- #
26
- # Parameters:
27
- # [x] The x-coordinate of the top left corner of the bounding box.
28
- # [y] The y-coordinate of the top left corner of the bounding box.
29
- # [w] The width of the bounding box.
30
- # [h] The height of the bounding box.
31
- # [passable] Whether a moving object can pass through this block when
32
- # coming from below. This is a common feature of platforms in platform
33
- # games.
34
- def initialize x, y, w, h, passable
35
- @x = x; @y = y; @w = w; @h = h
36
- @passable = passable
37
- end
38
-
39
- # Returns the bounding box of this block as a Rectangle.
40
- def bounds
41
- Rectangle.new @x, @y, @w, @h
42
- end
43
- end
44
-
45
- # Represents a ramp, i.e., an inclined structure which allows walking over
46
- # it while automatically going up or down. It can be imagined as a right
47
- # triangle, with a side parallel to the x axis and another one parallel to
48
- # the y axis. You must provide instances of this class (or derived classes)
49
- # to the +ramps+ array parameter of the +move+ method.
50
- class Ramp
51
- # The x-coordinate of the top left corner of a rectangle that completely
52
- # (and precisely) encloses the ramp (thought of as a right triangle).
53
- attr_reader :x
54
-
55
- # The y-coordinate of the top left corner of the rectangle described in
56
- # the +x+ attribute.
57
- attr_reader :y
58
-
59
- # The width of the ramp.
60
- attr_reader :w
61
-
62
- # The height of the ramp.
63
- attr_reader :h
64
-
65
- # Whether the height of the ramp increases from left to right (decreases
66
- # from left to right when +false+).
67
- attr_reader :left
68
-
69
- # Creates a new ramp.
70
- #
71
- # Parameters:
72
- # [x] The x-coordinate of the top left corner of a rectangle that
73
- # completely (and precisely) encloses the ramp (thought of as a right
74
- # triangle).
75
- # [y] The y-coordinate of the top left corner of the rectangle described
76
- # above.
77
- # [w] The width of the ramp (which corresponds to the width of the
78
- # rectangle described above).
79
- # [h] The height of the ramp (which corresponds to the height of the
80
- # rectangle described above, and to the difference between the lowest
81
- # point of the ramp, where it usually meets the floor, and the
82
- # highest).
83
- # [left] Whether the height of the ramp increases from left to right. Use
84
- # +false+ for a ramp that goes down from left to right.
85
- def initialize x, y, w, h, left
86
- @x = x
87
- @y = y
88
- @w = w
89
- @h = h
90
- @left = left
91
- end
92
-
93
- # Checks if an object is in contact with this ramp (standing over it).
94
- #
95
- # Parameters:
96
- # [obj] The object to check contact with. It must have the +x+, +y+, +w+
97
- # and +h+ accessible attributes determining its bounding box.
98
- def contact? obj
99
- obj.x.round(6) == get_x(obj).round(6) && obj.y.round(6) == get_y(obj).round(6)
100
- end
101
-
102
- # Checks if an object is intersecting this ramp (inside the corresponding
103
- # right triangle and at the floor level or above).
104
- #
105
- # Parameters:
106
- # [obj] The object to check intersection with. It must have the +x+, +y+,
107
- # +w+ and +h+ accessible attributes determining its bounding box.
108
- def intersects obj
109
- obj.x + obj.w > @x && obj.x < @x + @w && obj.y > get_y(obj) && obj.y <= @y + @h - obj.h
110
- end
111
-
112
- # :nodoc:
113
- def can_collide? obj
114
- @can_collide = (obj.speed.y >= 0 and not intersects(obj))
115
- end
116
-
117
- def check_intersection obj
118
- if @can_collide and intersects obj
119
- obj.y = get_y obj
120
- obj.speed.y = 0
121
- # a = @w / @h
122
- # x = get_x(obj)
123
- # y = get_y(obj)
124
- # w = obj.x - x
125
- # h = obj.y - y
126
- # dx = w * h / (w * a + h)
127
- # dy = dx * a
128
- #
129
- # obj.x -= dx
130
- # obj.y -= dy
131
- # obj.speed.x *= (@w / (@w + @h))
132
- # obj.speed.y = 0
133
- end
134
- end
135
-
136
- def get_x obj
137
- return @x + (1.0 * (@y + @h - obj.y - obj.h) * @w / @h) - obj.w if @left
138
- @x + (1.0 * (obj.y + obj.h - @y) * @w / @h)
139
- end
140
-
141
- def get_y obj
142
- return @y - obj.h if @left && obj.x + obj.w > @x + @w
143
- return @y + (1.0 * (@x + @w - obj.x - obj.w) * @h / @w) - obj.h if @left
144
- return @y - obj.h if obj.x < @x
145
- @y + (1.0 * (obj.x - @x) * @h / @w) - obj.h
146
- end
147
- end
148
-
149
- # This module provides objects with physical properties and methods for
150
- # moving. It allows moving with or without collision checking (based on
151
- # rectangular bounding boxes), including a method to behave as an elevator,
152
- # affecting other objects' positions as it moves.
153
- module Movement
154
- # The mass of the object, in arbitrary units. The default value for
155
- # GameObject instances, for example, is 1. The larger the mass (i.e., the
156
- # heavier the object), the more intense the forces applied to the object
157
- # have to be in order to move it.
158
- attr_reader :mass
159
-
160
- # A Vector with the current speed of the object (x: horizontal component,
161
- # y: vertical component).
162
- attr_reader :speed
163
-
164
- # Width of the bounding box.
165
- attr_reader :w
166
-
167
- # Height of the bounding box.
168
- attr_reader :h
169
-
170
- # Whether a moving object can pass through this block when coming from
171
- # below. This is a common feature of platforms in platform games.
172
- attr_reader :passable
173
-
174
- # The object that is making contact with this from above. If there's no
175
- # contact, returns +nil+.
176
- attr_reader :top
177
-
178
- # The object that is making contact with this from below. If there's no
179
- # contact, returns +nil+.
180
- attr_reader :bottom
181
-
182
- # The object that is making contact with this from the left. If there's no
183
- # contact, returns +nil+.
184
- attr_reader :left
185
-
186
- # The object that is making contact with this from the right. If there's
187
- # no contact, returns +nil+.
188
- attr_reader :right
189
-
190
- # The x-coordinate of the top left corner of the bounding box.
191
- attr_accessor :x
192
-
193
- # The y-coordinate of the top left corner of the bounding box.
194
- attr_accessor :y
195
-
196
- # A Vector with the horizontal and vertical components of a force that
197
- # be applied in the next time +move+ is called.
198
- attr_accessor :stored_forces
199
-
200
- # Returns the bounding box as a Rectangle.
201
- def bounds
202
- Rectangle.new @x, @y, @w, @h
203
- end
204
-
205
- # Moves this object, based on the forces being applied to it, and
206
- # performing collision checking.
207
- #
208
- # Parameters:
209
- # [forces] A Vector where x is the horizontal component of the resulting
210
- # force and y is the vertical component.
211
- # [obst] An array of obstacles to be considered in the collision checking.
212
- # Obstacles must be instances of Block (or derived classes), or
213
- # objects that <code>include Movement</code>.
214
- # [ramps] An array of ramps to be considered in the collision checking.
215
- # Ramps must be instances of Ramp (or derived classes).
216
- def move forces, obst, ramps
217
- forces.x += Game.gravity.x; forces.y += Game.gravity.y
218
- forces.x += @stored_forces.x; forces.y += @stored_forces.y
219
- @stored_forces.x = @stored_forces.y = 0
220
-
221
- # check_contact obst, ramps
222
- forces.x = 0 if (forces.x < 0 and @left) or (forces.x > 0 and @right)
223
- forces.y = 0 if (forces.y < 0 and @top) or (forces.y > 0 and @bottom)
224
-
225
- @speed.x += forces.x / @mass; @speed.y += forces.y / @mass
226
- @speed.x = 0 if @speed.x.abs < @min_speed.x
227
- @speed.y = 0 if @speed.y.abs < @min_speed.y
228
- @speed.x = (@speed.x <=> 0) * @max_speed.x if @speed.x.abs > @max_speed.x
229
- @speed.y = (@speed.y <=> 0) * @max_speed.y if @speed.y.abs > @max_speed.y
230
-
231
- ramps.each do |r|
232
- r.can_collide? self
233
- end
234
-
235
- x = @speed.x < 0 ? @x + @speed.x : @x
236
- y = @speed.y < 0 ? @y + @speed.y : @y
237
- w = @w + (@speed.x < 0 ? -@speed.x : @speed.x)
238
- h = @h + (@speed.y < 0 ? -@speed.y : @speed.y)
239
- move_bounds = Rectangle.new x, y, w, h
240
- coll_list = []
241
- obst.each do |o|
242
- coll_list << o if move_bounds.intersects o.bounds
243
- end
244
-
245
- if coll_list.length > 0
246
- up = @speed.y < 0; rt = @speed.x > 0; dn = @speed.y > 0; lf = @speed.x < 0
247
- if @speed.x == 0 || @speed.y == 0
248
- # Ortogonal
249
- if rt; x_lim = find_right_limit coll_list
250
- elsif lf; x_lim = find_left_limit coll_list
251
- elsif dn; y_lim = find_down_limit coll_list
252
- elsif up; y_lim = find_up_limit coll_list
253
- end
254
- if rt && @x + @w + @speed.x > x_lim; @x = x_lim - @w; @speed.x = 0
255
- elsif lf && @x + @speed.x < x_lim; @x = x_lim; @speed.x = 0
256
- elsif dn && @y + @h + @speed.y > y_lim; @y = y_lim - @h; @speed.y = 0
257
- elsif up && @y + @speed.y < y_lim; @y = y_lim; @speed.y = 0
258
- end
259
- else
260
- # Diagonal
261
- x_aim = @x + @speed.x + (rt ? @w : 0); x_lim_def = x_aim
262
- y_aim = @y + @speed.y + (dn ? @h : 0); y_lim_def = y_aim
263
- coll_list.each do |c|
264
- if c.passable; x_lim = x_aim
265
- elsif rt; x_lim = c.x
266
- else; x_lim = c.x + c.w
267
- end
268
- if dn; y_lim = c.y
269
- elsif c.passable; y_lim = y_aim
270
- else; y_lim = c.y + c.h
271
- end
272
-
273
- if c.passable
274
- y_lim_def = y_lim if dn && @y + @h <= y_lim && y_lim < y_lim_def
275
- elsif (rt && @x + @w > x_lim) || (lf && @x < x_lim)
276
- # Can't limit by x, will limit by y
277
- y_lim_def = y_lim if (dn && y_lim < y_lim_def) || (up && y_lim > y_lim_def)
278
- elsif (dn && @y + @h > y_lim) || (up && @y < y_lim)
279
- # Can't limit by y, will limit by x
280
- x_lim_def = x_lim if (rt && x_lim < x_lim_def) || (lf && x_lim > x_lim_def)
281
- else
282
- xTime = 1.0 * (x_lim - @x - (@speed.x < 0 ? 0 : @w)) / @speed.x
283
- yTime = 1.0 * (y_lim - @y - (@speed.y < 0 ? 0 : @h)) / @speed.y
284
- if xTime > yTime
285
- # Will limit by x
286
- x_lim_def = x_lim if (rt && x_lim < x_lim_def) || (lf && x_lim > x_lim_def)
287
- elsif (dn && y_lim < y_lim_def) || (up && y_lim > y_lim_def)
288
- y_lim_def = y_lim
289
- end
290
- end
291
- end
292
- if x_lim_def != x_aim
293
- @speed.x = 0
294
- if lf; @x = x_lim_def
295
- else; @x = x_lim_def - @w
296
- end
297
- end
298
- if y_lim_def != y_aim
299
- @speed.y = 0
300
- if up; @y = y_lim_def
301
- else; @y = y_lim_def - @h
302
- end
303
- end
304
- end
305
- end
306
- @x += @speed.x
307
- @y += @speed.y
308
-
309
- ramps.each do |r|
310
- r.check_intersection self
311
- end
312
- check_contact obst, ramps
313
- end
314
-
315
- # Moves this object as an elevator (i.e., potentially carrying other
316
- # objects) towards a given point.
317
- #
318
- # Parameters:
319
- # [aim] A Vector specifying where the object will move to.
320
- # [speed] The constant speed at which the object will move. This must be
321
- # provided as a scalar, not a vector.
322
- # [obstacles] An array of obstacles to be considered in the collision
323
- # checking, and carried along when colliding from above.
324
- # Obstacles must be instances of Block (or derived classes),
325
- # or objects that <code>include Movement</code>.
326
- def move_carrying aim, speed, obstacles
327
- x_d = aim.x - @x; y_d = aim.y - @y
328
- distance = Math.sqrt(x_d**2 + y_d**2)
329
- @speed.x = 1.0 * x_d * speed / distance
330
- @speed.y = 1.0 * y_d * speed / distance
331
-
332
- x_aim = @x + @speed.x; y_aim = @y + @speed.y
333
- passengers = []
334
- obstacles.each do |o|
335
- if @x + @w > o.x && o.x + o.w > @x
336
- foot = o.y + o.h
337
- if foot.round(6) == @y.round(6) || @speed.y < 0 && foot < @y && foot > y_aim
338
- passengers << o
339
- end
340
- end
341
- end
342
-
343
- if @speed.x > 0 && x_aim >= aim.x || @speed.x < 0 && x_aim <= aim.x
344
- passengers.each do |p| p.x += aim.x - @x end
345
- @x = aim.x; @speed.x = 0
346
- else
347
- passengers.each do |p| p.x += @speed.x end
348
- @x = x_aim
349
- end
350
- if @speed.y > 0 && y_aim >= aim.y || @speed.y < 0 && y_aim <= aim.y
351
- @y = aim.y; @speed.y = 0
352
- else
353
- @y = y_aim
354
- end
355
-
356
- passengers.each do |p| p.y = @y - p.h end
357
- end
358
-
359
- # Moves this object, without performing any collision checking, towards
360
- # the specified point.
361
- #
362
- # Parameters:
363
- # [aim] A Vector specifying where the object will move to.
364
- # [speed] The constant speed at which the object will move. This must be
365
- # provided as a scalar, not a vector.
366
- def move_free aim, speed
367
- x_d = aim.x - @x; y_d = aim.y - @y
368
- distance = Math.sqrt(x_d**2 + y_d**2)
369
- @speed.x = 1.0 * x_d * speed / distance
370
- @speed.y = 1.0 * y_d * speed / distance
371
-
372
- if (@speed.x < 0 and @x + @speed.x <= aim.x) or (@speed.x >= 0 and @x + @speed.x >= aim.x)
373
- @x = aim.x
374
- @speed.x = 0
375
- else
376
- @x += @speed.x
377
- end
378
-
379
- if (@speed.y < 0 and @y + @speed.y <= aim.y) or (@speed.y >= 0 and @y + @speed.y >= aim.y)
380
- @y = aim.y
381
- @speed.y = 0
382
- else
383
- @y += @speed.y
384
- end
385
- end
386
-
387
- # Causes the object to move in cycles across multiple given points (the
388
- # method must be called repeatedly, and it returns the value that must be
389
- # provided to +cur_point+ after the first call). If obstacles are
390
- # provided, it will behave as an elevator (as in +move_carrying+).
391
- #
392
- # Parameters:
393
- # [points] An array of Vectors representing the path that the object will
394
- # perform.
395
- # [cur_point] The index of the point in the path that the object is
396
- # currently moving to. In the first call, it is a good idea to
397
- # provide 0, while in the subsequent calls, you must provide
398
- # the return value of this method.
399
- # [speed] The constant speed at which the object will move. This must be
400
- # provided as a scalar, not a vector.
401
- # [obstacles] An array of obstacles to be considered in the collision
402
- # checking, and carried along when colliding from above.
403
- # Obstacles must be instances of Block (or derived classes),
404
- # or objects that <code>include Movement</code>.
405
- def cycle points, cur_point, speed, obstacles = nil
406
- if obstacles
407
- move_carrying points[cur_point], speed, obstacles
408
- else
409
- move_free points[cur_point], speed
410
- end
411
- if @speed.x == 0 and @speed.y == 0
412
- if cur_point == points.length - 1; cur_point = 0
413
- else; cur_point += 1; end
414
- end
415
- cur_point
416
- end
417
-
418
- private
419
-
420
- def check_contact obst, ramps
421
- @top = @bottom = @left = @right = nil
422
- obst.each do |o|
423
- x2 = @x + @w; y2 = @y + @h; x2o = o.x + o.w; y2o = o.y + o.h
424
- @right = o if !o.passable && x2.round(6) == o.x.round(6) && y2 > o.y && @y < y2o
425
- @left = o if !o.passable && @x.round(6) == x2o.round(6) && y2 > o.y && @y < y2o
426
- @bottom = o if y2.round(6) == o.y.round(6) && x2 > o.x && @x < x2o
427
- @top = o if !o.passable && @y.round(6) == y2o.round(6) && x2 > o.x && @x < x2o
428
- end
429
- if @bottom.nil?
430
- ramps.each do |r|
431
- if r.contact? self
432
- @bottom = r
433
- break
434
- end
435
- end
436
- end
437
- end
438
-
439
- def find_right_limit coll_list
440
- limit = @x + @w + @speed.x
441
- coll_list.each do |c|
442
- limit = c.x if !c.passable && c.x < limit
443
- end
444
- limit
445
- end
446
-
447
- def find_left_limit coll_list
448
- limit = @x + @speed.x
449
- coll_list.each do |c|
450
- limit = c.x + c.w if !c.passable && c.x + c.w > limit
451
- end
452
- limit
453
- end
454
-
455
- def find_down_limit coll_list
456
- limit = @y + @h + @speed.y
457
- coll_list.each do |c|
458
- limit = c.y if c.y < limit && c.y >= @y + @h
459
- end
460
- limit
461
- end
462
-
463
- def find_up_limit coll_list
464
- limit = @y + @speed.y
465
- coll_list.each do |c|
466
- limit = c.y + c.h if !c.passable && c.y + c.h > limit
467
- end
468
- limit
469
- end
470
- end
4
+ # Represents an object with a rectangular bounding box and the +passable+
5
+ # property. It is the simplest structure that can be passed as an element of
6
+ # the +obst+ array parameter of the +move+ method.
7
+ class Block
8
+ # The x-coordinate of the top left corner of the bounding box.
9
+ attr_reader :x
10
+
11
+ # The y-coordinate of the top left corner of the bounding box.
12
+ attr_reader :y
13
+
14
+ # The width of the bounding box.
15
+ attr_reader :w
16
+
17
+ # The height of the bounding box.
18
+ attr_reader :h
19
+
20
+ # Whether a moving object can pass through this block when coming from
21
+ # below. This is a common feature of platforms in platform games.
22
+ attr_reader :passable
23
+
24
+ # Creates a new block.
25
+ #
26
+ # Parameters:
27
+ # [x] The x-coordinate of the top left corner of the bounding box.
28
+ # [y] The y-coordinate of the top left corner of the bounding box.
29
+ # [w] The width of the bounding box.
30
+ # [h] The height of the bounding box.
31
+ # [passable] Whether a moving object can pass through this block when
32
+ # coming from below. This is a common feature of platforms in platform
33
+ # games.
34
+ def initialize x, y, w, h, passable
35
+ @x = x; @y = y; @w = w; @h = h
36
+ @passable = passable
37
+ end
38
+
39
+ # Returns the bounding box of this block as a Rectangle.
40
+ def bounds
41
+ Rectangle.new @x, @y, @w, @h
42
+ end
43
+ end
44
+
45
+ # Represents a ramp, i.e., an inclined structure which allows walking over
46
+ # it while automatically going up or down. It can be imagined as a right
47
+ # triangle, with a side parallel to the x axis and another one parallel to
48
+ # the y axis. You must provide instances of this class (or derived classes)
49
+ # to the +ramps+ array parameter of the +move+ method.
50
+ class Ramp
51
+ # The x-coordinate of the top left corner of a rectangle that completely
52
+ # (and precisely) encloses the ramp (thought of as a right triangle).
53
+ attr_reader :x
54
+
55
+ # The y-coordinate of the top left corner of the rectangle described in
56
+ # the +x+ attribute.
57
+ attr_reader :y
58
+
59
+ # The width of the ramp.
60
+ attr_reader :w
61
+
62
+ # The height of the ramp.
63
+ attr_reader :h
64
+
65
+ # Whether the height of the ramp increases from left to right (decreases
66
+ # from left to right when +false+).
67
+ attr_reader :left
68
+
69
+ # Creates a new ramp.
70
+ #
71
+ # Parameters:
72
+ # [x] The x-coordinate of the top left corner of a rectangle that
73
+ # completely (and precisely) encloses the ramp (thought of as a right
74
+ # triangle).
75
+ # [y] The y-coordinate of the top left corner of the rectangle described
76
+ # above.
77
+ # [w] The width of the ramp (which corresponds to the width of the
78
+ # rectangle described above).
79
+ # [h] The height of the ramp (which corresponds to the height of the
80
+ # rectangle described above, and to the difference between the lowest
81
+ # point of the ramp, where it usually meets the floor, and the
82
+ # highest).
83
+ # [left] Whether the height of the ramp increases from left to right. Use
84
+ # +false+ for a ramp that goes down from left to right.
85
+ def initialize x, y, w, h, left
86
+ @x = x
87
+ @y = y
88
+ @w = w
89
+ @h = h
90
+ @left = left
91
+ end
92
+
93
+ # Checks if an object is in contact with this ramp (standing over it).
94
+ #
95
+ # Parameters:
96
+ # [obj] The object to check contact with. It must have the +x+, +y+, +w+
97
+ # and +h+ accessible attributes determining its bounding box.
98
+ def contact? obj
99
+ obj.x.round(6) == get_x(obj).round(6) && obj.y.round(6) == get_y(obj).round(6)
100
+ end
101
+
102
+ # Checks if an object is intersecting this ramp (inside the corresponding
103
+ # right triangle and at the floor level or above).
104
+ #
105
+ # Parameters:
106
+ # [obj] The object to check intersection with. It must have the +x+, +y+,
107
+ # +w+ and +h+ accessible attributes determining its bounding box.
108
+ def intersects obj
109
+ obj.x + obj.w > @x && obj.x < @x + @w && obj.y > get_y(obj) && obj.y <= @y + @h - obj.h
110
+ end
111
+
112
+ # :nodoc:
113
+ def can_collide? obj
114
+ @can_collide = (obj.speed.y >= 0 and not intersects(obj))
115
+ end
116
+
117
+ def check_intersection obj
118
+ if @can_collide and intersects obj
119
+ obj.y = get_y obj
120
+ obj.speed.y = 0
121
+ # a = @w / @h
122
+ # x = get_x(obj)
123
+ # y = get_y(obj)
124
+ # w = obj.x - x
125
+ # h = obj.y - y
126
+ # dx = w * h / (w * a + h)
127
+ # dy = dx * a
128
+ #
129
+ # obj.x -= dx
130
+ # obj.y -= dy
131
+ # obj.speed.x *= (@w / (@w + @h))
132
+ # obj.speed.y = 0
133
+ end
134
+ end
135
+
136
+ def get_x obj
137
+ return @x + (1.0 * (@y + @h - obj.y - obj.h) * @w / @h) - obj.w if @left
138
+ @x + (1.0 * (obj.y + obj.h - @y) * @w / @h)
139
+ end
140
+
141
+ def get_y obj
142
+ return @y - obj.h if @left && obj.x + obj.w > @x + @w
143
+ return @y + (1.0 * (@x + @w - obj.x - obj.w) * @h / @w) - obj.h if @left
144
+ return @y - obj.h if obj.x < @x
145
+ @y + (1.0 * (obj.x - @x) * @h / @w) - obj.h
146
+ end
147
+ end
148
+
149
+ # This module provides objects with physical properties and methods for
150
+ # moving. It allows moving with or without collision checking (based on
151
+ # rectangular bounding boxes), including a method to behave as an elevator,
152
+ # affecting other objects' positions as it moves.
153
+ module Movement
154
+ # The mass of the object, in arbitrary units. The default value for
155
+ # GameObject instances, for example, is 1. The larger the mass (i.e., the
156
+ # heavier the object), the more intense the forces applied to the object
157
+ # have to be in order to move it.
158
+ attr_reader :mass
159
+
160
+ # A Vector with the current speed of the object (x: horizontal component,
161
+ # y: vertical component).
162
+ attr_reader :speed
163
+
164
+ # Width of the bounding box.
165
+ attr_reader :w
166
+
167
+ # Height of the bounding box.
168
+ attr_reader :h
169
+
170
+ # Whether a moving object can pass through this block when coming from
171
+ # below. This is a common feature of platforms in platform games.
172
+ attr_reader :passable
173
+
174
+ # The object that is making contact with this from above. If there's no
175
+ # contact, returns +nil+.
176
+ attr_reader :top
177
+
178
+ # The object that is making contact with this from below. If there's no
179
+ # contact, returns +nil+.
180
+ attr_reader :bottom
181
+
182
+ # The object that is making contact with this from the left. If there's no
183
+ # contact, returns +nil+.
184
+ attr_reader :left
185
+
186
+ # The object that is making contact with this from the right. If there's
187
+ # no contact, returns +nil+.
188
+ attr_reader :right
189
+
190
+ # The x-coordinate of the top left corner of the bounding box.
191
+ attr_accessor :x
192
+
193
+ # The y-coordinate of the top left corner of the bounding box.
194
+ attr_accessor :y
195
+
196
+ # A Vector with the horizontal and vertical components of a force that
197
+ # be applied in the next time +move+ is called.
198
+ attr_accessor :stored_forces
199
+
200
+ # Returns the bounding box as a Rectangle.
201
+ def bounds
202
+ Rectangle.new @x, @y, @w, @h
203
+ end
204
+
205
+ # Moves this object, based on the forces being applied to it, and
206
+ # performing collision checking.
207
+ #
208
+ # Parameters:
209
+ # [forces] A Vector where x is the horizontal component of the resulting
210
+ # force and y is the vertical component.
211
+ # [obst] An array of obstacles to be considered in the collision checking.
212
+ # Obstacles must be instances of Block (or derived classes), or
213
+ # objects that <code>include Movement</code>.
214
+ # [ramps] An array of ramps to be considered in the collision checking.
215
+ # Ramps must be instances of Ramp (or derived classes).
216
+ def move forces, obst, ramps
217
+ forces.x += Game.gravity.x; forces.y += Game.gravity.y
218
+ forces.x += @stored_forces.x; forces.y += @stored_forces.y
219
+ @stored_forces.x = @stored_forces.y = 0
220
+
221
+ # check_contact obst, ramps
222
+ forces.x = 0 if (forces.x < 0 and @left) or (forces.x > 0 and @right)
223
+ forces.y = 0 if (forces.y < 0 and @top) or (forces.y > 0 and @bottom)
224
+
225
+ @speed.x += forces.x / @mass; @speed.y += forces.y / @mass
226
+ @speed.x = 0 if @speed.x.abs < @min_speed.x
227
+ @speed.y = 0 if @speed.y.abs < @min_speed.y
228
+ @speed.x = (@speed.x <=> 0) * @max_speed.x if @speed.x.abs > @max_speed.x
229
+ @speed.y = (@speed.y <=> 0) * @max_speed.y if @speed.y.abs > @max_speed.y
230
+
231
+ ramps.each do |r|
232
+ r.can_collide? self
233
+ end
234
+
235
+ x = @speed.x < 0 ? @x + @speed.x : @x
236
+ y = @speed.y < 0 ? @y + @speed.y : @y
237
+ w = @w + (@speed.x < 0 ? -@speed.x : @speed.x)
238
+ h = @h + (@speed.y < 0 ? -@speed.y : @speed.y)
239
+ move_bounds = Rectangle.new x, y, w, h
240
+ coll_list = []
241
+ obst.each do |o|
242
+ coll_list << o if move_bounds.intersects o.bounds
243
+ end
244
+
245
+ if coll_list.length > 0
246
+ up = @speed.y < 0; rt = @speed.x > 0; dn = @speed.y > 0; lf = @speed.x < 0
247
+ if @speed.x == 0 || @speed.y == 0
248
+ # Ortogonal
249
+ if rt; x_lim = find_right_limit coll_list
250
+ elsif lf; x_lim = find_left_limit coll_list
251
+ elsif dn; y_lim = find_down_limit coll_list
252
+ elsif up; y_lim = find_up_limit coll_list
253
+ end
254
+ if rt && @x + @w + @speed.x > x_lim; @x = x_lim - @w; @speed.x = 0
255
+ elsif lf && @x + @speed.x < x_lim; @x = x_lim; @speed.x = 0
256
+ elsif dn && @y + @h + @speed.y > y_lim; @y = y_lim - @h; @speed.y = 0
257
+ elsif up && @y + @speed.y < y_lim; @y = y_lim; @speed.y = 0
258
+ end
259
+ else
260
+ # Diagonal
261
+ x_aim = @x + @speed.x + (rt ? @w : 0); x_lim_def = x_aim
262
+ y_aim = @y + @speed.y + (dn ? @h : 0); y_lim_def = y_aim
263
+ coll_list.each do |c|
264
+ if c.passable; x_lim = x_aim
265
+ elsif rt; x_lim = c.x
266
+ else; x_lim = c.x + c.w
267
+ end
268
+ if dn; y_lim = c.y
269
+ elsif c.passable; y_lim = y_aim
270
+ else; y_lim = c.y + c.h
271
+ end
272
+
273
+ if c.passable
274
+ y_lim_def = y_lim if dn && @y + @h <= y_lim && y_lim < y_lim_def
275
+ elsif (rt && @x + @w > x_lim) || (lf && @x < x_lim)
276
+ # Can't limit by x, will limit by y
277
+ y_lim_def = y_lim if (dn && y_lim < y_lim_def) || (up && y_lim > y_lim_def)
278
+ elsif (dn && @y + @h > y_lim) || (up && @y < y_lim)
279
+ # Can't limit by y, will limit by x
280
+ x_lim_def = x_lim if (rt && x_lim < x_lim_def) || (lf && x_lim > x_lim_def)
281
+ else
282
+ x_time = 1.0 * (x_lim - @x - (@speed.x < 0 ? 0 : @w)) / @speed.x
283
+ y_time = 1.0 * (y_lim - @y - (@speed.y < 0 ? 0 : @h)) / @speed.y
284
+ if x_time > y_time
285
+ # Will limit by x
286
+ x_lim_def = x_lim if (rt && x_lim < x_lim_def) || (lf && x_lim > x_lim_def)
287
+ elsif (dn && y_lim < y_lim_def) || (up && y_lim > y_lim_def)
288
+ y_lim_def = y_lim
289
+ end
290
+ end
291
+ end
292
+ if x_lim_def != x_aim
293
+ @speed.x = 0
294
+ if lf; @x = x_lim_def
295
+ else; @x = x_lim_def - @w
296
+ end
297
+ end
298
+ if y_lim_def != y_aim
299
+ @speed.y = 0
300
+ if up; @y = y_lim_def
301
+ else; @y = y_lim_def - @h
302
+ end
303
+ end
304
+ end
305
+ end
306
+ @x += @speed.x
307
+ @y += @speed.y
308
+
309
+ ramps.each do |r|
310
+ r.check_intersection self
311
+ end
312
+ check_contact obst, ramps
313
+ end
314
+
315
+ # Moves this object as an elevator (i.e., potentially carrying other
316
+ # objects) towards a given point.
317
+ #
318
+ # Parameters:
319
+ # [aim] A Vector specifying where the object will move to.
320
+ # [speed] The constant speed at which the object will move. This must be
321
+ # provided as a scalar, not a vector.
322
+ # [obstacles] An array of obstacles to be considered in the collision
323
+ # checking, and carried along when colliding from above.
324
+ # Obstacles must be instances of Block (or derived classes),
325
+ # or objects that <code>include Movement</code>.
326
+ def move_carrying aim, speed, obstacles
327
+ x_d = aim.x - @x; y_d = aim.y - @y
328
+ distance = Math.sqrt(x_d**2 + y_d**2)
329
+ @speed.x = 1.0 * x_d * speed / distance
330
+ @speed.y = 1.0 * y_d * speed / distance
331
+
332
+ x_aim = @x + @speed.x; y_aim = @y + @speed.y
333
+ passengers = []
334
+ obstacles.each do |o|
335
+ if @x + @w > o.x && o.x + o.w > @x
336
+ foot = o.y + o.h
337
+ if foot.round(6) == @y.round(6) || @speed.y < 0 && foot < @y && foot > y_aim
338
+ passengers << o
339
+ end
340
+ end
341
+ end
342
+
343
+ if @speed.x > 0 && x_aim >= aim.x || @speed.x < 0 && x_aim <= aim.x
344
+ passengers.each do |p| p.x += aim.x - @x end
345
+ @x = aim.x; @speed.x = 0
346
+ else
347
+ passengers.each do |p| p.x += @speed.x end
348
+ @x = x_aim
349
+ end
350
+ if @speed.y > 0 && y_aim >= aim.y || @speed.y < 0 && y_aim <= aim.y
351
+ @y = aim.y; @speed.y = 0
352
+ else
353
+ @y = y_aim
354
+ end
355
+
356
+ passengers.each do |p| p.y = @y - p.h end
357
+ end
358
+
359
+ # Moves this object, without performing any collision checking, towards
360
+ # the specified point.
361
+ #
362
+ # Parameters:
363
+ # [aim] A Vector specifying where the object will move to.
364
+ # [speed] The constant speed at which the object will move. This must be
365
+ # provided as a scalar, not a vector.
366
+ def move_free aim, speed
367
+ x_d = aim.x - @x; y_d = aim.y - @y
368
+ distance = Math.sqrt(x_d**2 + y_d**2)
369
+ @speed.x = 1.0 * x_d * speed / distance
370
+ @speed.y = 1.0 * y_d * speed / distance
371
+
372
+ if (@speed.x < 0 and @x + @speed.x <= aim.x) or (@speed.x >= 0 and @x + @speed.x >= aim.x)
373
+ @x = aim.x
374
+ @speed.x = 0
375
+ else
376
+ @x += @speed.x
377
+ end
378
+
379
+ if (@speed.y < 0 and @y + @speed.y <= aim.y) or (@speed.y >= 0 and @y + @speed.y >= aim.y)
380
+ @y = aim.y
381
+ @speed.y = 0
382
+ else
383
+ @y += @speed.y
384
+ end
385
+ end
386
+
387
+ # Causes the object to move in cycles across multiple given points (the
388
+ # method must be called repeatedly, and it returns the value that must be
389
+ # provided to +cur_point+ after the first call). If obstacles are
390
+ # provided, it will behave as an elevator (as in +move_carrying+).
391
+ #
392
+ # Parameters:
393
+ # [points] An array of Vectors representing the path that the object will
394
+ # perform.
395
+ # [cur_point] The index of the point in the path that the object is
396
+ # currently moving to. In the first call, it is a good idea to
397
+ # provide 0, while in the subsequent calls, you must provide
398
+ # the return value of this method.
399
+ # [speed] The constant speed at which the object will move. This must be
400
+ # provided as a scalar, not a vector.
401
+ # [obstacles] An array of obstacles to be considered in the collision
402
+ # checking, and carried along when colliding from above.
403
+ # Obstacles must be instances of Block (or derived classes),
404
+ # or objects that <code>include Movement</code>.
405
+ def cycle points, cur_point, speed, obstacles = nil
406
+ if obstacles
407
+ move_carrying points[cur_point], speed, obstacles
408
+ else
409
+ move_free points[cur_point], speed
410
+ end
411
+ if @speed.x == 0 and @speed.y == 0
412
+ if cur_point == points.length - 1; cur_point = 0
413
+ else; cur_point += 1; end
414
+ end
415
+ cur_point
416
+ end
417
+
418
+ private
419
+
420
+ def check_contact obst, ramps
421
+ @top = @bottom = @left = @right = nil
422
+ obst.each do |o|
423
+ x2 = @x + @w; y2 = @y + @h; x2o = o.x + o.w; y2o = o.y + o.h
424
+ @right = o if !o.passable && x2.round(6) == o.x.round(6) && y2 > o.y && @y < y2o
425
+ @left = o if !o.passable && @x.round(6) == x2o.round(6) && y2 > o.y && @y < y2o
426
+ @bottom = o if y2.round(6) == o.y.round(6) && x2 > o.x && @x < x2o
427
+ @top = o if !o.passable && @y.round(6) == y2o.round(6) && x2 > o.x && @x < x2o
428
+ end
429
+ if @bottom.nil?
430
+ ramps.each do |r|
431
+ if r.contact? self
432
+ @bottom = r
433
+ break
434
+ end
435
+ end
436
+ end
437
+ end
438
+
439
+ def find_right_limit coll_list
440
+ limit = @x + @w + @speed.x
441
+ coll_list.each do |c|
442
+ limit = c.x if !c.passable && c.x < limit
443
+ end
444
+ limit
445
+ end
446
+
447
+ def find_left_limit coll_list
448
+ limit = @x + @speed.x
449
+ coll_list.each do |c|
450
+ limit = c.x + c.w if !c.passable && c.x + c.w > limit
451
+ end
452
+ limit
453
+ end
454
+
455
+ def find_down_limit coll_list
456
+ limit = @y + @h + @speed.y
457
+ coll_list.each do |c|
458
+ limit = c.y if c.y < limit && c.y >= @y + @h
459
+ end
460
+ limit
461
+ end
462
+
463
+ def find_up_limit coll_list
464
+ limit = @y + @speed.y
465
+ coll_list.each do |c|
466
+ limit = c.y + c.h if !c.passable && c.y + c.h > limit
467
+ end
468
+ limit
469
+ end
470
+ end
471
471
  end