minigl 2.2.2 → 2.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,729 +1,729 @@
1
- require 'gosu'
2
-
3
- # The main module of the library, used only as a namespace.
4
- module MiniGL
5
- # This class represents a point or vector in a bidimensional space.
6
- class Vector
7
- # The x coordinate of the vector
8
- attr_accessor :x
9
-
10
- # The y coordinate of the vector
11
- attr_accessor :y
12
-
13
- # Creates a new bidimensional vector.
14
- #
15
- # Parameters:
16
- # [x] The x coordinate of the vector
17
- # [y] The y coordinate of the vector
18
- def initialize(x = 0, y = 0)
19
- @x = x
20
- @y = y
21
- end
22
-
23
- # Returns +true+ if both coordinates of this vector are equal to the
24
- # corresponding coordinates of +other_vector+, with +precision+ decimal
25
- # places of precision.
26
- def ==(other_vector, precision = 6)
27
- @x.round(precision) == other_vector.x.round(precision) and
28
- @y.round(precision) == other_vector.y.round(precision)
29
- end
30
-
31
- # Returns +true+ if at least one coordinate of this vector is different from
32
- # the corresponding coordinate of +other_vector+, with +precision+ decimal
33
- # places of precision.
34
- def !=(other_vector, precision = 6)
35
- @x.round(precision) != other_vector.x.round(precision) or
36
- @y.round(precision) != other_vector.y.round(precision)
37
- end
38
-
39
- # Sums this vector with +other_vector+, i.e., sums each coordinate of this
40
- # vector with the corresponding coordinate of +other_vector+.
41
- def +(other_vector)
42
- Vector.new @x + other_vector.x, @y + other_vector.y
43
- end
44
-
45
- # Subtracts +other_vector+ from this vector, i.e., subtracts from each
46
- # coordinate of this vector the corresponding coordinate of +other_vector+.
47
- def -(other_vector)
48
- Vector.new @x - other_vector.x, @y - other_vector.y
49
- end
50
-
51
- # Multiplies this vector by a scalar, i.e., each coordinate is multiplied by
52
- # the given number.
53
- def *(scalar)
54
- Vector.new @x * scalar, @y * scalar
55
- end
56
-
57
- # Divides this vector by a scalar, i.e., each coordinate is divided by the
58
- # given number.
59
- def /(scalar)
60
- Vector.new @x / scalar.to_f, @y / scalar.to_f
61
- end
62
-
63
- # Returns the euclidean distance between this vector and +other_vector+.
64
- def distance(other_vector)
65
- dx = @x - other_vector.x
66
- dy = @y - other_vector.y
67
- Math.sqrt(dx ** 2 + dy ** 2)
68
- end
69
-
70
- # Returns a vector corresponding to the rotation of this vector around the
71
- # origin (0, 0) by +radians+ radians.
72
- def rotate(radians)
73
- sin = Math.sin radians
74
- cos = Math.cos radians
75
- Vector.new cos * @x - sin * @y, sin * @x + cos * @y
76
- end
77
-
78
- # Rotates this vector by +radians+ radians around the origin (0, 0).
79
- def rotate!(radians)
80
- sin = Math.sin radians
81
- cos = Math.cos radians
82
- prev_x = @x
83
- @x = cos * @x - sin * @y
84
- @y = sin * prev_x + cos * @y
85
- end
86
- end
87
-
88
- # This class represents a rectangle by its x and y coordinates and width and
89
- # height.
90
- class Rectangle
91
- # The x-coordinate of the rectangle.
92
- attr_accessor :x
93
-
94
- # The y-coordinate of the rectangle.
95
- attr_accessor :y
96
-
97
- # The width of the rectangle.
98
- attr_accessor :w
99
-
100
- # The height of the rectangle.
101
- attr_accessor :h
102
-
103
- # Creates a new rectangle.
104
- #
105
- # Parameters:
106
- # [x] The x-coordinate of the rectangle.
107
- # [y] The y-coordinate of the rectangle.
108
- # [w] The width of the rectangle.
109
- # [h] The height of the rectangle.
110
- def initialize(x, y, w, h)
111
- @x = x; @y = y; @w = w; @h = h
112
- end
113
-
114
- # Returns whether this rectangle intersects another.
115
- #
116
- # Parameters:
117
- # [r] The rectangle to check intersection with.
118
- def intersect?(r)
119
- @x < r.x + r.w && @x + @w > r.x && @y < r.y + r.h && @y + @h > r.y
120
- end
121
- end
122
-
123
- # This module contains references to global objects/constants used by MiniGL.
124
- module G
125
- class << self
126
- # A reference to the game window.
127
- attr_accessor :window
128
-
129
- # Gets or sets the value of gravity. See
130
- # <code>GameWindow#initialize</code> for details.
131
- attr_accessor :gravity
132
-
133
- # Gets or sets the value of min_speed. See
134
- # <code>GameWindow#initialize</code> for details.
135
- attr_accessor :min_speed
136
-
137
- # Gets or sets the value of ramp_contact_threshold. See
138
- # <code>GameWindow#initialize</code> for details.
139
- attr_accessor :ramp_contact_threshold
140
-
141
- # Gets or sets the value of ramp_slip_threshold. See
142
- # <code>GameWindow#initialize</code> for details.
143
- attr_accessor :ramp_slip_threshold
144
-
145
- # Gets or sets the value of ramp_slip_force. See
146
- # <code>GameWindow#initialize</code> for details.
147
- attr_accessor :ramp_slip_force
148
-
149
- # Gets or sets the value of kb_held_delay. See
150
- # <code>GameWindow#initialize</code> for details.
151
- attr_accessor :kb_held_delay
152
-
153
- # Gets or sets the value of kb_held_interval. See
154
- # <code>GameWindow#initialize</code> for details.
155
- attr_accessor :kb_held_interval
156
-
157
- # Gets or sets the value of double_click_delay. See
158
- # <code>GameWindow#initialize</code> for details.
159
- attr_accessor :double_click_delay
160
- end
161
- end
162
-
163
- # The main class for a MiniGL game, holds references to globally accessible
164
- # objects and constants.
165
- class GameWindow < Gosu::Window
166
- # Creates a game window (initializing a game with all MiniGL features
167
- # enabled).
168
- #
169
- # Parameters:
170
- # [scr_w] Width of the window, in pixels.
171
- # [scr_h] Height of the window, in pixels.
172
- # [fullscreen] Whether the window must be initialized in full screen mode.
173
- # [gravity] A Vector object representing the horizontal and vertical
174
- # components of the force of gravity. Essentially, this force
175
- # will be applied to every object which calls +move+, from the
176
- # Movement module.
177
- # [min_speed] A Vector with the minimum speed for moving objects, i.e., the
178
- # value below which the speed will be rounded to zero.
179
- # [ramp_contact_threshold] The maximum horizontal movement an object can
180
- # perform in a single frame and keep contact with a
181
- # ramp when it's above one.
182
- # [ramp_slip_threshold] The maximum ratio between height and width of a ramp
183
- # above which the objects will always slip down when
184
- # trying to 'climb' that ramp.
185
- # [ramp_slip_force] The force that will be applied in the horizontal
186
- # direction when the object is slipping from a steep ramp.
187
- # [kb_held_delay] The number of frames a key must be held by the user
188
- # before the "held" event (that can be checked with
189
- # <code>KB.key_held?</code>) starts to trigger.
190
- # [kb_held_interval] The interval, in frames, between each triggering of
191
- # the "held" event, after the key has been held for
192
- # more than +kb_held_delay+ frames.
193
- # [double_click_delay] The maximum interval, in frames, between two
194
- # clicks, to trigger the "double click" event
195
- # (checked with <code>Mouse.double_click?</code>).
196
- #
197
- # *Obs.:* This method accepts named parameters, but +scr_w+ and +scr_h+ are
198
- # mandatory.
199
- def initialize(scr_w, scr_h = nil, fullscreen = true,
200
- gravity = Vector.new(0, 1), min_speed = Vector.new(0.01, 0.01),
201
- ramp_contact_threshold = 4, ramp_slip_threshold = 1, ramp_slip_force = 1,
202
- kb_held_delay = 40, kb_held_interval = 5, double_click_delay = 8)
203
- if scr_w.is_a? Hash
204
- scr_h = scr_w[:scr_h]
205
- fullscreen = scr_w.fetch(:fullscreen, true)
206
- gravity = scr_w.fetch(:gravity, Vector.new(0, 1))
207
- min_speed = scr_w.fetch(:min_speed, Vector.new(0.01, 0.01))
208
- ramp_contact_threshold = scr_w.fetch(:ramp_contact_threshold, 4)
209
- ramp_slip_threshold = scr_w.fetch(:ramp_slip_threshold, 1.1)
210
- ramp_slip_force = scr_w.fetch(:ramp_slip_force, 0.1)
211
- kb_held_delay = scr_w.fetch(:kb_held_delay, 40)
212
- kb_held_interval = scr_w.fetch(:kb_held_interval, 5)
213
- double_click_delay = scr_w.fetch(:double_click_delay, 8)
214
- scr_w = scr_w[:scr_w]
215
- end
216
-
217
- super scr_w, scr_h, fullscreen
218
- G.window = self
219
- G.gravity = gravity
220
- G.min_speed = min_speed
221
- G.ramp_contact_threshold = ramp_contact_threshold
222
- G.ramp_slip_threshold = ramp_slip_threshold
223
- G.ramp_slip_force = ramp_slip_force
224
- G.kb_held_delay = kb_held_delay
225
- G.kb_held_interval = kb_held_interval
226
- G.double_click_delay = double_click_delay
227
- KB.initialize
228
- Mouse.initialize
229
- Res.initialize
230
- end
231
-
232
- # Draws a rectangle with the size of the entire screen, in the given color.
233
- #
234
- # Parameters:
235
- # [color] Color of the rectangle to be drawn, in hexadecimal RRGGBB format.
236
- def clear(color)
237
- color |= 0xff000000
238
- draw_quad 0, 0, color,
239
- width, 0, color,
240
- width, height, color,
241
- 0, height, color, 0
242
- end
243
-
244
- # Toggles the window between windowed and full screen mode.
245
- def toggle_fullscreen
246
- self.fullscreen = !fullscreen?
247
- end
248
- end
249
-
250
- #class JSHelper
251
-
252
- # Exposes methods for controlling keyboard events.
253
- module KB
254
- class << self
255
- # This is called by <code>GameWindow.initialize</code>. Don't call it
256
- # explicitly.
257
- def initialize
258
- @keys = [
259
- Gosu::KB_A, Gosu::KB_B, Gosu::KB_C, Gosu::KB_D, Gosu::KB_E, Gosu::KB_F,
260
- Gosu::KB_G, Gosu::KB_H, Gosu::KB_I, Gosu::KB_J, Gosu::KB_K, Gosu::KB_L,
261
- Gosu::KB_M, Gosu::KB_N, Gosu::KB_O, Gosu::KB_P, Gosu::KB_Q, Gosu::KB_R,
262
- Gosu::KB_S, Gosu::KB_T, Gosu::KB_U, Gosu::KB_V, Gosu::KB_W, Gosu::KB_X,
263
- Gosu::KB_Y, Gosu::KB_Z, Gosu::KB_1, Gosu::KB_2, Gosu::KB_3, Gosu::KB_4,
264
- Gosu::KB_5, Gosu::KB_6, Gosu::KB_7, Gosu::KB_8, Gosu::KB_9, Gosu::KB_0,
265
- Gosu::KB_NUMPAD_1, Gosu::KB_NUMPAD_2, Gosu::KB_NUMPAD_3, Gosu::KB_NUMPAD_4,
266
- Gosu::KB_NUMPAD_5, Gosu::KB_NUMPAD_6, Gosu::KB_NUMPAD_7, Gosu::KB_NUMPAD_8,
267
- Gosu::KB_NUMPAD_9, Gosu::KB_NUMPAD_0, Gosu::KB_F1, Gosu::KB_F2,
268
- Gosu::KB_F3, Gosu::KB_F4, Gosu::KB_F5, Gosu::KB_F6, Gosu::KB_F7,
269
- Gosu::KB_F8, Gosu::KB_F9, Gosu::KB_F10, Gosu::KB_F11, Gosu::KB_F12,
270
- Gosu::KB_APOSTROPHE, Gosu::KB_BACKSLASH, Gosu::KB_BACKSPACE,
271
- Gosu::KB_BACKTICK, Gosu::KB_COMMA, Gosu::KB_DELETE, Gosu::KB_DOWN,
272
- Gosu::KB_END, Gosu::KB_ENTER, Gosu::KB_EQUALS, Gosu::KB_ESCAPE,
273
- Gosu::KB_HOME, Gosu::KB_INSERT, Gosu::KB_ISO, Gosu::KB_LEFT,
274
- Gosu::KB_LEFT_ALT, Gosu::KB_LEFT_BRACKET, Gosu::KB_LEFT_CONTROL,
275
- Gosu::KB_LEFT_META, Gosu::KB_LEFT_SHIFT, Gosu::KB_MINUS,
276
- Gosu::KB_NUMPAD_DIVIDE, Gosu::KB_NUMPAD_MINUS,
277
- Gosu::KB_NUMPAD_MULTIPLY, Gosu::KB_NUMPAD_PLUS, Gosu::KB_PAGE_DOWN,
278
- Gosu::KB_PAGE_UP, Gosu::KB_PERIOD, Gosu::KB_RETURN, Gosu::KB_RIGHT,
279
- Gosu::KB_RIGHT_ALT, Gosu::KB_RIGHT_BRACKET, Gosu::KB_RIGHT_CONTROL,
280
- Gosu::KB_RIGHT_META, Gosu::KB_RIGHT_SHIFT, Gosu::KB_SEMICOLON,
281
- Gosu::KB_SLASH, Gosu::KB_SPACE, Gosu::KB_TAB, Gosu::KB_UP
282
- ]
283
- @down = []
284
- @prev_down = []
285
- @held_timer = {}
286
- @held_interval = {}
287
- end
288
-
289
- # Updates the state of all keys.
290
- def update
291
- @held_timer.each do |k, v|
292
- if v < G.kb_held_delay; @held_timer[k] += 1
293
- else
294
- @held_interval[k] = 0
295
- @held_timer.delete k
296
- end
297
- end
298
-
299
- @held_interval.each do |k, v|
300
- if v < G.kb_held_interval; @held_interval[k] += 1
301
- else; @held_interval[k] = 0; end
302
- end
303
-
304
- @prev_down = @down.clone
305
- @down.clear
306
- @keys.each do |k|
307
- if G.window.button_down? k
308
- @down << k
309
- @held_timer[k] = 0 if @prev_down.index(k).nil?
310
- elsif @prev_down.index(k)
311
- @held_timer.delete k
312
- @held_interval.delete k
313
- end
314
- end
315
- end
316
-
317
- # Returns whether the given key is down in the current frame and was not
318
- # down in the frame before.
319
- #
320
- # Parameters:
321
- # [key] Code of the key to be checked. The available codes are all the
322
- # constants in +Gosu+ started with +KB_+.
323
- def key_pressed?(key)
324
- @prev_down.index(key).nil? and @down.index(key)
325
- end
326
-
327
- # Returns whether the given key is down in the current frame.
328
- #
329
- # Parameters:
330
- # [key] Code of the key to be checked. The available codes are all the
331
- # constants in +Gosu+ started with +KB_+.
332
- def key_down?(key)
333
- @down.index(key)
334
- end
335
-
336
- # Returns whether the given key is not down in the current frame but was
337
- # down in the frame before.
338
- #
339
- # Parameters:
340
- # [key] Code of the key to be checked. The available codes are all the
341
- # constants in +Gosu+ started with +KB_+.
342
- def key_released?(key)
343
- @prev_down.index(key) and @down.index(key).nil?
344
- end
345
-
346
- # Returns whether the given key is being held down. See
347
- # <code>GameWindow.initialize</code> for details.
348
- #
349
- # Parameters:
350
- # [key] Code of the key to be checked. The available codes are all the
351
- # constants in +Gosu+ started with +KB_+.
352
- def key_held?(key)
353
- @held_interval[key] == G.kb_held_interval
354
- end
355
- end
356
- end
357
-
358
- # Exposes methods for controlling mouse events.
359
- module Mouse
360
- class << self
361
- # The current x-coordinate of the mouse cursor in the screen.
362
- attr_reader :x
363
-
364
- # The current y-coordinate of the mouse cursor in the screen.
365
- attr_reader :y
366
-
367
- # This is called by <code>GameWindow.initialize</code>. Don't call it
368
- # explicitly.
369
- def initialize
370
- @down = {}
371
- @prev_down = {}
372
- @dbl_click = {}
373
- @dbl_click_timer = {}
374
- end
375
-
376
- # Updates the mouse position and the state of all buttons.
377
- def update
378
- @prev_down = @down.clone
379
- @down.clear
380
- @dbl_click.clear
381
-
382
- @dbl_click_timer.each do |k, v|
383
- if v < G.double_click_delay; @dbl_click_timer[k] += 1
384
- else; @dbl_click_timer.delete k; end
385
- end
386
-
387
- k1 = [Gosu::MsLeft, Gosu::MsMiddle, Gosu::MsRight]
388
- k2 = [:left, :middle, :right]
389
- (0..2).each do |i|
390
- if G.window.button_down? k1[i]
391
- @down[k2[i]] = true
392
- @dbl_click[k2[i]] = true if @dbl_click_timer[k2[i]]
393
- @dbl_click_timer.delete k2[i]
394
- elsif @prev_down[k2[i]]
395
- @dbl_click_timer[k2[i]] = 0
396
- end
397
- end
398
-
399
- @x = G.window.mouse_x.round
400
- @y = G.window.mouse_y.round
401
- end
402
-
403
- # Returns whether the given button is down in the current frame and was
404
- # not down in the frame before.
405
- #
406
- # Parameters:
407
- # [btn] Button to be checked. Valid values are +:left+, +:middle+ and
408
- # +:right+
409
- def button_pressed?(btn)
410
- @down[btn] and not @prev_down[btn]
411
- end
412
-
413
- # Returns whether the given button is down in the current frame.
414
- #
415
- # Parameters:
416
- # [btn] Button to be checked. Valid values are +:left+, +:middle+ and
417
- # +:right+
418
- def button_down?(btn)
419
- @down[btn]
420
- end
421
-
422
- # Returns whether the given button is not down in the current frame, but
423
- # was down in the frame before.
424
- #
425
- # Parameters:
426
- # [btn] Button to be checked. Valid values are +:left+, +:middle+ and
427
- # +:right+
428
- def button_released?(btn)
429
- @prev_down[btn] and not @down[btn]
430
- end
431
-
432
- # Returns whether the given button has just been double clicked.
433
- #
434
- # Parameters:
435
- # [btn] Button to be checked. Valid values are +:left+, +:middle+ and
436
- # +:right+
437
- def double_click?(btn)
438
- @dbl_click[btn]
439
- end
440
-
441
- # Returns whether the mouse cursor is currently inside the given area.
442
- #
443
- # Parameters:
444
- # [x] The x-coordinate of the top left corner of the area.
445
- # [y] The y-coordinate of the top left corner of the area.
446
- # [w] The width of the area.
447
- # [h] The height of the area.
448
- #
449
- # <b>Alternate syntax</b>
450
- #
451
- # <code>over?(rectangle)</code>
452
- #
453
- # Parameters:
454
- # [rectangle] A rectangle representing the area to be checked.
455
- def over?(x, y = nil, w = nil, h = nil)
456
- return @x >= x.x && @x < x.x + x.w && @y >= x.y && @y < x.y + x.h if x.is_a? Rectangle
457
- @x >= x && @x < x + w && @y >= y && @y < y + h
458
- end
459
- end
460
- end
461
-
462
- # This class is responsible for resource management. It keeps references to
463
- # all loaded resources until a call to +clear+ is made. Resources can be
464
- # loaded as global, so that their references won't be removed even when
465
- # +clear+ is called.
466
- #
467
- # It also provides an easier syntax for loading resources, assuming a
468
- # particular folder structure. All resources must be inside subdirectories
469
- # of a 'data' directory, so that you will only need to specify the type of
470
- # resource being loaded and the file name (either as string or as symbol).
471
- # There are default extensions for each type of resource, so the extension
472
- # must be specified only if the file is in a format other than the default.
473
- module Res
474
- class << self
475
- # Get the current prefix for searching data files. This is the directory
476
- # under which 'img', 'sound', 'song', etc. folders are located.
477
- attr_reader :prefix
478
-
479
- # Gets the current path to image files (under +prefix+). Default is 'img'.
480
- attr_reader :img_dir
481
-
482
- # Gets the current path to tileset files (under +prefix+). Default is
483
- # 'tileset'.
484
- attr_reader :tileset_dir
485
-
486
- # Gets the current path to sound files (under +prefix+). Default is 'sound'.
487
- attr_reader :sound_dir
488
-
489
- # Gets the current path to song files (under +prefix+). Default is 'song'.
490
- attr_reader :song_dir
491
-
492
- # Gets the current path to font files (under +prefix+). Default is 'font'.
493
- attr_reader :font_dir
494
-
495
- # Gets or sets the character that is currently being used in the +id+
496
- # parameter of the loading methods as a folder separator. Default is '_'.
497
- # Note that if you want to use symbols to specify paths, this separator
498
- # should be a valid character in a Ruby symbol. On the other hand, if you
499
- # want to use only slashes in Strings, you can specify a 'weird' character
500
- # that won't appear in any file name.
501
- attr_accessor :separator
502
-
503
- # Gets or sets a flag that indicates whether images will be loaded with
504
- # the 'retro' option set (see +Gosu::Image+ for details), when this
505
- # option is not specified in a 'Res.img' or 'Res.imgs' call.
506
- attr_accessor :retro_images
507
-
508
- # This is called by <code>GameWindow.initialize</code>. Don't call it
509
- # explicitly.
510
- def initialize
511
- @imgs = {}
512
- @global_imgs = {}
513
- @tilesets = {}
514
- @global_tilesets = {}
515
- @sounds = {}
516
- @global_sounds = {}
517
- @songs = {}
518
- @global_songs = {}
519
- @fonts = {}
520
- @global_fonts = {}
521
-
522
- @prefix = File.expand_path(File.dirname($0)) + '/data/'
523
- @img_dir = 'img/'
524
- @tileset_dir = 'tileset/'
525
- @sound_dir = 'sound/'
526
- @song_dir = 'song/'
527
- @font_dir = 'font/'
528
- @separator = '_'
529
- @retro_images = false
530
- end
531
-
532
- # Set a custom prefix for loading resources. By default, the prefix is the
533
- # directory of the game script. The prefix is the directory under which
534
- # 'img', 'sound', 'song', etc. folders are located.
535
- def prefix=(value)
536
- value += '/' if value != '' and value[-1] != '/'
537
- @prefix = value
538
- end
539
-
540
- # Sets the path to image files (under +prefix+). Default is 'img'.
541
- def img_dir=(value)
542
- value += '/' if value != '' and value[-1] != '/'
543
- @img_dir = value
544
- end
545
-
546
- # Sets the path to tilset files (under +prefix+). Default is 'tileset'.
547
- def tileset_dir=(value)
548
- value += '/' if value != '' and value[-1] != '/'
549
- @tileset_dir = value
550
- end
551
-
552
- # Sets the path to sound files (under +prefix+). Default is 'sound'.
553
- def sound_dir=(value)
554
- value += '/' if value != '' and value[-1] != '/'
555
- @sound_dir = value
556
- end
557
-
558
- # Sets the path to song files (under +prefix+). Default is 'song'.
559
- def song_dir=(value)
560
- value += '/' if value != '' and value[-1] != '/'
561
- @song_dir = value
562
- end
563
-
564
- # Sets the path to font files (under +prefix+). Default is 'font'.
565
- def font_dir=(value)
566
- value += '/' if value != '' and value[-1] != '/'
567
- @font_dir = value
568
- end
569
-
570
- # Returns a <code>Gosu::Image</code> object.
571
- #
572
- # Parameters:
573
- # [id] A string or symbol representing the path to the image. If the file
574
- # is inside +prefix+/+img_dir+, only the file name is needed. If it's
575
- # inside a subdirectory of +prefix+/+img_dir+, the id must be
576
- # prefixed by each subdirectory name followed by +separator+. Example:
577
- # to load 'data/img/sprite/1.png', with the default values of +prefix+,
578
- # +img_dir+ and +separator+, provide +:sprite_1+ or "sprite_1".
579
- # [global] Set to true if you want to keep the image in memory until the
580
- # game execution is finished. If false, the image will be
581
- # released when you call +clear+.
582
- # [tileable] Whether the image should be loaded in tileable mode, which is
583
- # proper for images that will be used as a tile, i.e., that
584
- # will be drawn repeated times, side by side, forming a
585
- # continuous composition.
586
- # [ext] The extension of the file being loaded. Specify only if it is
587
- # other than '.png'.
588
- # [retro] Whether the image should be loaded with the 'retro' option set
589
- # (see +Gosu::Image+ for details). If the value is omitted, the
590
- # +Res.retro_images+ value will be used.
591
- def img(id, global = false, tileable = false, ext = '.png', retro = nil)
592
- a = global ? @global_imgs : @imgs
593
- return a[id] if a[id]
594
- s = @prefix + @img_dir + id.to_s.split(@separator).join('/') + ext
595
- retro = Res.retro_images if retro.nil?
596
- img = Gosu::Image.new s, tileable: tileable, retro: retro
597
- a[id] = img
598
- end
599
-
600
- # Returns an array of <code>Gosu::Image</code> objects, using the image as
601
- # a spritesheet. The image with index 0 will be the top left sprite, and
602
- # the following indices raise first from left to right and then from top
603
- # to bottom.
604
- #
605
- # Parameters:
606
- # [id] A string or symbol representing the path to the image. See +img+
607
- # for details.
608
- # [sprite_cols] Number of columns in the spritesheet.
609
- # [sprite_rows] Number of rows in the spritesheet.
610
- # [global] Set to true if you want to keep the image in memory until the
611
- # game execution is finished. If false, the image will be
612
- # released when you call +clear+.
613
- # [ext] The extension of the file being loaded. Specify only if it is
614
- # other than ".png".
615
- # [retro] Whether the image should be loaded with the 'retro' option set
616
- # (see +Gosu::Image+ for details). If the value is omitted, the
617
- # +Res.retro_images+ value will be used.
618
- def imgs(id, sprite_cols, sprite_rows, global = false, ext = '.png', retro = nil, tileable = false)
619
- a = global ? @global_imgs : @imgs
620
- return a[id] if a[id]
621
- s = @prefix + @img_dir + id.to_s.split(@separator).join('/') + ext
622
- retro = Res.retro_images if retro.nil?
623
- imgs = Gosu::Image.load_tiles s, -sprite_cols, -sprite_rows, tileable: tileable, retro: retro
624
- a[id] = imgs
625
- end
626
-
627
- # Returns an array of <code>Gosu::Image</code> objects, using the image as
628
- # a tileset. Works the same as +imgs+, except you must provide the tile
629
- # size instead of the number of columns and rows, and that the images will
630
- # be loaded as tileable.
631
- #
632
- # Parameters:
633
- # [id] A string or symbol representing the path to the image. It must be
634
- # specified the same way as in +img+, but the base directory is
635
- # +prefix+/+tileset_dir+.
636
- # [tile_width] Width of each tile, in pixels.
637
- # [tile_height] Height of each tile, in pixels.
638
- # [global] Set to true if you want to keep the image in memory until the
639
- # game execution is finished. If false, the image will be
640
- # released when you call +clear+.
641
- # [ext] The extension of the file being loaded. Specify only if it is
642
- # other than ".png".
643
- # [retro] Whether the image should be loaded with the 'retro' option set
644
- # (see +Gosu::Image+ for details). If the value is omitted, the
645
- # +Res.retro_images+ value will be used.
646
- def tileset(id, tile_width = 32, tile_height = 32, global = false, ext = '.png', retro = nil)
647
- a = global ? @global_tilesets : @tilesets
648
- return a[id] if a[id]
649
- s = @prefix + @tileset_dir + id.to_s.split(@separator).join('/') + ext
650
- retro = Res.retro_images if retro.nil?
651
- tileset = Gosu::Image.load_tiles s, tile_width, tile_height, tileable: true, retro: retro
652
- a[id] = tileset
653
- end
654
-
655
- # Returns a <code>Gosu::Sample</code> object. This should be used for
656
- # simple and short sound effects.
657
- #
658
- # Parameters:
659
- # [id] A string or symbol representing the path to the sound. It must be
660
- # specified the same way as in +img+, but the base directory is
661
- # +prefix+/+sound_dir+.
662
- # [global] Set to true if you want to keep the sound in memory until the
663
- # game execution is finished. If false, the sound will be
664
- # released when you call +clear+.
665
- # [ext] The extension of the file being loaded. Specify only if it is
666
- # other than ".wav".
667
- def sound(id, global = false, ext = '.wav')
668
- a = global ? @global_sounds : @sounds
669
- return a[id] if a[id]
670
- s = @prefix + @sound_dir + id.to_s.split(@separator).join('/') + ext
671
- sound = Gosu::Sample.new s
672
- a[id] = sound
673
- end
674
-
675
- # Returns a <code>Gosu::Song</code> object. This should be used for the
676
- # background musics of your game.
677
- #
678
- # Parameters:
679
- # [id] A string or symbol representing the path to the song. It must be
680
- # specified the same way as in +img+, but the base directory is
681
- # +prefix+/+song_dir+.
682
- # [global] Set to true if you want to keep the song in memory until the
683
- # game execution is finished. If false, the song will be released
684
- # when you call +clear+.
685
- # [ext] The extension of the file being loaded. Specify only if it is
686
- # other than ".ogg".
687
- def song(id, global = false, ext = '.ogg')
688
- a = global ? @global_songs : @songs
689
- return a[id] if a[id]
690
- s = @prefix + @song_dir + id.to_s.split(@separator).join('/') + ext
691
- song = Gosu::Song.new s
692
- a[id] = song
693
- end
694
-
695
- # Returns a <code>Gosu::Font</code> object. Fonts are needed to draw text
696
- # and used by MiniGL elements like buttons, text fields and TextHelper
697
- # objects.
698
- #
699
- # Parameters:
700
- # [id] A string or symbol representing the path to the song. It must be
701
- # specified the same way as in +img+, but the base directory is
702
- # +prefix+/+font_dir+.
703
- # [size] The size of the font, in pixels. This will correspond,
704
- # approximately, to the height of the tallest character when drawn.
705
- # [global] Set to true if you want to keep the font in memory until the
706
- # game execution is finished. If false, the font will be released
707
- # when you call +clear+.
708
- # [ext] The extension of the file being loaded. Specify only if it is
709
- # other than ".ttf".
710
- def font(id, size, global = true, ext = '.ttf')
711
- a = global ? @global_fonts : @fonts
712
- id_size = "#{id}_#{size}"
713
- return a[id_size] if a[id_size]
714
- s = @prefix + @font_dir + id.to_s.split(@separator).join('/') + ext
715
- font = Gosu::Font.new size, name: s
716
- a[id_size] = font
717
- end
718
-
719
- # Releases the memory used by all non-global resources.
720
- def clear
721
- @imgs.clear
722
- @tilesets.clear
723
- @sounds.clear
724
- @songs.clear
725
- @fonts.clear
726
- end
727
- end
728
- end
729
- end
1
+ require 'gosu'
2
+
3
+ # The main module of the library, used only as a namespace.
4
+ module MiniGL
5
+ # This class represents a point or vector in a bidimensional space.
6
+ class Vector
7
+ # The x coordinate of the vector
8
+ attr_accessor :x
9
+
10
+ # The y coordinate of the vector
11
+ attr_accessor :y
12
+
13
+ # Creates a new bidimensional vector.
14
+ #
15
+ # Parameters:
16
+ # [x] The x coordinate of the vector
17
+ # [y] The y coordinate of the vector
18
+ def initialize(x = 0, y = 0)
19
+ @x = x
20
+ @y = y
21
+ end
22
+
23
+ # Returns +true+ if both coordinates of this vector are equal to the
24
+ # corresponding coordinates of +other_vector+, with +precision+ decimal
25
+ # places of precision.
26
+ def ==(other_vector, precision = 6)
27
+ @x.round(precision) == other_vector.x.round(precision) and
28
+ @y.round(precision) == other_vector.y.round(precision)
29
+ end
30
+
31
+ # Returns +true+ if at least one coordinate of this vector is different from
32
+ # the corresponding coordinate of +other_vector+, with +precision+ decimal
33
+ # places of precision.
34
+ def !=(other_vector, precision = 6)
35
+ @x.round(precision) != other_vector.x.round(precision) or
36
+ @y.round(precision) != other_vector.y.round(precision)
37
+ end
38
+
39
+ # Sums this vector with +other_vector+, i.e., sums each coordinate of this
40
+ # vector with the corresponding coordinate of +other_vector+.
41
+ def +(other_vector)
42
+ Vector.new @x + other_vector.x, @y + other_vector.y
43
+ end
44
+
45
+ # Subtracts +other_vector+ from this vector, i.e., subtracts from each
46
+ # coordinate of this vector the corresponding coordinate of +other_vector+.
47
+ def -(other_vector)
48
+ Vector.new @x - other_vector.x, @y - other_vector.y
49
+ end
50
+
51
+ # Multiplies this vector by a scalar, i.e., each coordinate is multiplied by
52
+ # the given number.
53
+ def *(scalar)
54
+ Vector.new @x * scalar, @y * scalar
55
+ end
56
+
57
+ # Divides this vector by a scalar, i.e., each coordinate is divided by the
58
+ # given number.
59
+ def /(scalar)
60
+ Vector.new @x / scalar.to_f, @y / scalar.to_f
61
+ end
62
+
63
+ # Returns the euclidean distance between this vector and +other_vector+.
64
+ def distance(other_vector)
65
+ dx = @x - other_vector.x
66
+ dy = @y - other_vector.y
67
+ Math.sqrt(dx ** 2 + dy ** 2)
68
+ end
69
+
70
+ # Returns a vector corresponding to the rotation of this vector around the
71
+ # origin (0, 0) by +radians+ radians.
72
+ def rotate(radians)
73
+ sin = Math.sin radians
74
+ cos = Math.cos radians
75
+ Vector.new cos * @x - sin * @y, sin * @x + cos * @y
76
+ end
77
+
78
+ # Rotates this vector by +radians+ radians around the origin (0, 0).
79
+ def rotate!(radians)
80
+ sin = Math.sin radians
81
+ cos = Math.cos radians
82
+ prev_x = @x
83
+ @x = cos * @x - sin * @y
84
+ @y = sin * prev_x + cos * @y
85
+ end
86
+ end
87
+
88
+ # This class represents a rectangle by its x and y coordinates and width and
89
+ # height.
90
+ class Rectangle
91
+ # The x-coordinate of the rectangle.
92
+ attr_accessor :x
93
+
94
+ # The y-coordinate of the rectangle.
95
+ attr_accessor :y
96
+
97
+ # The width of the rectangle.
98
+ attr_accessor :w
99
+
100
+ # The height of the rectangle.
101
+ attr_accessor :h
102
+
103
+ # Creates a new rectangle.
104
+ #
105
+ # Parameters:
106
+ # [x] The x-coordinate of the rectangle.
107
+ # [y] The y-coordinate of the rectangle.
108
+ # [w] The width of the rectangle.
109
+ # [h] The height of the rectangle.
110
+ def initialize(x, y, w, h)
111
+ @x = x; @y = y; @w = w; @h = h
112
+ end
113
+
114
+ # Returns whether this rectangle intersects another.
115
+ #
116
+ # Parameters:
117
+ # [r] The rectangle to check intersection with.
118
+ def intersect?(r)
119
+ @x < r.x + r.w && @x + @w > r.x && @y < r.y + r.h && @y + @h > r.y
120
+ end
121
+ end
122
+
123
+ # This module contains references to global objects/constants used by MiniGL.
124
+ module G
125
+ class << self
126
+ # A reference to the game window.
127
+ attr_accessor :window
128
+
129
+ # Gets or sets the value of gravity. See
130
+ # <code>GameWindow#initialize</code> for details.
131
+ attr_accessor :gravity
132
+
133
+ # Gets or sets the value of min_speed. See
134
+ # <code>GameWindow#initialize</code> for details.
135
+ attr_accessor :min_speed
136
+
137
+ # Gets or sets the value of ramp_contact_threshold. See
138
+ # <code>GameWindow#initialize</code> for details.
139
+ attr_accessor :ramp_contact_threshold
140
+
141
+ # Gets or sets the value of ramp_slip_threshold. See
142
+ # <code>GameWindow#initialize</code> for details.
143
+ attr_accessor :ramp_slip_threshold
144
+
145
+ # Gets or sets the value of ramp_slip_force. See
146
+ # <code>GameWindow#initialize</code> for details.
147
+ attr_accessor :ramp_slip_force
148
+
149
+ # Gets or sets the value of kb_held_delay. See
150
+ # <code>GameWindow#initialize</code> for details.
151
+ attr_accessor :kb_held_delay
152
+
153
+ # Gets or sets the value of kb_held_interval. See
154
+ # <code>GameWindow#initialize</code> for details.
155
+ attr_accessor :kb_held_interval
156
+
157
+ # Gets or sets the value of double_click_delay. See
158
+ # <code>GameWindow#initialize</code> for details.
159
+ attr_accessor :double_click_delay
160
+ end
161
+ end
162
+
163
+ # The main class for a MiniGL game, holds references to globally accessible
164
+ # objects and constants.
165
+ class GameWindow < Gosu::Window
166
+ # Creates a game window (initializing a game with all MiniGL features
167
+ # enabled).
168
+ #
169
+ # Parameters:
170
+ # [scr_w] Width of the window, in pixels.
171
+ # [scr_h] Height of the window, in pixels.
172
+ # [fullscreen] Whether the window must be initialized in full screen mode.
173
+ # [gravity] A Vector object representing the horizontal and vertical
174
+ # components of the force of gravity. Essentially, this force
175
+ # will be applied to every object which calls +move+, from the
176
+ # Movement module.
177
+ # [min_speed] A Vector with the minimum speed for moving objects, i.e., the
178
+ # value below which the speed will be rounded to zero.
179
+ # [ramp_contact_threshold] The maximum horizontal movement an object can
180
+ # perform in a single frame and keep contact with a
181
+ # ramp when it's above one.
182
+ # [ramp_slip_threshold] The maximum ratio between height and width of a ramp
183
+ # above which the objects will always slip down when
184
+ # trying to 'climb' that ramp.
185
+ # [ramp_slip_force] The force that will be applied in the horizontal
186
+ # direction when the object is slipping from a steep ramp.
187
+ # [kb_held_delay] The number of frames a key must be held by the user
188
+ # before the "held" event (that can be checked with
189
+ # <code>KB.key_held?</code>) starts to trigger.
190
+ # [kb_held_interval] The interval, in frames, between each triggering of
191
+ # the "held" event, after the key has been held for
192
+ # more than +kb_held_delay+ frames.
193
+ # [double_click_delay] The maximum interval, in frames, between two
194
+ # clicks, to trigger the "double click" event
195
+ # (checked with <code>Mouse.double_click?</code>).
196
+ #
197
+ # *Obs.:* This method accepts named parameters, but +scr_w+ and +scr_h+ are
198
+ # mandatory.
199
+ def initialize(scr_w, scr_h = nil, fullscreen = true,
200
+ gravity = Vector.new(0, 1), min_speed = Vector.new(0.01, 0.01),
201
+ ramp_contact_threshold = 4, ramp_slip_threshold = 1, ramp_slip_force = 1,
202
+ kb_held_delay = 40, kb_held_interval = 5, double_click_delay = 8)
203
+ if scr_w.is_a? Hash
204
+ scr_h = scr_w[:scr_h]
205
+ fullscreen = scr_w.fetch(:fullscreen, true)
206
+ gravity = scr_w.fetch(:gravity, Vector.new(0, 1))
207
+ min_speed = scr_w.fetch(:min_speed, Vector.new(0.01, 0.01))
208
+ ramp_contact_threshold = scr_w.fetch(:ramp_contact_threshold, 4)
209
+ ramp_slip_threshold = scr_w.fetch(:ramp_slip_threshold, 1.1)
210
+ ramp_slip_force = scr_w.fetch(:ramp_slip_force, 0.1)
211
+ kb_held_delay = scr_w.fetch(:kb_held_delay, 40)
212
+ kb_held_interval = scr_w.fetch(:kb_held_interval, 5)
213
+ double_click_delay = scr_w.fetch(:double_click_delay, 8)
214
+ scr_w = scr_w[:scr_w]
215
+ end
216
+
217
+ super scr_w, scr_h, fullscreen
218
+ G.window = self
219
+ G.gravity = gravity
220
+ G.min_speed = min_speed
221
+ G.ramp_contact_threshold = ramp_contact_threshold
222
+ G.ramp_slip_threshold = ramp_slip_threshold
223
+ G.ramp_slip_force = ramp_slip_force
224
+ G.kb_held_delay = kb_held_delay
225
+ G.kb_held_interval = kb_held_interval
226
+ G.double_click_delay = double_click_delay
227
+ KB.initialize
228
+ Mouse.initialize
229
+ Res.initialize
230
+ end
231
+
232
+ # Draws a rectangle with the size of the entire screen, in the given color.
233
+ #
234
+ # Parameters:
235
+ # [color] Color of the rectangle to be drawn, in hexadecimal RRGGBB format.
236
+ def clear(color)
237
+ color |= 0xff000000
238
+ draw_quad 0, 0, color,
239
+ width, 0, color,
240
+ width, height, color,
241
+ 0, height, color, 0
242
+ end
243
+
244
+ # Toggles the window between windowed and full screen mode.
245
+ def toggle_fullscreen
246
+ self.fullscreen = !fullscreen?
247
+ end
248
+ end
249
+
250
+ #class JSHelper
251
+
252
+ # Exposes methods for controlling keyboard events.
253
+ module KB
254
+ class << self
255
+ # This is called by <code>GameWindow.initialize</code>. Don't call it
256
+ # explicitly.
257
+ def initialize
258
+ @keys = [
259
+ Gosu::KB_A, Gosu::KB_B, Gosu::KB_C, Gosu::KB_D, Gosu::KB_E, Gosu::KB_F,
260
+ Gosu::KB_G, Gosu::KB_H, Gosu::KB_I, Gosu::KB_J, Gosu::KB_K, Gosu::KB_L,
261
+ Gosu::KB_M, Gosu::KB_N, Gosu::KB_O, Gosu::KB_P, Gosu::KB_Q, Gosu::KB_R,
262
+ Gosu::KB_S, Gosu::KB_T, Gosu::KB_U, Gosu::KB_V, Gosu::KB_W, Gosu::KB_X,
263
+ Gosu::KB_Y, Gosu::KB_Z, Gosu::KB_1, Gosu::KB_2, Gosu::KB_3, Gosu::KB_4,
264
+ Gosu::KB_5, Gosu::KB_6, Gosu::KB_7, Gosu::KB_8, Gosu::KB_9, Gosu::KB_0,
265
+ Gosu::KB_NUMPAD_1, Gosu::KB_NUMPAD_2, Gosu::KB_NUMPAD_3, Gosu::KB_NUMPAD_4,
266
+ Gosu::KB_NUMPAD_5, Gosu::KB_NUMPAD_6, Gosu::KB_NUMPAD_7, Gosu::KB_NUMPAD_8,
267
+ Gosu::KB_NUMPAD_9, Gosu::KB_NUMPAD_0, Gosu::KB_F1, Gosu::KB_F2,
268
+ Gosu::KB_F3, Gosu::KB_F4, Gosu::KB_F5, Gosu::KB_F6, Gosu::KB_F7,
269
+ Gosu::KB_F8, Gosu::KB_F9, Gosu::KB_F10, Gosu::KB_F11, Gosu::KB_F12,
270
+ Gosu::KB_APOSTROPHE, Gosu::KB_BACKSLASH, Gosu::KB_BACKSPACE,
271
+ Gosu::KB_BACKTICK, Gosu::KB_COMMA, Gosu::KB_DELETE, Gosu::KB_DOWN,
272
+ Gosu::KB_END, Gosu::KB_ENTER, Gosu::KB_EQUALS, Gosu::KB_ESCAPE,
273
+ Gosu::KB_HOME, Gosu::KB_INSERT, Gosu::KB_ISO, Gosu::KB_LEFT,
274
+ Gosu::KB_LEFT_ALT, Gosu::KB_LEFT_BRACKET, Gosu::KB_LEFT_CONTROL,
275
+ Gosu::KB_LEFT_META, Gosu::KB_LEFT_SHIFT, Gosu::KB_MINUS,
276
+ Gosu::KB_NUMPAD_DIVIDE, Gosu::KB_NUMPAD_MINUS,
277
+ Gosu::KB_NUMPAD_MULTIPLY, Gosu::KB_NUMPAD_PLUS, Gosu::KB_PAGE_DOWN,
278
+ Gosu::KB_PAGE_UP, Gosu::KB_PERIOD, Gosu::KB_RETURN, Gosu::KB_RIGHT,
279
+ Gosu::KB_RIGHT_ALT, Gosu::KB_RIGHT_BRACKET, Gosu::KB_RIGHT_CONTROL,
280
+ Gosu::KB_RIGHT_META, Gosu::KB_RIGHT_SHIFT, Gosu::KB_SEMICOLON,
281
+ Gosu::KB_SLASH, Gosu::KB_SPACE, Gosu::KB_TAB, Gosu::KB_UP
282
+ ]
283
+ @down = []
284
+ @prev_down = []
285
+ @held_timer = {}
286
+ @held_interval = {}
287
+ end
288
+
289
+ # Updates the state of all keys.
290
+ def update
291
+ @held_timer.each do |k, v|
292
+ if v < G.kb_held_delay; @held_timer[k] += 1
293
+ else
294
+ @held_interval[k] = 0
295
+ @held_timer.delete k
296
+ end
297
+ end
298
+
299
+ @held_interval.each do |k, v|
300
+ if v < G.kb_held_interval; @held_interval[k] += 1
301
+ else; @held_interval[k] = 0; end
302
+ end
303
+
304
+ @prev_down = @down.clone
305
+ @down.clear
306
+ @keys.each do |k|
307
+ if G.window.button_down? k
308
+ @down << k
309
+ @held_timer[k] = 0 if @prev_down.index(k).nil?
310
+ elsif @prev_down.index(k)
311
+ @held_timer.delete k
312
+ @held_interval.delete k
313
+ end
314
+ end
315
+ end
316
+
317
+ # Returns whether the given key is down in the current frame and was not
318
+ # down in the frame before.
319
+ #
320
+ # Parameters:
321
+ # [key] Code of the key to be checked. The available codes are all the
322
+ # constants in +Gosu+ started with +KB_+.
323
+ def key_pressed?(key)
324
+ @prev_down.index(key).nil? and @down.index(key)
325
+ end
326
+
327
+ # Returns whether the given key is down in the current frame.
328
+ #
329
+ # Parameters:
330
+ # [key] Code of the key to be checked. The available codes are all the
331
+ # constants in +Gosu+ started with +KB_+.
332
+ def key_down?(key)
333
+ @down.index(key)
334
+ end
335
+
336
+ # Returns whether the given key is not down in the current frame but was
337
+ # down in the frame before.
338
+ #
339
+ # Parameters:
340
+ # [key] Code of the key to be checked. The available codes are all the
341
+ # constants in +Gosu+ started with +KB_+.
342
+ def key_released?(key)
343
+ @prev_down.index(key) and @down.index(key).nil?
344
+ end
345
+
346
+ # Returns whether the given key is being held down. See
347
+ # <code>GameWindow.initialize</code> for details.
348
+ #
349
+ # Parameters:
350
+ # [key] Code of the key to be checked. The available codes are all the
351
+ # constants in +Gosu+ started with +KB_+.
352
+ def key_held?(key)
353
+ @held_interval[key] == G.kb_held_interval
354
+ end
355
+ end
356
+ end
357
+
358
+ # Exposes methods for controlling mouse events.
359
+ module Mouse
360
+ class << self
361
+ # The current x-coordinate of the mouse cursor in the screen.
362
+ attr_reader :x
363
+
364
+ # The current y-coordinate of the mouse cursor in the screen.
365
+ attr_reader :y
366
+
367
+ # This is called by <code>GameWindow.initialize</code>. Don't call it
368
+ # explicitly.
369
+ def initialize
370
+ @down = {}
371
+ @prev_down = {}
372
+ @dbl_click = {}
373
+ @dbl_click_timer = {}
374
+ end
375
+
376
+ # Updates the mouse position and the state of all buttons.
377
+ def update
378
+ @prev_down = @down.clone
379
+ @down.clear
380
+ @dbl_click.clear
381
+
382
+ @dbl_click_timer.each do |k, v|
383
+ if v < G.double_click_delay; @dbl_click_timer[k] += 1
384
+ else; @dbl_click_timer.delete k; end
385
+ end
386
+
387
+ k1 = [Gosu::MsLeft, Gosu::MsMiddle, Gosu::MsRight]
388
+ k2 = [:left, :middle, :right]
389
+ (0..2).each do |i|
390
+ if G.window.button_down? k1[i]
391
+ @down[k2[i]] = true
392
+ @dbl_click[k2[i]] = true if @dbl_click_timer[k2[i]]
393
+ @dbl_click_timer.delete k2[i]
394
+ elsif @prev_down[k2[i]]
395
+ @dbl_click_timer[k2[i]] = 0
396
+ end
397
+ end
398
+
399
+ @x = G.window.mouse_x.round
400
+ @y = G.window.mouse_y.round
401
+ end
402
+
403
+ # Returns whether the given button is down in the current frame and was
404
+ # not down in the frame before.
405
+ #
406
+ # Parameters:
407
+ # [btn] Button to be checked. Valid values are +:left+, +:middle+ and
408
+ # +:right+
409
+ def button_pressed?(btn)
410
+ @down[btn] and not @prev_down[btn]
411
+ end
412
+
413
+ # Returns whether the given button is down in the current frame.
414
+ #
415
+ # Parameters:
416
+ # [btn] Button to be checked. Valid values are +:left+, +:middle+ and
417
+ # +:right+
418
+ def button_down?(btn)
419
+ @down[btn]
420
+ end
421
+
422
+ # Returns whether the given button is not down in the current frame, but
423
+ # was down in the frame before.
424
+ #
425
+ # Parameters:
426
+ # [btn] Button to be checked. Valid values are +:left+, +:middle+ and
427
+ # +:right+
428
+ def button_released?(btn)
429
+ @prev_down[btn] and not @down[btn]
430
+ end
431
+
432
+ # Returns whether the given button has just been double clicked.
433
+ #
434
+ # Parameters:
435
+ # [btn] Button to be checked. Valid values are +:left+, +:middle+ and
436
+ # +:right+
437
+ def double_click?(btn)
438
+ @dbl_click[btn]
439
+ end
440
+
441
+ # Returns whether the mouse cursor is currently inside the given area.
442
+ #
443
+ # Parameters:
444
+ # [x] The x-coordinate of the top left corner of the area.
445
+ # [y] The y-coordinate of the top left corner of the area.
446
+ # [w] The width of the area.
447
+ # [h] The height of the area.
448
+ #
449
+ # <b>Alternate syntax</b>
450
+ #
451
+ # <code>over?(rectangle)</code>
452
+ #
453
+ # Parameters:
454
+ # [rectangle] A rectangle representing the area to be checked.
455
+ def over?(x, y = nil, w = nil, h = nil)
456
+ return @x >= x.x && @x < x.x + x.w && @y >= x.y && @y < x.y + x.h if x.is_a? Rectangle
457
+ @x >= x && @x < x + w && @y >= y && @y < y + h
458
+ end
459
+ end
460
+ end
461
+
462
+ # This class is responsible for resource management. It keeps references to
463
+ # all loaded resources until a call to +clear+ is made. Resources can be
464
+ # loaded as global, so that their references won't be removed even when
465
+ # +clear+ is called.
466
+ #
467
+ # It also provides an easier syntax for loading resources, assuming a
468
+ # particular folder structure. All resources must be inside subdirectories
469
+ # of a 'data' directory, so that you will only need to specify the type of
470
+ # resource being loaded and the file name (either as string or as symbol).
471
+ # There are default extensions for each type of resource, so the extension
472
+ # must be specified only if the file is in a format other than the default.
473
+ module Res
474
+ class << self
475
+ # Get the current prefix for searching data files. This is the directory
476
+ # under which 'img', 'sound', 'song', etc. folders are located.
477
+ attr_reader :prefix
478
+
479
+ # Gets the current path to image files (under +prefix+). Default is 'img'.
480
+ attr_reader :img_dir
481
+
482
+ # Gets the current path to tileset files (under +prefix+). Default is
483
+ # 'tileset'.
484
+ attr_reader :tileset_dir
485
+
486
+ # Gets the current path to sound files (under +prefix+). Default is 'sound'.
487
+ attr_reader :sound_dir
488
+
489
+ # Gets the current path to song files (under +prefix+). Default is 'song'.
490
+ attr_reader :song_dir
491
+
492
+ # Gets the current path to font files (under +prefix+). Default is 'font'.
493
+ attr_reader :font_dir
494
+
495
+ # Gets or sets the character that is currently being used in the +id+
496
+ # parameter of the loading methods as a folder separator. Default is '_'.
497
+ # Note that if you want to use symbols to specify paths, this separator
498
+ # should be a valid character in a Ruby symbol. On the other hand, if you
499
+ # want to use only slashes in Strings, you can specify a 'weird' character
500
+ # that won't appear in any file name.
501
+ attr_accessor :separator
502
+
503
+ # Gets or sets a flag that indicates whether images will be loaded with
504
+ # the 'retro' option set (see +Gosu::Image+ for details), when this
505
+ # option is not specified in a 'Res.img' or 'Res.imgs' call.
506
+ attr_accessor :retro_images
507
+
508
+ # This is called by <code>GameWindow.initialize</code>. Don't call it
509
+ # explicitly.
510
+ def initialize
511
+ @imgs = {}
512
+ @global_imgs = {}
513
+ @tilesets = {}
514
+ @global_tilesets = {}
515
+ @sounds = {}
516
+ @global_sounds = {}
517
+ @songs = {}
518
+ @global_songs = {}
519
+ @fonts = {}
520
+ @global_fonts = {}
521
+
522
+ @prefix = File.expand_path(File.dirname($0)) + '/data/'
523
+ @img_dir = 'img/'
524
+ @tileset_dir = 'tileset/'
525
+ @sound_dir = 'sound/'
526
+ @song_dir = 'song/'
527
+ @font_dir = 'font/'
528
+ @separator = '_'
529
+ @retro_images = false
530
+ end
531
+
532
+ # Set a custom prefix for loading resources. By default, the prefix is the
533
+ # directory of the game script. The prefix is the directory under which
534
+ # 'img', 'sound', 'song', etc. folders are located.
535
+ def prefix=(value)
536
+ value += '/' if value != '' and value[-1] != '/'
537
+ @prefix = value
538
+ end
539
+
540
+ # Sets the path to image files (under +prefix+). Default is 'img'.
541
+ def img_dir=(value)
542
+ value += '/' if value != '' and value[-1] != '/'
543
+ @img_dir = value
544
+ end
545
+
546
+ # Sets the path to tilset files (under +prefix+). Default is 'tileset'.
547
+ def tileset_dir=(value)
548
+ value += '/' if value != '' and value[-1] != '/'
549
+ @tileset_dir = value
550
+ end
551
+
552
+ # Sets the path to sound files (under +prefix+). Default is 'sound'.
553
+ def sound_dir=(value)
554
+ value += '/' if value != '' and value[-1] != '/'
555
+ @sound_dir = value
556
+ end
557
+
558
+ # Sets the path to song files (under +prefix+). Default is 'song'.
559
+ def song_dir=(value)
560
+ value += '/' if value != '' and value[-1] != '/'
561
+ @song_dir = value
562
+ end
563
+
564
+ # Sets the path to font files (under +prefix+). Default is 'font'.
565
+ def font_dir=(value)
566
+ value += '/' if value != '' and value[-1] != '/'
567
+ @font_dir = value
568
+ end
569
+
570
+ # Returns a <code>Gosu::Image</code> object.
571
+ #
572
+ # Parameters:
573
+ # [id] A string or symbol representing the path to the image. If the file
574
+ # is inside +prefix+/+img_dir+, only the file name is needed. If it's
575
+ # inside a subdirectory of +prefix+/+img_dir+, the id must be
576
+ # prefixed by each subdirectory name followed by +separator+. Example:
577
+ # to load 'data/img/sprite/1.png', with the default values of +prefix+,
578
+ # +img_dir+ and +separator+, provide +:sprite_1+ or "sprite_1".
579
+ # [global] Set to true if you want to keep the image in memory until the
580
+ # game execution is finished. If false, the image will be
581
+ # released when you call +clear+.
582
+ # [tileable] Whether the image should be loaded in tileable mode, which is
583
+ # proper for images that will be used as a tile, i.e., that
584
+ # will be drawn repeated times, side by side, forming a
585
+ # continuous composition.
586
+ # [ext] The extension of the file being loaded. Specify only if it is
587
+ # other than '.png'.
588
+ # [retro] Whether the image should be loaded with the 'retro' option set
589
+ # (see +Gosu::Image+ for details). If the value is omitted, the
590
+ # +Res.retro_images+ value will be used.
591
+ def img(id, global = false, tileable = false, ext = '.png', retro = nil)
592
+ a = global ? @global_imgs : @imgs
593
+ return a[id] if a[id]
594
+ s = @prefix + @img_dir + id.to_s.split(@separator).join('/') + ext
595
+ retro = Res.retro_images if retro.nil?
596
+ img = Gosu::Image.new s, tileable: tileable, retro: retro
597
+ a[id] = img
598
+ end
599
+
600
+ # Returns an array of <code>Gosu::Image</code> objects, using the image as
601
+ # a spritesheet. The image with index 0 will be the top left sprite, and
602
+ # the following indices raise first from left to right and then from top
603
+ # to bottom.
604
+ #
605
+ # Parameters:
606
+ # [id] A string or symbol representing the path to the image. See +img+
607
+ # for details.
608
+ # [sprite_cols] Number of columns in the spritesheet.
609
+ # [sprite_rows] Number of rows in the spritesheet.
610
+ # [global] Set to true if you want to keep the image in memory until the
611
+ # game execution is finished. If false, the image will be
612
+ # released when you call +clear+.
613
+ # [ext] The extension of the file being loaded. Specify only if it is
614
+ # other than ".png".
615
+ # [retro] Whether the image should be loaded with the 'retro' option set
616
+ # (see +Gosu::Image+ for details). If the value is omitted, the
617
+ # +Res.retro_images+ value will be used.
618
+ def imgs(id, sprite_cols, sprite_rows, global = false, ext = '.png', retro = nil, tileable = false)
619
+ a = global ? @global_imgs : @imgs
620
+ return a[id] if a[id]
621
+ s = @prefix + @img_dir + id.to_s.split(@separator).join('/') + ext
622
+ retro = Res.retro_images if retro.nil?
623
+ imgs = Gosu::Image.load_tiles s, -sprite_cols, -sprite_rows, tileable: tileable, retro: retro
624
+ a[id] = imgs
625
+ end
626
+
627
+ # Returns an array of <code>Gosu::Image</code> objects, using the image as
628
+ # a tileset. Works the same as +imgs+, except you must provide the tile
629
+ # size instead of the number of columns and rows, and that the images will
630
+ # be loaded as tileable.
631
+ #
632
+ # Parameters:
633
+ # [id] A string or symbol representing the path to the image. It must be
634
+ # specified the same way as in +img+, but the base directory is
635
+ # +prefix+/+tileset_dir+.
636
+ # [tile_width] Width of each tile, in pixels.
637
+ # [tile_height] Height of each tile, in pixels.
638
+ # [global] Set to true if you want to keep the image in memory until the
639
+ # game execution is finished. If false, the image will be
640
+ # released when you call +clear+.
641
+ # [ext] The extension of the file being loaded. Specify only if it is
642
+ # other than ".png".
643
+ # [retro] Whether the image should be loaded with the 'retro' option set
644
+ # (see +Gosu::Image+ for details). If the value is omitted, the
645
+ # +Res.retro_images+ value will be used.
646
+ def tileset(id, tile_width = 32, tile_height = 32, global = false, ext = '.png', retro = nil)
647
+ a = global ? @global_tilesets : @tilesets
648
+ return a[id] if a[id]
649
+ s = @prefix + @tileset_dir + id.to_s.split(@separator).join('/') + ext
650
+ retro = Res.retro_images if retro.nil?
651
+ tileset = Gosu::Image.load_tiles s, tile_width, tile_height, tileable: true, retro: retro
652
+ a[id] = tileset
653
+ end
654
+
655
+ # Returns a <code>Gosu::Sample</code> object. This should be used for
656
+ # simple and short sound effects.
657
+ #
658
+ # Parameters:
659
+ # [id] A string or symbol representing the path to the sound. It must be
660
+ # specified the same way as in +img+, but the base directory is
661
+ # +prefix+/+sound_dir+.
662
+ # [global] Set to true if you want to keep the sound in memory until the
663
+ # game execution is finished. If false, the sound will be
664
+ # released when you call +clear+.
665
+ # [ext] The extension of the file being loaded. Specify only if it is
666
+ # other than ".wav".
667
+ def sound(id, global = false, ext = '.wav')
668
+ a = global ? @global_sounds : @sounds
669
+ return a[id] if a[id]
670
+ s = @prefix + @sound_dir + id.to_s.split(@separator).join('/') + ext
671
+ sound = Gosu::Sample.new s
672
+ a[id] = sound
673
+ end
674
+
675
+ # Returns a <code>Gosu::Song</code> object. This should be used for the
676
+ # background musics of your game.
677
+ #
678
+ # Parameters:
679
+ # [id] A string or symbol representing the path to the song. It must be
680
+ # specified the same way as in +img+, but the base directory is
681
+ # +prefix+/+song_dir+.
682
+ # [global] Set to true if you want to keep the song in memory until the
683
+ # game execution is finished. If false, the song will be released
684
+ # when you call +clear+.
685
+ # [ext] The extension of the file being loaded. Specify only if it is
686
+ # other than ".ogg".
687
+ def song(id, global = false, ext = '.ogg')
688
+ a = global ? @global_songs : @songs
689
+ return a[id] if a[id]
690
+ s = @prefix + @song_dir + id.to_s.split(@separator).join('/') + ext
691
+ song = Gosu::Song.new s
692
+ a[id] = song
693
+ end
694
+
695
+ # Returns a <code>Gosu::Font</code> object. Fonts are needed to draw text
696
+ # and used by MiniGL elements like buttons, text fields and TextHelper
697
+ # objects.
698
+ #
699
+ # Parameters:
700
+ # [id] A string or symbol representing the path to the song. It must be
701
+ # specified the same way as in +img+, but the base directory is
702
+ # +prefix+/+font_dir+.
703
+ # [size] The size of the font, in pixels. This will correspond,
704
+ # approximately, to the height of the tallest character when drawn.
705
+ # [global] Set to true if you want to keep the font in memory until the
706
+ # game execution is finished. If false, the font will be released
707
+ # when you call +clear+.
708
+ # [ext] The extension of the file being loaded. Specify only if it is
709
+ # other than ".ttf".
710
+ def font(id, size, global = true, ext = '.ttf')
711
+ a = global ? @global_fonts : @fonts
712
+ id_size = "#{id}_#{size}"
713
+ return a[id_size] if a[id_size]
714
+ s = @prefix + @font_dir + id.to_s.split(@separator).join('/') + ext
715
+ font = Gosu::Font.new size, name: s
716
+ a[id_size] = font
717
+ end
718
+
719
+ # Releases the memory used by all non-global resources.
720
+ def clear
721
+ @imgs.clear
722
+ @tilesets.clear
723
+ @sounds.clear
724
+ @songs.clear
725
+ @fonts.clear
726
+ end
727
+ end
728
+ end
729
+ end