-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsample.rs
280 lines (247 loc) · 10.4 KB
/
sample.rs
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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
//! This example shows how data can be extracted from an Ogmo project and level, and converted into a format
//! suitable for rendering.
//!
//! The below code should not be taken as the 'one true way' of handling level data - it is recommended that
//! you use it as a reference so that you can implement a runtime format that is more tailored to your
//! particular game or engine. For example, if you know you're never going to use the 2D storage
//! variants of layers, there's no reason to write code to parse them!
//!
//! This code is adapted from https://github.com/Ogmo-Editor-3/ogmo-3-lib/blob/master/sample/Main.hx.
use std::path::PathBuf;
use hashbrown::HashMap;
use ogmo3::{Layer, Level, Project};
use tetra::graphics::{self, Color, DrawParams, Rectangle, Texture};
use tetra::math::Vec2;
use tetra::{Context, ContextBuilder, State};
struct GameState {
color_texture: Texture,
tilesets: Vec<TilesetData>,
decals: Vec<Texture>,
sprites: Vec<Sprite>,
}
impl GameState {
fn new(ctx: &mut Context) -> anyhow::Result<GameState> {
// All paths in Ogmo projects and levels are relative to the project folder.
let base_path = PathBuf::from("./examples/sample_project");
let project = Project::from_file(base_path.join("test.ogmo"))?;
let level = Level::from_file(base_path.join("levels/uno.json"))?;
// Most of the project file's data can be ignored at runtime, but it does
// provide info which can be used to slice up tilesets.
//
// In this example, we store the tileset data in a Vec, and convert the
// string IDs to indices for quick lookup.
let mut tilesets = Vec::new();
let mut tileset_mappings = HashMap::new();
for tileset in project.tilesets {
let texture = Texture::new(ctx, base_path.join(&tileset.path))?;
let tiles = tileset
.tile_coords(texture.width(), texture.height())
.map(|t| {
Rectangle::new(
t.x as f32,
t.y as f32,
tileset.tile_width as f32,
tileset.tile_height as f32,
)
})
.collect();
let id = tilesets.len();
tilesets.push(TilesetData { texture, tiles });
tileset_mappings.insert(tileset.label, id);
}
// In this example, we convert the layers into a single flat list of sprite data, which we can
// then iterate over in order to render the level. You may want to take a different approach
// in your game - this crate does not enforce any particular runtime format.
//
// We also parse the decals from the level data, rather than the project data, as that way
// we can only load what is used rather than having to load the entire folder. Like the
// tilesets, we'll store them using indexes for quick lookups.
let mut sprites = Vec::new();
let mut decals = Vec::new();
// TODO: Ogmo allows you to specify layer offsets, which can be useful for creating
// chunked levels - this example does not currently take those fields into account.
for layer in level.layers {
match layer {
// Ogmo's tile data can be quite involved to unpack, and there are multiple different
// storage options available in the editor. The `unpack` method abstracts over these,
// allowing you to quickly pull tile data out of the layer.
Layer::Tile(layer) => {
for tile in layer.unpack() {
if let Some(id) = tile.id {
sprites.push(Sprite::TileIndex {
tileset: tileset_mappings[&layer.tileset],
tile: id as usize,
position: Vec2::new(
tile.pixel_position.x as f32,
tile.pixel_position.y as f32,
),
});
}
}
}
// An `unpack` method is also available for layers defined using tile co-ordinates.
Layer::TileCoords(layer) => {
for tile in layer.unpack() {
if let Some(coords) = tile.pixel_coords {
sprites.push(Sprite::TileUV {
tileset: tileset_mappings[&layer.tileset],
uv: Rectangle::new(
coords.x as f32,
coords.y as f32,
layer.grid_cell_width as f32,
layer.grid_cell_height as f32,
),
position: Vec2::new(
tile.pixel_position.x as f32,
tile.pixel_position.y as f32,
),
});
}
}
}
// An `unpack` method is also available for grid data.
Layer::Grid(layer) => {
for cell in layer.unpack() {
if cell.value != "0" {
sprites.push(Sprite::Rect {
rect: Rectangle::new(
cell.pixel_position.x as f32,
cell.pixel_position.y as f32,
layer.grid_cell_width as f32,
layer.grid_cell_height as f32,
),
color: Color::BLACK,
});
}
}
}
// In a real game, you would probably not want to draw entity data directly like
// this - instead, you would use them to spawn game entities at the specified
// location.
Layer::Entity(layer) => {
for entity in &layer.entities {
sprites.push(Sprite::Rect {
color: Color::RED,
rect: Rectangle::new(entity.x, entity.y, 16.0, 16.0),
});
}
}
// Decal data is stored as a path (relative to the layer's defined folder) and
// positioning data.
//
// In this example, we load a seperate texture for every decal - in a real game,
// you would probably want to make sure you don't load the same texture multiple
// times!
Layer::Decal(layer) => {
let folder_path = base_path.join(layer.folder);
for decal in layer.decals {
let texture = Texture::new(ctx, folder_path.join(decal.texture))?;
let id = decals.len();
decals.push(texture);
sprites.push(Sprite::Decal {
decal: id,
position: Vec2::new(decal.x, decal.y),
rotation: decal.rotation.unwrap_or(0.0),
scale: Vec2::new(
decal.scale_x.unwrap_or(1.0),
decal.scale_y.unwrap_or(1.0),
),
});
}
}
}
}
Ok(GameState {
color_texture: Texture::from_rgba(ctx, 1, 1, &[255, 255, 255, 255])?,
tilesets,
decals,
sprites,
})
}
}
impl State<anyhow::Error> for GameState {
fn draw(&mut self, ctx: &mut Context) -> anyhow::Result<()> {
graphics::clear(ctx, Color::WHITE);
for sprite in &self.sprites {
match sprite {
Sprite::TileIndex {
tileset,
tile,
position,
} => {
let tileset = &self.tilesets[*tileset];
let uv = tileset.tiles[*tile];
tileset.texture.draw_region(ctx, uv, *position);
}
Sprite::TileUV {
tileset,
uv,
position,
} => {
let tileset = &self.tilesets[*tileset];
tileset.texture.draw_region(ctx, *uv, *position);
}
Sprite::Rect { rect, color } => {
self.color_texture.draw(
ctx,
DrawParams::new()
.position(Vec2::new(rect.x, rect.y))
.scale(Vec2::new(rect.width, rect.height))
.color(*color),
);
}
Sprite::Decal {
decal,
position,
rotation,
scale,
} => {
let texture = &self.decals[*decal];
texture.draw(
ctx,
DrawParams::new()
.origin(Vec2::new(
texture.width() as f32 / 2.0,
texture.height() as f32 / 2.0,
))
.position(*position)
.rotation(*rotation)
.scale(*scale),
);
}
}
}
Ok(())
}
}
fn main() -> anyhow::Result<()> {
ContextBuilder::new("Rendering an Ogmo Project", 640, 480)
.build()?
.run(GameState::new)
}
enum Sprite {
TileIndex {
tileset: usize,
tile: usize,
position: Vec2<f32>,
},
TileUV {
tileset: usize,
uv: Rectangle,
position: Vec2<f32>,
},
Rect {
rect: Rectangle,
color: Color,
},
Decal {
decal: usize,
position: Vec2<f32>,
rotation: f32,
scale: Vec2<f32>,
},
}
struct TilesetData {
texture: Texture,
tiles: Vec<Rectangle>,
}