minigl 1.3.7 → 1.3.8

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.
@@ -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