metro 0.3.4 → 0.3.5

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