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 Session) {
143 session
145 .install_plugin(DefaultSessionPlugin)
147 .init_startup_resource::<MenuData>();
149
150 session
152 .add_system_to_stage(Update, menu_system)
153 .add_system_to_stage(CoreStage::Startup, 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 Session) {
284 session
285 .install_plugin(DefaultSessionPlugin)
286 .add_system_to_stage(CoreStage::Startup, 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 Session) {
348 session
349 .install_plugin(DefaultSessionPlugin)
350 .add_system_to_stage(CoreStage::Startup, 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
369 let mut layer = TileLayer::new(map_info.map_size, map_info.atlas);
371
372 for tile in &map_info.tiles {
374 let tile_ent = entities.create();
375 tiles.insert(
376 tile_ent,
377 Tile {
378 idx: tile.idx,
379 ..default()
380 },
381 );
382 layer.set(tile.pos, Some(tile_ent))
383 }
384
385 let layer_ent = entities.create();
387 tile_layers.insert(layer_ent, layer);
388 transforms.insert(layer_ent, default());
389}
390
391fn atlas_demo_plugin(session: &mut Session) {
393 session
394 .install_plugin(DefaultSessionPlugin)
395 .add_system_to_stage(CoreStage::Startup, atlas_demo_startup)
396 .add_system_to_stage(Update, back_to_menu_ui);
397}
398
399fn atlas_demo_startup(
401 mut entities: ResMut<Entities>,
402 mut transforms: CompMut<Transform>,
403 mut cameras: CompMut<Camera>,
404 mut atlas_sprites: CompMut<AtlasSprite>,
405 mut animated_sprites: CompMut<AnimatedSprite>,
406 mut clear_color: ResMutInit<ClearColor>,
407 meta: Root<GameMeta>,
408 assets: Res<AssetServer>,
409) {
410 **clear_color = Color::GRAY;
412
413 let demo = assets.get(meta.atlas_demo);
415
416 let camera_ent = spawn_default_camera(&mut entities, &mut transforms, &mut cameras);
418 cameras.get_mut(camera_ent).unwrap().size = demo.camera_size;
419
420 let sprite_ent = entities.create();
422 transforms.insert(sprite_ent, default());
423 atlas_sprites.insert(
424 sprite_ent,
425 AtlasSprite {
426 atlas: demo.atlas,
427 ..default()
428 },
429 );
430 animated_sprites.insert(
431 sprite_ent,
432 AnimatedSprite {
433 frames: demo.animation.iter().copied().collect(),
434 fps: demo.fps,
435 ..default()
436 },
437 );
438}
439
440fn audio_demo_plugin(session: &mut Session) {
441 session
442 .install_plugin(DefaultSessionPlugin)
443 .add_system_to_stage(Update, back_to_menu_ui)
444 .add_system_to_stage(Update, audio_demo_ui);
445}
446
447fn audio_demo_ui(
448 ctx: Res<EguiCtx>,
449 localization: Localization<GameMeta>,
450 mut audio: ResMut<AudioManager>,
451 meta: Root<GameMeta>,
452 assets: Res<AssetServer>,
453) {
454 egui::CentralPanel::default()
455 .frame(egui::Frame::none())
456 .show(&ctx, |ui| {
457 ui.vertical_centered(|ui| {
458 ui.add_space(50.0);
459 if ui.button(localization.get("play-sound")).clicked() {
460 audio.play(&*assets.get(meta.audio_demo)).unwrap();
461 }
462 })
463 });
464}
465
466fn storage_demo_plugin(session: &mut Session) {
467 session
468 .install_plugin(DefaultSessionPlugin)
469 .add_system_to_stage(Update, storage_demo_ui)
470 .add_system_to_stage(Update, back_to_menu_ui);
471}
472
473fn storage_demo_ui(
474 ctx: Res<EguiCtx>,
475 mut storage: ResMut<Storage>,
476 localization: Localization<GameMeta>,
477) {
478 egui::CentralPanel::default().show(&ctx, |ui| {
479 ui.add_space(20.0);
480
481 ui.vertical_centered(|ui| {
482 ui.set_width(300.0);
483 {
484 let data = storage.get_or_insert_default_mut::<PersistedTextData>();
485 egui::TextEdit::singleline(&mut data.0)
486 .hint_text(localization.get("persisted-text-box-content"))
487 .show(ui);
488 }
489 if ui.button(localization.get("save")).clicked() {
490 storage.save()
491 }
492 });
493 });
494}
495
496fn path2d_demo_plugin(session: &mut Session) {
497 session
498 .install_plugin(DefaultSessionPlugin)
499 .add_system_to_stage(CoreStage::Startup, path2d_demo_startup)
500 .add_system_to_stage(Update, back_to_menu_ui);
501}
502
503fn path2d_demo_startup(
504 meta: Root<GameMeta>,
505 mut entities: ResMut<Entities>,
506 mut transforms: CompMut<Transform>,
507 mut cameras: CompMut<Camera>,
508 mut path2ds: CompMut<Path2d>,
509) {
510 spawn_default_camera(&mut entities, &mut transforms, &mut cameras);
511
512 let ent = entities.create();
513 transforms.insert(ent, default());
514 const SIZE: f32 = 40.;
515 path2ds.insert(
516 ent,
517 Path2d {
518 color: meta.path2d_color,
519 points: vec![
520 vec2(-SIZE, 0.),
521 vec2(0., SIZE),
522 vec2(SIZE, 0.),
523 vec2(-SIZE, 0.),
524 ],
525 thickness: 2.0,
526 ..default()
527 },
528 );
529}
530
531fn back_to_menu_ui(
534 ctx: Res<EguiCtx>,
535 mut sessions: ResMut<Sessions>,
536 mut session_options: ResMut<SessionOptions>,
537 localization: Localization<GameMeta>,
538) {
539 egui::TopBottomPanel::bottom("back-to-menu")
540 .frame(egui::Frame::none())
541 .show_separator_line(false)
542 .show(&ctx, |ui| {
543 ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| {
544 ui.add_space(20.0);
545 if ui.button(localization.get("back-to-menu")).clicked() {
546 session_options.delete = true;
547 sessions.create_with("menu", menu_plugin);
548 }
549 });
550 });
551}
552
553fn demo_widget(
555 mut ui: In<&mut egui::Ui>,
557 meta: Root<GameMeta>,
559 egui_textures: Res<EguiTextures>,
560 ) -> egui::Response {
562 ui.label("Demo Widget");
563 ui.image(egui::load::SizedTexture::new(
566 egui_textures.get(meta.menu_image),
567 [50., 50.],
568 ))
569}