minigl 1.3.10 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/minigl/map.rb CHANGED
@@ -1,12 +1,12 @@
1
1
  require_relative 'global'
2
2
 
3
- module AGL
3
+ module MiniGL
4
4
  # This class provides easy control of a tile map, i.e., a map consisting of
5
5
  # a grid of equally sized tiles. It also provides viewport control, through
6
6
  # its camera property and methods.
7
7
  class Map
8
- Sqrt2Div2 = Math.sqrt(2) / 2 # :nodoc:
9
- MinusPiDiv4 = -Math::PI / 4 # :nodoc:
8
+ SQRT_2_DIV_2 = Math.sqrt(2) / 2 # :nodoc:
9
+ MINUS_PI_DIV_4 = -Math::PI / 4 # :nodoc:
10
10
 
11
11
  # A Vector where x is the tile width and y is the tile height.
12
12
  attr_reader :tile_size
@@ -34,7 +34,7 @@ module AGL
34
34
  # outside the map to appear in the screen, the camera would
35
35
  # move to the nearest position where only the map shows up
36
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
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
38
  @tile_size = Vector.new t_w, t_h
39
39
  @size = Vector.new t_x_count, t_y_count
40
40
  @cam = Rectangle.new 0, 0, scr_w, scr_h
@@ -71,7 +71,7 @@ module AGL
71
71
  # the interval <code>0..t_x_count</code>.
72
72
  # [map_y] The index of the tile in the vertical direction. It must be in
73
73
  # the interval <code>0..t_y_count</code>.
74
- def get_screen_pos map_x, map_y
74
+ def get_screen_pos(map_x, map_y)
75
75
  return Vector.new(map_x * @tile_size.x - @cam.x, map_y * @tile_size.y - @cam.y) unless @isometric
76
76
  Vector.new ((map_x - map_y - 1) * @tile_size.x * 0.5) - @cam.x + @x_offset,
77
77
  ((map_x + map_y) * @tile_size.y * 0.5) - @cam.y
@@ -84,7 +84,7 @@ module AGL
84
84
  # Parameters:
85
85
  # [scr_x] The x-coordinate in the screen.
86
86
  # [scr_y] The y-coordinate in the screen.
87
- def get_map_pos scr_x, scr_y
87
+ def get_map_pos(scr_x, scr_y)
88
88
  return Vector.new((scr_x + @cam.x) / @tile_size.x, (scr_y + @cam.y) / @tile_size.y) unless @isometric
89
89
 
90
90
  # Obtém a posição transformada para as coordenadas isométricas
@@ -99,7 +99,7 @@ module AGL
99
99
  # Parameters:
100
100
  # [v] A Vector representing the tile, with x as the horizontal index and
101
101
  # y as the vertical index.
102
- def is_in_map v
102
+ def is_in_map(v)
103
103
  v.x >= 0 && v.y >= 0 && v.x < @size.x && v.y < @size.y
104
104
  end
105
105
 
@@ -109,7 +109,7 @@ module AGL
109
109
  # Parameters:
110
110
  # [cam_x] The x-coordinate inside the map, in pixels (not a tile index).
111
111
  # [cam_y] The y-coordinate inside the map, in pixels (not a tile index).
112
- def set_camera cam_x, cam_y
112
+ def set_camera(cam_x, cam_y)
113
113
  @cam.x = cam_x
114
114
  @cam.y = cam_y
115
115
  set_bounds
@@ -122,7 +122,7 @@ module AGL
122
122
  # cause the camera to move to the left.
123
123
  # [y] The amount of pixels to move vertically. Negative values will cause
124
124
  # the camera to move up.
125
- def move_camera x, y
125
+ def move_camera(x, y)
126
126
  @cam.x += x
127
127
  @cam.y += y
128
128
  set_bounds
@@ -158,26 +158,26 @@ module AGL
158
158
  v4 = get_isometric_position(0, @cam.h - 1)
159
159
  if v1.x < -@max_offset
160
160
  offset = -(v1.x + @max_offset)
161
- @cam.x += offset * Sqrt2Div2
162
- @cam.y += offset * Sqrt2Div2 / @tile_ratio
161
+ @cam.x += offset * SQRT_2_DIV_2
162
+ @cam.y += offset * SQRT_2_DIV_2 / @tile_ratio
163
163
  v1.x = -@max_offset
164
164
  end
165
165
  if v2.y < -@max_offset
166
166
  offset = -(v2.y + @max_offset)
167
- @cam.x -= offset * Sqrt2Div2
168
- @cam.y += offset * Sqrt2Div2 / @tile_ratio
167
+ @cam.x -= offset * SQRT_2_DIV_2
168
+ @cam.y += offset * SQRT_2_DIV_2 / @tile_ratio
169
169
  v2.y = -@max_offset
170
170
  end
171
171
  if v3.x > @iso_abs_size.x + @max_offset
172
172
  offset = v3.x - @iso_abs_size.x - @max_offset
173
- @cam.x -= offset * Sqrt2Div2
174
- @cam.y -= offset * Sqrt2Div2 / @tile_ratio
173
+ @cam.x -= offset * SQRT_2_DIV_2
174
+ @cam.y -= offset * SQRT_2_DIV_2 / @tile_ratio
175
175
  v3.x = @iso_abs_size.x + @max_offset
176
176
  end
177
177
  if v4.y > @iso_abs_size.y + @max_offset
178
178
  offset = v4.y - @iso_abs_size.y - @max_offset
179
- @cam.x += offset * Sqrt2Div2
180
- @cam.y -= offset * Sqrt2Div2 / @tile_ratio
179
+ @cam.x += offset * SQRT_2_DIV_2
180
+ @cam.y -= offset * SQRT_2_DIV_2 / @tile_ratio
181
181
  v4.y = @iso_abs_size.y + @max_offset
182
182
  end
183
183
  else
@@ -218,7 +218,7 @@ module AGL
218
218
  def initialize_isometric
219
219
  @x_offset = (@size.y * 0.5 * @tile_size.x).round
220
220
  @tile_ratio = @tile_size.x.to_f / @tile_size.y
221
- square_size = @tile_size.x * Sqrt2Div2
221
+ square_size = @tile_size.x * SQRT_2_DIV_2
222
222
  @inverse_square_size = 1 / square_size
223
223
  @iso_abs_size = Vector.new(square_size * @size.x, square_size * @size.y)
224
224
  a = (@size.x + @size.y) * 0.5 * @tile_size.x
@@ -227,11 +227,11 @@ module AGL
227
227
  if @limit_cam
228
228
  actual_cam_h = @cam.h * @tile_ratio
229
229
  @max_offset = actual_cam_h < @cam.w ? actual_cam_h : @cam.w
230
- @max_offset *= Sqrt2Div2
230
+ @max_offset *= SQRT_2_DIV_2
231
231
  end
232
232
  end
233
233
 
234
- def get_isometric_position scr_x, scr_y
234
+ def get_isometric_position(scr_x, scr_y)
235
235
  # Escreve a posição em relação a origem (no centro do mapa)
236
236
  center = get_center
237
237
  position = Vector.new scr_x + @cam.x - center.x, scr_y + @cam.y - center.y
@@ -243,7 +243,7 @@ module AGL
243
243
  center.y *= @tile_ratio
244
244
 
245
245
  # Rotaciona o vetor posição -45°
246
- position.rotate! MinusPiDiv4
246
+ position.rotate! MINUS_PI_DIV_4
247
247
 
248
248
  # Retorna a referência da posição para o canto da tela
249
249
  position += center
@@ -1,6 +1,6 @@
1
1
  require_relative 'global'
2
2
 
3
- module AGL
3
+ module MiniGL
4
4
  # Represents an object with a rectangular bounding box and the +passable+
5
5
  # property. It is the simplest structure that can be passed as an element of
6
6
  # the +obst+ array parameter of the +move+ method.
@@ -31,7 +31,7 @@ module AGL
31
31
  # [passable] Whether a moving object can pass through this block when
32
32
  # coming from below. This is a common feature of platforms in platform
33
33
  # games.
34
- def initialize x, y, w, h, passable
34
+ def initialize(x, y, w, h, passable)
35
35
  @x = x; @y = y; @w = w; @h = h
36
36
  @passable = passable
37
37
  end
@@ -66,6 +66,8 @@ module AGL
66
66
  # from left to right when +false+).
67
67
  attr_reader :left
68
68
 
69
+ attr_reader :ratio # :nodoc:
70
+
69
71
  # Creates a new ramp.
70
72
  #
71
73
  # Parameters:
@@ -82,12 +84,14 @@ module AGL
82
84
  # highest).
83
85
  # [left] Whether the height of the ramp increases from left to right. Use
84
86
  # +false+ for a ramp that goes down from left to right.
85
- def initialize x, y, w, h, left
87
+ def initialize(x, y, w, h, left)
86
88
  @x = x
87
89
  @y = y
88
90
  @w = w
89
91
  @h = h
90
92
  @left = left
93
+ @ratio = @h.to_f / @w
94
+ @factor = Math.cos(@w / Math.sqrt(@w**2 + @h**2))
91
95
  end
92
96
 
93
97
  # Checks if an object is in contact with this ramp (standing over it).
@@ -95,8 +99,8 @@ module AGL
95
99
  # Parameters:
96
100
  # [obj] The object to check contact with. It must have the +x+, +y+, +w+
97
101
  # 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)
102
+ def contact?(obj)
103
+ obj.x + obj.w > @x && obj.x < @x + @w && obj.x.round(6) == get_x(obj).round(6) && obj.y.round(6) == get_y(obj).round(6)
100
104
  end
101
105
 
102
106
  # Checks if an object is intersecting this ramp (inside the corresponding
@@ -105,40 +109,41 @@ module AGL
105
109
  # Parameters:
106
110
  # [obj] The object to check intersection with. It must have the +x+, +y+,
107
111
  # +w+ and +h+ accessible attributes determining its bounding box.
108
- def intersects obj
112
+ def intersect?(obj)
109
113
  obj.x + obj.w > @x && obj.x < @x + @w && obj.y > get_y(obj) && obj.y <= @y + @h - obj.h
110
114
  end
111
115
 
112
116
  # :nodoc:
113
- def can_collide? obj
114
- @can_collide = (obj.speed.y >= 0 and not intersects(obj))
117
+ def check_can_collide(m)
118
+ y = get_y(m) + m.h
119
+ @can_collide = m.x + m.w > @x && @x + @w > m.x && m.y < y && m.y + m.h > y
115
120
  end
116
121
 
117
- def check_intersection obj
118
- if @can_collide and intersects obj
122
+ def check_intersection(obj)
123
+ if @can_collide and intersect? obj
124
+ counter = @left && obj.prev_speed.x > 0 || !@left && obj.prev_speed.x < 0
125
+ if obj.prev_speed.y > 0 && counter
126
+ dx = get_x(obj) - obj.x
127
+ s = (obj.prev_speed.y.to_f / obj.prev_speed.x).abs
128
+ dx /= s + @ratio
129
+ obj.x += dx
130
+ end
119
131
  obj.y = get_y obj
132
+ if counter
133
+ obj.speed.x *= @factor
134
+ end
120
135
  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
136
  end
134
137
  end
135
138
 
136
- def get_x obj
139
+ def get_x(obj)
140
+ return obj.x if @left && obj.x + obj.w > @x + @w
137
141
  return @x + (1.0 * (@y + @h - obj.y - obj.h) * @w / @h) - obj.w if @left
142
+ return obj.x if obj.x < @x
138
143
  @x + (1.0 * (obj.y + obj.h - @y) * @w / @h)
139
144
  end
140
145
 
141
- def get_y obj
146
+ def get_y(obj)
142
147
  return @y - obj.h if @left && obj.x + obj.w > @x + @w
143
148
  return @y + (1.0 * (@x + @w - obj.x - obj.w) * @h / @w) - obj.h if @left
144
149
  return @y - obj.h if obj.x < @x
@@ -161,6 +166,10 @@ module AGL
161
166
  # y: vertical component).
162
167
  attr_reader :speed
163
168
 
169
+ # A Vector with the speed limits for the object (x: horizontal component,
170
+ # y: vertical component).
171
+ attr_reader :max_speed
172
+
164
173
  # Width of the bounding box.
165
174
  attr_reader :w
166
175
 
@@ -197,6 +206,8 @@ module AGL
197
206
  # be applied in the next time +move+ is called.
198
207
  attr_accessor :stored_forces
199
208
 
209
+ attr_reader :prev_speed # :nodoc:
210
+
200
211
  # Returns the bounding box as a Rectangle.
201
212
  def bounds
202
213
  Rectangle.new @x, @y, @w, @h
@@ -213,24 +224,25 @@ module AGL
213
224
  # objects that <code>include Movement</code>.
214
225
  # [ramps] An array of ramps to be considered in the collision checking.
215
226
  # 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
227
+ def move(forces, obst, ramps)
228
+ forces.x += G.gravity.x; forces.y += G.gravity.y
218
229
  forces.x += @stored_forces.x; forces.y += @stored_forces.y
219
230
  @stored_forces.x = @stored_forces.y = 0
220
231
 
221
- # check_contact obst, ramps
222
232
  forces.x = 0 if (forces.x < 0 and @left) or (forces.x > 0 and @right)
223
233
  forces.y = 0 if (forces.y < 0 and @top) or (forces.y > 0 and @bottom)
224
234
 
235
+ if @bottom.is_a? Ramp and @bottom.ratio >= G.ramp_slip_threshold
236
+ forces.x = (@bottom.left ? -1 : 1) * 0.1
237
+ end
238
+
225
239
  @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
240
+ @speed.x = 0 if @speed.x.abs < G.min_speed.x
241
+ @speed.y = 0 if @speed.y.abs < G.min_speed.y
228
242
  @speed.x = (@speed.x <=> 0) * @max_speed.x if @speed.x.abs > @max_speed.x
229
243
  @speed.y = (@speed.y <=> 0) * @max_speed.y if @speed.y.abs > @max_speed.y
230
244
 
231
- ramps.each do |r|
232
- r.can_collide? self
233
- end
245
+ @prev_speed = @speed.clone
234
246
 
235
247
  x = @speed.x < 0 ? @x + @speed.x : @x
236
248
  y = @speed.y < 0 ? @y + @speed.y : @y
@@ -239,7 +251,10 @@ module AGL
239
251
  move_bounds = Rectangle.new x, y, w, h
240
252
  coll_list = []
241
253
  obst.each do |o|
242
- coll_list << o if move_bounds.intersects o.bounds
254
+ coll_list << o if move_bounds.intersect? o.bounds
255
+ end
256
+ ramps.each do |r|
257
+ r.check_can_collide move_bounds
243
258
  end
244
259
 
245
260
  if coll_list.length > 0
@@ -251,8 +266,12 @@ module AGL
251
266
  elsif dn; y_lim = find_down_limit coll_list
252
267
  elsif up; y_lim = find_up_limit coll_list
253
268
  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
269
+ if rt && @x + @w + @speed.x > x_lim
270
+ @x = x_lim - @w
271
+ @speed.x = 0
272
+ elsif lf && @x + @speed.x < x_lim
273
+ @x = x_lim
274
+ @speed.x = 0
256
275
  elsif dn && @y + @h + @speed.y > y_lim; @y = y_lim - @h; @speed.y = 0
257
276
  elsif up && @y + @speed.y < y_lim; @y = y_lim; @speed.y = 0
258
277
  end
@@ -306,6 +325,11 @@ module AGL
306
325
  @x += @speed.x
307
326
  @y += @speed.y
308
327
 
328
+ # Keeping contact with ramp
329
+ # if @speed.y == 0 and @speed.x.abs <= G.ramp_contact_threshold and @bottom.is_a? Ramp
330
+ # @y = @bottom.get_y(self)
331
+ # puts 'aqui'
332
+ # end
309
333
  ramps.each do |r|
310
334
  r.check_intersection self
311
335
  end
@@ -323,9 +347,15 @@ module AGL
323
347
  # checking, and carried along when colliding from above.
324
348
  # Obstacles must be instances of Block (or derived classes),
325
349
  # or objects that <code>include Movement</code>.
326
- def move_carrying aim, speed, obstacles
350
+ def move_carrying(aim, speed, obstacles)
327
351
  x_d = aim.x - @x; y_d = aim.y - @y
328
352
  distance = Math.sqrt(x_d**2 + y_d**2)
353
+
354
+ if distance == 0
355
+ @speed.x = @speed.y = 0
356
+ return
357
+ end
358
+
329
359
  @speed.x = 1.0 * x_d * speed / distance
330
360
  @speed.y = 1.0 * y_d * speed / distance
331
361
 
@@ -363,9 +393,15 @@ module AGL
363
393
  # [aim] A Vector specifying where the object will move to.
364
394
  # [speed] The constant speed at which the object will move. This must be
365
395
  # provided as a scalar, not a vector.
366
- def move_free aim, speed
396
+ def move_free(aim, speed)
367
397
  x_d = aim.x - @x; y_d = aim.y - @y
368
398
  distance = Math.sqrt(x_d**2 + y_d**2)
399
+
400
+ if distance == 0
401
+ @speed.x = @speed.y = 0
402
+ return
403
+ end
404
+
369
405
  @speed.x = 1.0 * x_d * speed / distance
370
406
  @speed.y = 1.0 * y_d * speed / distance
371
407
 
@@ -385,39 +421,37 @@ module AGL
385
421
  end
386
422
 
387
423
  # 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+).
424
+ # first point in the array is the first point the object will move towards,
425
+ # so it doesn't need to be equal to the current/initial position). If
426
+ # obstacles are provided, it will behave as an elevator (as in
427
+ # +move_carrying+).
391
428
  #
392
429
  # Parameters:
393
430
  # [points] An array of Vectors representing the path that the object will
394
431
  # 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
432
  # [speed] The constant speed at which the object will move. This must be
400
433
  # provided as a scalar, not a vector.
401
434
  # [obstacles] An array of obstacles to be considered in the collision
402
435
  # checking, and carried along when colliding from above.
403
436
  # Obstacles must be instances of Block (or derived classes),
404
437
  # or objects that <code>include Movement</code>.
405
- def cycle points, cur_point, speed, obstacles = nil
438
+ def cycle(points, speed, obstacles = nil)
439
+ @cur_point = 0 if @cur_point.nil?
406
440
  if obstacles
407
- move_carrying points[cur_point], speed, obstacles
441
+ move_carrying points[@cur_point], speed, obstacles
408
442
  else
409
- move_free points[cur_point], speed
443
+ move_free points[@cur_point], speed
410
444
  end
411
445
  if @speed.x == 0 and @speed.y == 0
412
- if cur_point == points.length - 1; cur_point = 0
413
- else; cur_point += 1; end
446
+ if @cur_point == points.length - 1; @cur_point = 0
447
+ else; @cur_point += 1; end
414
448
  end
415
- cur_point
416
449
  end
417
450
 
418
451
  private
419
452
 
420
- def check_contact obst, ramps
453
+ def check_contact(obst, ramps)
454
+ prev_bottom = @bottom
421
455
  @top = @bottom = @left = @right = nil
422
456
  obst.each do |o|
423
457
  x2 = @x + @w; y2 = @y + @h; x2o = o.x + o.w; y2o = o.y + o.h
@@ -433,10 +467,21 @@ module AGL
433
467
  break
434
468
  end
435
469
  end
470
+ if @bottom.nil?
471
+ ramps.each do |r|
472
+ if r == prev_bottom && @x + @w > r.x && r.x + r.w > @x &&
473
+ @prev_speed.x.abs <= G.ramp_contact_threshold &&
474
+ @prev_speed.y >= 0
475
+ @y = r.get_y self
476
+ @bottom = r
477
+ break
478
+ end
479
+ end
480
+ end
436
481
  end
437
482
  end
438
483
 
439
- def find_right_limit coll_list
484
+ def find_right_limit(coll_list)
440
485
  limit = @x + @w + @speed.x
441
486
  coll_list.each do |c|
442
487
  limit = c.x if !c.passable && c.x < limit
@@ -444,7 +489,7 @@ module AGL
444
489
  limit
445
490
  end
446
491
 
447
- def find_left_limit coll_list
492
+ def find_left_limit(coll_list)
448
493
  limit = @x + @speed.x
449
494
  coll_list.each do |c|
450
495
  limit = c.x + c.w if !c.passable && c.x + c.w > limit
@@ -452,7 +497,7 @@ module AGL
452
497
  limit
453
498
  end
454
499
 
455
- def find_down_limit coll_list
500
+ def find_down_limit(coll_list)
456
501
  limit = @y + @h + @speed.y
457
502
  coll_list.each do |c|
458
503
  limit = c.y if c.y < limit && c.y >= @y + @h
@@ -460,7 +505,7 @@ module AGL
460
505
  limit
461
506
  end
462
507
 
463
- def find_up_limit coll_list
508
+ def find_up_limit(coll_list)
464
509
  limit = @y + @speed.y
465
510
  coll_list.each do |c|
466
511
  limit = c.y + c.h if !c.passable && c.y + c.h > limit