gamebox 0.1.1 → 0.2.1
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.
- data/.gitignore +4 -0
- data/Gemfile +9 -0
- data/History.txt +5 -0
- data/README.txt +4 -3
- data/Rakefile +12 -4
- data/TODO.txt +1 -5
- data/VERSION +1 -1
- data/docs/getting_started.rdoc +2 -2
- data/gamebox.gemspec +26 -10
- data/lib/gamebox/actor.rb +10 -3
- data/lib/gamebox/actor_factory.rb +2 -2
- data/lib/gamebox/actor_view.rb +14 -11
- data/lib/gamebox/actors/collidable_debugger.rb +35 -0
- data/lib/gamebox/actors/curtain.rb +2 -2
- data/lib/gamebox/actors/logo.rb +0 -6
- data/lib/gamebox/actors/score.rb +2 -5
- data/lib/gamebox/actors/spatial_debugger.rb +47 -0
- data/lib/gamebox/actors/svg_actor.rb +4 -6
- data/lib/gamebox/arbiter.rb +108 -15
- data/lib/gamebox/behavior.rb +29 -1
- data/lib/gamebox/behaviors/animated.rb +14 -23
- data/lib/gamebox/behaviors/audible.rb +1 -12
- data/lib/gamebox/behaviors/collidable.rb +29 -22
- data/lib/gamebox/behaviors/collidable/aabb_collidable.rb +61 -0
- data/lib/gamebox/behaviors/collidable/circle_collidable.rb +17 -0
- data/lib/gamebox/behaviors/collidable/collidable_shape.rb +35 -0
- data/lib/gamebox/behaviors/collidable/polygon_collidable.rb +85 -0
- data/lib/gamebox/behaviors/graphical.rb +13 -10
- data/lib/gamebox/behaviors/layered.rb +6 -20
- data/lib/gamebox/behaviors/physical.rb +116 -93
- data/lib/gamebox/class_finder.rb +6 -2
- data/lib/gamebox/config_manager.rb +24 -4
- data/lib/gamebox/data/config/objects.yml +5 -3
- data/lib/gamebox/ftor.rb +372 -0
- data/lib/gamebox/gamebox_application.rb +2 -8
- data/lib/gamebox/hooked_gosu_window.rb +30 -0
- data/lib/gamebox/input_manager.rb +78 -79
- data/lib/gamebox/lib/code_statistics.rb +1 -1
- data/lib/gamebox/lib/numbers_ext.rb +1 -1
- data/lib/gamebox/lib/rect.rb +612 -0
- data/lib/gamebox/physical_stage.rb +12 -2
- data/lib/gamebox/physics.rb +14 -3
- data/lib/gamebox/resource_manager.rb +28 -65
- data/lib/gamebox/sound_manager.rb +7 -13
- data/lib/gamebox/spatial_hash.rb +60 -14
- data/lib/gamebox/spatial_stagehand.rb +19 -0
- data/lib/gamebox/stage.rb +16 -1
- data/lib/gamebox/stage_manager.rb +1 -1
- data/lib/gamebox/svg_document.rb +1 -0
- data/lib/gamebox/tasks/gamebox_tasks.rb +23 -11
- data/lib/gamebox/templates/template_app/.gitignore +1 -0
- data/lib/gamebox/templates/template_app/Gemfile +1 -1
- data/lib/gamebox/templates/template_app/Rakefile +6 -21
- data/lib/gamebox/templates/template_app/config/environment.rb +14 -0
- data/lib/gamebox/templates/template_app/config/game.yml +2 -1
- data/lib/gamebox/templates/template_app/script/generate +0 -3
- data/lib/gamebox/templates/template_app/src/demo_stage.rb +1 -11
- data/lib/gamebox/templates/template_app/src/game.rb +0 -1
- data/lib/gamebox/templates/template_app/src/my_actor.rb +2 -2
- data/lib/gamebox/version.rb +1 -1
- data/lib/gamebox/views/graphical_actor_view.rb +15 -9
- data/lib/gamebox/wrapped_screen.rb +114 -7
- data/load_paths.rb +20 -0
- data/script/perf_spatial_hash.rb +73 -0
- data/spec/actor_view_spec.rb +1 -1
- data/spec/arbiter_spec.rb +264 -0
- data/spec/behavior_spec.rb +19 -2
- data/spec/collidable_spec.rb +105 -5
- data/spec/helper.rb +1 -1
- data/spec/label_spec.rb +1 -1
- data/spec/resource_manager_spec.rb +1 -1
- data/spec/spatial_hash_spec.rb +1 -1
- metadata +52 -10
- data/lib/gamebox/lib/surface_ext.rb +0 -76
@@ -1,9 +1,5 @@
|
|
1
|
-
require 'actor'
|
2
|
-
require 'actor_view'
|
3
|
-
|
4
1
|
require "enumerator"
|
5
2
|
|
6
|
-
|
7
3
|
# SvgActor knows how to build himself based on an svg document based on the :name
|
8
4
|
# passed in being the group name in the doc (layer).
|
9
5
|
class SvgActor < Actor
|
@@ -38,11 +34,13 @@ class SvgActor < Actor
|
|
38
34
|
end
|
39
35
|
|
40
36
|
class SvgActorView < ActorView
|
41
|
-
def draw(target,x_off,y_off)
|
37
|
+
def draw(target, x_off, y_off, z)
|
42
38
|
@actor.segments.each do |seg|
|
43
39
|
p1 = seg[0]
|
44
40
|
p2 = seg[1]
|
45
|
-
|
41
|
+
# TODO pull in draw_line_s?
|
42
|
+
target.draw_line p1.x+x_off, p1.y+y_off, p2.x+x_off, p2.y+y_off, [25,255,25,255], z
|
43
|
+
#target.draw_line_s [p1.x+x_off,p1.y+y_off], [p2.x+x_off,p2.y+y_off], [25,255,25,255], 6
|
46
44
|
end
|
47
45
|
end
|
48
46
|
end
|
data/lib/gamebox/arbiter.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# this module gets mixed into a stage to allow it to handle collision detection
|
2
2
|
module Arbiter
|
3
|
+
attr_reader :checks, :collisions
|
3
4
|
|
4
5
|
def register_collidable(actor)
|
5
6
|
@spatial_hash = stagehand(:spatial)
|
@@ -27,42 +28,62 @@ module Arbiter
|
|
27
28
|
|
28
29
|
first_objs.each do |fobj|
|
29
30
|
second_objs.each do |sobj|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
31
|
+
if fobj.to_i < sobj.to_i
|
32
|
+
@collision_handlers[fobj] ||= {}
|
33
|
+
@collision_handlers[fobj][sobj] = [false,block]
|
34
|
+
else
|
35
|
+
@collision_handlers[sobj] ||= {}
|
36
|
+
@collision_handlers[sobj][fobj] = [true,block]
|
37
|
+
end
|
35
38
|
end
|
36
39
|
end
|
37
40
|
end
|
38
41
|
|
39
42
|
def run_callbacks(collisions)
|
43
|
+
@collision_handlers ||= {}
|
40
44
|
collisions.each do |collision|
|
41
45
|
first = collision.first
|
42
46
|
second = collision.last
|
47
|
+
unless first.actor_type.to_i < second.actor_type.to_i
|
48
|
+
tmp = first
|
49
|
+
first = second
|
50
|
+
second = tmp
|
51
|
+
end
|
43
52
|
|
44
53
|
colliders = @collision_handlers[first.actor_type]
|
45
|
-
callback = colliders[second.actor_type] unless colliders.nil?
|
46
|
-
|
54
|
+
swapped, callback = colliders[second.actor_type] unless colliders.nil?
|
55
|
+
unless callback.nil?
|
56
|
+
if swapped
|
57
|
+
callback.call second, first
|
58
|
+
else
|
59
|
+
callback.call first, second
|
60
|
+
end
|
61
|
+
end
|
47
62
|
end
|
48
63
|
end
|
49
64
|
|
50
65
|
def find_collisions
|
51
66
|
@collidable_actors ||= []
|
67
|
+
@checks = 0
|
68
|
+
@collisions = 0
|
52
69
|
tmp_collidable_actors = @collidable_actors.dup
|
53
70
|
collisions = {}
|
54
71
|
|
55
72
|
@collidable_actors.each do |first|
|
56
73
|
x = first.x - @spatial_hash.cell_size
|
57
74
|
y = first.y - @spatial_hash.cell_size
|
75
|
+
# TODO base this on size of object
|
58
76
|
w = @spatial_hash.cell_size * 3
|
59
77
|
h = w
|
60
|
-
|
78
|
+
|
79
|
+
tmp_collidable_actors = @spatial_hash.neighbors_of(first)
|
61
80
|
|
62
81
|
tmp_collidable_actors.each do |second|
|
82
|
+
@checks += 1
|
63
83
|
if first != second && collide?(first, second)
|
64
84
|
collisions[second] ||= []
|
65
85
|
if !collisions[second].include?(first)
|
86
|
+
@collisions += 1
|
66
87
|
collisions[first] ||= []
|
67
88
|
collisions[first] << second
|
68
89
|
end
|
@@ -79,21 +100,93 @@ module Arbiter
|
|
79
100
|
end
|
80
101
|
|
81
102
|
def collide?(object, other)
|
82
|
-
|
103
|
+
# TODO perf analysis of this
|
104
|
+
self.send "collide_#{object.collidable_shape}_#{other.collidable_shape}?", object, other
|
83
105
|
end
|
84
106
|
|
85
107
|
def collide_circle_circle?(object, other)
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
y_prime = other.y + other.radius
|
108
|
+
x = object.center_x
|
109
|
+
y = object.center_y
|
110
|
+
x_prime = other.center_x
|
111
|
+
y_prime = other.center_y
|
91
112
|
|
92
113
|
x_dist = (x_prime - x) * (x_prime - x)
|
93
114
|
y_dist = (y_prime - y) * (y_prime - y)
|
94
115
|
|
95
116
|
total_radius = object.radius + other.radius
|
96
|
-
x_dist + y_dist
|
117
|
+
x_dist + y_dist <= (total_radius * total_radius)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Idea from:
|
121
|
+
# http://gpwiki.org/index.php/Polygon_Collision
|
122
|
+
# and http://www.gamedev.net/community/forums/topic.asp?topic_id=540755&whichpage=1�
|
123
|
+
def collide_polygon_polygon?(object, other)
|
124
|
+
if collide_circle_circle? object, other
|
125
|
+
# collect vector's perp
|
126
|
+
potential_sep_axis =
|
127
|
+
(object.cw_world_edge_normals | other.cw_world_edge_normals).uniq
|
128
|
+
potential_sep_axis.each do |axis|
|
129
|
+
return false unless project_and_detect(axis, object, other)
|
130
|
+
end
|
131
|
+
else
|
132
|
+
return false
|
133
|
+
end
|
134
|
+
true
|
135
|
+
end
|
136
|
+
alias collide_aabb_aabb? collide_polygon_polygon?
|
137
|
+
|
138
|
+
# returns true if the projections overlap
|
139
|
+
def project_and_detect(axis, a, b)
|
140
|
+
a_min, a_max = send("#{a.collidable_shape}_interval", axis, a)
|
141
|
+
b_min, b_max = send("#{b.collidable_shape}_interval", axis, b)
|
142
|
+
|
143
|
+
a_min <= b_max && b_min <= a_max
|
97
144
|
end
|
98
145
|
|
146
|
+
def polygon_interval(axis, object)
|
147
|
+
min = max = nil
|
148
|
+
object.cw_world_points.each do |edge|
|
149
|
+
# vector dot product
|
150
|
+
d = edge[0] * axis[0] + edge[1] * axis[1]
|
151
|
+
min ||= d
|
152
|
+
max ||= d
|
153
|
+
min = d if d < min
|
154
|
+
max = d if d > max
|
155
|
+
end
|
156
|
+
[min,max]
|
157
|
+
end
|
158
|
+
alias aabb_interval polygon_interval
|
159
|
+
|
160
|
+
def circle_interval(axis, object)
|
161
|
+
axis_x = axis[0]
|
162
|
+
axis_y = axis[1]
|
163
|
+
|
164
|
+
obj_x = object.center_x
|
165
|
+
obj_y = object.center_y
|
166
|
+
|
167
|
+
length = Math.sqrt(axis_x * axis_x + axis_y * axis_y)
|
168
|
+
cn = axis_x*obj_x + axis_y*obj_y
|
169
|
+
rlength = object.radius*length
|
170
|
+
min = cn - rlength
|
171
|
+
max = cn + rlength
|
172
|
+
[min,max]
|
173
|
+
end
|
174
|
+
|
175
|
+
def collide_polygon_circle?(object, other)
|
176
|
+
collide_circle_polygon?(other, object)
|
177
|
+
end
|
178
|
+
alias collide_aabb_circle? collide_polygon_circle?
|
179
|
+
|
180
|
+
def collide_circle_polygon?(object, other)
|
181
|
+
if collide_circle_circle? object, other
|
182
|
+
potential_sep_axis = other.cw_world_edge_normals
|
183
|
+
potential_sep_axis.each do |axis|
|
184
|
+
return false unless project_and_detect(axis, object, other)
|
185
|
+
end
|
186
|
+
true
|
187
|
+
else
|
188
|
+
false
|
189
|
+
end
|
190
|
+
end
|
191
|
+
alias collide_circle_aabb? collide_circle_polygon?
|
99
192
|
end
|
data/lib/gamebox/behavior.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
# Behavior is any type of behavior an actor can exibit.
|
2
2
|
class Behavior
|
3
|
-
attr_accessor :actor, :opts
|
3
|
+
attr_accessor :actor, :opts, :relegated_methods
|
4
4
|
|
5
5
|
def initialize(actor,opts={})
|
6
6
|
@actor = actor
|
7
7
|
@opts = opts
|
8
|
+
@relegated_methods = []
|
9
|
+
|
8
10
|
req_behs = self.class.required_behaviors
|
9
11
|
req_behs.each do |beh|
|
10
12
|
unless @actor.is? beh
|
@@ -18,6 +20,15 @@ class Behavior
|
|
18
20
|
end
|
19
21
|
|
20
22
|
def removed
|
23
|
+
target = self
|
24
|
+
|
25
|
+
@actor.instance_eval do
|
26
|
+
(class << self; self; end).class_eval do
|
27
|
+
target.relegated_methods.each do |meth|
|
28
|
+
remove_method meth
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
21
32
|
end
|
22
33
|
|
23
34
|
def update(time)
|
@@ -39,4 +50,21 @@ class Behavior
|
|
39
50
|
requires_behaviors(*args)
|
40
51
|
end
|
41
52
|
|
53
|
+
def relegates(*methods)
|
54
|
+
target = self
|
55
|
+
|
56
|
+
@actor.instance_eval do
|
57
|
+
(class << self; self; end).class_eval do
|
58
|
+
methods.each do |meth|
|
59
|
+
log("redefining #{meth} for #{@actor.class}") if @actor.respond_to? meth
|
60
|
+
target.relegated_methods << meth
|
61
|
+
|
62
|
+
define_method meth do |*args|
|
63
|
+
target.send meth, *args
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
42
70
|
end
|
@@ -18,31 +18,22 @@ class Animated < Behavior
|
|
18
18
|
@frame_num = 0
|
19
19
|
self.action = :idle
|
20
20
|
|
21
|
-
|
21
|
+
relegates :image, :width, :height,
|
22
|
+
:start_animating, :stop_animating, :animated,
|
23
|
+
:action, :action=
|
22
24
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
animated_obj.stop_animating
|
33
|
-
end
|
34
|
-
define_method :action do
|
35
|
-
animated_obj.action
|
36
|
-
end
|
37
|
-
define_method :action= do |action_sym|
|
38
|
-
animated_obj.action = action_sym
|
39
|
-
end
|
40
|
-
define_method :animated do
|
41
|
-
animated_obj
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def animated
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def width
|
32
|
+
image.width
|
33
|
+
end
|
45
34
|
|
35
|
+
def height
|
36
|
+
image.height
|
46
37
|
end
|
47
38
|
|
48
39
|
def update(time)
|
@@ -6,18 +6,7 @@ class Audible < Behavior
|
|
6
6
|
@sound_manager = @actor.stage.sound_manager
|
7
7
|
|
8
8
|
audible_obj = self
|
9
|
-
|
10
|
-
(class << self; self; end).class_eval do
|
11
|
-
define_method :play_sound do |*args|
|
12
|
-
audible_obj.play_sound *args
|
13
|
-
end
|
14
|
-
define_method :stop_sound do |*args|
|
15
|
-
audible_obj.stop_sound *args
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
|
9
|
+
relegates :play_sound, :stop_sound
|
21
10
|
end
|
22
11
|
|
23
12
|
# Plays a sound via the SoundManager. See SoundManager for
|
@@ -1,26 +1,19 @@
|
|
1
1
|
require 'behavior'
|
2
|
+
require 'collidable/circle_collidable'
|
3
|
+
require 'collidable/aabb_collidable'
|
4
|
+
require 'collidable/polygon_collidable'
|
2
5
|
|
6
|
+
# available collidable_shapes are :circle, :polygon, :aabb
|
3
7
|
class Collidable < Behavior
|
4
8
|
|
5
|
-
attr_accessor :
|
9
|
+
attr_accessor :collidable_shape, :cw_local_points, :shape
|
6
10
|
|
7
11
|
def setup
|
8
|
-
@shape =
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
@actor.instance_eval do
|
15
|
-
(class << self; self; end).class_eval do
|
16
|
-
define_method :shape do |*args|
|
17
|
-
collidable_obj.shape *args
|
18
|
-
end
|
19
|
-
define_method :radius do |*args|
|
20
|
-
collidable_obj.radius *args
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
12
|
+
@shape = build_shape
|
13
|
+
|
14
|
+
relegates :collidable_shape, :radius, :cw_world_points, :cw_world_lines, :center_x, :center_y, :cw_world_edge_normals
|
15
|
+
relegates :width unless @actor.respond_to? :width
|
16
|
+
relegates :height unless @actor.respond_to? :height
|
24
17
|
|
25
18
|
register_actor
|
26
19
|
end
|
@@ -29,13 +22,27 @@ class Collidable < Behavior
|
|
29
22
|
@actor.stage.register_collidable @actor
|
30
23
|
end
|
31
24
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
25
|
+
def build_shape
|
26
|
+
shape = nil
|
27
|
+
@collidable_shape = opts[:shape]
|
28
|
+
case @collidable_shape
|
29
|
+
when :circle
|
30
|
+
shape = CircleCollidable.new @actor, opts
|
31
|
+
when :aabb
|
32
|
+
shape = AaBbCollidable.new @actor, opts
|
33
|
+
when :polygon
|
34
|
+
shape = PolygonCollidable.new @actor, opts
|
35
|
+
end
|
36
|
+
|
37
|
+
shape.setup
|
38
|
+
shape
|
35
39
|
end
|
36
40
|
|
37
|
-
def
|
38
|
-
|
41
|
+
def update(time)
|
42
|
+
shape.update(time)
|
39
43
|
end
|
40
44
|
|
45
|
+
def method_missing(name, *args)
|
46
|
+
@shape.send(name, *args)
|
47
|
+
end
|
41
48
|
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'collidable/collidable_shape'
|
2
|
+
|
3
|
+
class AaBbCollidable < CollidableShape
|
4
|
+
attr_accessor :cw_local_points
|
5
|
+
|
6
|
+
# TODO infinite loop if actor hasn't defined width and it gets relegated to us
|
7
|
+
def calculate_radius
|
8
|
+
w = @actor.width
|
9
|
+
hw = w * 0.5
|
10
|
+
h = @actor.height
|
11
|
+
hh = h * 0.5
|
12
|
+
Math.sqrt(hw*hw+hh*hh)
|
13
|
+
end
|
14
|
+
|
15
|
+
def center_x
|
16
|
+
actor_x + @actor.width * 0.5
|
17
|
+
end
|
18
|
+
|
19
|
+
def center_y
|
20
|
+
actor_y + @actor.height * 0.5
|
21
|
+
end
|
22
|
+
|
23
|
+
def cw_world_points
|
24
|
+
@cached_points ||= @cw_local_points.map{|lp| [lp[0]+actor_x,lp[1]+actor_y]}
|
25
|
+
end
|
26
|
+
|
27
|
+
def cw_world_lines
|
28
|
+
return @cached_lines if @cached_lines
|
29
|
+
lines = []
|
30
|
+
|
31
|
+
hw = @actor.width * 0.5
|
32
|
+
hh = @actor.height * 0.5
|
33
|
+
lines = [
|
34
|
+
[[actor_x-hw,actor_y+hh], [actor_x+hw,actor_y+hh]],
|
35
|
+
[[actor_x+hw,actor_y+hh], [actor_x+hw,actor_y-hh]],
|
36
|
+
[[actor_x+hw,actor_y-hh], [actor_x-hw,actor_y-hh]],
|
37
|
+
[[actor_x-hw,actor_y-hh], [actor_x-hw,actor_y+hh]]
|
38
|
+
]
|
39
|
+
|
40
|
+
@cached_lines = lines
|
41
|
+
end
|
42
|
+
|
43
|
+
def cw_world_edge_normals
|
44
|
+
@cached_normals ||= [[1,0],[0,1]]
|
45
|
+
end
|
46
|
+
|
47
|
+
def recalculate_collidable_cache
|
48
|
+
unless @old_x == actor_x && @old_y == actor_y
|
49
|
+
clear_collidable_cache
|
50
|
+
@old_x = actor_x
|
51
|
+
@old_y = actor_y
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def clear_collidable_cache
|
56
|
+
@cached_points = nil
|
57
|
+
@cached_lines = nil
|
58
|
+
@cached_poly_center = nil
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|