1#![allow(clippy::too_many_arguments)]
2
3use bones_bevy_renderer::{bevy::diagnostic::LogDiagnosticsPlugin, BonesBevyRenderer};
4use bones_framework::prelude::*;
5
6#[derive(HasSchema, Default, Clone)]
10#[repr(C)]
11#[type_data(metadata_asset("game"))]
13struct GameMeta {
14 menu_script: Handle<LuaScript>,
16 menu_image: Handle<Image>,
18 sprite_demo: Handle<Image>,
20 atlas_demo: Handle<AtlasDemoMeta>,
22 tilemap_demo: Handle<TilemapDemoMeta>,
24 audio_demo: Handle<AudioSource>,
26 path2d_color: Color,
28 localization: Handle<LocalizationAsset>,
30 title_font: FontMeta,
32 fonts: SVec<Handle<Font>>,
34 menu_border: BorderImageMeta,
36 button_style: ButtonThemeMeta,
38}
39
40#[derive(HasSchema, Default, Clone)]
42#[repr(C)]
43#[type_data(metadata_asset("atlas-demo"))]
44struct AtlasDemoMeta {
45 camera_size: CameraSize,
47 pub atlas: Handle<Atlas>,
49 pub fps: f32,
51 pub animation: SVec<u32>,
56}
57
58#[derive(HasSchema, Default, Clone)]
60#[repr(C)]
61#[type_data(metadata_asset("tilemap"))]
62struct TilemapDemoMeta {
63 pub atlas: Handle<Atlas>,
65 pub map_size: UVec2,
67 pub tiles: SVec<TileMeta>,
69}
70
71#[derive(HasSchema, Default, Clone)]
73#[repr(C)]
74struct TileMeta {
75 pos: UVec2,
77 idx: u32,
79}
80
81#[derive(HasSchema, Default, Clone)]
83#[repr(C)]
84struct PersistedTextData(String);
85
86fn main() {
87 setup_logs!();
89
90 PersistedTextData::register_schema();
92
93 let mut renderer = BonesBevyRenderer::new(create_game());
95 renderer.app_namespace = (
98 "org".into(),
99 "fishfolk".into(),
100 "bones.demo_features".into(),
101 );
102 renderer
104 .app()
105 .add_plugins(LogDiagnosticsPlugin::default())
107 .run()
109}
110
111pub fn create_game() -> Game {
113 let mut game = Game::new();
115
116 game.install_plugin(DefaultGamePlugin)
118 .init_shared_resource::<AssetServer>()
119 .register_default_assets();
121
122 GameMeta::register_schema();
124 AtlasDemoMeta::register_schema();
125 TilemapDemoMeta::register_schema();
126
127 game.sessions.create_with("menu", menu_plugin);
129
130 game
131}
132
133#[derive(HasSchema, Default, Clone)]
135#[repr(C)]
136struct MenuData {
137 pub frame: u32,
139}
140
141pub fn menu_plugin(session: &mut SessionBuilder) {
143 session
145 .install_plugin(DefaultSessionPlugin)
147 .init_resource::<MenuData>();
149
150 session
152 .add_system_to_stage(Update, menu_system)
153 .add_startup_system(menu_startup);
154}
155
156fn menu_startup(
158 mut egui_settings: ResMutInit<EguiSettings>,
159 mut clear_color: ResMutInit<ClearColor>,
160) {
161 **clear_color = Color::BLACK;
163 egui_settings.scale = 2.0;
165}
166
167fn menu_system(
169 meta: Root<GameMeta>,
170 ctx: Res<EguiCtx>,
171 mut sessions: ResMut<Sessions>,
172 mut session_options: ResMut<SessionOptions>,
173 mut exit_bones: Option<ResMut<ExitBones>>,
174 localization: Localization<GameMeta>,
176 world: &World,
177 lua_engine: Res<LuaEngine>,
178) {
179 lua_engine.run_script_system(world, meta.menu_script);
181
182 egui::CentralPanel::default()
184 .frame(egui::Frame::none())
185 .show(&ctx, |ui| {
186 BorderedFrame::new(&meta.menu_border).show(ui, |ui| {
187 ui.vertical_centered(|ui| {
188 ui.add_space(20.0);
189 ui.label(meta.title_font.rich(localization.get("title")));
190 ui.add_space(20.0);
191
192 if BorderedButton::themed(&meta.button_style, localization.get("sprite-demo"))
193 .show(ui)
194 .clicked()
195 {
196 session_options.delete = true;
198
199 sessions.create_with("sprite_demo", sprite_demo_plugin);
201 }
202
203 if BorderedButton::themed(&meta.button_style, localization.get("atlas-demo"))
204 .show(ui)
205 .clicked()
206 {
207 session_options.delete = true;
209
210 sessions.create_with("atlas_demo", atlas_demo_plugin);
212 }
213
214 if BorderedButton::themed(&meta.button_style, localization.get("tilemap-demo"))
215 .show(ui)
216 .clicked()
217 {
218 session_options.delete = true;
220
221 sessions.create_with("tilemap_demo", tilemap_demo_plugin);
223 }
224
225 if BorderedButton::themed(&meta.button_style, localization.get("audio-demo"))
226 .show(ui)
227 .clicked()
228 {
229 session_options.delete = true;
231
232 sessions.create_with("audio_demo", audio_demo_plugin);
234 }
235
236 if BorderedButton::themed(&meta.button_style, localization.get("storage-demo"))
237 .show(ui)
238 .clicked()
239 {
240 session_options.delete = true;
242
243 sessions.create_with("storage_demo", storage_demo_plugin);
245 }
246
247 if BorderedButton::themed(&meta.button_style, localization.get("path2d-demo"))
248 .show(ui)
249 .clicked()
250 {
251 session_options.delete = true;
253
254 sessions.create_with("path2d_demo", path2d_demo_plugin);
256 }
257
258 if let Some(exit_bones) = &mut exit_bones {
259 if BorderedButton::themed(&meta.button_style, localization.get("quit"))
260 .show(ui)
261 .clicked()
262 {
263 ***exit_bones = true;
264 }
265 }
266
267 ui.add_space(10.0);
268
269 world.run_system(demo_widget, ui);
275
276 ui.add_space(30.0);
277 });
278 })
279 });
280}
281
282fn sprite_demo_plugin(session: &mut SessionBuilder) {
284 session
285 .install_plugin(DefaultSessionPlugin)
286 .add_startup_system(sprite_demo_startup)
287 .add_system_to_stage(Update, back_to_menu_ui)
288 .add_system_to_stage(Update, move_sprite);
289}
290
291fn sprite_demo_startup(
293 mut entities: ResMut<Entities>,
294 mut sprites: CompMut<Sprite>,
295 mut transforms: CompMut<Transform>,
296 mut cameras: CompMut<Camera>,
297 meta: Root<GameMeta>,
298) {
299 spawn_default_camera(&mut entities, &mut transforms, &mut cameras);
300
301 let sprite_ent = entities.create();
302 transforms.insert(sprite_ent, default());
303 sprites.insert(
304 sprite_ent,
305 Sprite {
306 image: meta.sprite_demo,
307 ..default()
308 },
309 );
310}
311
312fn move_sprite(
313 entities: Res<Entities>,
314 sprite: Comp<Sprite>,
315 mut transforms: CompMut<Transform>,
316 input: Res<KeyboardInputs>,
317 ctx: Res<EguiCtx>,
318) {
319 egui::CentralPanel::default()
320 .frame(egui::Frame::none())
321 .show(&ctx, |ui| {
322 ui.label("Press left and right arrow keys to move sprite");
323 });
324
325 let mut left = false;
326 let mut right = false;
327
328 for input in &input.key_events {
329 match input.key_code {
330 Set(KeyCode::Right) => right = true,
331 Set(KeyCode::Left) => left = true,
332 _ => (),
333 }
334 }
335
336 for (_ent, (_sprite, transform)) in entities.iter_with((&sprite, &mut transforms)) {
337 if left {
338 transform.translation.x -= 2.0;
339 }
340 if right {
341 transform.translation.x += 2.0;
342 }
343 }
344}
345
346fn tilemap_demo_plugin(session: &mut SessionBuilder) {
348 session
349 .install_plugin(DefaultSessionPlugin)
350 .add_startup_system(tilemap_startup_system)
351 .add_system_to_stage(Update, back_to_menu_ui);
352}
353
354fn tilemap_startup_system(
356 mut entities: ResMut<Entities>,
357 mut transforms: CompMut<Transform>,
358 mut tile_layers: CompMut<TileLayer>,
359 mut cameras: CompMut<Camera>,
360 mut tiles: CompMut<Tile>,
361 meta: Root<GameMeta>,
362 assets: Res<AssetServer>,
363) {
364 spawn_default_camera(&mut entities, &mut transforms, &mut cameras);
365
366 let map_info = assets.get(meta.tilemap_demo);
368 let atlas = assets.get(map_info.atlas);
369
370 let mut layer = TileLayer::new(map_info.map_size, atlas.tile_size, map_info.atlas);
372
373 for tile in &map_info.tiles {
375 let tile_ent = entities.create();
376 tiles.insert(
377 tile_ent,
378 Tile {
379 idx: tile.idx,
380 ..default()
381 },
382 );
383 layer.set(tile.pos, Some(tile_ent))
384 }
385
386 let layer_ent = entities.create();
388 tile_layers.insert(layer_ent, layer);
389 transforms.insert(layer_ent, default());
390}
391
392fn atlas_demo_plugin(session: &mut SessionBuilder) {
394 session
395 .install_plugin(DefaultSessionPlugin)
396 .add_startup_system(atlas_demo_startup)
397 .add_system_to_stage(Update, back_to_menu_ui);
398}
399
400fn atlas_demo_startup(
402 mut entities: ResMut<Entities>,
403 mut transforms: CompMut<Transform>,
404 mut cameras: CompMut<Camera>,
405 mut atlas_sprites: CompMut<AtlasSprite>,
406 mut animated_sprites: CompMut<AnimatedSprite>,
407 mut clear_color: ResMutInit<ClearColor>,
408 meta: Root<GameMeta>,
409 assets: Res<AssetServer>,
410) {
411 **clear_color = Color::GRAY;
413
414 let demo = assets.get(meta.atlas_demo);
416
417 let camera_ent = spawn_default_camera(&mut entities, &mut transforms, &mut cameras);
419 cameras.get_mut(camera_ent).unwrap().size = demo.camera_size;
420
421 let sprite_ent = entities.create();
423 transforms.insert(sprite_ent, default());
424 atlas_sprites.insert(
425 sprite_ent,
426 AtlasSprite {
427 atlas: demo.atlas,
428 ..default()
429 },
430 );
431 animated_sprites.insert(
432 sprite_ent,
433 AnimatedSprite {
434 frames: demo.animation.iter().copied().collect(),
435 fps: demo.fps,
436 ..default()
437 },
438 );
439}
440
441fn audio_demo_plugin(session: &mut SessionBuilder) {
442 session
443 .install_plugin(DefaultSessionPlugin)
444 .add_system_to_stage(Update, back_to_menu_ui)
445 .add_system_to_stage(Update, audio_demo_ui);
446}
447
448fn audio_demo_ui(
449 ctx: Res<EguiCtx>,
450 localization: Localization<GameMeta>,
451 mut audio: ResMut<AudioManager>,
452 meta: Root<GameMeta>,
453 assets: Res<AssetServer>,
454) {
455 egui::CentralPanel::default()
456 .frame(egui::Frame::none())
457 .show(&ctx, |ui| {
458 ui.vertical_centered(|ui| {
459 ui.add_space(50.0);
460 if ui.button(localization.get("play-sound")).clicked() {
461 audio.play(&*assets.get(meta.audio_demo)).unwrap();
462 }
463 })
464 });
465}
466
467fn storage_demo_plugin(session: &mut SessionBuilder) {
468 session
469 .install_plugin(DefaultSessionPlugin)
470 .add_system_to_stage(Update, storage_demo_ui)
471 .add_system_to_stage(Update, back_to_menu_ui);
472}
473
474fn storage_demo_ui(
475 ctx: Res<EguiCtx>,
476 mut storage: ResMut<Storage>,
477 localization: Localization<GameMeta>,
478) {
479 egui::CentralPanel::default().show(&ctx, |ui| {
480 ui.add_space(20.0);
481
482 ui.vertical_centered(|ui| {
483 ui.set_width(300.0);
484 {
485 let data = storage.get_or_insert_default_mut::<PersistedTextData>();
486 egui::TextEdit::singleline(&mut data.0)
487 .hint_text(localization.get("persisted-text-box-content"))
488 .show(ui);
489 }
490 if ui.button(localization.get("save")).clicked() {
491 storage.save()
492 }
493 });
494 });
495}
496
497fn path2d_demo_plugin(session: &mut SessionBuilder) {
498 session
499 .install_plugin(DefaultSessionPlugin)
500 .add_startup_system(path2d_demo_startup)
501 .add_system_to_stage(Update, back_to_menu_ui);
502}
503
504fn path2d_demo_startup(
505 meta: Root<GameMeta>,
506 mut entities: ResMut<Entities>,
507 mut transforms: CompMut<Transform>,
508 mut cameras: CompMut<Camera>,
509 mut path2ds: CompMut<Path2d>,
510) {
511 spawn_default_camera(&mut entities, &mut transforms, &mut cameras);
512
513 let ent = entities.create();
514 transforms.insert(ent, default());
515 const SIZE: f32 = 40.;
516 path2ds.insert(
517 ent,
518 Path2d {
519 color: meta.path2d_color,
520 points: vec![
521 vec2(-SIZE, 0.),
522 vec2(0., SIZE),
523 vec2(SIZE, 0.),
524 vec2(-SIZE, 0.),
525 ],
526 thickness: 2.0,
527 ..default()
528 },
529 );
530}
531
532fn back_to_menu_ui(
535 ctx: Res<EguiCtx>,
536 mut sessions: ResMut<Sessions>,
537 mut session_options: ResMut<SessionOptions>,
538 localization: Localization<GameMeta>,
539) {
540 egui::TopBottomPanel::bottom("back-to-menu")
541 .frame(egui::Frame::none())
542 .show_separator_line(false)
543 .show(&ctx, |ui| {
544 ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| {
545 ui.add_space(20.0);
546 if ui.button(localization.get("back-to-menu")).clicked() {
547 session_options.delete = true;
548 sessions.create_with("menu", menu_plugin);
549 }
550 });
551 });
552}
553
554fn demo_widget(
556 mut ui: In<&mut egui::Ui>,
558 meta: Root<GameMeta>,
560 egui_textures: Res<EguiTextures>,
561 ) -> egui::Response {
563 ui.label("Demo Widget");
564 ui.image(egui::load::SizedTexture::new(
567 egui_textures.get(meta.menu_image),
568 [50., 50.],
569 ))
570}