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.
Files changed (74) hide show
  1. data/.gitignore +4 -0
  2. data/Gemfile +9 -0
  3. data/History.txt +5 -0
  4. data/README.txt +4 -3
  5. data/Rakefile +12 -4
  6. data/TODO.txt +1 -5
  7. data/VERSION +1 -1
  8. data/docs/getting_started.rdoc +2 -2
  9. data/gamebox.gemspec +26 -10
  10. data/lib/gamebox/actor.rb +10 -3
  11. data/lib/gamebox/actor_factory.rb +2 -2
  12. data/lib/gamebox/actor_view.rb +14 -11
  13. data/lib/gamebox/actors/collidable_debugger.rb +35 -0
  14. data/lib/gamebox/actors/curtain.rb +2 -2
  15. data/lib/gamebox/actors/logo.rb +0 -6
  16. data/lib/gamebox/actors/score.rb +2 -5
  17. data/lib/gamebox/actors/spatial_debugger.rb +47 -0
  18. data/lib/gamebox/actors/svg_actor.rb +4 -6
  19. data/lib/gamebox/arbiter.rb +108 -15
  20. data/lib/gamebox/behavior.rb +29 -1
  21. data/lib/gamebox/behaviors/animated.rb +14 -23
  22. data/lib/gamebox/behaviors/audible.rb +1 -12
  23. data/lib/gamebox/behaviors/collidable.rb +29 -22
  24. data/lib/gamebox/behaviors/collidable/aabb_collidable.rb +61 -0
  25. data/lib/gamebox/behaviors/collidable/circle_collidable.rb +17 -0
  26. data/lib/gamebox/behaviors/collidable/collidable_shape.rb +35 -0
  27. data/lib/gamebox/behaviors/collidable/polygon_collidable.rb +85 -0
  28. data/lib/gamebox/behaviors/graphical.rb +13 -10
  29. data/lib/gamebox/behaviors/layered.rb +6 -20
  30. data/lib/gamebox/behaviors/physical.rb +116 -93
  31. data/lib/gamebox/class_finder.rb +6 -2
  32. data/lib/gamebox/config_manager.rb +24 -4
  33. data/lib/gamebox/data/config/objects.yml +5 -3
  34. data/lib/gamebox/ftor.rb +372 -0
  35. data/lib/gamebox/gamebox_application.rb +2 -8
  36. data/lib/gamebox/hooked_gosu_window.rb +30 -0
  37. data/lib/gamebox/input_manager.rb +78 -79
  38. data/lib/gamebox/lib/code_statistics.rb +1 -1
  39. data/lib/gamebox/lib/numbers_ext.rb +1 -1
  40. data/lib/gamebox/lib/rect.rb +612 -0
  41. data/lib/gamebox/physical_stage.rb +12 -2
  42. data/lib/gamebox/physics.rb +14 -3
  43. data/lib/gamebox/resource_manager.rb +28 -65
  44. data/lib/gamebox/sound_manager.rb +7 -13
  45. data/lib/gamebox/spatial_hash.rb +60 -14
  46. data/lib/gamebox/spatial_stagehand.rb +19 -0
  47. data/lib/gamebox/stage.rb +16 -1
  48. data/lib/gamebox/stage_manager.rb +1 -1
  49. data/lib/gamebox/svg_document.rb +1 -0
  50. data/lib/gamebox/tasks/gamebox_tasks.rb +23 -11
  51. data/lib/gamebox/templates/template_app/.gitignore +1 -0
  52. data/lib/gamebox/templates/template_app/Gemfile +1 -1
  53. data/lib/gamebox/templates/template_app/Rakefile +6 -21
  54. data/lib/gamebox/templates/template_app/config/environment.rb +14 -0
  55. data/lib/gamebox/templates/template_app/config/game.yml +2 -1
  56. data/lib/gamebox/templates/template_app/script/generate +0 -3
  57. data/lib/gamebox/templates/template_app/src/demo_stage.rb +1 -11
  58. data/lib/gamebox/templates/template_app/src/game.rb +0 -1
  59. data/lib/gamebox/templates/template_app/src/my_actor.rb +2 -2
  60. data/lib/gamebox/version.rb +1 -1
  61. data/lib/gamebox/views/graphical_actor_view.rb +15 -9
  62. data/lib/gamebox/wrapped_screen.rb +114 -7
  63. data/load_paths.rb +20 -0
  64. data/script/perf_spatial_hash.rb +73 -0
  65. data/spec/actor_view_spec.rb +1 -1
  66. data/spec/arbiter_spec.rb +264 -0
  67. data/spec/behavior_spec.rb +19 -2
  68. data/spec/collidable_spec.rb +105 -5
  69. data/spec/helper.rb +1 -1
  70. data/spec/label_spec.rb +1 -1
  71. data/spec/resource_manager_spec.rb +1 -1
  72. data/spec/spatial_hash_spec.rb +1 -1
  73. metadata +52 -10
  74. 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
- 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
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
@@ -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
- # puts "registering #{fobj} and #{sobj}"
31
- @collision_handlers[fobj] ||= {}
32
- @collision_handlers[sobj] ||= {}
33
- @collision_handlers[fobj][sobj] = block
34
- @collision_handlers[sobj][fobj] = block
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
- callback.call first, second unless callback.nil?
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
- tmp_collidable_actors = @spatial_hash.items_in(x,y,w,h)
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
- self.send "collide_#{object.shape}_#{object.shape}?", object, other
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
- # puts "comparing #{object.actor_type}[#{object.object_id}] to #{other.actor_type}[#{other.object_id}]"
87
- x = object.x + object.radius
88
- y = object.y + object.radius
89
- x_prime = other.x + other.radius
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 < (total_radius) * (total_radius)
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&#3488866
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
@@ -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
- animated_obj = self
21
+ relegates :image, :width, :height,
22
+ :start_animating, :stop_animating, :animated,
23
+ :action, :action=
22
24
 
23
- @actor.instance_eval do
24
- (class << self; self; end).class_eval do
25
- define_method :image do
26
- animated_obj.image
27
- end
28
- define_method :start_animating do
29
- animated_obj.start_animating
30
- end
31
- define_method :stop_animating do
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
- @actor.instance_eval do
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 :shape, :radius, :width
9
+ attr_accessor :collidable_shape, :cw_local_points, :shape
6
10
 
7
11
  def setup
8
- @shape = opts[:shape]
9
- @radius = opts[:radius]
10
- @width = opts[:width]
11
-
12
- collidable_obj = self
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 bounding_box
33
- [ @actor.x-@width,@actor.y-@width,
34
- @actor.x+@width,@actor.y+@width ]
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 bounding_circle
38
- [ @actor.x+@radius, @actor.y+@radius, @radius]
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