minigl 2.2.3 → 2.2.4

Sign up to get free protection for your applications and to get access to all the features.
data/lib/minigl/map.rb CHANGED
@@ -1,256 +1,261 @@
1
- require_relative 'global'
2
-
3
- module MiniGL
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
- SQRT_2_DIV_2 = Math.sqrt(2) / 2 # :nodoc:
9
- MINUS_PI_DIV_4 = -Math::PI / 4 # :nodoc:
10
-
11
- # A Vector where x is the tile width and y is the tile height.
12
- attr_reader :tile_size
13
-
14
- # A Vector where x is the horizontal tile count and y the vertical count.
15
- attr_reader :size
16
-
17
- # A Rectangle representing the region of the map that is currently
18
- # visible.
19
- attr_reader :cam
20
-
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.
30
- # [isometric] Whether to use a isometric map. By default, an ortogonal map
31
- # is used.
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
43
- if isometric
44
- initialize_isometric
45
- elsif limit_cam
46
- @max_x = t_x_count * t_w - scr_w
47
- @max_y = t_y_count * t_h - scr_h
48
- end
49
- set_camera 0, 0
50
- end
51
-
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
59
-
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
65
-
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
79
-
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
89
-
90
- # Obtém a posição transformada para as coordenadas isométricas
91
- v = get_isometric_position scr_x, scr_y
92
-
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
96
-
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
105
-
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
117
-
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
130
-
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
149
-
150
- private
151
-
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)
159
- if v1.x < -@max_offset
160
- offset = -(v1.x + @max_offset)
161
- @cam.x += offset * SQRT_2_DIV_2
162
- @cam.y += offset * SQRT_2_DIV_2 / @tile_ratio
163
- v1.x = -@max_offset
164
- end
165
- if v2.y < -@max_offset
166
- offset = -(v2.y + @max_offset)
167
- @cam.x -= offset * SQRT_2_DIV_2
168
- @cam.y += offset * SQRT_2_DIV_2 / @tile_ratio
169
- v2.y = -@max_offset
170
- end
171
- if v3.x > @iso_abs_size.x + @max_offset
172
- offset = v3.x - @iso_abs_size.x - @max_offset
173
- @cam.x -= offset * SQRT_2_DIV_2
174
- @cam.y -= offset * SQRT_2_DIV_2 / @tile_ratio
175
- v3.x = @iso_abs_size.x + @max_offset
176
- end
177
- if v4.y > @iso_abs_size.y + @max_offset
178
- offset = v4.y - @iso_abs_size.y - @max_offset
179
- @cam.x += offset * SQRT_2_DIV_2
180
- @cam.y -= offset * SQRT_2_DIV_2 / @tile_ratio
181
- v4.y = @iso_abs_size.y + @max_offset
182
- end
183
- else
184
- @cam.x = @max_x if @cam.x > @max_x
185
- @cam.x = 0 if @cam.x < 0
186
- @cam.y = @max_y if @cam.y > @max_y
187
- @cam.y = 0 if @cam.y < 0
188
- end
189
- end
190
-
191
- @cam.x = @cam.x.round
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
204
-
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
207
-
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
210
-
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
213
-
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
217
-
218
- def initialize_isometric
219
- @x_offset = (@size.y * 0.5 * @tile_size.x).round
220
- @tile_ratio = @tile_size.x.to_f / @tile_size.y
221
- square_size = @tile_size.x * SQRT_2_DIV_2
222
- @inverse_square_size = 1 / square_size
223
- @iso_abs_size = Vector.new(square_size * @size.x, square_size * @size.y)
224
- a = (@size.x + @size.y) * 0.5 * @tile_size.x
225
- @isometric_offset_x = (a - square_size * @size.x) * 0.5
226
- @isometric_offset_y = (a - square_size * @size.y) * 0.5
227
- if @limit_cam
228
- actual_cam_h = @cam.h * @tile_ratio
229
- @max_offset = actual_cam_h < @cam.w ? actual_cam_h : @cam.w
230
- @max_offset *= SQRT_2_DIV_2
231
- end
232
- end
233
-
234
- def get_isometric_position(scr_x, scr_y)
235
- # Escreve a posição em relação a origem (no centro do mapa)
236
- center = get_center
237
- position = Vector.new scr_x + @cam.x - center.x, scr_y + @cam.y - center.y
238
-
239
- # Multiplica por tile_ratio para obter tiles quadrados
240
- position.y *= @tile_ratio
241
-
242
- # O centro do mapa também é deslocado
243
- center.y *= @tile_ratio
244
-
245
- # Rotaciona o vetor posição -45°
246
- position.rotate! MINUS_PI_DIV_4
247
-
248
- # Retorna a referência da posição para o canto da tela
249
- position += center
250
-
251
- # O mapa quadrado está centralizado no centro do losango, precisa retornar ao canto da tela
252
- position.x -= @isometric_offset_x; position.y -= @isometric_offset_y
253
- position
254
- end
255
- end
256
- end
1
+ require_relative 'global'
2
+
3
+ module MiniGL
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
+ # When working with +GameObject+, +Sprite+ and +Effect+ inside a +Map+, you
8
+ # should provide the map instance as parameter to the +draw+ method of these;
9
+ # when using Gosu's +Image+, you'll need to manually subtract the coordinates
10
+ # of the camera from the position of the image, like this:
11
+ # <code>img.draw(x - map.cam.x, y - map.cam.y, z)</code>
12
+ class Map
13
+ SQRT_2_DIV_2 = Math.sqrt(2) / 2 # :nodoc:
14
+ MINUS_PI_DIV_4 = -Math::PI / 4 # :nodoc:
15
+
16
+ # A Vector where x is the tile width and y is the tile height.
17
+ attr_reader :tile_size
18
+
19
+ # A Vector where x is the horizontal tile count and y the vertical count.
20
+ attr_reader :size
21
+
22
+ # A Rectangle representing the region of the map that is currently
23
+ # visible.
24
+ attr_reader :cam
25
+
26
+ # Creates a new map.
27
+ #
28
+ # Parameters:
29
+ # [t_w] The width of the tiles.
30
+ # [t_h] The height of the tiles.
31
+ # [t_x_count] The horizontal count of tiles in the map.
32
+ # [t_y_count] The vertical count of tiles in the map.
33
+ # [scr_w] Width of the viewport for the map.
34
+ # [scr_h] Height of the viewport for the map.
35
+ # [isometric] Whether to use a isometric map. By default, an ortogonal map
36
+ # is used.
37
+ # [limit_cam] Whether the camera should respect the bounds of the map
38
+ # (i.e., when given coordinates that would imply regions
39
+ # outside the map to appear in the screen, the camera would
40
+ # move to the nearest position where only the map shows up
41
+ # in the screen).
42
+ def initialize(t_w, t_h, t_x_count, t_y_count, scr_w = 800, scr_h = 600, isometric = false, limit_cam = true)
43
+ @tile_size = Vector.new t_w, t_h
44
+ @size = Vector.new t_x_count, t_y_count
45
+ @cam = Rectangle.new 0, 0, scr_w, scr_h
46
+ @limit_cam = limit_cam
47
+ @isometric = isometric
48
+ if isometric
49
+ initialize_isometric
50
+ elsif limit_cam
51
+ @max_x = t_x_count * t_w - scr_w
52
+ @max_y = t_y_count * t_h - scr_h
53
+ end
54
+ set_camera 0, 0
55
+ end
56
+
57
+ # Returns a Vector with the total size of the map, in pixels (x for the
58
+ # width and y for the height).
59
+ def get_absolute_size
60
+ return Vector.new(@tile_size.x * @size.x, @tile_size.y * @size.y) unless @isometric
61
+ avg = (@size.x + @size.y) * 0.5
62
+ Vector.new (avg * @tile_size.x).to_i, (avg * @tile_size.y).to_i
63
+ end
64
+
65
+ # Returns a Vector with the coordinates of the center of the map.
66
+ def get_center
67
+ abs_size = get_absolute_size
68
+ Vector.new(abs_size.x * 0.5, abs_size.y * 0.5)
69
+ end
70
+
71
+ # Returns the position in the screen corresponding to the given tile
72
+ # indices.
73
+ #
74
+ # Parameters:
75
+ # [map_x] The index of the tile in the horizontal direction. It must be in
76
+ # the interval <code>0..t_x_count</code>.
77
+ # [map_y] The index of the tile in the vertical direction. It must be in
78
+ # the interval <code>0..t_y_count</code>.
79
+ def get_screen_pos(map_x, map_y)
80
+ return Vector.new(map_x * @tile_size.x - @cam.x, map_y * @tile_size.y - @cam.y) unless @isometric
81
+ Vector.new ((map_x - map_y - 1) * @tile_size.x * 0.5) - @cam.x + @x_offset,
82
+ ((map_x + map_y) * @tile_size.y * 0.5) - @cam.y
83
+ end
84
+
85
+ # Returns the tile in the map that corresponds to the given position in
86
+ # the screen, as a Vector, where x is the horizontal index and y the
87
+ # vertical index.
88
+ #
89
+ # Parameters:
90
+ # [scr_x] The x-coordinate in the screen.
91
+ # [scr_y] The y-coordinate in the screen.
92
+ def get_map_pos(scr_x, scr_y)
93
+ return Vector.new((scr_x + @cam.x) / @tile_size.x, (scr_y + @cam.y) / @tile_size.y) unless @isometric
94
+
95
+ # Gets the position transformed to isometric coordinates
96
+ v = get_isometric_position scr_x, scr_y
97
+
98
+ # divides by the square size to find the position in the matrix
99
+ Vector.new((v.x * @inverse_square_size).to_i, (v.y * @inverse_square_size).to_i)
100
+ end
101
+
102
+ # Verifies whether a tile is inside the map.
103
+ #
104
+ # Parameters:
105
+ # [v] A Vector representing the tile, with x as the horizontal index and
106
+ # y as the vertical index.
107
+ def is_in_map(v)
108
+ v.x >= 0 && v.y >= 0 && v.x < @size.x && v.y < @size.y
109
+ end
110
+
111
+ # Sets the top left corner of the viewport to the given position of the
112
+ # map. Note that this is not the position in the screen.
113
+ #
114
+ # Parameters:
115
+ # [cam_x] The x-coordinate inside the map, in pixels (not a tile index).
116
+ # [cam_y] The y-coordinate inside the map, in pixels (not a tile index).
117
+ def set_camera(cam_x, cam_y)
118
+ @cam.x = cam_x
119
+ @cam.y = cam_y
120
+ set_bounds
121
+ end
122
+
123
+ # Moves the viewport by the given amount of pixels.
124
+ #
125
+ # Parameters:
126
+ # [x] The amount of pixels to move horizontally. Negative values will
127
+ # cause the camera to move to the left.
128
+ # [y] The amount of pixels to move vertically. Negative values will cause
129
+ # the camera to move up.
130
+ def move_camera(x, y)
131
+ @cam.x += x
132
+ @cam.y += y
133
+ set_bounds
134
+ end
135
+
136
+ # Iterates through the currently visible tiles, providing the horizontal
137
+ # tile index, the vertical tile index, the x-coordinate (in pixels) and
138
+ # the y-coordinate (in pixels), of each tile, in that order, to a given
139
+ # block of code.
140
+ #
141
+ # Example:
142
+ #
143
+ # map.foreach do |i, j, x, y|
144
+ # draw_tile tiles[i][j], x, y
145
+ # end
146
+ def foreach
147
+ for j in @min_vis_y..@max_vis_y
148
+ for i in @min_vis_x..@max_vis_x
149
+ pos = get_screen_pos i, j
150
+ yield i, j, pos.x, pos.y
151
+ end
152
+ end
153
+ end
154
+
155
+ private
156
+
157
+ def set_bounds
158
+ if @limit_cam
159
+ if @isometric
160
+ v1 = get_isometric_position(0, 0)
161
+ v2 = get_isometric_position(@cam.w - 1, 0)
162
+ v3 = get_isometric_position(@cam.w - 1, @cam.h - 1)
163
+ v4 = get_isometric_position(0, @cam.h - 1)
164
+ if v1.x < -@max_offset
165
+ offset = -(v1.x + @max_offset)
166
+ @cam.x += offset * SQRT_2_DIV_2
167
+ @cam.y += offset * SQRT_2_DIV_2 / @tile_ratio
168
+ v1.x = -@max_offset
169
+ end
170
+ if v2.y < -@max_offset
171
+ offset = -(v2.y + @max_offset)
172
+ @cam.x -= offset * SQRT_2_DIV_2
173
+ @cam.y += offset * SQRT_2_DIV_2 / @tile_ratio
174
+ v2.y = -@max_offset
175
+ end
176
+ if v3.x > @iso_abs_size.x + @max_offset
177
+ offset = v3.x - @iso_abs_size.x - @max_offset
178
+ @cam.x -= offset * SQRT_2_DIV_2
179
+ @cam.y -= offset * SQRT_2_DIV_2 / @tile_ratio
180
+ v3.x = @iso_abs_size.x + @max_offset
181
+ end
182
+ if v4.y > @iso_abs_size.y + @max_offset
183
+ offset = v4.y - @iso_abs_size.y - @max_offset
184
+ @cam.x += offset * SQRT_2_DIV_2
185
+ @cam.y -= offset * SQRT_2_DIV_2 / @tile_ratio
186
+ v4.y = @iso_abs_size.y + @max_offset
187
+ end
188
+ else
189
+ @cam.x = @max_x if @cam.x > @max_x
190
+ @cam.x = 0 if @cam.x < 0
191
+ @cam.y = @max_y if @cam.y > @max_y
192
+ @cam.y = 0 if @cam.y < 0
193
+ end
194
+ end
195
+
196
+ @cam.x = @cam.x.round
197
+ @cam.y = @cam.y.round
198
+ if @isometric
199
+ @min_vis_x = get_map_pos(0, 0).x
200
+ @min_vis_y = get_map_pos(@cam.w - 1, 0).y
201
+ @max_vis_x = get_map_pos(@cam.w - 1, @cam.h - 1).x
202
+ @max_vis_y = get_map_pos(0, @cam.h - 1).y
203
+ else
204
+ @min_vis_x = @cam.x / @tile_size.x
205
+ @min_vis_y = @cam.y / @tile_size.y
206
+ @max_vis_x = (@cam.x + @cam.w - 1) / @tile_size.x
207
+ @max_vis_y = (@cam.y + @cam.h - 1) / @tile_size.y
208
+ end
209
+
210
+ if @min_vis_y < 0; @min_vis_y = 0
211
+ elsif @min_vis_y > @size.y - 1; @min_vis_y = @size.y - 1; end
212
+
213
+ if @max_vis_y < 0; @max_vis_y = 0
214
+ elsif @max_vis_y > @size.y - 1; @max_vis_y = @size.y - 1; end
215
+
216
+ if @min_vis_x < 0; @min_vis_x = 0
217
+ elsif @min_vis_x > @size.x - 1; @min_vis_x = @size.x - 1; end
218
+
219
+ if @max_vis_x < 0; @max_vis_x = 0
220
+ elsif @max_vis_x > @size.x - 1; @max_vis_x = @size.x - 1; end
221
+ end
222
+
223
+ def initialize_isometric
224
+ @x_offset = (@size.y * 0.5 * @tile_size.x).round
225
+ @tile_ratio = @tile_size.x.to_f / @tile_size.y
226
+ square_size = @tile_size.x * SQRT_2_DIV_2
227
+ @inverse_square_size = 1 / square_size
228
+ @iso_abs_size = Vector.new(square_size * @size.x, square_size * @size.y)
229
+ a = (@size.x + @size.y) * 0.5 * @tile_size.x
230
+ @isometric_offset_x = (a - square_size * @size.x) * 0.5
231
+ @isometric_offset_y = (a - square_size * @size.y) * 0.5
232
+ if @limit_cam
233
+ actual_cam_h = @cam.h * @tile_ratio
234
+ @max_offset = actual_cam_h < @cam.w ? actual_cam_h : @cam.w
235
+ @max_offset *= SQRT_2_DIV_2
236
+ end
237
+ end
238
+
239
+ def get_isometric_position(scr_x, scr_y)
240
+ # Gets the position relative to the center of the map
241
+ center = get_center
242
+ position = Vector.new scr_x + @cam.x - center.x, scr_y + @cam.y - center.y
243
+
244
+ # Multiplies by tile_ratio to get square tiles
245
+ position.y *= @tile_ratio
246
+
247
+ # Moves the center of the map accordingly
248
+ center.y *= @tile_ratio
249
+
250
+ # Rotates the position -45 degrees
251
+ position.rotate! MINUS_PI_DIV_4
252
+
253
+ # Returns the reference to the center of the map
254
+ position += center
255
+
256
+ # Returns to the corner of the screen
257
+ position.x -= @isometric_offset_x; position.y -= @isometric_offset_y
258
+ position
259
+ end
260
+ end
261
+ end