adventure_rl 0.0.1.pre.ld42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.travis.yml +5 -0
  4. data/Gemfile +12 -0
  5. data/Gemfile.lock +47 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +31 -0
  8. data/Rakefile +11 -0
  9. data/adventure_rl.gemspec +48 -0
  10. data/bin/console +7 -0
  11. data/bin/mkaudio +196 -0
  12. data/bin/mkclip +223 -0
  13. data/bin/rdoc +9 -0
  14. data/bin/setup +8 -0
  15. data/bin/vimall +5 -0
  16. data/doc/Mask.md +183 -0
  17. data/doc/Point.md +95 -0
  18. data/doc/Window.md +139 -0
  19. data/lib/AdventureRL/Animation.rb +63 -0
  20. data/lib/AdventureRL/Audio.rb +75 -0
  21. data/lib/AdventureRL/AudioPlayer.rb +65 -0
  22. data/lib/AdventureRL/Button.rb +51 -0
  23. data/lib/AdventureRL/Clip.rb +91 -0
  24. data/lib/AdventureRL/ClipPlayer.rb +187 -0
  25. data/lib/AdventureRL/Deltatime.rb +51 -0
  26. data/lib/AdventureRL/EventHandlers/Buttons.rb +225 -0
  27. data/lib/AdventureRL/EventHandlers/EventHandler.rb +62 -0
  28. data/lib/AdventureRL/EventHandlers/MouseButtons.rb +142 -0
  29. data/lib/AdventureRL/Events/Event.rb +69 -0
  30. data/lib/AdventureRL/Events/Mouse.rb +60 -0
  31. data/lib/AdventureRL/FileGroup.rb +100 -0
  32. data/lib/AdventureRL/FileGroupPlayer.rb +226 -0
  33. data/lib/AdventureRL/Helpers/Error.rb +68 -0
  34. data/lib/AdventureRL/Helpers/MethodHelper.rb +20 -0
  35. data/lib/AdventureRL/Helpers/PipeMethods.rb +26 -0
  36. data/lib/AdventureRL/Image.rb +77 -0
  37. data/lib/AdventureRL/Layer.rb +273 -0
  38. data/lib/AdventureRL/Mask.rb +462 -0
  39. data/lib/AdventureRL/Menu.rb +92 -0
  40. data/lib/AdventureRL/Modifiers/Gravity.rb +60 -0
  41. data/lib/AdventureRL/Modifiers/Inventory.rb +104 -0
  42. data/lib/AdventureRL/Modifiers/Pusher.rb +61 -0
  43. data/lib/AdventureRL/Modifiers/Solid.rb +302 -0
  44. data/lib/AdventureRL/Modifiers/Velocity.rb +163 -0
  45. data/lib/AdventureRL/Point.rb +188 -0
  46. data/lib/AdventureRL/Quadtree.rb +237 -0
  47. data/lib/AdventureRL/Rectangle.rb +62 -0
  48. data/lib/AdventureRL/Settings.rb +80 -0
  49. data/lib/AdventureRL/SolidsManager.rb +170 -0
  50. data/lib/AdventureRL/Textbox.rb +195 -0
  51. data/lib/AdventureRL/TimingHandler.rb +225 -0
  52. data/lib/AdventureRL/Window.rb +152 -0
  53. data/lib/AdventureRL/misc/extensions.rb +80 -0
  54. data/lib/AdventureRL/misc/require_files.rb +45 -0
  55. data/lib/AdventureRL/version.rb +3 -0
  56. data/lib/adventure_rl.rb +22 -0
  57. data/lib/default_settings.yml +20 -0
  58. data/vimrc +4 -0
  59. metadata +237 -0
@@ -0,0 +1,104 @@
1
+ module AdventureRL
2
+ module Modifiers
3
+ module Inventory
4
+ DEFAULT_INVENTORY_ID = :NO_NAME
5
+
6
+ def initialize *args
7
+ @inventory = {}
8
+ super
9
+ end
10
+
11
+ # Add any object to this Inventory.
12
+ # Pass an optional <tt>id</tt>, which can be used to
13
+ # access or remove the object afterwards.
14
+ def add_object object, id = DEFAULT_INVENTORY_ID
15
+ @inventory[id] = [] unless (@inventory[id])
16
+ @inventory[id] << object
17
+ end
18
+ alias_method :add_item, :add_object
19
+ alias_method :add, :add_object
20
+ alias_method :<<, :add_object
21
+
22
+ # Returns true, if object with <tt>id</tt> has been added to this Inventory.
23
+ # <tt>id</tt> can also be the object itself.
24
+ def added_object? id
25
+ return (
26
+ @inventory.key?(id) ||
27
+ get_objects.include?(id)
28
+ )
29
+ end
30
+ alias_method :added_item?, :added_object?
31
+ alias_method :added?, :added_object?
32
+ alias_method :has?, :added_object?
33
+
34
+ # Returns all its objects.
35
+ # If optional argument <tt>id</tt> is passed,
36
+ # then return all objects with that <tt>id</tt>.
37
+ def get_objects id = nil
38
+ return @inventory.values.flatten unless (id)
39
+ return @inventory[id]
40
+ end
41
+
42
+ # Returns the _last_ object with the given <tt>id</tt>.
43
+ # If no <tt>id</tt> is passed, return the last object with the unnamed <tt>id</tt>.
44
+ def get_object id = DEFAULT_INVENTORY_ID
45
+ return @inventory[id].last
46
+ end
47
+ alias_method :get, :get_object
48
+
49
+ # Removes all objects with the given <tt>id</tt>.
50
+ # <tt>id</tt> can also be an added object itself;
51
+ # all objects with the same <tt>id</tt> will be removed.
52
+ # If no <tt>id</tt> is given, remove _all_ objects.
53
+ def remove_objects id = nil
54
+ unless (id)
55
+ get_objects.each do |object|
56
+ object.removed if (object.methods.include? :removed)
57
+ end
58
+ return @inventory.clear
59
+ end
60
+ if (@inventory.key? id)
61
+ objects = @inventory.delete(id)
62
+ objects.each do |object|
63
+ object.removed if (object.methods.include? :removed)
64
+ end
65
+ return objects
66
+ end
67
+ return @inventory.delete((@inventory.detect do |key, val|
68
+ if (id == val)
69
+ @inventory[key].each do |object|
70
+ object.removed if (object.methods.include? :removed)
71
+ end
72
+ next true
73
+ end
74
+ next false
75
+ end || []).first)
76
+ end
77
+
78
+ # Removes the _last_ object with the given <tt>id</tt>.
79
+ # <tt>id</tt> can also be the object to be removed itself.
80
+ # If no <tt>id</tt> is given, remove the _last_ object with the unnamed <tt>id</tt>.
81
+ def remove_object id = DEFAULT_INVENTORY_ID, call_removed_method = true
82
+ if (@inventory.key? id)
83
+ object = @inventory[id].delete_at(-1)
84
+ object.removed if (call_removed_method && object.methods.include?(:removed))
85
+ return object
86
+ end
87
+ key = (@inventory.detect do |k, val|
88
+ next val.include?(id)
89
+ end || []) .first
90
+ return nil unless (@inventory.key? key)
91
+ object = @inventory[key].delete id
92
+ object.removed if (call_removed_method && object.methods.include?(:removed))
93
+ return object
94
+ end
95
+ alias_method :remove, :remove_object
96
+
97
+ # When removed is called on an object that has an Inventory,
98
+ # then also call #remove_objects on that object.
99
+ def removed
100
+ remove_objects
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,61 @@
1
+ module AdventureRL
2
+ module Modifiers
3
+ # A Modifiers::Pusher is a Modifers::Solid Mask,
4
+ # which has the ability to __push other solid Masks__ (that are _not static_)
5
+ # out of the way when moving with #move_by.
6
+ module Pusher
7
+ #include AdventureRL::Modifiers::Solid # NOTE: This modifier relies on Modifiers::Solid
8
+
9
+ # Overwrite Modifiers::Solid#move_by to add the
10
+ # <tt>:pushed_by_pusher</tt> option.
11
+ # This skips pushing the Pusher that pushed this Pusher,
12
+ # to avoid an endless pushing of Pushers, where one Pusher
13
+ # pushes the other Pusher before that Pusher pushes the first Pusher, ...
14
+ def move_by *args
15
+ return if (is_static?)
16
+
17
+ if (args.last.is_a?(Hash))
18
+ @pushed_by_pusher = [args.last[:pushed_by_pusher], self].flatten.reject { |x| !x }
19
+ else
20
+ @pushed_by_pusher = [self]
21
+ end
22
+ super
23
+ @pushed_by_pusher = false
24
+ end
25
+
26
+ private
27
+
28
+ def move_by_handle_collision_with_previous_position previous_position
29
+ colliding_objects = get_colliding_objects
30
+ if (colliding_objects.any?)
31
+ if (colliding_objects.any? &:is_static?)
32
+ @position = previous_position
33
+ return false
34
+ end
35
+ direction = get_position_difference_from previous_position
36
+ if (push_objects(colliding_objects, direction))
37
+ return true
38
+ else
39
+ @position = previous_position
40
+ return false
41
+ end
42
+ end
43
+ return true
44
+ end
45
+
46
+ def get_position_difference_from previous_position
47
+ return get_position.map do |axis, position|
48
+ next [axis, (position - previous_position[axis])]
49
+ end .to_h
50
+ end
51
+
52
+ def push_objects objects, direction
53
+ direction[:precision_over_performance] = @precision_over_performance
54
+ return objects.all? do |object|
55
+ next true if (@pushed_by_pusher && @pushed_by_pusher.include?(object))
56
+ next object.move_by(direction.merge(pushed_by_pusher: @pushed_by_pusher))
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,302 @@
1
+ module AdventureRL
2
+ module Modifiers
3
+ # This module is supposed to be <tt>include</tt>d in Mask child classes.
4
+ # It will tag that Mask instance as <tt>'solid'</tt>,
5
+ # and check collision with other solid Masks when calling #move_by.
6
+ # You can give it a specific <tt>solid_tag</tt>, which can be passed as
7
+ # the <tt>:solid_tag</tt> key's value upon initialization.
8
+ # Multiple solid tags may be passed as an array.
9
+ # Solid Masks will only collide with other Solid Masks that have a mutual solid tag.
10
+ # The default solid tag is <tt>:default</tt>.
11
+ module Solid
12
+ # NOTE: possible <tt>:precision_over_performance</tt> values:
13
+ # :low (or anything other than the higher values)::
14
+ # Lowest precision, highest performance.
15
+ # Never check every pixel between previous and new positions.
16
+ # If there is collision at new position, jump to previous position and return.
17
+ # The larger the movement steps, the more distance there will be to the colliding object.
18
+ # - -- __CANNOT__ fully close gaps to Solids.
19
+ # - -- __CAN__ phase through Solids at high speeds (especially when it lags).
20
+ # - -- __CAN__ get stuck in place temporarily when moving on both axes but only colliding on one of them.
21
+ # - + Only __one collision check__ per call to #move_by, highest performance.
22
+ #
23
+ # :medium::
24
+ # Medium precision, medium (varying) performance.
25
+ # Only checks every pixel in path if the expected destination collides.
26
+ # Even then, collision checking is used somewhat sparingly.
27
+ # - -- __CAN__ phase through Solids at high speeds (especially when it lags).
28
+ # - -- __CAN__ get stuck in place temporarily when moving on both axes but only colliding on one of them.
29
+ # - + __CAN__ _almost_ fully close gaps to Solids (no sub-pixel collision checks).
30
+ #
31
+ # :high::
32
+ # High precision, low to medium (varying) performance.
33
+ # Only checks every pixel in path if the expected destination collides.
34
+ # When checking every pixel in path, check both axes separately, to improve precision.
35
+ # - -- __CAN__ phase through Solids at high speeds (especially when it lags).
36
+ # - + __CANNOT__ get stuck in place temporarily when moving on both axes but only colliding on one of them.
37
+ # - + __CAN__ fully close gaps to Solids.
38
+ #
39
+ # :highest::
40
+ # Highest precision, least performance.
41
+ # Always check every pixel between previous and new positions.
42
+ # Depending on the amount of (moving) Solid objects on screen,
43
+ # - -- Depending on the amount of (moving) Solids,
44
+ # this can get very laggy at high speeds =>
45
+ # lag produces larger steps (usually), because of Deltatime =>
46
+ # larger steps produce more collision checks and more lag.
47
+ # - + __CANNOT__ phase through Solids, no matter what the speed is.
48
+ # - + __CANNOT__ get stuck in place temporarily when moving on both axes but only colliding on one of them.
49
+ # - + __CAN__ fully close gaps to Solids.
50
+ DEFAULT_SOLID_SETTINGS = Settings.new(
51
+ solid_tag: SolidsManager::DEFAULT_SOLID_TAG,
52
+ solid_tag_collides_with: nil,
53
+ precision_over_performance: :medium,
54
+ static: false,
55
+ auto_update: false
56
+ )
57
+
58
+ # Additionally to the Mask's settings Hash or Settings instance,
59
+ # you may pass the extra key <tt>:solid_tag</tt>, to define
60
+ # a custom solid tag (or multiple solid tags) upon initialization.
61
+ # They are used for collision checking with other Solid Mask objects
62
+ # that have a mutual solid tag.
63
+ def initialize settings = {}
64
+ @settings = DEFAULT_SOLID_SETTINGS.merge settings
65
+ @solid_tags = [@settings.get(:solid_tag)].flatten.sort
66
+ @solid_tags_collides_with = [@settings.get(:solid_tag_collides_with) || @solid_tags].flatten.sort
67
+ @solid_static = @settings.get :static # Basically disables #move_by
68
+ @precision_over_performance = @settings.get :precision_over_performance
69
+ assign_to_solids_manager if (@settings.get :auto_update)
70
+ super @settings
71
+ end
72
+
73
+ def add_to_solids_manager solids_manager
74
+ Helpers::Error.error(
75
+ "Expected argument to be a SolidsManager, but got",
76
+ "'#{solids_manager.inspect}:#{solids_manager.class.name}`."
77
+ ) unless (solids_manager.is_a? SolidsManager)
78
+ @solids_manager = solids_manager
79
+ @solids_manager.add_object self, get_solid_tags
80
+ end
81
+
82
+ # Overwrite #set_layer method, so we can get the SolidsManager
83
+ # from the Layer, if it has one.
84
+ def set_layer layer
85
+ super
86
+ assign_to_solids_manager if (@settings.get :auto_update)
87
+ end
88
+
89
+ # This method is called when this object is removed from an Inventory.
90
+ def removed
91
+ remove_from_solids_manager
92
+ end
93
+
94
+ # When it is removed, also remove it from the SolidsManager.
95
+ # TODO: Do this properly.
96
+ def remove_from_solids_manager
97
+ #@solids_manager.remove_object self, [get_solid_tags, get_solid_tags_collides_with].flatten if (@solids_manager)
98
+ @solids_manager.remove_object_from_all_quadtrees self if (@solids_manager)
99
+ end
100
+
101
+ # Overwrite #move_by method, so that collision checking with other objects
102
+ # with a mutual solid tag is done, and movement prevented if necessary.
103
+ def move_by *args
104
+ return false if (is_static?)
105
+ return super unless (@solids_manager)
106
+
107
+ @real_point = nil
108
+ previous_position = get_position.dup
109
+ incremental_position = parse_position(*args)
110
+ expected_position = {
111
+ x: (previous_position[:x] + (incremental_position.key?(:x) ? incremental_position[:x] : 0)),
112
+ y: (previous_position[:y] + (incremental_position.key?(:y) ? incremental_position[:y] : 0))
113
+ }
114
+
115
+ # NOTE:
116
+ # This is a bit of a hacky workaround for some
117
+ # weird Pusher behavior with Velocity and Gravity.
118
+ previous_precision_over_performance = @precision_over_performance.dup
119
+ opts = args.last.is_a?(Hash) ? args.last : nil
120
+
121
+ @precision_over_performance = opts[:precision_over_performance] if (opts.key? :precision_over_performance)
122
+
123
+ if ([:highest].include? @precision_over_performance)
124
+ move_by_steps incremental_position
125
+ else
126
+ @position[:x] += incremental_position[:x] if (incremental_position.key? :x)
127
+ @position[:y] += incremental_position[:y] if (incremental_position.key? :y)
128
+
129
+ # TODO
130
+ #puts 'PUSHING' if (is_a?(Player) && opts && opts[:pushed_by_pusher])
131
+
132
+ unless (move_by_handle_collision_with_previous_position previous_position)
133
+ move_by_steps incremental_position if ([:medium, :high].include? @precision_over_performance)
134
+ end
135
+ end
136
+
137
+ @precision_over_performance = previous_precision_over_performance
138
+ @solids_manager.reset_object self, get_solid_tags unless (@position == previous_position)
139
+ return @position == expected_position
140
+ end
141
+
142
+ # Overwrite the #move_to method, so we can
143
+ # reset the object for the solids_manager if necessary.
144
+ def move_to *args
145
+ previous_position = get_position.dup
146
+ super
147
+ return unless (@solids_manager)
148
+ @solids_manager.reset_object self, get_solid_tags if (@position != previous_position)
149
+ end
150
+
151
+ # Returns <tt>true</tt> if this Mask is currently in collision
152
+ # with another solid Mask which has a mutual solid tag.
153
+ # TODO: Write documentation for callback method.
154
+ def in_collision?
155
+ if (@solids_manager)
156
+ is_colliding = @solids_manager.collides?(self, get_solid_tags_collides_with)
157
+ else
158
+ is_colliding = false
159
+ end
160
+ is_colliding if (is_colliding && methods.include?(:is_colliding))
161
+ return is_colliding
162
+ end
163
+
164
+ # Returns all currently colliding objects (if any).
165
+ # TODO: Write documentation for callback method.
166
+ def get_colliding_objects
167
+ if (@solids_manager)
168
+ colliding_objects = @solids_manager.get_colliding_objects(self, get_solid_tags_collides_with)
169
+ else
170
+ colliding_objects = []
171
+ end
172
+ is_colliding_with_objects colliding_objects if (colliding_objects.any? && methods.include?(:is_colliding_with_objects))
173
+ return colliding_objects
174
+ end
175
+
176
+ # Makes this Solid Mask static.
177
+ def make_static
178
+ @solid_static = true
179
+ end
180
+
181
+ # Returns <tt>true</tt> if this is a static solid Mask,
182
+ # which means it cannot be moved with #move_by.
183
+ def is_static?
184
+ return !!@solid_static
185
+ end
186
+
187
+ def set_solid_tags *new_solid_tags
188
+ @solids_manager.remove_object self, get_solid_tags if (@solids_manager)
189
+ @solid_tags = [new_solid_tags].flatten.compact
190
+ @solids_manager.add_object self, get_solid_tags if (@solids_manager)
191
+ end
192
+
193
+ def set_solid_tags_collides_with *new_solid_tags_collides_with
194
+ @solid_tags_collides_with = [new_solid_tags_collides_with].flatten.compact
195
+ end
196
+
197
+ # Returns this Mask's solid tags,
198
+ # which other Masks use to check collision against _this_ Mask.
199
+ def get_solid_tags
200
+ return @solid_tags
201
+ end
202
+
203
+ # Returns the solid tags,
204
+ # which this Mask uses to check collision against _other_ Masks.
205
+ def get_solid_tags_collides_with
206
+ return @solid_tags_collides_with || @solid_tags
207
+ end
208
+
209
+ private
210
+
211
+ def assign_to_solids_manager
212
+ layer = get_layer
213
+ return unless (layer && layer.has_solids_manager?)
214
+ @solids_manager = layer.get_solids_manager
215
+ @solids_manager.add_object self, get_solid_tags if (@solids_manager)
216
+ end
217
+
218
+ # This is the ugliest method in the project.
219
+ # I can live with there being __one__ ugly method.
220
+ # Also, it _does_ do some complicated stuff, so cut it some slack.
221
+ # It didn't ask to be this way.
222
+ def move_by_steps incremental_position
223
+ incremental_position[:x] ||= 0
224
+ incremental_position[:y] ||= 0
225
+
226
+ larger_axis = :x if (incremental_position[:x].abs >= incremental_position[:y].abs)
227
+ larger_axis = :y if (incremental_position[:y].abs > incremental_position[:x].abs)
228
+ smaller_axis = (larger_axis == :x) ? :y : :x
229
+ larger_axis_sign = incremental_position[larger_axis].sign
230
+ smaller_axis_sign = incremental_position[smaller_axis].sign
231
+ smaller_axis_increment_at = (incremental_position[larger_axis].abs.to_f / incremental_position[smaller_axis].abs.to_f).round rescue nil
232
+ remaining_values = {
233
+ larger_axis => ((incremental_position[larger_axis].abs % 1) * larger_axis_sign),
234
+ smaller_axis => ((incremental_position[smaller_axis].abs % 1) * smaller_axis_sign),
235
+ }
236
+
237
+ return unless (move_by_steps_for_remaining_values remaining_values)
238
+
239
+ # NOTE
240
+ # We use #to_i here, because a negative float's #floor method decreases its value. Example:
241
+ # 1.75.floor # => 1.0
242
+ # -1.75.floor # => -2.0
243
+ # 1.75.to_i # => 1.0
244
+ # -1.75.to_i # => -1.0
245
+ incremental_position[larger_axis].to_i.abs.times do |axis_index|
246
+ initial_previous_position = @position.dup
247
+
248
+ tmp_in_collision_count = 0
249
+
250
+ previous_position = @position.dup
251
+ @position[larger_axis] += larger_axis_sign
252
+ tmp_in_collision_count += 1 unless (
253
+ move_by_handle_collision_with_previous_position(previous_position)
254
+ ) if ([:high, :highest].include? @precision_over_performance)
255
+
256
+ if (smaller_axis_increment_at &&
257
+ (((axis_index + 1) % smaller_axis_increment_at) == 0)
258
+ )
259
+ previous_position = @position.dup
260
+ @position[smaller_axis] += smaller_axis_sign
261
+ tmp_in_collision_count += 1 unless (
262
+ move_by_handle_collision_with_previous_position(previous_position)
263
+ ) if ([:high, :highest].include? @precision_over_performance)
264
+ end
265
+
266
+ return unless (tmp_in_collision_count < 2)
267
+ if ([:medium].include? @precision_over_performance)
268
+ return unless (move_by_handle_collision_with_previous_position initial_previous_position)
269
+ end
270
+ end
271
+ end
272
+
273
+ def move_by_steps_for_remaining_values remaining_values
274
+ return true unless ([:high, :highest].include? @precision_over_performance)
275
+ return true if (remaining_values.values.all? { |val| val == 0 })
276
+ tmp_in_collision_count = 0
277
+ remaining_values.each do |remaining_axis, remaining_value|
278
+ next if (remaining_value == 0)
279
+ previous_position = @position.dup
280
+ @position[remaining_axis] += remaining_value
281
+ remaining_values[remaining_axis] = 0
282
+ unless (move_by_handle_collision_with_previous_position previous_position)
283
+ tmp_in_collision_count += 1
284
+ next # break
285
+ end
286
+ end
287
+ return false if (tmp_in_collision_count == 2) # NOTE: Slight performance improvement
288
+ return true
289
+ end
290
+
291
+ # Returns <tt>true</tt> if there was no collision, and
292
+ # returns <tt>false</tt> if there was and it had to reset to the <tt>previous_position</tt>.
293
+ def move_by_handle_collision_with_previous_position previous_position
294
+ if (in_collision?)
295
+ @position = previous_position
296
+ return false
297
+ end
298
+ return true
299
+ end
300
+ end
301
+ end
302
+ end
@@ -0,0 +1,163 @@
1
+ module AdventureRL
2
+ module Modifiers
3
+ module Velocity
4
+ DEFAULT_VELOCITY_SETTINGS = Settings.new(
5
+ # Does NOT use Deltatime
6
+ max_velocity: {
7
+ x: 100,
8
+ y: 100
9
+ },
10
+ # DOES use Deltatime
11
+ velocity_decay: {
12
+ x: 750,
13
+ y: 750
14
+ },
15
+ # Does NOT use Deltatime
16
+ base_velocity: {
17
+ x: 0,
18
+ y: 0
19
+ },
20
+ quick_turn_around: false
21
+ )
22
+
23
+ def initialize settings = {}
24
+ @settings = DEFAULT_VELOCITY_SETTINGS.merge settings
25
+ @velocity = {
26
+ x: 0.0,
27
+ y: 0.0
28
+ }
29
+ @max_velocity_original = @settings.get :max_velocity
30
+ @max_velocity = @max_velocity_original.dup
31
+ @velocity_decay = @settings.get :velocity_decay
32
+ @velocity_quick_turn_around = @settings.get :quick_turn_around
33
+ @base_velocity = @settings.get :base_velocity
34
+ @velocity_deltatime = Deltatime.new
35
+ @has_increased_velocity_for = {
36
+ x: false,
37
+ y: false
38
+ }
39
+ super @settings
40
+ end
41
+
42
+ # Returns the velocity.
43
+ # Pass an optional <tt>target</tt> argument,
44
+ # which can be either an axis (<tt>:x</tt> or <tt>:y</tt>),
45
+ # or <tt>:all</tt>, which will return a Hash with both values.
46
+ def get_velocity target = :all
47
+ target = target.to_sym
48
+ return @velocity if (target == :all)
49
+ return @velocity[target] if (@velocity.keys.include?(target))
50
+ return nil
51
+ end
52
+
53
+ # Returns the max velocity.
54
+ # Pass an optional <tt>target</tt> argument,
55
+ # which can be either an axis (<tt>:x</tt> or <tt>:y</tt>),
56
+ # or <tt>:all</tt>, which will return a Hash with both values.
57
+ def get_max_velocity target = :all
58
+ target = target.to_sym
59
+ return @max_velocity if (target == :all)
60
+ return @max_velocity[target] if (@max_velocity.keys.include?(target))
61
+ return nil
62
+ end
63
+
64
+ def set_velocity *args
65
+ new_velocity = parse_position *args
66
+ @velocity[:x] = new_velocity[:x] if (new_velocity.key? :x)
67
+ @velocity[:y] = new_velocity[:y] if (new_velocity.key? :y)
68
+ end
69
+
70
+ # Increase the velocity.
71
+ # <tt>args</tt> may be:
72
+ # Two integers, representing the <tt>x</tt> and <tt>y</tt> axes, respectively.
73
+ # A hash containing one or both of the keys <tt>:x</tt> and <tt>:y</tt>.
74
+ def increase_velocity_by *args
75
+ opts = {}
76
+ opts = args.last if (args.last.is_a? Hash)
77
+ quick_turn_around = @velocity_quick_turn_around
78
+ quick_turn_around = opts[:quick_turn_around] unless (opts[:quick_turn_around].nil?)
79
+ incremental_velocity = parse_position *args
80
+ @velocity.keys.each do |axis|
81
+ next unless (incremental_velocity.key? axis)
82
+ velocity_sign = @velocity[axis].sign
83
+ incremental_velocity_sign = incremental_velocity[axis].sign
84
+ @velocity[axis] = 0 unless (velocity_sign == incremental_velocity_sign) if (quick_turn_around && !opts[:no_quick_turn_around])
85
+ @velocity[axis] = @base_velocity[axis] * incremental_velocity_sign if (@velocity[axis] == 0)
86
+ @velocity[axis] += incremental_velocity[axis]
87
+ case velocity_sign
88
+ when 1
89
+ @velocity[axis] = get_max_velocity(axis) if (@velocity[axis] > get_max_velocity(axis))
90
+ when -1
91
+ @velocity[axis] = -get_max_velocity(axis) if (@velocity[axis] < -get_max_velocity(axis))
92
+ end
93
+ @has_increased_velocity_for[axis] = true
94
+ end
95
+ end
96
+ alias_method :add_velocity, :increase_velocity_by
97
+
98
+ def set_max_velocity *args
99
+ new_max_velocity = parse_position *args
100
+ @max_velocity_original.keys.each do |axis|
101
+ @max_velocity_original[axis] = new_max_velocity[axis] if (new_max_velocity.key? axis)
102
+ end
103
+ @max_velocity = @max_velocity_original.dup
104
+ end
105
+
106
+ def set_temporary_max_velocity *args
107
+ new_max_velocity = parse_position *args
108
+ @max_velocity.keys.each do |axis|
109
+ @max_velocity[axis] = new_max_velocity[axis] if (new_max_velocity.key? axis)
110
+ end
111
+ end
112
+
113
+ # Resets the max velocity to the original values.
114
+ def reset_max_velocity
115
+ @max_velocity = @max_velocity_original.dup
116
+ end
117
+
118
+ # Call this every frame to move with the stored velocity.
119
+ def move
120
+ move_by get_incremental_position_for_velocity if (any_velocity?)
121
+ decrease_velocity
122
+ @has_increased_velocity_for = {
123
+ x: false,
124
+ y: false
125
+ }
126
+ @velocity_deltatime.update
127
+ end
128
+
129
+ private
130
+
131
+ # Returns <tt>true</tt> if there is any stored velocity,
132
+ # for any axis.
133
+ def any_velocity?
134
+ return @velocity.values.any? do |velocity|
135
+ next velocity != 0
136
+ end
137
+ end
138
+
139
+ def get_incremental_position_for_velocity
140
+ return get_velocity.map do |axis, speed|
141
+ next [axis, speed * @velocity_deltatime.dt]
142
+ end .to_h
143
+ end
144
+
145
+ # Decrease the velocity, based on the decay rate
146
+ # and deltatime.
147
+ def decrease_velocity
148
+ return unless (any_velocity?)
149
+ @velocity.keys.each do |axis|
150
+ next if (@velocity[axis] == 0 || @has_increased_velocity_for[axis])
151
+ case @velocity[axis].sign
152
+ when 1
153
+ @velocity[axis] -= @velocity_decay[axis] * @velocity_deltatime.dt
154
+ @velocity[axis] = 0 if (@velocity[axis] < 0)
155
+ when -1
156
+ @velocity[axis] += @velocity_decay[axis] * @velocity_deltatime.dt
157
+ @velocity[axis] = 0 if (@velocity[axis] > 0)
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end