module AdventureRL::Modifiers::Solid
This module is supposed to be include
d in Mask
child classes. It will tag that Mask
instance as 'solid'
, and check collision with other solid Masks when calling move_by
. You can give it a specific solid_tag
, which can be passed as the :solid_tag
key's value upon initialization. Multiple solid tags may be passed as an array. Solid
Masks will only collide with other Solid
Masks that have a mutual solid tag. The default solid tag is :default
.
Constants
- DEFAULT_SOLID_SETTINGS
NOTE: possible
:precision_over_performance
values:- :low (or anything other than the higher values)
-
Lowest precision, highest performance. Never check every pixel between previous and new positions. If there is collision at new position, jump to previous position and return. The larger the movement steps, the more distance there will be to the colliding object.
-
– __CANNOT__ fully close gaps to Solids.
-
– __CAN__ phase through Solids at high speeds (especially when it lags).
-
– __CAN__ get stuck in place temporarily when moving on both axes but only colliding on one of them.
-
+ Only __one collision check__ per call to
move_by
, highest performance.
-
- :medium
-
Medium precision, medium (varying) performance. Only checks every pixel in path if the expected destination collides. Even then, collision checking is used somewhat sparingly.
-
– __CAN__ phase through Solids at high speeds (especially when it lags).
-
– __CAN__ get stuck in place temporarily when moving on both axes but only colliding on one of them.
-
+ __CAN__ almost fully close gaps to Solids (no sub-pixel collision checks).
-
- :high
-
High precision, low to medium (varying) performance. Only checks every pixel in path if the expected destination collides. When checking every pixel in path, check both axes separately, to improve precision.
-
– __CAN__ phase through Solids at high speeds (especially when it lags).
-
+ __CANNOT__ get stuck in place temporarily when moving on both axes but only colliding on one of them.
-
+ __CAN__ fully close gaps to Solids.
-
- :highest
-
Highest precision, least performance. Always check every pixel between previous and new positions. Depending on the amount of (moving)
Solid
objects on screen,-
– Depending on the amount of (moving) Solids, this can get very laggy at high speeds => lag produces larger steps (usually), because of
Deltatime
=> larger steps produce more collision checks and more lag. -
+ __CANNOT__ phase through Solids, no matter what the speed is.
-
+ __CANNOT__ get stuck in place temporarily when moving on both axes but only colliding on one of them.
-
+ __CAN__ fully close gaps to Solids.
-
Public Class Methods
Additionally to the Mask's settings Hash or Settings
instance, you may pass the extra key :solid_tag
, to define a custom solid tag (or multiple solid tags) upon initialization. They are used for collision checking with other Solid
Mask
objects that have a mutual solid tag.
# File lib/AdventureRL/Modifiers/Solid.rb, line 63 def initialize settings = {} @settings = DEFAULT_SOLID_SETTINGS.merge settings @solid_tags = [@settings.get(:solid_tag)].flatten.sort @solid_tags_collides_with = [@settings.get(:solid_tag_collides_with) || @solid_tags].flatten.sort @solid_static = @settings.get :static # Basically disables #move_by @precision_over_performance = @settings.get :precision_over_performance assign_to_solids_manager if (@settings.get :auto_update) super @settings end
Public Instance Methods
# File lib/AdventureRL/Modifiers/Solid.rb, line 73 def add_to_solids_manager solids_manager Helpers::Error.error( "Expected argument to be a SolidsManager, but got", "'#{solids_manager.inspect}:#{solids_manager.class.name}`." ) unless (solids_manager.is_a? SolidsManager) @solids_manager = solids_manager @solids_manager.add_object self, get_solid_tags end
Returns all currently colliding objects (if any). TODO: Write documentation for callback method.
# File lib/AdventureRL/Modifiers/Solid.rb, line 166 def get_colliding_objects if (@solids_manager) colliding_objects = @solids_manager.get_colliding_objects(self, get_solid_tags_collides_with) else colliding_objects = [] end is_colliding_with_objects colliding_objects if (colliding_objects.any? && methods.include?(:is_colliding_with_objects)) return colliding_objects end
Returns true
if this Mask
is currently in collision with another solid Mask
which has a mutual solid tag. TODO: Write documentation for callback method.
# File lib/AdventureRL/Modifiers/Solid.rb, line 154 def in_collision? if (@solids_manager) is_colliding = @solids_manager.collides?(self, get_solid_tags_collides_with) else is_colliding = false end is_colliding if (is_colliding && methods.include?(:is_colliding)) return is_colliding end
Overwrite move_by
method, so that collision checking with other objects with a mutual solid tag is done, and movement prevented if necessary.
# File lib/AdventureRL/Modifiers/Solid.rb, line 103 def move_by *args return false if (is_static?) return super unless (@solids_manager) @real_point = nil previous_position = get_position.dup incremental_position = parse_position(*args) expected_position = { x: (previous_position[:x] + (incremental_position.key?(:x) ? incremental_position[:x] : 0)), y: (previous_position[:y] + (incremental_position.key?(:y) ? incremental_position[:y] : 0)) } # NOTE: # This is a bit of a hacky workaround for some # weird Pusher behavior with Velocity and Gravity. previous_precision_over_performance = @precision_over_performance.dup opts = args.last.is_a?(Hash) ? args.last : nil @precision_over_performance = opts[:precision_over_performance] if (opts && opts.key?(:precision_over_performance)) if ([:highest].include? @precision_over_performance) move_by_steps incremental_position else @position[:x] += incremental_position[:x] if (incremental_position.key? :x) @position[:y] += incremental_position[:y] if (incremental_position.key? :y) # TODO #puts 'PUSHING' if (is_a?(Player) && opts && opts[:pushed_by_pusher]) unless (move_by_handle_collision_with_previous_position previous_position) move_by_steps incremental_position if ([:medium, :high].include? @precision_over_performance) end end @precision_over_performance = previous_precision_over_performance @solids_manager.reset_object self, get_solid_tags unless (@position == previous_position) return @position == expected_position end
Overwrite the move_to
method, so we can reset the object for the solids_manager if necessary.
# File lib/AdventureRL/Modifiers/Solid.rb, line 144 def move_to *args previous_position = get_position.dup super return unless (@solids_manager) @solids_manager.reset_object self, get_solid_tags if (@position != previous_position) end
When it is removed, also remove it from the SolidsManager
. TODO: Do this properly.
# File lib/AdventureRL/Modifiers/Solid.rb, line 96 def remove_from_solids_manager #@solids_manager.remove_object self, [get_solid_tags, get_solid_tags_collides_with].flatten if (@solids_manager) @solids_manager.remove_object_from_all_quadtrees self if (@solids_manager) end
This method is called when this object is removed from an Inventory
.
# File lib/AdventureRL/Modifiers/Solid.rb, line 90 def removed remove_from_solids_manager end
Overwrite set_layer
method, so we can get the SolidsManager
from the Layer
, if it has one.
# File lib/AdventureRL/Modifiers/Solid.rb, line 84 def set_layer layer super assign_to_solids_manager if (@settings.get :auto_update) end
Private Instance Methods
# File lib/AdventureRL/Modifiers/Solid.rb, line 211 def assign_to_solids_manager layer = get_layer return unless (layer && layer.has_solids_manager?) @solids_manager = layer.get_solids_manager @solids_manager.add_object self, get_solid_tags if (@solids_manager) end
Returns true
if there was no collision, and returns false
if there was and it had to reset to the previous_position
.
# File lib/AdventureRL/Modifiers/Solid.rb, line 293 def move_by_handle_collision_with_previous_position previous_position if (in_collision?) @position = previous_position return false end return true end
This is the ugliest method in the project. I can live with there being __one__ ugly method. Also, it does do some complicated stuff, so cut it some slack. It didn't ask to be this way.
# File lib/AdventureRL/Modifiers/Solid.rb, line 222 def move_by_steps incremental_position incremental_position[:x] ||= 0 incremental_position[:y] ||= 0 larger_axis = :x if (incremental_position[:x].abs >= incremental_position[:y].abs) larger_axis = :y if (incremental_position[:y].abs > incremental_position[:x].abs) smaller_axis = (larger_axis == :x) ? :y : :x larger_axis_sign = incremental_position[larger_axis].sign smaller_axis_sign = incremental_position[smaller_axis].sign smaller_axis_increment_at = (incremental_position[larger_axis].abs.to_f / incremental_position[smaller_axis].abs.to_f).round rescue nil remaining_values = { larger_axis => ((incremental_position[larger_axis].abs % 1) * larger_axis_sign), smaller_axis => ((incremental_position[smaller_axis].abs % 1) * smaller_axis_sign), } return unless (move_by_steps_for_remaining_values remaining_values) # NOTE # We use #to_i here, because a negative float's #floor method decreases its value. Example: # 1.75.floor # => 1.0 # -1.75.floor # => -2.0 # 1.75.to_i # => 1.0 # -1.75.to_i # => -1.0 incremental_position[larger_axis].to_i.abs.times do |axis_index| initial_previous_position = @position.dup tmp_in_collision_count = 0 previous_position = @position.dup @position[larger_axis] += larger_axis_sign tmp_in_collision_count += 1 unless ( move_by_handle_collision_with_previous_position(previous_position) ) if ([:high, :highest].include? @precision_over_performance) if (smaller_axis_increment_at && (((axis_index + 1) % smaller_axis_increment_at) == 0) ) previous_position = @position.dup @position[smaller_axis] += smaller_axis_sign tmp_in_collision_count += 1 unless ( move_by_handle_collision_with_previous_position(previous_position) ) if ([:high, :highest].include? @precision_over_performance) end return unless (tmp_in_collision_count < 2) if ([:medium].include? @precision_over_performance) return unless (move_by_handle_collision_with_previous_position initial_previous_position) end end end
# File lib/AdventureRL/Modifiers/Solid.rb, line 273 def move_by_steps_for_remaining_values remaining_values return true unless ([:high, :highest].include? @precision_over_performance) return true if (remaining_values.values.all? { |val| val == 0 }) tmp_in_collision_count = 0 remaining_values.each do |remaining_axis, remaining_value| next if (remaining_value == 0) previous_position = @position.dup @position[remaining_axis] += remaining_value remaining_values[remaining_axis] = 0 unless (move_by_handle_collision_with_previous_position previous_position) tmp_in_collision_count += 1 next # break end end return false if (tmp_in_collision_count == 2) # NOTE: Slight performance improvement return true end