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.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.travis.yml +5 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +47 -0
- data/LICENSE.txt +21 -0
- data/README.md +31 -0
- data/Rakefile +11 -0
- data/adventure_rl.gemspec +48 -0
- data/bin/console +7 -0
- data/bin/mkaudio +196 -0
- data/bin/mkclip +223 -0
- data/bin/rdoc +9 -0
- data/bin/setup +8 -0
- data/bin/vimall +5 -0
- data/doc/Mask.md +183 -0
- data/doc/Point.md +95 -0
- data/doc/Window.md +139 -0
- data/lib/AdventureRL/Animation.rb +63 -0
- data/lib/AdventureRL/Audio.rb +75 -0
- data/lib/AdventureRL/AudioPlayer.rb +65 -0
- data/lib/AdventureRL/Button.rb +51 -0
- data/lib/AdventureRL/Clip.rb +91 -0
- data/lib/AdventureRL/ClipPlayer.rb +187 -0
- data/lib/AdventureRL/Deltatime.rb +51 -0
- data/lib/AdventureRL/EventHandlers/Buttons.rb +225 -0
- data/lib/AdventureRL/EventHandlers/EventHandler.rb +62 -0
- data/lib/AdventureRL/EventHandlers/MouseButtons.rb +142 -0
- data/lib/AdventureRL/Events/Event.rb +69 -0
- data/lib/AdventureRL/Events/Mouse.rb +60 -0
- data/lib/AdventureRL/FileGroup.rb +100 -0
- data/lib/AdventureRL/FileGroupPlayer.rb +226 -0
- data/lib/AdventureRL/Helpers/Error.rb +68 -0
- data/lib/AdventureRL/Helpers/MethodHelper.rb +20 -0
- data/lib/AdventureRL/Helpers/PipeMethods.rb +26 -0
- data/lib/AdventureRL/Image.rb +77 -0
- data/lib/AdventureRL/Layer.rb +273 -0
- data/lib/AdventureRL/Mask.rb +462 -0
- data/lib/AdventureRL/Menu.rb +92 -0
- data/lib/AdventureRL/Modifiers/Gravity.rb +60 -0
- data/lib/AdventureRL/Modifiers/Inventory.rb +104 -0
- data/lib/AdventureRL/Modifiers/Pusher.rb +61 -0
- data/lib/AdventureRL/Modifiers/Solid.rb +302 -0
- data/lib/AdventureRL/Modifiers/Velocity.rb +163 -0
- data/lib/AdventureRL/Point.rb +188 -0
- data/lib/AdventureRL/Quadtree.rb +237 -0
- data/lib/AdventureRL/Rectangle.rb +62 -0
- data/lib/AdventureRL/Settings.rb +80 -0
- data/lib/AdventureRL/SolidsManager.rb +170 -0
- data/lib/AdventureRL/Textbox.rb +195 -0
- data/lib/AdventureRL/TimingHandler.rb +225 -0
- data/lib/AdventureRL/Window.rb +152 -0
- data/lib/AdventureRL/misc/extensions.rb +80 -0
- data/lib/AdventureRL/misc/require_files.rb +45 -0
- data/lib/AdventureRL/version.rb +3 -0
- data/lib/adventure_rl.rb +22 -0
- data/lib/default_settings.yml +20 -0
- data/vimrc +4 -0
- 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
|