minigl 2.2.3 → 2.2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,585 +1,585 @@
1
- require_relative 'global'
2
-
3
- module MiniGL
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. Default is +false+.
34
- def initialize(x, y, w, h, passable = false)
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
- attr_reader :ratio # :nodoc:
70
- attr_reader :factor # :nodoc:
71
-
72
- # Creates a new ramp.
73
- #
74
- # Parameters:
75
- # [x] The x-coordinate of the top left corner of a rectangle that
76
- # completely (and precisely) encloses the ramp (thought of as a right
77
- # triangle).
78
- # [y] The y-coordinate of the top left corner of the rectangle described
79
- # above.
80
- # [w] The width of the ramp (which corresponds to the width of the
81
- # rectangle described above).
82
- # [h] The height of the ramp (which corresponds to the height of the
83
- # rectangle described above, and to the difference between the lowest
84
- # point of the ramp, where it usually meets the floor, and the
85
- # highest).
86
- # [left] Whether the height of the ramp increases from left to right. Use
87
- # +false+ for a ramp that goes down from left to right.
88
- def initialize(x, y, w, h, left)
89
- @x = x
90
- @y = y
91
- @w = w
92
- @h = h
93
- @left = left
94
- @ratio = @h.to_f / @w
95
- @factor = @w / Math.sqrt(@w**2 + @h**2)
96
- end
97
-
98
- # Checks if an object is in contact with this ramp (standing over it).
99
- #
100
- # Parameters:
101
- # [obj] The object to check contact with. It must have the +x+, +y+, +w+
102
- # and +h+ accessible attributes determining its bounding box.
103
- def contact?(obj)
104
- 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)
105
- end
106
-
107
- # Checks if an object is intersecting this ramp (inside the corresponding
108
- # right triangle and at the floor level or above).
109
- #
110
- # Parameters:
111
- # [obj] The object to check intersection with. It must have the +x+, +y+,
112
- # +w+ and +h+ accessible attributes determining its bounding box.
113
- def intersect?(obj)
114
- obj.x + obj.w > @x && obj.x < @x + @w && obj.y > get_y(obj) && obj.y <= @y + @h - obj.h
115
- end
116
-
117
- # :nodoc:
118
- def check_can_collide(m)
119
- y = get_y(m) + m.h
120
- @can_collide = m.x + m.w > @x && @x + @w > m.x && m.y < y && m.y + m.h > y
121
- end
122
-
123
- def check_intersection(obj)
124
- if @can_collide and intersect? obj
125
- counter = @left && obj.prev_speed.x > 0 || !@left && obj.prev_speed.x < 0
126
- if obj.prev_speed.y > 0 && counter
127
- dx = get_x(obj) - obj.x
128
- s = (obj.prev_speed.y.to_f / obj.prev_speed.x).abs
129
- dx /= s + @ratio
130
- obj.x += dx
131
- end
132
- obj.y = get_y obj
133
- if counter && obj.bottom != self
134
- obj.speed.x *= @factor
135
- end
136
- obj.speed.y = 0
137
- end
138
- end
139
-
140
- def get_x(obj)
141
- return obj.x if @left && obj.x + obj.w > @x + @w
142
- return @x + (1.0 * (@y + @h - obj.y - obj.h) * @w / @h) - obj.w if @left
143
- return obj.x if obj.x < @x
144
- @x + (1.0 * (obj.y + obj.h - @y) * @w / @h)
145
- end
146
-
147
- def get_y(obj)
148
- return @y - obj.h if @left && obj.x + obj.w > @x + @w
149
- return @y + (1.0 * (@x + @w - obj.x - obj.w) * @h / @w) - obj.h if @left
150
- return @y - obj.h if obj.x < @x
151
- @y + (1.0 * (obj.x - @x) * @h / @w) - obj.h
152
- end
153
- end
154
-
155
- # This module provides objects with physical properties and methods for
156
- # moving. It allows moving with or without collision checking (based on
157
- # rectangular bounding boxes), including a method to behave as an elevator,
158
- # affecting other objects' positions as it moves.
159
- module Movement
160
- # The mass of the object, in arbitrary units. The default value for
161
- # GameObject instances, for example, is 1. The larger the mass (i.e., the
162
- # heavier the object), the more intense the forces applied to the object
163
- # have to be in order to move it.
164
- attr_reader :mass
165
-
166
- # A Vector with the current speed of the object (x: horizontal component,
167
- # y: vertical component).
168
- attr_reader :speed
169
-
170
- # A Vector with the speed limits for the object (x: horizontal component,
171
- # y: vertical component).
172
- attr_reader :max_speed
173
-
174
- # Width of the bounding box.
175
- attr_reader :w
176
-
177
- # Height of the bounding box.
178
- attr_reader :h
179
-
180
- # The object that is making contact with this from above. If there's no
181
- # contact, returns +nil+.
182
- attr_reader :top
183
-
184
- # The object that is making contact with this from below. If there's no
185
- # contact, returns +nil+.
186
- attr_reader :bottom
187
-
188
- # The object that is making contact with this from the left. If there's no
189
- # contact, returns +nil+.
190
- attr_reader :left
191
-
192
- # The object that is making contact with this from the right. If there's
193
- # no contact, returns +nil+.
194
- attr_reader :right
195
-
196
- # The x-coordinate of the top left corner of the bounding box.
197
- attr_accessor :x
198
-
199
- # The y-coordinate of the top left corner of the bounding box.
200
- attr_accessor :y
201
-
202
- # Whether a moving object can pass through this block when coming from
203
- # below. This is a common feature of platforms in platform games.
204
- attr_accessor :passable
205
-
206
- # A Vector with the horizontal and vertical components of a force that
207
- # be applied in the next time +move+ is called.
208
- attr_accessor :stored_forces
209
-
210
- # A Vector containing the speed of the object in the previous frame.
211
- attr_reader :prev_speed
212
-
213
- # Returns the bounding box as a Rectangle.
214
- def bounds
215
- Rectangle.new @x, @y, @w, @h
216
- end
217
-
218
- # Moves this object, based on the forces being applied to it, and
219
- # performing collision checking.
220
- #
221
- # Parameters:
222
- # [forces] A Vector where x is the horizontal component of the resulting
223
- # force and y is the vertical component.
224
- # [obst] An array of obstacles to be considered in the collision checking.
225
- # Obstacles must be instances of Block (or derived classes), or
226
- # objects that <code>include Movement</code>.
227
- # [ramps] An array of ramps to be considered in the collision checking.
228
- # Ramps must be instances of Ramp (or derived classes).
229
- # [set_speed] Set this flag to +true+ to cause the +forces+ vector to be
230
- # treated as a speed vector, i.e., the object's speed will be
231
- # directly set to the given values. The force of gravity will
232
- # also be ignored in this case.
233
- def move(forces, obst, ramps, set_speed = false)
234
- if set_speed
235
- @speed.x = forces.x
236
- @speed.y = forces.y
237
- else
238
- forces.x += G.gravity.x; forces.y += G.gravity.y
239
- forces.x += @stored_forces.x; forces.y += @stored_forces.y
240
- @stored_forces.x = @stored_forces.y = 0
241
-
242
- forces.x = 0 if (forces.x < 0 and @left) or (forces.x > 0 and @right)
243
- forces.y = 0 if (forces.y < 0 and @top) or (forces.y > 0 and @bottom)
244
-
245
- if @bottom.is_a? Ramp
246
- if @bottom.ratio > G.ramp_slip_threshold
247
- forces.x += (@bottom.left ? -1 : 1) * (@bottom.ratio - G.ramp_slip_threshold) * G.ramp_slip_force / G.ramp_slip_threshold
248
- elsif forces.x > 0 && @bottom.left || forces.x < 0 && !@bottom.left
249
- forces.x *= @bottom.factor
250
- end
251
- end
252
-
253
- @speed.x += forces.x / @mass; @speed.y += forces.y / @mass
254
- end
255
-
256
- @speed.x = 0 if @speed.x.abs < G.min_speed.x
257
- @speed.y = 0 if @speed.y.abs < G.min_speed.y
258
- @speed.x = (@speed.x <=> 0) * @max_speed.x if @speed.x.abs > @max_speed.x
259
- @speed.y = (@speed.y <=> 0) * @max_speed.y if @speed.y.abs > @max_speed.y
260
- @prev_speed = @speed.clone
261
-
262
- x = @speed.x < 0 ? @x + @speed.x : @x
263
- y = @speed.y < 0 ? @y + @speed.y : @y
264
- w = @w + (@speed.x < 0 ? -@speed.x : @speed.x)
265
- h = @h + (@speed.y < 0 ? -@speed.y : @speed.y)
266
- move_bounds = Rectangle.new x, y, w, h
267
- coll_list = []
268
- obst.each do |o|
269
- coll_list << o if o != self && move_bounds.intersect?(o.bounds)
270
- end
271
- ramps.each do |r|
272
- r.check_can_collide move_bounds
273
- end
274
-
275
- if coll_list.length > 0
276
- up = @speed.y < 0; rt = @speed.x > 0; dn = @speed.y > 0; lf = @speed.x < 0
277
- if @speed.x == 0 || @speed.y == 0
278
- # Ortogonal
279
- if rt; x_lim = find_right_limit coll_list
280
- elsif lf; x_lim = find_left_limit coll_list
281
- elsif dn; y_lim = find_down_limit coll_list
282
- elsif up; y_lim = find_up_limit coll_list
283
- end
284
- if rt && @x + @w + @speed.x > x_lim
285
- @x = x_lim - @w
286
- @speed.x = 0
287
- elsif lf && @x + @speed.x < x_lim
288
- @x = x_lim
289
- @speed.x = 0
290
- elsif dn && @y + @h + @speed.y > y_lim; @y = y_lim - @h; @speed.y = 0
291
- elsif up && @y + @speed.y < y_lim; @y = y_lim; @speed.y = 0
292
- end
293
- else
294
- # Diagonal
295
- x_aim = @x + @speed.x + (rt ? @w : 0); x_lim_def = x_aim
296
- y_aim = @y + @speed.y + (dn ? @h : 0); y_lim_def = y_aim
297
- coll_list.each do |c|
298
- if c.passable; x_lim = x_aim
299
- elsif rt; x_lim = c.x
300
- else; x_lim = c.x + c.w
301
- end
302
- if dn; y_lim = c.y
303
- elsif c.passable; y_lim = y_aim
304
- else; y_lim = c.y + c.h
305
- end
306
-
307
- if c.passable
308
- y_lim_def = y_lim if dn && @y + @h <= y_lim && y_lim < y_lim_def
309
- elsif (rt && @x + @w > x_lim) || (lf && @x < x_lim)
310
- # Can't limit by x, will limit by y
311
- y_lim_def = y_lim if (dn && y_lim < y_lim_def) || (up && y_lim > y_lim_def)
312
- elsif (dn && @y + @h > y_lim) || (up && @y < y_lim)
313
- # Can't limit by y, will limit by x
314
- x_lim_def = x_lim if (rt && x_lim < x_lim_def) || (lf && x_lim > x_lim_def)
315
- else
316
- x_time = 1.0 * (x_lim - @x - (@speed.x < 0 ? 0 : @w)) / @speed.x
317
- y_time = 1.0 * (y_lim - @y - (@speed.y < 0 ? 0 : @h)) / @speed.y
318
- if x_time > y_time
319
- # Will limit by x
320
- x_lim_def = x_lim if (rt && x_lim < x_lim_def) || (lf && x_lim > x_lim_def)
321
- elsif (dn && y_lim < y_lim_def) || (up && y_lim > y_lim_def)
322
- y_lim_def = y_lim
323
- end
324
- end
325
- end
326
- if x_lim_def != x_aim
327
- @speed.x = 0
328
- if lf; @x = x_lim_def
329
- else; @x = x_lim_def - @w
330
- end
331
- end
332
- if y_lim_def != y_aim
333
- @speed.y = 0
334
- if up; @y = y_lim_def
335
- else; @y = y_lim_def - @h
336
- end
337
- end
338
- end
339
- end
340
- @x += @speed.x
341
- @y += @speed.y
342
-
343
- # Keeping contact with ramp
344
- # if @speed.y == 0 and @speed.x.abs <= G.ramp_contact_threshold and @bottom.is_a? Ramp
345
- # @y = @bottom.get_y(self)
346
- # puts 'aqui'
347
- # end
348
- ramps.each do |r|
349
- r.check_intersection self
350
- end
351
- check_contact obst, ramps
352
- end
353
-
354
- # Moves this object as an elevator (i.e., potentially carrying other
355
- # objects) with the specified forces or towards a given point.
356
- #
357
- # Parameters:
358
- # [arg] A Vector specifying either the forces acting on this object or a
359
- # point towards the object should move.
360
- # [speed] If the first argument is a forces vector, then this should be
361
- # +nil+. If it is a point, then this is the constant speed at which
362
- # the object will move (provided as a scalar, not a vector).
363
- # [obstacles] An array of obstacles to be considered in the collision
364
- # checking, and carried along when colliding from above.
365
- # Obstacles must be instances of Block (or derived classes),
366
- # or objects that <code>include Movement</code>.
367
- # [obst_obstacles] Obstacles that should be considered when moving objects
368
- # from the +obstacles+ array, i.e., these obstacles won't
369
- # interfere in the elevator's movement, but in the movement
370
- # of the objects being carried.
371
- # [obst_ramps] Ramps to consider when moving objects from the +obstacles+
372
- # array, as described for +obst_obstacles+.
373
- def move_carrying(arg, speed, obstacles, obst_obstacles, obst_ramps)
374
- if speed
375
- x_d = arg.x - @x; y_d = arg.y - @y
376
- distance = Math.sqrt(x_d**2 + y_d**2)
377
-
378
- if distance == 0
379
- @speed.x = @speed.y = 0
380
- return
381
- end
382
-
383
- @speed.x = 1.0 * x_d * speed / distance
384
- @speed.y = 1.0 * y_d * speed / distance
385
- else
386
- arg += G.gravity
387
- @speed.x += arg.x / @mass; @speed.y += arg.y / @mass
388
- @speed.x = 0 if @speed.x.abs < G.min_speed.x
389
- @speed.y = 0 if @speed.y.abs < G.min_speed.y
390
- @speed.x = (@speed.x <=> 0) * @max_speed.x if @speed.x.abs > @max_speed.x
391
- @speed.y = (@speed.y <=> 0) * @max_speed.y if @speed.y.abs > @max_speed.y
392
- end
393
-
394
- x_aim = @x + @speed.x; y_aim = @y + @speed.y
395
- passengers = []
396
- obstacles.each do |o|
397
- if @x + @w > o.x && o.x + o.w > @x
398
- foot = o.y + o.h
399
- if foot.round(6) == @y.round(6) || @speed.y < 0 && foot < @y && foot > y_aim
400
- passengers << o
401
- end
402
- end
403
- end
404
-
405
- prev_x = @x; prev_y = @y
406
- if speed
407
- if @speed.x > 0 && x_aim >= arg.x || @speed.x < 0 && x_aim <= arg.x
408
- @x = arg.x; @speed.x = 0
409
- else
410
- @x = x_aim
411
- end
412
- if @speed.y > 0 && y_aim >= arg.y || @speed.y < 0 && y_aim <= arg.y
413
- @y = arg.y; @speed.y = 0
414
- else
415
- @y = y_aim
416
- end
417
- else
418
- @x = x_aim; @y = y_aim
419
- end
420
-
421
- forces = Vector.new @x - prev_x, @y - prev_y
422
- prev_g = G.gravity.clone
423
- G.gravity.x = G.gravity.y = 0
424
- passengers.each do |p|
425
- prev_speed = p.speed.clone
426
- prev_forces = p.stored_forces.clone
427
- prev_bottom = p.bottom
428
- p.speed.x = p.speed.y = 0
429
- p.stored_forces.x = p.stored_forces.y = 0
430
- p.instance_exec { @bottom = nil }
431
- p.move forces * p.mass, obst_obstacles, obst_ramps
432
- p.speed.x = prev_speed.x
433
- p.speed.y = prev_speed.y
434
- p.stored_forces.x = prev_forces.x
435
- p.stored_forces.y = prev_forces.y
436
- p.instance_exec(prev_bottom) { |b| @bottom = b }
437
- end
438
- G.gravity = prev_g
439
- end
440
-
441
- # Moves this object, without performing any collision checking, towards
442
- # a specified point or in a specified direction.
443
- #
444
- # Parameters:
445
- # [aim] A +Vector+ specifying where the object will move to or an angle (in
446
- # degrees) indicating the direction of the movement. Angles are
447
- # measured starting from the right (i.e., to move to the right, the
448
- # angle must be 0) and raising clockwise.
449
- # [speed] The constant speed at which the object will move. This must be
450
- # provided as a scalar, not a vector.
451
- def move_free(aim, speed)
452
- if aim.is_a? Vector
453
- x_d = aim.x - @x; y_d = aim.y - @y
454
- distance = Math.sqrt(x_d**2 + y_d**2)
455
-
456
- if distance == 0
457
- @speed.x = @speed.y = 0
458
- return
459
- end
460
-
461
- @speed.x = 1.0 * x_d * speed / distance
462
- @speed.y = 1.0 * y_d * speed / distance
463
-
464
- if (@speed.x < 0 and @x + @speed.x <= aim.x) or (@speed.x >= 0 and @x + @speed.x >= aim.x)
465
- @x = aim.x
466
- @speed.x = 0
467
- else
468
- @x += @speed.x
469
- end
470
-
471
- if (@speed.y < 0 and @y + @speed.y <= aim.y) or (@speed.y >= 0 and @y + @speed.y >= aim.y)
472
- @y = aim.y
473
- @speed.y = 0
474
- else
475
- @y += @speed.y
476
- end
477
- else
478
- rads = aim * Math::PI / 180
479
- @speed.x = speed * Math.cos(rads)
480
- @speed.y = speed * Math.sin(rads)
481
- @x += @speed.x
482
- @y += @speed.y
483
- end
484
- end
485
-
486
- # Causes the object to move in cycles across multiple given points (the
487
- # first point in the array is the first point the object will move towards,
488
- # so it doesn't need to be equal to the current/initial position). If
489
- # obstacles are provided, it will behave as an elevator (as in
490
- # +move_carrying+).
491
- #
492
- # Parameters:
493
- # [points] An array of Vectors representing the path that the object will
494
- # perform.
495
- # [speed] The constant speed at which the object will move. This must be
496
- # provided as a scalar, not a vector.
497
- # [obstacles] An array of obstacles to be considered in the collision
498
- # checking, and carried along when colliding from above.
499
- # Obstacles must be instances of Block (or derived classes),
500
- # or objects that <code>include Movement</code>.
501
- # [obst_obstacles] Obstacles that should be considered when moving objects
502
- # from the +obstacles+ array, i.e., these obstacles won't
503
- # interfere in the elevator's movement, but in the movement
504
- # of the objects being carried.
505
- # [obst_ramps] Ramps to consider when moving objects from the +obstacles+
506
- # array, as described for +obst_obstacles+.
507
- def cycle(points, speed, obstacles = nil, obst_obstacles = nil, obst_ramps = nil)
508
- @cur_point = 0 if @cur_point.nil?
509
- if obstacles
510
- move_carrying points[@cur_point], speed, obstacles, obst_obstacles, obst_ramps
511
- else
512
- move_free points[@cur_point], speed
513
- end
514
- if @speed.x == 0 and @speed.y == 0
515
- if @cur_point == points.length - 1; @cur_point = 0
516
- else; @cur_point += 1; end
517
- end
518
- end
519
-
520
- private
521
-
522
- def check_contact(obst, ramps)
523
- prev_bottom = @bottom
524
- @top = @bottom = @left = @right = nil
525
- obst.each do |o|
526
- x2 = @x + @w; y2 = @y + @h; x2o = o.x + o.w; y2o = o.y + o.h
527
- @right = o if !o.passable && x2.round(6) == o.x.round(6) && y2 > o.y && @y < y2o
528
- @left = o if !o.passable && @x.round(6) == x2o.round(6) && y2 > o.y && @y < y2o
529
- @bottom = o if y2.round(6) == o.y.round(6) && x2 > o.x && @x < x2o
530
- @top = o if !o.passable && @y.round(6) == y2o.round(6) && x2 > o.x && @x < x2o
531
- end
532
- if @bottom.nil?
533
- ramps.each do |r|
534
- if r.contact? self
535
- @bottom = r
536
- break
537
- end
538
- end
539
- if @bottom.nil?
540
- ramps.each do |r|
541
- if r == prev_bottom && @x + @w > r.x && r.x + r.w > @x &&
542
- @prev_speed.x.abs <= G.ramp_contact_threshold &&
543
- @prev_speed.y >= 0
544
- @y = r.get_y self
545
- @bottom = r
546
- break
547
- end
548
- end
549
- end
550
- end
551
- end
552
-
553
- def find_right_limit(coll_list)
554
- limit = @x + @w + @speed.x
555
- coll_list.each do |c|
556
- limit = c.x if !c.passable && c.x < limit
557
- end
558
- limit
559
- end
560
-
561
- def find_left_limit(coll_list)
562
- limit = @x + @speed.x
563
- coll_list.each do |c|
564
- limit = c.x + c.w if !c.passable && c.x + c.w > limit
565
- end
566
- limit
567
- end
568
-
569
- def find_down_limit(coll_list)
570
- limit = @y + @h + @speed.y
571
- coll_list.each do |c|
572
- limit = c.y if c.y < limit && c.y >= @y + @h
573
- end
574
- limit
575
- end
576
-
577
- def find_up_limit(coll_list)
578
- limit = @y + @speed.y
579
- coll_list.each do |c|
580
- limit = c.y + c.h if !c.passable && c.y + c.h > limit
581
- end
582
- limit
583
- end
584
- end
585
- end
1
+ require_relative 'global'
2
+
3
+ module MiniGL
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. Default is +false+.
34
+ def initialize(x, y, w, h, passable = false)
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
+ attr_reader :ratio # :nodoc:
70
+ attr_reader :factor # :nodoc:
71
+
72
+ # Creates a new ramp.
73
+ #
74
+ # Parameters:
75
+ # [x] The x-coordinate of the top left corner of a rectangle that
76
+ # completely (and precisely) encloses the ramp (thought of as a right
77
+ # triangle).
78
+ # [y] The y-coordinate of the top left corner of the rectangle described
79
+ # above.
80
+ # [w] The width of the ramp (which corresponds to the width of the
81
+ # rectangle described above).
82
+ # [h] The height of the ramp (which corresponds to the height of the
83
+ # rectangle described above, and to the difference between the lowest
84
+ # point of the ramp, where it usually meets the floor, and the
85
+ # highest).
86
+ # [left] Whether the height of the ramp increases from left to right. Use
87
+ # +false+ for a ramp that goes down from left to right.
88
+ def initialize(x, y, w, h, left)
89
+ @x = x
90
+ @y = y
91
+ @w = w
92
+ @h = h
93
+ @left = left
94
+ @ratio = @h.to_f / @w
95
+ @factor = @w / Math.sqrt(@w**2 + @h**2)
96
+ end
97
+
98
+ # Checks if an object is in contact with this ramp (standing over it).
99
+ #
100
+ # Parameters:
101
+ # [obj] The object to check contact with. It must have the +x+, +y+, +w+
102
+ # and +h+ accessible attributes determining its bounding box.
103
+ def contact?(obj)
104
+ 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)
105
+ end
106
+
107
+ # Checks if an object is intersecting this ramp (inside the corresponding
108
+ # right triangle and at the floor level or above).
109
+ #
110
+ # Parameters:
111
+ # [obj] The object to check intersection with. It must have the +x+, +y+,
112
+ # +w+ and +h+ accessible attributes determining its bounding box.
113
+ def intersect?(obj)
114
+ obj.x + obj.w > @x && obj.x < @x + @w && obj.y > get_y(obj) && obj.y <= @y + @h - obj.h
115
+ end
116
+
117
+ # :nodoc:
118
+ def check_can_collide(m)
119
+ y = get_y(m) + m.h
120
+ @can_collide = m.x + m.w > @x && @x + @w > m.x && m.y < y && m.y + m.h > y
121
+ end
122
+
123
+ def check_intersection(obj)
124
+ if @can_collide and intersect? obj
125
+ counter = @left && obj.prev_speed.x > 0 || !@left && obj.prev_speed.x < 0
126
+ if obj.prev_speed.y > 0 && counter
127
+ dx = get_x(obj) - obj.x
128
+ s = (obj.prev_speed.y.to_f / obj.prev_speed.x).abs
129
+ dx /= s + @ratio
130
+ obj.x += dx
131
+ end
132
+ obj.y = get_y obj
133
+ if counter && obj.bottom != self
134
+ obj.speed.x *= @factor
135
+ end
136
+ obj.speed.y = 0
137
+ end
138
+ end
139
+
140
+ def get_x(obj)
141
+ return obj.x if @left && obj.x + obj.w > @x + @w
142
+ return @x + (1.0 * (@y + @h - obj.y - obj.h) * @w / @h) - obj.w if @left
143
+ return obj.x if obj.x < @x
144
+ @x + (1.0 * (obj.y + obj.h - @y) * @w / @h)
145
+ end
146
+
147
+ def get_y(obj)
148
+ return @y - obj.h if @left && obj.x + obj.w > @x + @w
149
+ return @y + (1.0 * (@x + @w - obj.x - obj.w) * @h / @w) - obj.h if @left
150
+ return @y - obj.h if obj.x < @x
151
+ @y + (1.0 * (obj.x - @x) * @h / @w) - obj.h
152
+ end
153
+ end
154
+
155
+ # This module provides objects with physical properties and methods for
156
+ # moving. It allows moving with or without collision checking (based on
157
+ # rectangular bounding boxes), including a method to behave as an elevator,
158
+ # affecting other objects' positions as it moves.
159
+ module Movement
160
+ # The mass of the object, in arbitrary units. The default value for
161
+ # GameObject instances, for example, is 1. The larger the mass (i.e., the
162
+ # heavier the object), the more intense the forces applied to the object
163
+ # have to be in order to move it.
164
+ attr_reader :mass
165
+
166
+ # A Vector with the current speed of the object (x: horizontal component,
167
+ # y: vertical component).
168
+ attr_reader :speed
169
+
170
+ # A Vector with the speed limits for the object (x: horizontal component,
171
+ # y: vertical component).
172
+ attr_reader :max_speed
173
+
174
+ # Width of the bounding box.
175
+ attr_reader :w
176
+
177
+ # Height of the bounding box.
178
+ attr_reader :h
179
+
180
+ # The object that is making contact with this from above. If there's no
181
+ # contact, returns +nil+.
182
+ attr_reader :top
183
+
184
+ # The object that is making contact with this from below. If there's no
185
+ # contact, returns +nil+.
186
+ attr_reader :bottom
187
+
188
+ # The object that is making contact with this from the left. If there's no
189
+ # contact, returns +nil+.
190
+ attr_reader :left
191
+
192
+ # The object that is making contact with this from the right. If there's
193
+ # no contact, returns +nil+.
194
+ attr_reader :right
195
+
196
+ # The x-coordinate of the top left corner of the bounding box.
197
+ attr_accessor :x
198
+
199
+ # The y-coordinate of the top left corner of the bounding box.
200
+ attr_accessor :y
201
+
202
+ # Whether a moving object can pass through this block when coming from
203
+ # below. This is a common feature of platforms in platform games.
204
+ attr_accessor :passable
205
+
206
+ # A Vector with the horizontal and vertical components of a force that
207
+ # be applied in the next time +move+ is called.
208
+ attr_accessor :stored_forces
209
+
210
+ # A Vector containing the speed of the object in the previous frame.
211
+ attr_reader :prev_speed
212
+
213
+ # Returns the bounding box as a Rectangle.
214
+ def bounds
215
+ Rectangle.new @x, @y, @w, @h
216
+ end
217
+
218
+ # Moves this object, based on the forces being applied to it, and
219
+ # performing collision checking.
220
+ #
221
+ # Parameters:
222
+ # [forces] A Vector where x is the horizontal component of the resulting
223
+ # force and y is the vertical component.
224
+ # [obst] An array of obstacles to be considered in the collision checking.
225
+ # Obstacles must be instances of Block (or derived classes), or
226
+ # objects that <code>include Movement</code>.
227
+ # [ramps] An array of ramps to be considered in the collision checking.
228
+ # Ramps must be instances of Ramp (or derived classes).
229
+ # [set_speed] Set this flag to +true+ to cause the +forces+ vector to be
230
+ # treated as a speed vector, i.e., the object's speed will be
231
+ # directly set to the given values. The force of gravity will
232
+ # also be ignored in this case.
233
+ def move(forces, obst, ramps, set_speed = false)
234
+ if set_speed
235
+ @speed.x = forces.x
236
+ @speed.y = forces.y
237
+ else
238
+ forces.x += G.gravity.x; forces.y += G.gravity.y
239
+ forces.x += @stored_forces.x; forces.y += @stored_forces.y
240
+ @stored_forces.x = @stored_forces.y = 0
241
+
242
+ forces.x = 0 if (forces.x < 0 and @left) or (forces.x > 0 and @right)
243
+ forces.y = 0 if (forces.y < 0 and @top) or (forces.y > 0 and @bottom)
244
+
245
+ if @bottom.is_a? Ramp
246
+ if @bottom.ratio > G.ramp_slip_threshold
247
+ forces.x += (@bottom.left ? -1 : 1) * (@bottom.ratio - G.ramp_slip_threshold) * G.ramp_slip_force / G.ramp_slip_threshold
248
+ elsif forces.x > 0 && @bottom.left || forces.x < 0 && !@bottom.left
249
+ forces.x *= @bottom.factor
250
+ end
251
+ end
252
+
253
+ @speed.x += forces.x / @mass; @speed.y += forces.y / @mass
254
+ end
255
+
256
+ @speed.x = 0 if @speed.x.abs < G.min_speed.x
257
+ @speed.y = 0 if @speed.y.abs < G.min_speed.y
258
+ @speed.x = (@speed.x <=> 0) * @max_speed.x if @speed.x.abs > @max_speed.x
259
+ @speed.y = (@speed.y <=> 0) * @max_speed.y if @speed.y.abs > @max_speed.y
260
+ @prev_speed = @speed.clone
261
+
262
+ x = @speed.x < 0 ? @x + @speed.x : @x
263
+ y = @speed.y < 0 ? @y + @speed.y : @y
264
+ w = @w + (@speed.x < 0 ? -@speed.x : @speed.x)
265
+ h = @h + (@speed.y < 0 ? -@speed.y : @speed.y)
266
+ move_bounds = Rectangle.new x, y, w, h
267
+ coll_list = []
268
+ obst.each do |o|
269
+ coll_list << o if o != self && move_bounds.intersect?(o.bounds)
270
+ end
271
+ ramps.each do |r|
272
+ r.check_can_collide move_bounds
273
+ end
274
+
275
+ if coll_list.length > 0
276
+ up = @speed.y < 0; rt = @speed.x > 0; dn = @speed.y > 0; lf = @speed.x < 0
277
+ if @speed.x == 0 || @speed.y == 0
278
+ # Ortogonal
279
+ if rt; x_lim = find_right_limit coll_list
280
+ elsif lf; x_lim = find_left_limit coll_list
281
+ elsif dn; y_lim = find_down_limit coll_list
282
+ elsif up; y_lim = find_up_limit coll_list
283
+ end
284
+ if rt && @x + @w + @speed.x > x_lim
285
+ @x = x_lim - @w
286
+ @speed.x = 0
287
+ elsif lf && @x + @speed.x < x_lim
288
+ @x = x_lim
289
+ @speed.x = 0
290
+ elsif dn && @y + @h + @speed.y > y_lim; @y = y_lim - @h; @speed.y = 0
291
+ elsif up && @y + @speed.y < y_lim; @y = y_lim; @speed.y = 0
292
+ end
293
+ else
294
+ # Diagonal
295
+ x_aim = @x + @speed.x + (rt ? @w : 0); x_lim_def = x_aim
296
+ y_aim = @y + @speed.y + (dn ? @h : 0); y_lim_def = y_aim
297
+ coll_list.each do |c|
298
+ if c.passable; x_lim = x_aim
299
+ elsif rt; x_lim = c.x
300
+ else; x_lim = c.x + c.w
301
+ end
302
+ if dn; y_lim = c.y
303
+ elsif c.passable; y_lim = y_aim
304
+ else; y_lim = c.y + c.h
305
+ end
306
+
307
+ if c.passable
308
+ y_lim_def = y_lim if dn && @y + @h <= y_lim && y_lim < y_lim_def
309
+ elsif (rt && @x + @w > x_lim) || (lf && @x < x_lim)
310
+ # Can't limit by x, will limit by y
311
+ y_lim_def = y_lim if (dn && y_lim < y_lim_def) || (up && y_lim > y_lim_def)
312
+ elsif (dn && @y + @h > y_lim) || (up && @y < y_lim)
313
+ # Can't limit by y, will limit by x
314
+ x_lim_def = x_lim if (rt && x_lim < x_lim_def) || (lf && x_lim > x_lim_def)
315
+ else
316
+ x_time = 1.0 * (x_lim - @x - (@speed.x < 0 ? 0 : @w)) / @speed.x
317
+ y_time = 1.0 * (y_lim - @y - (@speed.y < 0 ? 0 : @h)) / @speed.y
318
+ if x_time > y_time
319
+ # Will limit by x
320
+ x_lim_def = x_lim if (rt && x_lim < x_lim_def) || (lf && x_lim > x_lim_def)
321
+ elsif (dn && y_lim < y_lim_def) || (up && y_lim > y_lim_def)
322
+ y_lim_def = y_lim
323
+ end
324
+ end
325
+ end
326
+ if x_lim_def != x_aim
327
+ @speed.x = 0
328
+ if lf; @x = x_lim_def
329
+ else; @x = x_lim_def - @w
330
+ end
331
+ end
332
+ if y_lim_def != y_aim
333
+ @speed.y = 0
334
+ if up; @y = y_lim_def
335
+ else; @y = y_lim_def - @h
336
+ end
337
+ end
338
+ end
339
+ end
340
+ @x += @speed.x
341
+ @y += @speed.y
342
+
343
+ # Keeping contact with ramp
344
+ # if @speed.y == 0 and @speed.x.abs <= G.ramp_contact_threshold and @bottom.is_a? Ramp
345
+ # @y = @bottom.get_y(self)
346
+ # puts 'aqui'
347
+ # end
348
+ ramps.each do |r|
349
+ r.check_intersection self
350
+ end
351
+ check_contact obst, ramps
352
+ end
353
+
354
+ # Moves this object as an elevator (i.e., potentially carrying other
355
+ # objects) with the specified forces or towards a given point.
356
+ #
357
+ # Parameters:
358
+ # [arg] A Vector specifying either the forces acting on this object or a
359
+ # point towards the object should move.
360
+ # [speed] If the first argument is a forces vector, then this should be
361
+ # +nil+. If it is a point, then this is the constant speed at which
362
+ # the object will move (provided as a scalar, not a vector).
363
+ # [obstacles] An array of obstacles to be considered in the collision
364
+ # checking, and carried along when colliding from above.
365
+ # Obstacles must be instances of Block (or derived classes),
366
+ # or objects that <code>include Movement</code>.
367
+ # [obst_obstacles] Obstacles that should be considered when moving objects
368
+ # from the +obstacles+ array, i.e., these obstacles won't
369
+ # interfere in the elevator's movement, but in the movement
370
+ # of the objects being carried.
371
+ # [obst_ramps] Ramps to consider when moving objects from the +obstacles+
372
+ # array, as described for +obst_obstacles+.
373
+ def move_carrying(arg, speed, obstacles, obst_obstacles, obst_ramps)
374
+ if speed
375
+ x_d = arg.x - @x; y_d = arg.y - @y
376
+ distance = Math.sqrt(x_d**2 + y_d**2)
377
+
378
+ if distance == 0
379
+ @speed.x = @speed.y = 0
380
+ return
381
+ end
382
+
383
+ @speed.x = 1.0 * x_d * speed / distance
384
+ @speed.y = 1.0 * y_d * speed / distance
385
+ else
386
+ arg += G.gravity
387
+ @speed.x += arg.x / @mass; @speed.y += arg.y / @mass
388
+ @speed.x = 0 if @speed.x.abs < G.min_speed.x
389
+ @speed.y = 0 if @speed.y.abs < G.min_speed.y
390
+ @speed.x = (@speed.x <=> 0) * @max_speed.x if @speed.x.abs > @max_speed.x
391
+ @speed.y = (@speed.y <=> 0) * @max_speed.y if @speed.y.abs > @max_speed.y
392
+ end
393
+
394
+ x_aim = @x + @speed.x; y_aim = @y + @speed.y
395
+ passengers = []
396
+ obstacles.each do |o|
397
+ if @x + @w > o.x && o.x + o.w > @x
398
+ foot = o.y + o.h
399
+ if foot.round(6) == @y.round(6) || @speed.y < 0 && foot < @y && foot > y_aim
400
+ passengers << o
401
+ end
402
+ end
403
+ end
404
+
405
+ prev_x = @x; prev_y = @y
406
+ if speed
407
+ if @speed.x > 0 && x_aim >= arg.x || @speed.x < 0 && x_aim <= arg.x
408
+ @x = arg.x; @speed.x = 0
409
+ else
410
+ @x = x_aim
411
+ end
412
+ if @speed.y > 0 && y_aim >= arg.y || @speed.y < 0 && y_aim <= arg.y
413
+ @y = arg.y; @speed.y = 0
414
+ else
415
+ @y = y_aim
416
+ end
417
+ else
418
+ @x = x_aim; @y = y_aim
419
+ end
420
+
421
+ forces = Vector.new @x - prev_x, @y - prev_y
422
+ prev_g = G.gravity.clone
423
+ G.gravity.x = G.gravity.y = 0
424
+ passengers.each do |p|
425
+ prev_speed = p.speed.clone
426
+ prev_forces = p.stored_forces.clone
427
+ prev_bottom = p.bottom
428
+ p.speed.x = p.speed.y = 0
429
+ p.stored_forces.x = p.stored_forces.y = 0
430
+ p.instance_exec { @bottom = nil }
431
+ p.move forces * p.mass, obst_obstacles, obst_ramps
432
+ p.speed.x = prev_speed.x
433
+ p.speed.y = prev_speed.y
434
+ p.stored_forces.x = prev_forces.x
435
+ p.stored_forces.y = prev_forces.y
436
+ p.instance_exec(prev_bottom) { |b| @bottom = b }
437
+ end
438
+ G.gravity = prev_g
439
+ end
440
+
441
+ # Moves this object, without performing any collision checking, towards
442
+ # a specified point or in a specified direction.
443
+ #
444
+ # Parameters:
445
+ # [aim] A +Vector+ specifying where the object will move to or an angle (in
446
+ # degrees) indicating the direction of the movement. Angles are
447
+ # measured starting from the right (i.e., to move to the right, the
448
+ # angle must be 0) and raising clockwise.
449
+ # [speed] The constant speed at which the object will move. This must be
450
+ # provided as a scalar, not a vector.
451
+ def move_free(aim, speed)
452
+ if aim.is_a? Vector
453
+ x_d = aim.x - @x; y_d = aim.y - @y
454
+ distance = Math.sqrt(x_d**2 + y_d**2)
455
+
456
+ if distance == 0
457
+ @speed.x = @speed.y = 0
458
+ return
459
+ end
460
+
461
+ @speed.x = 1.0 * x_d * speed / distance
462
+ @speed.y = 1.0 * y_d * speed / distance
463
+
464
+ if (@speed.x < 0 and @x + @speed.x <= aim.x) or (@speed.x >= 0 and @x + @speed.x >= aim.x)
465
+ @x = aim.x
466
+ @speed.x = 0
467
+ else
468
+ @x += @speed.x
469
+ end
470
+
471
+ if (@speed.y < 0 and @y + @speed.y <= aim.y) or (@speed.y >= 0 and @y + @speed.y >= aim.y)
472
+ @y = aim.y
473
+ @speed.y = 0
474
+ else
475
+ @y += @speed.y
476
+ end
477
+ else
478
+ rads = aim * Math::PI / 180
479
+ @speed.x = speed * Math.cos(rads)
480
+ @speed.y = speed * Math.sin(rads)
481
+ @x += @speed.x
482
+ @y += @speed.y
483
+ end
484
+ end
485
+
486
+ # Causes the object to move in cycles across multiple given points (the
487
+ # first point in the array is the first point the object will move towards,
488
+ # so it doesn't need to be equal to the current/initial position). If
489
+ # obstacles are provided, it will behave as an elevator (as in
490
+ # +move_carrying+).
491
+ #
492
+ # Parameters:
493
+ # [points] An array of Vectors representing the path that the object will
494
+ # perform.
495
+ # [speed] The constant speed at which the object will move. This must be
496
+ # provided as a scalar, not a vector.
497
+ # [obstacles] An array of obstacles to be considered in the collision
498
+ # checking, and carried along when colliding from above.
499
+ # Obstacles must be instances of Block (or derived classes),
500
+ # or objects that <code>include Movement</code>.
501
+ # [obst_obstacles] Obstacles that should be considered when moving objects
502
+ # from the +obstacles+ array, i.e., these obstacles won't
503
+ # interfere in the elevator's movement, but in the movement
504
+ # of the objects being carried.
505
+ # [obst_ramps] Ramps to consider when moving objects from the +obstacles+
506
+ # array, as described for +obst_obstacles+.
507
+ def cycle(points, speed, obstacles = nil, obst_obstacles = nil, obst_ramps = nil)
508
+ @cur_point = 0 if @cur_point.nil?
509
+ if obstacles
510
+ move_carrying points[@cur_point], speed, obstacles, obst_obstacles, obst_ramps
511
+ else
512
+ move_free points[@cur_point], speed
513
+ end
514
+ if @speed.x == 0 and @speed.y == 0
515
+ if @cur_point == points.length - 1; @cur_point = 0
516
+ else; @cur_point += 1; end
517
+ end
518
+ end
519
+
520
+ private
521
+
522
+ def check_contact(obst, ramps)
523
+ prev_bottom = @bottom
524
+ @top = @bottom = @left = @right = nil
525
+ obst.each do |o|
526
+ x2 = @x + @w; y2 = @y + @h; x2o = o.x + o.w; y2o = o.y + o.h
527
+ @right = o if !o.passable && x2.round(6) == o.x.round(6) && y2 > o.y && @y < y2o
528
+ @left = o if !o.passable && @x.round(6) == x2o.round(6) && y2 > o.y && @y < y2o
529
+ @bottom = o if y2.round(6) == o.y.round(6) && x2 > o.x && @x < x2o
530
+ @top = o if !o.passable && @y.round(6) == y2o.round(6) && x2 > o.x && @x < x2o
531
+ end
532
+ if @bottom.nil?
533
+ ramps.each do |r|
534
+ if r.contact? self
535
+ @bottom = r
536
+ break
537
+ end
538
+ end
539
+ if @bottom.nil?
540
+ ramps.each do |r|
541
+ if r == prev_bottom && @x + @w > r.x && r.x + r.w > @x &&
542
+ @prev_speed.x.abs <= G.ramp_contact_threshold &&
543
+ @prev_speed.y >= 0
544
+ @y = r.get_y self
545
+ @bottom = r
546
+ break
547
+ end
548
+ end
549
+ end
550
+ end
551
+ end
552
+
553
+ def find_right_limit(coll_list)
554
+ limit = @x + @w + @speed.x
555
+ coll_list.each do |c|
556
+ limit = c.x if !c.passable && c.x < limit
557
+ end
558
+ limit
559
+ end
560
+
561
+ def find_left_limit(coll_list)
562
+ limit = @x + @speed.x
563
+ coll_list.each do |c|
564
+ limit = c.x + c.w if !c.passable && c.x + c.w > limit
565
+ end
566
+ limit
567
+ end
568
+
569
+ def find_down_limit(coll_list)
570
+ limit = @y + @h + @speed.y
571
+ coll_list.each do |c|
572
+ limit = c.y if c.y < limit && c.y >= @y + @h
573
+ end
574
+ limit
575
+ end
576
+
577
+ def find_up_limit(coll_list)
578
+ limit = @y + @speed.y
579
+ coll_list.each do |c|
580
+ limit = c.y + c.h if !c.passable && c.y + c.h > limit
581
+ end
582
+ limit
583
+ end
584
+ end
585
+ end