Levels stored outside program

This commit is contained in:
Pascal Engélibert 2022-08-25 19:55:34 +02:00
parent 21d98a4a1d
commit 1d22c11cde
Signed by: tuxmain
GPG key ID: 3504BC6D362F7DCA
10 changed files with 280 additions and 347 deletions

14
Cargo.lock generated
View file

@ -316,6 +316,18 @@ dependencies = [
"rodio", "rodio",
] ]
[[package]]
name = "bevy_common_assets"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f7be9ee39085d8319d5cd853447b0b5c4f8b4bfd647aec91e2bd996e9db67ef"
dependencies = [
"anyhow",
"bevy",
"serde",
"serde_json",
]
[[package]] [[package]]
name = "bevy_core" name = "bevy_core"
version = "0.8.1" version = "0.8.1"
@ -898,6 +910,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"bevy", "bevy",
"bevy-inspector-egui", "bevy-inspector-egui",
"bevy_common_assets",
"bevy_rapier2d", "bevy_rapier2d",
"cpal 0.14.0", "cpal 0.14.0",
"crossbeam-channel", "crossbeam-channel",
@ -905,6 +918,7 @@ dependencies = [
"rand", "rand",
"rand_distr", "rand_distr",
"rapier2d", "rapier2d",
"serde",
"ticktock", "ticktock",
] ]

View file

@ -7,12 +7,14 @@ edition = "2021"
[dependencies] [dependencies]
bevy = "0.8.1" bevy = "0.8.1"
bevy_common_assets = { version = "0.3.0", features = ["json"] }
bevy-inspector-egui = "0.12.1" bevy-inspector-egui = "0.12.1"
bevy_rapier2d = "0.16.2" bevy_rapier2d = "0.16.2"
crossbeam-channel = "0.5.6" crossbeam-channel = "0.5.6"
rand = "0.8.5" rand = "0.8.5"
rand_distr = "0.4.3" rand_distr = "0.4.3"
rapier2d = "0.14.0" rapier2d = "0.14.0"
serde = { version = "1.0.144", features = ["derive"] }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies] [target."cfg(not(target_arch = \"wasm32\"))".dependencies]
cpal = "0.14.0" cpal = "0.14.0"

119
assets/game.levels.json Normal file
View file

@ -0,0 +1,119 @@
{
"levels": [
{
"comment": "Movement tutorial",
"platforms": [
{"pos": [0, -256], "size": [800, 16]}
],
"characters": [
{"pos": [0, -192], "color": [1,0,0,1]},
{"pos": [-128, -192], "color": [0,1,0,1]},
{"pos": [128, -192], "color": [0,0,1,1]}
],
"absorbing_filters": [],
"rotating_filters": [],
"texts": [
{
"pos": [0, 0],
"font_size": 32,
"text": "Combine the colors to synthetize a white light.\nUse arrows to move."
}
]
},
{
"comment": "Switch tutorial",
"platforms": [
{"pos": [0, -256], "size": [800, 16]},
{"pos": [128, 256], "size": [96, 16]}
],
"characters": [
{"pos": [0, -192], "color": [0,1,0,1]},
{"pos": [-128, -192], "color": [1,0,0,1]},
{"pos": [128, 320], "color": [0,0,1,1]}
],
"absorbing_filters": [],
"rotating_filters": [],
"texts": [
{
"pos": [0, 0],
"font_size": 32,
"text": "Press Tab to switch."
}
]
},
{
"comment": "Absorbing filter tutorial",
"platforms": [
{"pos": [0, -256], "size": [800, 16]},
{"pos": [0, -128], "size": [800, 16]}
],
"characters": [
{"pos": [-128, -192], "color": [1,0.64,0,1]},
{"pos": [128, -192], "color": [0,0.37,1,1]}
],
"absorbing_filters": [
{
"pos": [0, -192],
"size": [16, 112],
"color": [1,0,0,1]
}
],
"rotating_filters": [],
"texts": [
{
"pos": [0, 0],
"font_size": 32,
"text": "Press R to reset."
}
]
},
{
"comment": "Rotating filter tutorial",
"platforms": [
{"pos": [0, -256], "size": [800, 16]}
],
"characters": [
{"pos": [0, -192], "color": [1,0,0,1]},
{"pos": [-128, -192], "color": [1,0,0,1]},
{"pos": [128, -192], "color": [1,0,0,1]}
],
"absorbing_filters": [],
"rotating_filters": [
{
"pos": [0, -64],
"angle": 45
}
],
"texts": [
{
"pos": [0, 0],
"font_size": 32,
"text": "Let's rotate the hue!"
}
]
},
{
"comment": "Game over",
"platforms": [
{"pos": [0, -256], "size": [800, 16]}
],
"characters": [
{"pos": [0, -64], "color": [1,0,0,1]}
],
"absorbing_filters": [],
"rotating_filters": [],
"texts": [
{
"pos": [0, 128],
"font_size": 48,
"text": "Thank you for playing!"
},
{
"pos": [0, 0],
"font_size": 32,
"text": "There is no more light to combine."
}
]
}
]
}

View file

@ -1,14 +1,9 @@
#![allow(clippy::too_many_arguments)] #![allow(clippy::too_many_arguments)]
mod game_over;
mod level0;
mod level1;
mod level2;
mod level3;
use crate::game::*; use crate::game::*;
use bevy::prelude::*; use bevy::{prelude::*, reflect::TypeUuid};
use serde::{Deserialize, Serialize};
pub fn setup_level( pub fn setup_level(
level_startup_event: &mut EventWriter<LevelStartupEvent>, level_startup_event: &mut EventWriter<LevelStartupEvent>,
@ -35,51 +30,152 @@ pub fn post_setup_level(
mut level_startup_event: EventReader<LevelStartupEvent>, mut level_startup_event: EventReader<LevelStartupEvent>,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
audio: Res<crossbeam_channel::Sender<AudioMsg>>, audio: Res<crossbeam_channel::Sender<AudioMsg>>,
stored_levels_assets: Res<Assets<StoredLevels>>,
stored_levels_handle: Res<Handle<StoredLevels>>,
) { ) {
for _ in level_startup_event.iter() { for _ in level_startup_event.iter() {
if let Some(level_id) = current_level.0 { if let Some(level_id) = current_level.0 {
match level_id.0 { if let Some(stored_level) = stored_levels_assets
0 => level0::setup( .get(&stored_levels_handle)
.unwrap()
.levels
.get(level_id.0 as usize)
{
spawn_stored_level(
&mut commands, &mut commands,
&mut meshes,
&character_meshes, &character_meshes,
&mut materials,
&audio,
&asset_server,
),
1 => level1::setup(
&mut commands,
&mut meshes, &mut meshes,
&character_meshes,
&mut materials, &mut materials,
&audio,
&asset_server, &asset_server,
),
2 => level2::setup(
&mut commands,
&mut meshes,
&character_meshes,
&mut materials,
&audio, &audio,
&asset_server, stored_level,
), );
3 => level3::setup(
&mut commands,
&mut meshes,
&character_meshes,
&mut materials,
&audio,
&asset_server,
),
_ => game_over::setup(
&mut commands,
&mut meshes,
&character_meshes,
&mut materials,
&audio,
&asset_server,
),
} }
} }
} }
} }
#[derive(Deserialize, Serialize, TypeUuid)]
#[uuid = "1fbba930-644b-0d62-2514-4b302b945327"]
pub struct StoredLevels {
levels: Vec<StoredLevel>,
}
#[derive(Deserialize, Serialize, TypeUuid)]
#[uuid = "a1464a30-1f57-a654-d56c-ded41032af0b"]
pub struct StoredLevel {
pub comment: String,
pub characters: Vec<StoredCharacter>,
pub platforms: Vec<StoredPlatform>,
pub absorbing_filters: Vec<StoredAbsorbingFilter>,
pub rotating_filters: Vec<StoredRotatingFilter>,
pub texts: Vec<StoredText>,
}
#[derive(Deserialize, Serialize, TypeUuid)]
#[uuid = "1c798f8c-ef15-c528-693e-76becdef6b10"]
pub struct StoredCharacter {
pub pos: Vec2,
pub color: Vec4,
}
#[derive(Deserialize, Serialize, TypeUuid)]
#[uuid = "31696095-59de-93be-b5e9-333c2afbc900"]
pub struct StoredPlatform {
pub pos: Vec2,
pub size: Vec2,
}
#[derive(Deserialize, Serialize, TypeUuid)]
#[uuid = "bcad7fff-0605-c4e3-3cd4-42d5bbaad926"]
pub struct StoredAbsorbingFilter {
pub pos: Vec2,
pub size: Vec2,
pub color: Vec4,
}
#[derive(Deserialize, Serialize, TypeUuid)]
#[uuid = "fa2843f2-6e34-601b-6c46-4827b0370b3f"]
pub struct StoredRotatingFilter {
pub pos: Vec2,
pub angle: f32,
}
#[derive(Deserialize, Serialize, TypeUuid)]
#[uuid = "72f6321a-f01f-6eea-9b17-3159837a2fd3"]
pub struct StoredText {
pub pos: Vec2,
pub font_size: f32,
pub text: String,
}
pub fn spawn_stored_level(
commands: &mut Commands,
character_meshes: &Res<CharacterMeshes>,
meshes: &mut ResMut<Assets<Mesh>>,
materials: &mut ResMut<Assets<ColorMaterial>>,
asset_server: &Res<AssetServer>,
audio: &Res<crossbeam_channel::Sender<AudioMsg>>,
stored_level: &StoredLevel,
) {
let font = asset_server.get_handle("UacariLegacy-Thin.ttf");
spawn_platforms(
commands,
meshes,
materials,
stored_level.platforms.iter().map(|platform| {
(
Transform::from_xyz(platform.pos.x, platform.pos.y, 0.),
platform.size,
)
}),
);
spawn_characters(
commands,
character_meshes,
materials,
audio,
stored_level.characters.iter().map(|character| {
(
Transform::from_xyz(character.pos.x, character.pos.y, 0.),
character.color.into(),
)
}),
);
for absorbing_filter in stored_level.absorbing_filters.iter() {
spawn_absorbing_filter(
commands,
meshes,
materials,
Transform::from_xyz(absorbing_filter.pos.x, absorbing_filter.pos.y, 2.),
absorbing_filter.size,
absorbing_filter.color.into(),
);
}
for rotating_filter in stored_level.rotating_filters.iter() {
spawn_rotating_filter(
commands,
asset_server,
Transform::from_xyz(rotating_filter.pos.x, rotating_filter.pos.y, 2.),
rotating_filter.angle,
);
}
for text in stored_level.texts.iter() {
commands
.spawn_bundle(Text2dBundle {
text: Text::from_section(
&text.text,
TextStyle {
font: font.clone(),
font_size: text.font_size,
color: Color::WHITE,
},
)
.with_alignment(TextAlignment::CENTER),
transform: Transform::from_xyz(text.pos.x, text.pos.y, 0.),
..Default::default()
})
.insert(Level);
}
}

View file

@ -1,61 +0,0 @@
use crate::game::*;
use bevy::prelude::*;
pub fn setup(
commands: &mut Commands,
meshes: &mut ResMut<Assets<Mesh>>,
character_meshes: &Res<CharacterMeshes>,
materials: &mut ResMut<Assets<ColorMaterial>>,
audio: &Res<crossbeam_channel::Sender<AudioMsg>>,
asset_server: &Res<AssetServer>,
) {
let font = asset_server.get_handle("UacariLegacy-Thin.ttf");
commands
.spawn_bundle(Text2dBundle {
text: Text::from_section(
"Thank you for playing!",
TextStyle {
font: font.clone(),
font_size: 48.0,
color: Color::WHITE,
},
)
.with_alignment(TextAlignment::CENTER),
transform: Transform::from_xyz(0., 128.0, 0.),
..Default::default()
})
.insert(Level);
commands
.spawn_bundle(Text2dBundle {
text: Text::from_section(
"There is no more light to combine.",
TextStyle {
font,
font_size: 32.0,
color: Color::WHITE,
},
)
.with_alignment(TextAlignment::CENTER),
..Default::default()
})
.insert(Level);
spawn_platform(
commands,
meshes,
materials,
Transform::from_xyz(0.0, -256.0, 0.0),
Vec2 { x: 800.0, y: 16.0 },
);
spawn_character(
commands,
character_meshes,
materials,
audio,
Transform::from_xyz(0., -64., 0.),
Color::RED,
true,
);
}

View file

@ -1,50 +0,0 @@
// Movement tutorial
use crate::game::*;
use bevy::prelude::*;
pub fn setup(
commands: &mut Commands,
meshes: &mut ResMut<Assets<Mesh>>,
character_meshes: &Res<CharacterMeshes>,
materials: &mut ResMut<Assets<ColorMaterial>>,
audio: &Res<crossbeam_channel::Sender<AudioMsg>>,
asset_server: &Res<AssetServer>,
) {
let font = asset_server.get_handle("UacariLegacy-Thin.ttf");
commands
.spawn_bundle(Text2dBundle {
text: Text::from_section(
"Combine the colors to synthetize a white light.\nUse arrows to move.",
TextStyle {
font,
font_size: 32.0,
color: Color::WHITE,
},
)
.with_alignment(TextAlignment::CENTER),
..Default::default()
})
.insert(Level);
spawn_platform(
commands,
meshes,
materials,
Transform::from_xyz(0.0, -256.0, 0.0),
Vec2 { x: 800.0, y: 16.0 },
);
spawn_characters(
commands,
character_meshes,
materials,
audio,
[
(Transform::from_xyz(0., -192., 0.), Color::RED),
(Transform::from_xyz(-128., -192., 0.), Color::GREEN),
(Transform::from_xyz(128., -192., 0.), Color::BLUE),
],
);
}

View file

@ -1,58 +0,0 @@
// Switch tutorial
use crate::game::*;
use bevy::prelude::*;
pub fn setup(
commands: &mut Commands,
meshes: &mut ResMut<Assets<Mesh>>,
character_meshes: &Res<CharacterMeshes>,
materials: &mut ResMut<Assets<ColorMaterial>>,
audio: &Res<crossbeam_channel::Sender<AudioMsg>>,
asset_server: &Res<AssetServer>,
) {
let font = asset_server.get_handle("UacariLegacy-Thin.ttf");
commands
.spawn_bundle(Text2dBundle {
text: Text::from_section(
"Press Tab to switch.",
TextStyle {
font,
font_size: 32.0,
color: Color::WHITE,
},
)
.with_alignment(TextAlignment::CENTER),
..Default::default()
})
.insert(Level);
spawn_platforms(
commands,
meshes,
materials,
[
(
Transform::from_xyz(0.0, -256.0, 0.0),
Vec2 { x: 800.0, y: 16.0 },
),
(
Transform::from_xyz(128.0, 256.0, 0.0),
Vec2 { x: 96.0, y: 16.0 },
),
],
);
spawn_characters(
commands,
character_meshes,
materials,
audio,
[
(Transform::from_xyz(0., -192., 0.), Color::GREEN),
(Transform::from_xyz(-128., -192., 0.), Color::RED),
(Transform::from_xyz(128., 320., 0.), Color::BLUE),
],
);
}

View file

@ -1,72 +0,0 @@
// Absorbing filter tutorial
use crate::game::*;
use bevy::prelude::*;
pub fn setup(
commands: &mut Commands,
meshes: &mut ResMut<Assets<Mesh>>,
character_meshes: &Res<CharacterMeshes>,
materials: &mut ResMut<Assets<ColorMaterial>>,
audio: &Res<crossbeam_channel::Sender<AudioMsg>>,
asset_server: &Res<AssetServer>,
) {
let font = asset_server.get_handle("UacariLegacy-Thin.ttf");
commands
.spawn_bundle(Text2dBundle {
text: Text::from_section(
"Press R to reset.",
TextStyle {
font,
font_size: 32.0,
color: Color::WHITE,
},
)
.with_alignment(TextAlignment::CENTER),
..Default::default()
})
.insert(Level);
spawn_platforms(
commands,
meshes,
materials,
[
(
Transform::from_xyz(0.0, -256.0, 0.0),
Vec2 { x: 800.0, y: 16.0 },
),
(
Transform::from_xyz(0.0, -128.0, 0.0),
Vec2 { x: 800.0, y: 16.0 },
),
],
);
spawn_characters(
commands,
character_meshes,
materials,
audio,
[
(
Transform::from_xyz(-128., -192., 0.),
Color::rgba(1., 0.64, 0., 1.),
),
(
Transform::from_xyz(128., -192., 0.),
Color::rgba(0., 0.37, 1., 1.),
),
],
);
spawn_absorbing_filter(
commands,
meshes,
materials,
Transform::from_xyz(0., -192., 2.),
Vec2 { x: 16.0, y: 112.0 },
Color::RED,
);
}

View file

@ -1,59 +0,0 @@
// Rotating filter tutorial
use crate::game::*;
use bevy::prelude::*;
pub fn setup(
commands: &mut Commands,
meshes: &mut ResMut<Assets<Mesh>>,
character_meshes: &Res<CharacterMeshes>,
materials: &mut ResMut<Assets<ColorMaterial>>,
audio: &Res<crossbeam_channel::Sender<AudioMsg>>,
asset_server: &Res<AssetServer>,
) {
let font = asset_server.get_handle("UacariLegacy-Thin.ttf");
commands
.spawn_bundle(Text2dBundle {
text: Text::from_section(
"Let's rotate the hue!",
TextStyle {
font,
font_size: 32.0,
color: Color::WHITE,
},
)
.with_alignment(TextAlignment::CENTER),
..Default::default()
})
.insert(Level);
spawn_platforms(
commands,
meshes,
materials,
[(
Transform::from_xyz(0.0, -256.0, 0.0),
Vec2 { x: 800.0, y: 16.0 },
)],
);
spawn_characters(
commands,
character_meshes,
materials,
audio,
[
(Transform::from_xyz(0., -192., 0.), Color::RED),
(Transform::from_xyz(-128., -192., 0.), Color::RED),
(Transform::from_xyz(128., -192., 0.), Color::RED),
],
);
spawn_rotating_filter(
commands,
asset_server,
Transform::from_xyz(0., -64., 2.),
45.,
);
}

View file

@ -10,6 +10,7 @@ use bevy::{
prelude::*, prelude::*,
window::{WindowId, WindowMode}, window::{WindowId, WindowMode},
}; };
use bevy_common_assets::json::JsonAssetPlugin;
use bevy_rapier2d::prelude::*; use bevy_rapier2d::prelude::*;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
@ -42,6 +43,9 @@ fn main() {
.insert_resource(game::FirstLevel(first_level)) .insert_resource(game::FirstLevel(first_level))
.insert_resource(ClearColor(Color::BLACK)) .insert_resource(ClearColor(Color::BLACK))
.add_plugins(DefaultPlugins) .add_plugins(DefaultPlugins)
.add_plugin(JsonAssetPlugin::<levels::StoredLevels>::new(&[
"levels.json",
]))
.add_plugin(RapierPhysicsPlugin::<NoUserData>::pixels_per_meter(64.0)) .add_plugin(RapierPhysicsPlugin::<NoUserData>::pixels_per_meter(64.0))
//.add_plugin(RapierDebugRenderPlugin::default()) //.add_plugin(RapierDebugRenderPlugin::default())
.add_plugin(menu::MenuPlugin) .add_plugin(menu::MenuPlugin)
@ -59,11 +63,9 @@ fn setup(mut commands: Commands, mut windows: ResMut<Windows>, asset_server: Res
.unwrap() .unwrap()
.set_title(String::from("Bevyjam")); .set_title(String::from("Bevyjam"));
let font: Handle<Font> = asset_server.load("UacariLegacy-Thin.ttf"); commands.insert_resource(asset_server.load::<levels::StoredLevels, _>("game.levels.json"));
commands.insert_resource(font); commands.insert_resource(asset_server.load::<Font, _>("UacariLegacy-Thin.ttf"));
commands.insert_resource(asset_server.load::<Image, _>("bevy.png"));
let bevy_icon: Handle<Image> = asset_server.load("bevy.png");
commands.insert_resource(bevy_icon);
commands.spawn_bundle(Camera2dBundle::default()); commands.spawn_bundle(Camera2dBundle::default());
commands.insert_resource(AmbientLight { commands.insert_resource(AmbientLight {