1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
use bevy::{ecs::system::EntityCommands, prelude::*, utils::HashMap};
use crate::{DebugConfig, Map, loader::TiledMapLoader};
#[derive(Debug)]
pub struct ObjectGroup {
pub name: String,
pub opacity: f32,
pub visible: bool,
pub objects: Vec<Object>,
}
impl ObjectGroup {
pub fn new_with_tile_ids(
inner: &tiled::ObjectGroup,
tile_gids: &HashMap<u32, u32>,
) -> ObjectGroup {
// println!("grp {}", inner.name.to_string());
ObjectGroup {
name: inner.name.to_string(),
opacity: inner.opacity,
visible: inner.visible,
objects: inner
.objects
.iter()
.map(|obj| Object::new_with_tile_ids(obj, tile_gids))
.collect(),
}
}
}
#[derive(Debug, Clone)]
pub struct Object {
pub shape: tiled::ObjectShape,
pub props: tiled::Properties,
pub position: Vec2,
pub size: Vec2,
pub name: String,
pub obj_type: String,
pub visible: bool,
pub gid: u32, // sprite ID from tiled::Object
pub tileset_gid: Option<u32>, // AKA first_gid
pub sprite_index: Option<u32>,
}
impl Object {
pub fn new(original_object: &tiled::Object) -> Object {
// println!("obj {} {}", original_object.name, original_object.visible.to_string());
Object {
shape: original_object.shape.clone(),
props: original_object.properties.clone(),
gid: TiledMapLoader::remove_tile_flags(original_object.gid), // zero for most non-tile objects
visible: original_object.visible,
tileset_gid: None,
sprite_index: None,
position: Vec2::new(original_object.x, original_object.y),
size: Vec2::new(original_object.width, original_object.height),
name: original_object.name.clone(),
obj_type: original_object.obj_type.clone(),
}
}
pub fn is_shape(&self) -> bool {
self.tileset_gid.is_none()
}
pub fn new_with_tile_ids(
original_object: &tiled::Object,
tile_gids: &HashMap<u32, u32>,
) -> Object {
// println!("obj {}", original_object.gid.to_string());
let mut o = Object::new(original_object);
o.set_tile_ids(tile_gids);
o
}
pub fn set_tile_ids(&mut self, tile_gids: &HashMap<u32, u32>) {
self.tileset_gid = tile_gids.get(&self.gid).cloned();
self.sprite_index = self.tileset_gid.map(|first_gid| &self.gid - first_gid);
}
pub fn transform_from_map(
&self,
map: &tiled::Map,
map_transform: &Transform,
tile_scale: Option<Vec3>,
) -> Transform {
// tile scale being None means this is not a tile object
// clone entire map transform
let mut transform = map_transform.clone();
//// this was made obsolete by Kurble's branch changes
// let map_tile_width = map.tile_width as f32;
// let map_tile_height = map.tile_height as f32;
//// offset transform position by 1/2 map tile
// transform.translation -= map_transform.scale * Vec3::new(map_tile_width, -map_tile_height, 0.0) / 2.0;
let map_orientation: tiled::Orientation = map.orientation;
// replacing map Z with something far in front for objects -- should probably be configurable
// transform.translation.z = 1000.0;
let z_relative_to_map = 15.0; // used for a range of 5-25 above tile Z coordinate for items (max 20k map)
match self.shape {
tiled::ObjectShape::Rect { width, height } => {
match map_orientation {
tiled::Orientation::Orthogonal => {
let mut center_offset = Vec2::new(self.position.x, -self.position.y);
match tile_scale {
None => {
// shape object x/y represent top left corner
center_offset += Vec2::new(width, -height) / 2.0;
}
Some(tile_scale) => {
// tile object x/y represents bottom left corner
center_offset += Vec2::new(width, height) / 2.0;
// tile object scale based on map scale and passed-in scale from image dimensions
transform.scale = tile_scale * transform.scale;
}
}
// apply map scale to object position, if this is a tile
center_offset *= map_transform.scale.truncate();
// offset transform by object position
transform.translation +=
center_offset.extend(z_relative_to_map - center_offset.y / 2000.0);
// ^ HACK only support up to 20k pixels maps, TODO: configure in API
}
// tiled::Orientation::Isometric => {
// }
_ => panic!("Sorry, {:?} objects aren't supported -- please hide this object layer for now.", map_orientation),
}
}
tiled::ObjectShape::Ellipse {
width: _,
height: _,
} => {}
tiled::ObjectShape::Polyline { points: _ } => {}
tiled::ObjectShape::Polygon { points: _ } => {}
tiled::ObjectShape::Point(_, _) => {}
}
transform
}
pub fn spawn<'a, 'b>(
&self,
commands: &'b mut Commands<'a>,
texture_atlas: Option<&Handle<TextureAtlas>>,
map: &tiled::Map,
map_handle: Handle<Map>,
tile_map_transform: &Transform,
debug_config: &DebugConfig,
) -> EntityCommands<'a, 'b> {
let mut new_entity_commands = if let Some(texture_atlas) = texture_atlas {
let sprite_index = self.sprite_index.expect("missing sprite index");
let tileset_gid = self.tileset_gid.expect("missing tileset");
// fetch tile for this object if it exists
let object_tile_size = map
.tilesets
.iter()
.find(|ts| ts.first_gid == tileset_gid)
.map(|ts| Vec2::new(ts.tile_width as f32, ts.tile_height as f32));
// object dimensions
let dims = self.dimensions();
// use object dimensions and tile size to determine extra scale to apply for tile objects
let tile_scale = if let (Some(dims), Some(size)) = (dims, object_tile_size) {
Some((dims / size).extend(1.0))
} else {
None
};
commands.spawn_bundle(SpriteSheetBundle {
transform: self.transform_from_map(&map, tile_map_transform, tile_scale),
texture_atlas: texture_atlas.clone(),
sprite: TextureAtlasSprite {
index: sprite_index,
..Default::default()
},
visible: Visible {
is_visible: self.visible,
is_transparent: true,
..Default::default()
},
..Default::default()
})
} else {
// commands.spawn((self.map_transform(&map.map, &tile_map_transform, None), GlobalTransform::default()))
let dimensions = self
.dimensions()
.expect("Don't know how to handle object without dimensions");
let transform = self.transform_from_map(&map, &tile_map_transform, None);
commands
// Debug box.
.spawn_bundle(SpriteBundle {
material: debug_config
.material
.clone()
.unwrap_or_else(|| Handle::<ColorMaterial>::default()),
sprite: Sprite::new(dimensions),
transform,
visible: Visible {
is_visible: debug_config.enabled,
is_transparent: true,
..Default::default()
},
..Default::default()
})
};
new_entity_commands.insert_bundle((map_handle, self.clone()));
new_entity_commands
}
pub fn dimensions(&self) -> Option<Vec2> {
match self.shape {
tiled::ObjectShape::Rect { width, height }
| tiled::ObjectShape::Ellipse { width, height } => Some(Vec2::new(width, height)),
tiled::ObjectShape::Polyline { points: _ }
| tiled::ObjectShape::Polygon { points: _ }
| tiled::ObjectShape::Point(_, _) => Some(Vec2::splat(1.0)),
}
}
}