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.
- checksums.yaml +15 -0
- data/Gemfile +2 -0
- data/README.md +18 -19
- data/changelog.md +5 -1
- data/lib/core_ext/numeric.rb +17 -0
- data/lib/metro.rb +8 -3
- data/lib/metro/animation/on_update_operation.rb +7 -7
- data/lib/metro/events/event_relay.rb +43 -1
- data/lib/metro/events/event_state_manager.rb +7 -0
- data/lib/metro/image.rb +13 -0
- data/lib/metro/models/model.rb +12 -2
- data/lib/metro/models/properties/options_property/options.rb +4 -0
- data/lib/metro/models/ui/physics_sprite.rb +105 -0
- data/lib/metro/models/ui/space.rb +152 -0
- data/lib/metro/models/ui/sprite.rb +2 -2
- data/lib/metro/models/ui/tile_map.rb +133 -0
- data/lib/metro/models/ui/tmx/isometric_position.rb +43 -0
- data/lib/metro/models/ui/tmx/orthogonal_position.rb +15 -0
- data/lib/metro/models/ui/tmx/tile_layer.rb +78 -0
- data/lib/metro/models/ui/ui.rb +3 -0
- data/lib/metro/scene.rb +2 -1
- data/lib/metro/units/rectangle_bounds.rb +75 -5
- data/lib/metro/version.rb +4 -4
- data/lib/metro/window.rb +1 -0
- data/lib/templates/game/assets/missing.png +0 -0
- data/lib/tmx_ext/object.rb +61 -0
- data/lib/tmx_ext/object_shape.rb +93 -0
- data/lib/tmx_ext/tile_set.rb +41 -0
- data/metro.gemspec +3 -1
- data/metro.png +0 -0
- data/spec/core_ext/numeric_spec.rb +28 -0
- data/spec/metro/image_spec.rb +33 -0
- data/spec/metro/units/rectangle_bounds_spec.rb +56 -0
- data/spec/tmx_ext/object_spec.rb +49 -0
- data/spec/tmx_ext/tile_set_spec.rb +24 -0
- metadata +56 -27
|
@@ -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
|
data/lib/metro/models/ui/ui.rb
CHANGED