gamebox 0.4.0.rc5 → 0.4.0.rc11
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +205 -127
- data/bin/gamebox +49 -3
- data/bin/gb +87 -0
- data/gamebox.gemspec +4 -3
- data/lib/gamebox.rb +1 -1
- data/lib/gamebox/actors/collidable_debugger.rb +4 -4
- data/lib/gamebox/actors/icon.rb +7 -0
- data/lib/gamebox/actors/label.rb +41 -42
- data/lib/gamebox/behaviors/animated.rb +6 -0
- data/lib/gamebox/behaviors/audible.rb +1 -2
- data/lib/gamebox/behaviors/collidable.rb +1 -1
- data/lib/gamebox/behaviors/graphical.rb +8 -4
- data/lib/gamebox/behaviors/physical.rb +6 -1
- data/lib/gamebox/behaviors/positioned.rb +4 -11
- data/lib/gamebox/behaviors/projectile.rb +8 -0
- data/lib/gamebox/behaviors/visible.rb +3 -3
- data/lib/gamebox/core/aabb_tree.rb +1 -1
- data/lib/gamebox/core/actor.rb +37 -50
- data/lib/gamebox/core/actor_definition.rb +41 -0
- data/lib/gamebox/core/actor_view.rb +6 -21
- data/lib/gamebox/core/actor_view_definition.rb +19 -0
- data/lib/gamebox/core/actor_view_factory.rb +9 -3
- data/lib/gamebox/core/behavior.rb +8 -27
- data/lib/gamebox/core/behavior_definition.rb +24 -0
- data/lib/gamebox/core/config_manager.rb +45 -30
- data/lib/gamebox/core/configuration.rb +5 -0
- data/lib/gamebox/core/core.rb +4 -0
- data/lib/gamebox/core/debug_helpers.rb +46 -0
- data/lib/gamebox/core/director.rb +32 -5
- data/lib/gamebox/core/event_symbols.rb +214 -0
- data/lib/gamebox/core/game.rb +1 -1
- data/lib/gamebox/core/input_manager.rb +1 -4
- data/lib/gamebox/core/input_mapper.rb +85 -0
- data/lib/gamebox/core/physics.rb +7 -3
- data/lib/gamebox/core/physics_manager.rb +5 -1
- data/lib/gamebox/core/renderer.rb +72 -0
- data/lib/gamebox/core/stage.rb +25 -81
- data/lib/gamebox/core/stage_definition.rb +60 -0
- data/lib/gamebox/core/stage_factory.rb +56 -0
- data/lib/gamebox/core/stage_manager.rb +5 -11
- data/lib/gamebox/core/timer_manager.rb +6 -2
- data/lib/gamebox/core/viewport.rb +12 -5
- data/lib/gamebox/core/wrapped_screen.rb +8 -5
- data/lib/gamebox/gamebox_application.rb +21 -19
- data/lib/gamebox/lib/array_ext.rb +9 -0
- data/lib/gamebox/lib/observable_attributes.rb +24 -0
- data/lib/gamebox/lib/vector2.rb +432 -0
- data/lib/gamebox/post_setup_handlers/file_watcher.rb +37 -0
- data/lib/gamebox/post_setup_handlers/gamebox_debug_helpers.rb +13 -0
- data/lib/gamebox/post_setup_handlers/pry_remote_server.rb +29 -0
- data/lib/gamebox/spec/helper.rb +165 -17
- data/lib/gamebox/tasks/gamebox_tasks.rake +27 -12
- data/lib/gamebox/version.rb +1 -1
- data/lib/gamebox/views/graphical_actor_view.rb +4 -5
- data/script/perf_aabb.rb +13 -8
- data/spec/acceptance/animation_spec.rb +1 -3
- data/spec/acceptance/basic_actor_lifecycle_spec.rb +1 -1
- data/spec/acceptance/fps_actor_spec.rb +8 -12
- data/spec/acceptance/input_mapper_spec.rb +17 -24
- data/spec/acceptance/update_ordering_spec.rb +64 -0
- data/spec/actors/label_spec.rb +90 -5
- data/spec/behaviors/animated_spec.rb +1 -1
- data/spec/behaviors/collidable_spec.rb +7 -15
- data/spec/behaviors/positioned_spec.rb +12 -5
- data/spec/core/actor_spec.rb +31 -3
- data/spec/core/actor_view_spec.rb +1 -1
- data/spec/core/behavior_spec.rb +3 -0
- data/spec/core/configuration_spec.rb +49 -2
- data/spec/core/input_mapper_spec.rb +7 -0
- data/spec/core/renderer_spec.rb +89 -0
- data/spec/core/stage_definition_spec.rb +41 -0
- data/spec/core/stage_manager_spec.rb +11 -11
- data/spec/core/stage_spec.rb +38 -78
- data/spec/core/viewport_spec.rb +5 -2
- data/spec/core/wrapped_screen_spec.rb +18 -12
- data/spec/views/graphical_actor_view_spec.rb +33 -62
- data/templates/actor_template.erb +11 -0
- data/templates/app/README.md +1 -0
- data/templates/app/src/actors/{player.rb → player_actor.rb} +3 -1
- data/templates/app/src/behaviors/.gitkeep +0 -0
- data/templates/app/src/stages/demo_stage.rb +14 -0
- data/templates/behavior_template.erb +13 -0
- data/templates/stage_template.erb +13 -0
- metadata +60 -21
- data/component_generators/actor_generator.rb +0 -17
- data/lib/gamebox/actors/emitter.rb +0 -12
- data/lib/gamebox/behaviors/emitting.rb +0 -48
- data/lib/gamebox/behaviors/input_mapper.rb +0 -11
- data/lib/gamebox/lib/ftor.rb +0 -372
- data/spec/actors/emitter_spec.rb +0 -5
- data/templates/app/NEXT_STEPS.txt +0 -1
- data/templates/app/README.rdoc +0 -24
- data/templates/app/src/demo_stage.rb +0 -7
@@ -1,7 +1,7 @@
|
|
1
1
|
class StageManager
|
2
2
|
|
3
|
-
construct_with :input_manager, :config_manager,
|
4
|
-
:this_object_context
|
3
|
+
construct_with :input_manager, :config_manager,
|
4
|
+
:stage_factory, :this_object_context
|
5
5
|
|
6
6
|
attr_reader :stage_names, :stage_opts
|
7
7
|
|
@@ -78,13 +78,13 @@ class StageManager
|
|
78
78
|
@stage = stage_name
|
79
79
|
@stage_args = args
|
80
80
|
@stages[@stage] = create_stage(@stage, @stage_opts[@stage_names.index(@stage)])
|
81
|
-
@stages[@stage].
|
81
|
+
@stages[@stage].curtain_up *args
|
82
82
|
end
|
83
83
|
|
84
84
|
def shutdown_current_stage(*args)
|
85
85
|
if @stage and @stages and @stages[@stage]
|
86
86
|
current_stage = @stages[@stage]
|
87
|
-
current_stage.
|
87
|
+
current_stage.curtain_down *args
|
88
88
|
input_manager.clear_hooks(current_stage)
|
89
89
|
@stages.delete @stage
|
90
90
|
@stage = nil
|
@@ -93,13 +93,7 @@ class StageManager
|
|
93
93
|
end
|
94
94
|
|
95
95
|
def create_stage(name, opts)
|
96
|
-
stage_instance =
|
97
|
-
this_object_context.in_subcontext do |stage_context|
|
98
|
-
name_or_klass = opts[:class] || name
|
99
|
-
stage_instance = stage_context["#{name_or_klass}_stage"]
|
100
|
-
stage_context[:stage] = stage_instance
|
101
|
-
end
|
102
|
-
stage_instance.configure(backstage, opts)
|
96
|
+
stage_instance = stage_factory.build(name, opts)
|
103
97
|
|
104
98
|
stage_instance.when :next_stage do |*args|
|
105
99
|
next_stage *args
|
@@ -3,6 +3,7 @@ class TimerManager
|
|
3
3
|
def initialize
|
4
4
|
@timers ||= {}
|
5
5
|
@dead_timers = []
|
6
|
+
@callbacks = []
|
6
7
|
end
|
7
8
|
|
8
9
|
# add block to be executed every interval_ms millis
|
@@ -23,18 +24,21 @@ class TimerManager
|
|
23
24
|
|
24
25
|
# update each timers counts, call any blocks that are over their interval
|
25
26
|
def update(time_delta)
|
26
|
-
|
27
|
+
@callbacks.clear
|
28
|
+
@dead_timers.clear
|
29
|
+
|
27
30
|
@timers.each do |name, timer_hash|
|
28
31
|
timer_hash[:count] += time_delta
|
29
32
|
if timer_hash[:count] > timer_hash[:interval_ms]
|
30
33
|
timer_hash[:count] -= timer_hash[:interval_ms]
|
31
|
-
timer_hash[:callback]
|
34
|
+
@callbacks << timer_hash[:callback]
|
32
35
|
@dead_timers << name unless timer_hash[:recurring]
|
33
36
|
end
|
34
37
|
end
|
35
38
|
@dead_timers.each do |name|
|
36
39
|
remove_timer name
|
37
40
|
end
|
41
|
+
@callbacks.each &:call
|
38
42
|
end
|
39
43
|
|
40
44
|
def pause
|
@@ -3,9 +3,11 @@
|
|
3
3
|
class Viewport
|
4
4
|
extend Publisher
|
5
5
|
can_fire :scrolled
|
6
|
+
|
7
|
+
construct_with :config_manager
|
6
8
|
|
7
9
|
attr_accessor :x_offset, :y_offset, :follow_target, :width,
|
8
|
-
:height, :x_offset_range, :y_offset_range, :boundary
|
10
|
+
:height, :x_offset_range, :y_offset_range, :boundary, :rotation
|
9
11
|
|
10
12
|
attr_reader :speed
|
11
13
|
|
@@ -13,13 +15,18 @@ class Viewport
|
|
13
15
|
"xoff:#{@x_offset} yoff:#{@y_offset}"
|
14
16
|
end
|
15
17
|
|
16
|
-
def initialize
|
18
|
+
def initialize
|
19
|
+
res = config_manager[:screen_resolution]
|
20
|
+
@width = res[0]
|
21
|
+
@height = res[1]
|
22
|
+
reset
|
23
|
+
end
|
24
|
+
|
25
|
+
def reset
|
26
|
+
@rotation = 0
|
17
27
|
@speed = 1
|
18
28
|
@x_offset = 0
|
19
29
|
@y_offset = 0
|
20
|
-
|
21
|
-
@width = width
|
22
|
-
@height = height
|
23
30
|
end
|
24
31
|
|
25
32
|
def scroll(x_delta,y_delta)
|
@@ -4,16 +4,15 @@ class WrappedScreen
|
|
4
4
|
def initialize
|
5
5
|
width, height = *config_manager[:screen_resolution]
|
6
6
|
fullscreen = config_manager[:fullscreen]
|
7
|
-
needs_cursor = config_manager[:needs_cursor]
|
8
7
|
@screen = HookedGosuWindow.new width, height, fullscreen
|
9
8
|
@screen.tap do |screen|
|
10
|
-
screen.caption =
|
11
|
-
screen.needs_cursor =
|
9
|
+
screen.caption = Gamebox.configuration.game_name
|
10
|
+
screen.needs_cursor = Gamebox.configuration.needs_cursor?
|
12
11
|
end
|
13
12
|
end
|
14
13
|
|
15
|
-
def method_missing(name,*args)
|
16
|
-
@screen.send name, *args
|
14
|
+
def method_missing(name,*args, &blk)
|
15
|
+
@screen.send name, *args, &blk
|
17
16
|
end
|
18
17
|
|
19
18
|
def width
|
@@ -129,5 +128,9 @@ class WrappedScreen
|
|
129
128
|
def draw_image(image, x, y, z, x_scale = 1, y_scale = 1, color = 0xffffffff, mode = :default)
|
130
129
|
image.draw x, y, z, x_scale, y_scale, color, mode
|
131
130
|
end
|
131
|
+
|
132
|
+
def draw_rotated_image(image, x, y, z, angle, center_x = 0.5, center_y = 0.5, x_scale = 1, y_scale = 1, color = 0xffffffff, mode = :default)
|
133
|
+
image.draw_rot x, y, z, angle, center_x, center_y, x_scale, y_scale, color, mode
|
134
|
+
end
|
132
135
|
end
|
133
136
|
|
@@ -3,12 +3,13 @@ $: << "#{File.dirname(__FILE__)}/../config"
|
|
3
3
|
|
4
4
|
begin
|
5
5
|
# optional file
|
6
|
-
require "environment"
|
6
|
+
require "environment"
|
7
7
|
rescue LoadError => err
|
8
8
|
end
|
9
9
|
|
10
10
|
class GameboxApp
|
11
11
|
attr_reader :context, :game
|
12
|
+
|
12
13
|
def self.run(argv,env)
|
13
14
|
GameboxApp.new.start argv, env
|
14
15
|
end
|
@@ -17,19 +18,16 @@ class GameboxApp
|
|
17
18
|
@context = Conject.default_object_context
|
18
19
|
end
|
19
20
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
setup_debug_server if @config_manager[:debug_server] || ARGV.include?("-debug-server")
|
21
|
+
def start(argv,env)
|
22
|
+
setup(argv,env)
|
23
|
+
main_loop
|
24
|
+
shutdown
|
25
25
|
end
|
26
26
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
end
|
32
|
-
end
|
27
|
+
def setup(argv,env)
|
28
|
+
@game = @context[:game]
|
29
|
+
@game.configure
|
30
|
+
self.class.post_setup_handlers.each { |handler| handler.setup(argv, env, @context[:config_manager]) }
|
33
31
|
end
|
34
32
|
|
35
33
|
def main_loop
|
@@ -38,18 +36,22 @@ class GameboxApp
|
|
38
36
|
@input_manager.show
|
39
37
|
end
|
40
38
|
|
41
|
-
def shutdown
|
42
|
-
end
|
43
|
-
|
44
|
-
def start(argv,env)
|
45
|
-
setup
|
39
|
+
def shutdown ; end
|
46
40
|
|
47
|
-
|
41
|
+
def self.register_post_setup_handler(handler)
|
42
|
+
post_setup_handlers.push handler
|
43
|
+
end
|
48
44
|
|
49
|
-
|
45
|
+
def self.post_setup_handlers
|
46
|
+
@post_setup_handlers ||= [ ]
|
50
47
|
end
|
48
|
+
|
51
49
|
end
|
52
50
|
|
51
|
+
GameboxApp.register_post_setup_handler PostSetupHandlers::FileWatcher
|
52
|
+
GameboxApp.register_post_setup_handler PostSetupHandlers::GameboxAppAddDebugHelpers
|
53
|
+
GameboxApp.register_post_setup_handler PostSetupHandlers::PryRemoteServer
|
54
|
+
|
53
55
|
if $0 == __FILE__
|
54
56
|
GameboxApp.run ARGV, ENV
|
55
57
|
end
|
@@ -1,5 +1,29 @@
|
|
1
1
|
module ObservableAttributes
|
2
2
|
|
3
|
+
# atomically update all attributes
|
4
|
+
# it updates all of the values; then triggers the events
|
5
|
+
# my_actor.update_attributes(x: 5, y: 7)
|
6
|
+
# will emit both x_changed and y_changed after setting _both_ values
|
7
|
+
def update_attributes(attributes)
|
8
|
+
# TODO put this in kvo gem; there is too much internal knowlege of where
|
9
|
+
# kvo stores stuff
|
10
|
+
#
|
11
|
+
# self.kvo_set('x', new_val, silent: true)
|
12
|
+
# self.changed('x', old_val, new_val)
|
13
|
+
old_values = attributes.keys.inject({}) do |hash, attr_name|
|
14
|
+
hash[attr_name] = self.send(attr_name)
|
15
|
+
hash
|
16
|
+
end
|
17
|
+
|
18
|
+
attributes.each do |name, val|
|
19
|
+
self.instance_variable_set("@kvo_#{name}", val)
|
20
|
+
end
|
21
|
+
|
22
|
+
attributes.each do |name, val|
|
23
|
+
fire "#{name}_changed".to_sym, old_values[name], val
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
3
27
|
def has_attributes(*names)
|
4
28
|
if names.first.is_a? Hash
|
5
29
|
names.first.each do |name, default|
|
@@ -0,0 +1,432 @@
|
|
1
|
+
#++
|
2
|
+
# Copyright (C) 2008-2010 John Croisant
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights to
|
7
|
+
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
8
|
+
# of the Software, and to permit persons to whom the Software is furnished to do
|
9
|
+
# so, subject to the following conditions: #
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software. #
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
20
|
+
# SOFTWARE.
|
21
|
+
|
22
|
+
# The Vector2 class implements two-dimensional vectors.
|
23
|
+
# It is used to represent positions, movements, and velocities
|
24
|
+
# in 2D space.
|
25
|
+
#
|
26
|
+
class Vector2
|
27
|
+
include Enumerable
|
28
|
+
|
29
|
+
RAD_TO_DEG = 180.0 / Math::PI
|
30
|
+
DEG_TO_RAD = Math::PI / 180.0
|
31
|
+
|
32
|
+
class << self
|
33
|
+
|
34
|
+
alias :[] :new
|
35
|
+
|
36
|
+
|
37
|
+
# Creates a new Vector2 from an angle in radians and a
|
38
|
+
# magnitude. Use #new_polar_deg for degrees.
|
39
|
+
#
|
40
|
+
def new_polar( angle_rad, magnitude )
|
41
|
+
self.new( Math::cos(angle_rad)*magnitude,
|
42
|
+
Math::sin(angle_rad)*magnitude )
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
# Creates a new Vector2 from an angle in degrees and a
|
47
|
+
# magnitude. Use #new_polar for radians.
|
48
|
+
#
|
49
|
+
def new_polar_deg( angle_deg, magnitude )
|
50
|
+
self.new_polar( angle_deg * DEG_TO_RAD, magnitude )
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
# call-seq:
|
55
|
+
# Vector2.many( [x1,y1], [x2,y2], ... )
|
56
|
+
#
|
57
|
+
# Converts multiple [x,y] Arrays to Vector2s.
|
58
|
+
# Returns the resulting vectors in an Array.
|
59
|
+
#
|
60
|
+
def many( *pairs )
|
61
|
+
pairs.collect { |pair| self.new(*pair) }
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
# Creates a new Vector2 with the given x and y values.
|
68
|
+
def initialize( x, y )
|
69
|
+
@x, @y = x.to_f, y.to_f
|
70
|
+
end
|
71
|
+
|
72
|
+
attr_reader :x, :y
|
73
|
+
|
74
|
+
def x=( value )
|
75
|
+
raise "can't modify frozen object" if frozen?
|
76
|
+
@x = value.to_f
|
77
|
+
@hash = nil
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
def y=( value )
|
82
|
+
raise "can't modify frozen object" if frozen?
|
83
|
+
@y = value.to_f
|
84
|
+
@hash = nil
|
85
|
+
self
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
# Sets this vector's x and y components.
|
90
|
+
def set!( x, y )
|
91
|
+
raise "can't modify frozen object" if frozen?
|
92
|
+
@x = x.to_f
|
93
|
+
@y = y.to_f
|
94
|
+
@hash = nil
|
95
|
+
self
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
# Sets this vector's angle (in radians) and magnitude.
|
100
|
+
# Use #set_polar_deg! for degrees.
|
101
|
+
#
|
102
|
+
def set_polar!( angle_rad, mag )
|
103
|
+
raise "can't modify frozen object" if frozen?
|
104
|
+
@x = Math::cos(angle_rad) * mag
|
105
|
+
@y = Math::sin(angle_rad) * mag
|
106
|
+
@hash = nil
|
107
|
+
self
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
# Sets this vector's angle (in degrees) and magnitude.
|
112
|
+
# Use #set_polar! for radians.
|
113
|
+
#
|
114
|
+
def set_polar_deg!( angle_deg, mag )
|
115
|
+
set_polar!( angle_deg * DEG_TO_RAD, mag )
|
116
|
+
self
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
# Adds the given vector to this one and return the
|
121
|
+
# resulting vector.
|
122
|
+
#
|
123
|
+
def +( vector )
|
124
|
+
self.class.new( @x + vector.at(0), @y + vector.at(1) )
|
125
|
+
end
|
126
|
+
|
127
|
+
# call-seq:
|
128
|
+
# move!( [x,y] )
|
129
|
+
# move!( x,y )
|
130
|
+
#
|
131
|
+
# Moves the vector by the given x and y amounts.
|
132
|
+
def move!( x, y=nil )
|
133
|
+
raise "can't modify frozen object" if frozen?
|
134
|
+
if y.nil?
|
135
|
+
a = x.to_ary
|
136
|
+
@x += a[0]
|
137
|
+
@y += a[1]
|
138
|
+
else
|
139
|
+
@x += x
|
140
|
+
@y += y
|
141
|
+
end
|
142
|
+
@hash = nil
|
143
|
+
self
|
144
|
+
end
|
145
|
+
|
146
|
+
# call-seq:
|
147
|
+
# move( [x,y] )
|
148
|
+
# move( x,y )
|
149
|
+
#
|
150
|
+
# Like #move!, but returns a new vector.
|
151
|
+
def move( x, y=nil )
|
152
|
+
self.dup.move!(x, y)
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
# Subtracts the given vector from this one and return
|
157
|
+
# the resulting vector.
|
158
|
+
#
|
159
|
+
def -( vector )
|
160
|
+
self.class.new( @x - vector.at(0), @y - vector.at(1) )
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
# Returns the opposite of this vector, i.e. Vector2[-x, -y].
|
165
|
+
def -@
|
166
|
+
self.class.new( -@x, -@y )
|
167
|
+
end
|
168
|
+
|
169
|
+
# Reverses the vector's direction, i.e. Vector2[-x, -y].
|
170
|
+
def reverse!
|
171
|
+
raise "can't modify frozen object" if frozen?
|
172
|
+
@x, @y = -@x, -@y
|
173
|
+
@hash = nil
|
174
|
+
self
|
175
|
+
end
|
176
|
+
|
177
|
+
# Like #reverse!, but returns a new vector.
|
178
|
+
def reverse
|
179
|
+
self.dup.reverse!
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
# Multiplies this vector by the given scalar (Numeric),
|
184
|
+
# and return the resulting vector.
|
185
|
+
#
|
186
|
+
def *( scalar )
|
187
|
+
self.class.new( @x * scalar, @y * scalar )
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
# True if the given vector's x and y components are
|
192
|
+
# equal to this vector's components (within a small margin
|
193
|
+
# of error to compensate for floating point imprecision).
|
194
|
+
#
|
195
|
+
def ==( vector )
|
196
|
+
_nearly_equal?(@x, vector.at(0)) and _nearly_equal?(@y, vector.at(1))
|
197
|
+
end
|
198
|
+
|
199
|
+
|
200
|
+
# Returns a component of this vector as if it were an
|
201
|
+
# [x,y] Array.
|
202
|
+
#
|
203
|
+
def []( index )
|
204
|
+
[@x, @y][index]
|
205
|
+
end
|
206
|
+
|
207
|
+
alias :at :[]
|
208
|
+
|
209
|
+
|
210
|
+
def hash # :nodoc:
|
211
|
+
@hash ||= (((@x - @x.modulo(1E-10)).hash << 2) +
|
212
|
+
((@y - @y.modulo(1E-10)).hash << 1) +
|
213
|
+
self.class.hash)
|
214
|
+
end
|
215
|
+
|
216
|
+
|
217
|
+
# Iterates over this vector as if it were an [x,y] Array.
|
218
|
+
#
|
219
|
+
def each( &block )
|
220
|
+
[@x, @y].each( &block )
|
221
|
+
end
|
222
|
+
|
223
|
+
|
224
|
+
# Returns the angle of this vector, relative to the positive
|
225
|
+
# X axis, in radians. Use #angle_deg for degrees.
|
226
|
+
#
|
227
|
+
def angle
|
228
|
+
Math.atan2( @y, @x )
|
229
|
+
end
|
230
|
+
|
231
|
+
|
232
|
+
# Sets the vector's angle in radians. The vector keeps the same
|
233
|
+
# magnitude as before.
|
234
|
+
#
|
235
|
+
def angle=( angle_rad )
|
236
|
+
raise "can't modify frozen object" if frozen?
|
237
|
+
m = magnitude
|
238
|
+
@x = Math::cos(angle_rad) * m
|
239
|
+
@y = Math::sin(angle_rad) * m
|
240
|
+
@hash = nil
|
241
|
+
self
|
242
|
+
end
|
243
|
+
|
244
|
+
|
245
|
+
# Returns the angle of this vector relative to the other vector,
|
246
|
+
# in radians. Use #angle_deg_with for degrees.
|
247
|
+
#
|
248
|
+
def angle_with( vector )
|
249
|
+
Math.acos( udot(vector) )
|
250
|
+
end
|
251
|
+
|
252
|
+
|
253
|
+
# Returns the angle of this vector, relative to the positive
|
254
|
+
# X axis, in degrees. Use #angle for radians.
|
255
|
+
#
|
256
|
+
def angle_deg
|
257
|
+
angle * RAD_TO_DEG
|
258
|
+
end
|
259
|
+
|
260
|
+
|
261
|
+
# Sets the vector's angle in degrees. The vector keeps the same
|
262
|
+
# magnitude as before.
|
263
|
+
#
|
264
|
+
def angle_deg=( angle_deg )
|
265
|
+
self.angle = angle_deg * DEG_TO_RAD
|
266
|
+
self
|
267
|
+
end
|
268
|
+
|
269
|
+
|
270
|
+
# Returns the angle of this vector relative to the other vector,
|
271
|
+
# in degrees. Use #angle_with for radians.
|
272
|
+
#
|
273
|
+
def angle_deg_with( vector )
|
274
|
+
angle_with(vector) * RAD_TO_DEG
|
275
|
+
end
|
276
|
+
|
277
|
+
|
278
|
+
# Returns the dot product between this vector and the other vector.
|
279
|
+
def dot( vector )
|
280
|
+
(@x * vector.at(0)) + (@y * vector.at(1))
|
281
|
+
end
|
282
|
+
|
283
|
+
|
284
|
+
# Returns the magnitude (distance) of this vector.
|
285
|
+
def magnitude
|
286
|
+
Math.hypot( @x, @y )
|
287
|
+
end
|
288
|
+
|
289
|
+
|
290
|
+
# Sets the vector's magnitude (distance). The vector keeps the
|
291
|
+
# same angle as before.
|
292
|
+
#
|
293
|
+
def magnitude=( mag )
|
294
|
+
raise "can't modify frozen object" if frozen?
|
295
|
+
angle_rad = angle
|
296
|
+
@x = Math::cos(angle_rad) * mag
|
297
|
+
@y = Math::sin(angle_rad) * mag
|
298
|
+
@hash = nil
|
299
|
+
self
|
300
|
+
end
|
301
|
+
|
302
|
+
|
303
|
+
# Returns a copy of this vector, but rotated 90 degrees
|
304
|
+
# counter-clockwise.
|
305
|
+
#
|
306
|
+
def perp
|
307
|
+
self.class.new( -@y, @x )
|
308
|
+
end
|
309
|
+
|
310
|
+
|
311
|
+
# Sets this vector to the vector projection (aka vector resolute)
|
312
|
+
# of this vector onto the other vector. See also #projected_onto.
|
313
|
+
#
|
314
|
+
def project_onto!( vector )
|
315
|
+
raise "can't modify frozen object" if frozen?
|
316
|
+
@x, @y = *(vector * vector.dot(self) * (1/vector.magnitude**2))
|
317
|
+
@hash = nil
|
318
|
+
self
|
319
|
+
end
|
320
|
+
|
321
|
+
# Like #project_onto!, but returns a new vector.
|
322
|
+
def projected_onto( vector )
|
323
|
+
dup.project_onto!( vector )
|
324
|
+
end
|
325
|
+
|
326
|
+
|
327
|
+
# Rotates the vector the given number of radians.
|
328
|
+
# Use #rotate_deg! for degrees.
|
329
|
+
#
|
330
|
+
def rotate!( angle_rad )
|
331
|
+
self.angle += angle_rad
|
332
|
+
self
|
333
|
+
end
|
334
|
+
|
335
|
+
# Like #rotate!, but returns a new vector.
|
336
|
+
def rotate( angle_rad )
|
337
|
+
dup.rotate!( angle_rad )
|
338
|
+
end
|
339
|
+
|
340
|
+
|
341
|
+
# Rotates the vector the given number of degrees.
|
342
|
+
# Use #rotate for radians.
|
343
|
+
#
|
344
|
+
def rotate_deg!( angle_deg )
|
345
|
+
self.angle_deg += angle_deg
|
346
|
+
self
|
347
|
+
end
|
348
|
+
|
349
|
+
# Like #rotate_deg!, but returns a new vector.
|
350
|
+
def rotate_deg( angle_deg )
|
351
|
+
dup.rotate_deg!( angle_deg )
|
352
|
+
end
|
353
|
+
|
354
|
+
|
355
|
+
# call-seq:
|
356
|
+
# scale!( scale )
|
357
|
+
# scale!( scale_x, scale_y )
|
358
|
+
#
|
359
|
+
# Multiplies this vector's x and y values.
|
360
|
+
#
|
361
|
+
# If one number is given, the vector will be equal to
|
362
|
+
# Vector2[x*scale, y*scale]. If two numbers are given, it will be
|
363
|
+
# equal to Vector2[x*scale_x, y*scale_y].
|
364
|
+
#
|
365
|
+
# Example:
|
366
|
+
#
|
367
|
+
# v = Vector2[1.5,2.5]
|
368
|
+
# v.scale!( 2 ) # => Vector2[3,5]
|
369
|
+
# v.scale!( 3, 4 ) # => Vector2[9,20]
|
370
|
+
#
|
371
|
+
def scale!( scale_x, scale_y = scale_x )
|
372
|
+
raise "can't modify frozen object" if frozen?
|
373
|
+
@x, @y = @x * scale_x, @y * scale_y
|
374
|
+
@hash = nil
|
375
|
+
self
|
376
|
+
end
|
377
|
+
|
378
|
+
# Like #scale!, but returns a new vector.
|
379
|
+
def scale( scale_x, scale_y = scale_x )
|
380
|
+
dup.scale!(scale_x, scale_y)
|
381
|
+
end
|
382
|
+
|
383
|
+
|
384
|
+
# Returns this vector as an [x,y] Array.
|
385
|
+
def to_ary
|
386
|
+
[@x, @y]
|
387
|
+
end
|
388
|
+
|
389
|
+
alias :to_a :to_ary
|
390
|
+
|
391
|
+
|
392
|
+
def to_s
|
393
|
+
"Vector2[#{@x}, #{@y}]"
|
394
|
+
end
|
395
|
+
|
396
|
+
alias :inspect :to_s
|
397
|
+
|
398
|
+
|
399
|
+
# Returns the dot product of this vector's #unit and the other
|
400
|
+
# vector's #unit.
|
401
|
+
#
|
402
|
+
def udot( vector )
|
403
|
+
unit.dot( vector.unit )
|
404
|
+
end
|
405
|
+
|
406
|
+
|
407
|
+
# Sets this vector's magnitude to 1.
|
408
|
+
def unit!
|
409
|
+
raise "can't modify frozen object" if frozen?
|
410
|
+
scale = 1/magnitude
|
411
|
+
@x, @y = @x * scale, @y * scale
|
412
|
+
@hash = nil
|
413
|
+
self
|
414
|
+
end
|
415
|
+
|
416
|
+
alias :normalize! :unit!
|
417
|
+
|
418
|
+
# Like #unit!, but returns a new vector.
|
419
|
+
def unit
|
420
|
+
self.dup.unit!
|
421
|
+
end
|
422
|
+
|
423
|
+
alias :normalized :unit
|
424
|
+
|
425
|
+
|
426
|
+
private
|
427
|
+
|
428
|
+
def _nearly_equal?( a, b, threshold=1E-10 ) # :nodoc:
|
429
|
+
(a - b).abs <= threshold
|
430
|
+
end
|
431
|
+
|
432
|
+
end
|