metro 0.3.4 → 0.3.5

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.
@@ -0,0 +1,152 @@
1
+ module Metro
2
+ module UI
3
+
4
+ #
5
+ # Adds a 2D Physics Space to the Scene
6
+ #
7
+ # @see http://beoran.github.io/chipmunk/
8
+ #
9
+ # @example Adding a space to the scene
10
+ #
11
+ # class MainScene < GameScene
12
+ # draw :space, model: "metro::ui::space"
13
+ # draw :hero, position: Game.center
14
+ #
15
+ # def show
16
+ # space.add_object(hero)
17
+ # space.gravity_affects(hero)
18
+ # end
19
+ #
20
+ # def update
21
+ # space.step
22
+ # space.clean_up
23
+ # end
24
+ # end
25
+ #
26
+ # Adding a space to a scene allows for objects with a
27
+ # `body` and `shape` to be added and affected by
28
+ # collisions and gravity.
29
+ #
30
+ class Space < Model
31
+
32
+ # @attribute
33
+ # Amount of viscous damping to apply to the space. A value of 0.9 means
34
+ # that each body will lose 10% of it’s velocity per second. Defaults to 1.
35
+ # Like gravity can be overridden on a per body basis.
36
+ property :damping, default: 0.5
37
+
38
+ # @attribute
39
+ # Each update cycle the space will perform a number of steps equal to
40
+ # this value. The higher the value the better the resolution will be for
41
+ # collisions between objects, the lower the value the sloppier the
42
+ # resolution will be for collisions.
43
+ property :sampling_per_update, default: 6
44
+
45
+ # @attribute
46
+ # The amount of time the space should be stepped. This step value is
47
+ # multiplied by the #sampling_per_update value.
48
+ property :delta, default: (1.0/60.0)
49
+
50
+ # @attribute
51
+ # The amount of gravitationl force to apply to bodies within the space.
52
+ # This is by default a force at the center of the object applied downward.
53
+ property :gravitational_forces, type: :array,
54
+ default: [ CP::Vec2.new(0,1000), CP::Vec2.new(0,0) ]
55
+
56
+ # Add a single object to the space. This object will have a reference
57
+ # stored here and it's body and shape added to the space so that it
58
+ # collide with other objects added to the space.
59
+ #
60
+ # @param [Object<#body,#shape>] object add a new object to the space. The
61
+ # object has a `body` and a `shape`.
62
+ #
63
+ def add_object(object)
64
+ space_objects.push(object)
65
+ space.add_body object.body
66
+ space.add_shape object.shape
67
+ end
68
+
69
+ # Remove a single object from the space. This object will remove the
70
+ # reference stored within the space as well as removing the body and
71
+ # shape that was added to the space.
72
+ #
73
+ def remove_object(object)
74
+ space_objects.delete(object)
75
+ space.remove_body(object.body)
76
+ space.remove_shape(object.shape)
77
+ end
78
+
79
+ # Add multiple objects to the space.
80
+ # @see #add_object
81
+ def add_objects(objects)
82
+ Array(objects).each {|object| add_object(object) }
83
+ end
84
+
85
+ # When the first shape collides with the second shape perform
86
+ # the following block of code.
87
+ def collision_between(first,second,&block)
88
+ space.add_collision_func(first,second,&block)
89
+ end
90
+
91
+ # This method allows the space to declare which objects are effected by
92
+ # gravity during their time within the space.
93
+ #
94
+ # @note for the object to be affected by gravity it still needs to be
95
+ # added to the space first.
96
+ #
97
+ def gravity_affects(object)
98
+ victims_of_gravity.push object
99
+ end
100
+
101
+ # Access to the raw Chimpmunk Space object
102
+ # @see http://beoran.github.io/chipmunk/#Space
103
+ attr_reader :space
104
+
105
+
106
+ # This method needs to be called each update loop. This will update and
107
+ # move all the objects currently within the space and start the resolution
108
+ # of collisions.
109
+ def step
110
+ sampling_per_update.to_i.times { space.step(delta) }
111
+ end
112
+
113
+ # This method needs to be called each update loop. This will reset all
114
+ # the forces on the existing objects. This essentially keeps them from
115
+ # accelerating out of control. It will also rebuild the shape positioning
116
+ # for collisions. In a static frame, the level does not scroll, the
117
+ # rebuiling is not necessary, but for levels where it moves this is
118
+ # important.
119
+ #
120
+ def clean_up
121
+ space_objects.each {|object| object.body.reset_forces }
122
+ space.rehash_static
123
+ end
124
+
125
+ def show
126
+ @space = CP::Space.new
127
+ @space.damping = damping
128
+ end
129
+
130
+ def update
131
+ apply_gravity_to victims_of_gravity
132
+ end
133
+
134
+ # @return [Array<Object>] a list of objects currently mantained by the
135
+ # space.
136
+ def space_objects
137
+ @space_objects ||= []
138
+ end
139
+
140
+ # @return [Array<Object>] all of the objects that are affected by gravity
141
+ def victims_of_gravity
142
+ @victims_of_gravity ||= []
143
+ end
144
+
145
+ # Apply gravity to the specified objects.
146
+ def apply_gravity_to(objects)
147
+ objects.each {|object| object.body.apply_force *gravitational_forces }
148
+ end
149
+
150
+ end
151
+ end
152
+ end
@@ -50,7 +50,7 @@ module Metro
50
50
 
51
51
  # @return [Float] the left-most x position of the sprite
52
52
  def left
53
- x - width * center_x
53
+ x - width * center_x * x_factor
54
54
  end
55
55
 
56
56
  # @return [Float] the right-most x position of the sprite
@@ -60,7 +60,7 @@ module Metro
60
60
 
61
61
  # @return [Float] the top-most y position of the sprite
62
62
  def top
63
- y - height * center_y
63
+ y - height * center_y * y_factor
64
64
  end
65
65
 
66
66
  # @return [Float] the bottom-most y position of the sprite
@@ -0,0 +1,133 @@
1
+ module Metro
2
+ module UI
3
+ #
4
+ # Draws a TileMap within the scene.
5
+ #
6
+ # @example Creating a tile map with a Tiled TMX file
7
+ #
8
+ # class MainScene < GameScene
9
+ # draw :tile_map, model: "metro::ui::tile_map",
10
+ # file: "first_level.tmx"
11
+ # end
12
+ #
13
+ # The viewport, or camera, of the TileMap is static but
14
+ # can be set to follow a particular actor within the scene.
15
+ # The viewport will continue to move according to the position
16
+ # of the specified actor.
17
+ #
18
+ # @example Creating a tile map that will follow the hero
19
+ #
20
+ # class MainScene < GameScene
21
+ # draw :hero
22
+ # draw :tile_map, model: "metro::ui::tile_map",
23
+ # file: "first_level.tmx", follow: :hero
24
+ # end
25
+ #
26
+ #
27
+ class TileMap < ::Metro::Model
28
+
29
+ # @attribute
30
+ # The Tiled File (*.tmx) that will be parsed and loaded for the tile map
31
+ property :file, type: :text
32
+
33
+ # @attribute
34
+ # The actor that the viewport of the scene will follow.
35
+ property :follow, type: :text
36
+
37
+ # @attribute
38
+ # The rotation of each tile within the scene. This is by default
39
+ # 0 and should likely remain 0.
40
+ property :rotation
41
+
42
+ #
43
+ # Return the viewport for the map. The default viewport will be
44
+ # the dimensions of the current Game.
45
+ #
46
+ # @return [RectangleBounds] the bounds of the current viewport
47
+ def viewport
48
+ @viewport ||= Game.bounds
49
+ end
50
+
51
+ # @attribute
52
+ # Set the viewport of the map, by default ths viewport will be
53
+ # the dimensions of the current Game, so this may not be
54
+ # necessary to set.
55
+ attr_writer :viewport
56
+
57
+ #
58
+ # @return [TMX::Map] the map object found in the specified TMX file.
59
+ #
60
+ def map
61
+ @map ||= begin
62
+ map = ::Tmx.load asset_path(file)
63
+ map.tilesets.each {|tileset| tileset.window = window }
64
+ map
65
+ end
66
+ end
67
+
68
+ #
69
+ # Find objects that match the specified type or by the specified
70
+ # parameters. If no parameter is provided then all the objects are
71
+ # returned
72
+ #
73
+ # @example Finding all the objects with the type :floor
74
+ #
75
+ # objects(:floor)
76
+ #
77
+ # @example Finding all the objects with the name 'tree'
78
+ #
79
+ # objects(name: 'tree')
80
+ def objects(params=nil)
81
+ params ? map.objects.find(params) : map.objects
82
+ end
83
+
84
+ def update
85
+ shift_viewport_to_center_who_we_are_following
86
+ end
87
+
88
+ def draw
89
+ layers.each {|layer| layer.draw }
90
+ end
91
+
92
+ private
93
+
94
+ def layers
95
+ @layers ||= map.layers.collect do |layer|
96
+ tml = TileLayer.new
97
+ tml.extend layer_positioning
98
+ tml.rotation = rotation
99
+ tml.viewport = viewport
100
+ tml.map = map
101
+ tml.layer = layer
102
+ tml.tilesets = map.tilesets
103
+ tml
104
+ end
105
+ end
106
+
107
+ def layer_positioning
108
+ { orthogonal: "Metro::Tmx::TileLayer::OrthogonalPositioning",
109
+ isometric: "Metro::Tmx::TileLayer::IsometricPositioning" }[map.orientation.to_sym].constantize
110
+ end
111
+
112
+ def following
113
+ scene.send(follow) if follow
114
+ end
115
+
116
+ def shift_viewport_to_center_who_we_are_following
117
+ return unless following
118
+
119
+ diff_x = (following.x - Game.center.x).to_i
120
+
121
+ if diff_x >= 1 or diff_x <= -1
122
+ viewport.shift(Point.at(diff_x,0))
123
+ end
124
+
125
+ end
126
+
127
+ end
128
+ end
129
+ end
130
+
131
+ require_relative 'tmx/tile_layer'
132
+ require_relative 'tmx/isometric_position'
133
+ require_relative 'tmx/orthogonal_position'
@@ -0,0 +1,43 @@
1
+ module Metro
2
+ module Tmx
3
+ class TileLayer
4
+
5
+ module IsometricPositioning
6
+
7
+ def position_of_image(image,row,column)
8
+ pos_x = x_position(row,column) - (map.tilewidth - image.width)/2
9
+ pos_y = y_position(row,column) + (map.tileheight - image.height)/2
10
+ ::Metro::Units::Bounds.new left: pos_x, top: pos_y, right: pos_x + map.tilewidth, bottom: pos_y + map.tileheight
11
+ end
12
+
13
+ def half_tilewidth
14
+ @half_tilewidth ||= map.tilewidth/2
15
+ end
16
+
17
+ def half_tileheight
18
+ @half_tileheight ||= map.tileheight/2
19
+ end
20
+
21
+ def start_x
22
+ @start_x ||= x + map.tilewidth * map.width / 2
23
+ end
24
+
25
+ def x_position(row,column)
26
+ row_start_x = start_x - half_tilewidth * row
27
+ row_start_x + half_tilewidth * column
28
+ end
29
+
30
+ def start_y
31
+ y
32
+ end
33
+
34
+ def y_position(row,column)
35
+ row_start_y = start_y + half_tileheight * row
36
+ row_start_y + half_tileheight * column
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,15 @@
1
+ module Metro
2
+ module Tmx
3
+ class TileLayer
4
+
5
+ module OrthogonalPositioning
6
+ def position_of_image(image,row,column)
7
+ pos_x = x + column * map.tilewidth + map.tilewidth / 2
8
+ pos_y = y + row * map.tileheight + map.tileheight / 2
9
+ ::Metro::Units::RectangleBounds.new left: pos_x, top: pos_y, right: pos_x + map.tilewidth, bottom: pos_y + map.tileheight/2
10
+ end
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,78 @@
1
+ module Metro
2
+ module UI
3
+ class TileLayer < ::Metro::Model
4
+ property :rotation
5
+
6
+ attr_accessor :map
7
+ attr_accessor :layer
8
+ attr_accessor :tilesets
9
+ attr_accessor :viewport
10
+
11
+ def data
12
+ layer.data
13
+ end
14
+
15
+ def x
16
+ viewport.left
17
+ end
18
+
19
+ def y
20
+ viewport.top
21
+ end
22
+
23
+ def z_order
24
+ -1
25
+ end
26
+
27
+ def draw
28
+ tiles_within_viewport.each do |bounds,image|
29
+ image.draw_rot(bounds.left - x,bounds.top - y,z_order,rotation)
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def tiles_within_viewport
36
+ enlarged_viewport = viewport.enlarge(left: map.tilewidth, right: map.tilewidth, top: map.tileheight, bottom: map.tileheight)
37
+ tile_bounds.find_all {|bounds,images| enlarged_viewport.intersect?(bounds) }
38
+ end
39
+
40
+ def tile_bounds
41
+ @tile_bounds ||= build_tiles_index
42
+ end
43
+
44
+ def build_tiles_index
45
+ data.each_with_index.map do |image_index,position|
46
+ next if image_index == 0
47
+ image = tileset_image(image_index)
48
+ [ position_of_image(image,row(position),column(position)), image ]
49
+ end.compact
50
+ end
51
+
52
+ def tileset_image(image_index)
53
+ unless cached_images[image_index]
54
+ tileset = map.tilesets.find do |t|
55
+ image_index >= t.firstgid && image_index < t.firstgid + t.images.count
56
+ end
57
+ tileset_image_index = image_index - tileset.firstgid
58
+ cached_images[image_index] = tileset.images[tileset_image_index]
59
+ end
60
+
61
+ cached_images[image_index]
62
+ end
63
+
64
+ def row(position)
65
+ position / layer.width
66
+ end
67
+
68
+ def column(position)
69
+ position % layer.width
70
+ end
71
+
72
+ def cached_images
73
+ @cached_images ||= {}
74
+ end
75
+ end
76
+
77
+ end
78
+ end
@@ -9,4 +9,7 @@ require_relative 'model_label'
9
9
  require_relative 'model_labeler'
10
10
  require_relative 'fps'
11
11
  require_relative 'sprite'
12
+ require_relative 'physics_sprite'
12
13
  require_relative 'animated_sprite'
14
+ require_relative 'tile_map'
15
+ require_relative 'space'