gamebox 0.1.1 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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