Compare commits

...

9 commits

10 changed files with 1135 additions and 202 deletions

25
Cargo.lock generated
View file

@ -588,6 +588,25 @@ dependencies = [
"glam",
]
[[package]]
name = "bevy_mod_picking"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db42ac84b1409452bbfa696d9071b9a7a2505c73620c939b758b5bf23573976a"
dependencies = [
"bevy",
"bevy_mod_raycast",
]
[[package]]
name = "bevy_mod_raycast"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7aead49a20f5e694f4fb59c7312f9a1813b65a2a0ac2c385d53d40f25cae896f"
dependencies = [
"bevy",
]
[[package]]
name = "bevy_pbr"
version = "0.8.1"
@ -911,6 +930,7 @@ dependencies = [
"bevy",
"bevy-inspector-egui",
"bevy_common_assets",
"bevy_mod_picking",
"bevy_rapier2d",
"cpal 0.14.0",
"crossbeam-channel",
@ -919,6 +939,7 @@ dependencies = [
"rand_distr",
"rapier2d",
"serde",
"serde_json",
"ticktock",
]
@ -1563,9 +1584,9 @@ dependencies = [
[[package]]
name = "erased-serde"
version = "0.3.22"
version = "0.3.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "003000e712ad0f95857bd4d2ef8d1890069e06554101697d12050668b2f6f020"
checksum = "54558e0ba96fbe24280072642eceb9d7d442e32c7ec0ea9e7ecd7b4ea2cf4e11"
dependencies = [
"serde",
]

View file

@ -8,7 +8,6 @@ edition = "2021"
[dependencies]
bevy = "0.8.1"
bevy_common_assets = { version = "0.3.0", features = ["json"] }
bevy-inspector-egui = "0.12.1"
bevy_rapier2d = "0.16.2"
crossbeam-channel = "0.5.6"
rand = "0.8.5"
@ -17,8 +16,11 @@ rapier2d = "0.14.0"
serde = { version = "1.0.144", features = ["derive"] }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
bevy-inspector-egui = "0.12.1"
bevy_mod_picking = "0.9.0"
cpal = "0.14.0"
hexodsp = { git = "https://github.com/WeirdConstructor/HexoDSP", default-features = false }
serde_json = "1.0.85"
ticktock = "0.8.0"
[target."cfg(target_arch = \"wasm32\")".dependencies]

View file

@ -45,6 +45,19 @@ This game uses [HexoDSP](https://github.com/WeirdConstructor/HexoDSP) for audio
The synthetizer matrix can be edited using [HexoSynth](https://github.com/WeirdConstructor/HexoSynth) visual editor.
## Develop
Skip to level `N: u32` with the command `bevyjam <N>`.
Edit the level `N: u32` with the command `bevyjam <N> e`.
### Editor controls
* **Select**: left click to select, click in void to deselect, CTRL+click to select many, CTRL+A to select all
* **Move selection**: arrows to move one step, Shift+arrows to move continuously
* **Move camera**: CTRL+arrows
* **Save**: CTRL+S
## License
GNU AGPL v3, CopyLeft 2022 Pascal Engélibert, Nixon Cheng

View file

@ -1,119 +1,346 @@
{
"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."
}
]
}
]
"levels": [
{
"comment": "Movement tutorial",
"characters": [
{
"pos": [
0.0,
-192.0
],
"color": [
1.0,
0.0,
0.0,
1.0
]
},
{
"pos": [
-128.0,
-192.0
],
"color": [
0.0,
1.0,
0.0,
1.0
]
},
{
"pos": [
128.0,
-192.0
],
"color": [
0.0,
0.0,
1.0,
1.0
]
}
],
"platforms": [
{
"pos": [
0.0,
-256.0
],
"size": [
800.0,
16.0
]
}
],
"absorbing_filters": [],
"rotating_filters": [],
"texts": [
{
"pos": [
0.0,
0.0
],
"font_size": 32.0,
"text": "Combine the colors to synthetize a white light.\nUse arrows to move."
}
]
},
{
"comment": "Switch tutorial",
"characters": [
{
"pos": [
0.0,
-192.0
],
"color": [
0.0,
1.0,
0.0,
1.0
]
},
{
"pos": [
-128.0,
-192.0
],
"color": [
1.0,
0.0,
0.0,
1.0
]
},
{
"pos": [
128.0,
320.0
],
"color": [
0.0,
0.0,
1.0,
1.0
]
}
],
"platforms": [
{
"pos": [
0.0,
-256.0
],
"size": [
800.0,
16.0
]
},
{
"pos": [
128.0,
256.0
],
"size": [
96.0,
16.0
]
}
],
"absorbing_filters": [],
"rotating_filters": [],
"texts": [
{
"pos": [
0.0,
0.0
],
"font_size": 32.0,
"text": "Press Tab to switch."
}
]
},
{
"comment": "Absorbing filter tutorial",
"characters": [
{
"pos": [
-128.0,
-192.0
],
"color": [
1.0,
0.64,
0.0,
1.0
]
},
{
"pos": [
128.0,
-192.0
],
"color": [
0.0,
0.37,
1.0,
1.0
]
}
],
"platforms": [
{
"pos": [
0.0,
-256.0
],
"size": [
800.0,
16.0
]
},
{
"pos": [
0.0,
-128.0
],
"size": [
800.0,
16.0
]
}
],
"absorbing_filters": [
{
"pos": [
0.0,
-192.0
],
"size": [
16.0,
112.0
],
"color": [
1.0,
0.0,
0.0,
1.0
]
}
],
"rotating_filters": [],
"texts": [
{
"pos": [
0.0,
0.0
],
"font_size": 32.0,
"text": "Press R to reset."
}
]
},
{
"comment": "Rotating filter tutorial",
"characters": [
{
"pos": [
0.0,
-192.0
],
"color": [
1.0,
0.0,
0.0,
1.0
]
},
{
"pos": [
-128.0,
-192.0
],
"color": [
1.0,
0.0,
0.0,
1.0
]
},
{
"pos": [
128.0,
-192.0
],
"color": [
1.0,
0.0,
0.0,
1.0
]
}
],
"platforms": [
{
"pos": [
0.0,
-256.0
],
"size": [
800.0,
16.0
]
}
],
"absorbing_filters": [],
"rotating_filters": [
{
"pos": [
0.0,
-64.0
],
"angle": 45.0
}
],
"texts": [
{
"pos": [
0.0,
0.0
],
"font_size": 32.0,
"text": "Let's rotate the hue!"
}
]
},
{
"comment": "Game over",
"characters": [
{
"pos": [
0.0,
-64.0
],
"color": [
1.0,
0.0,
0.0,
1.0
]
}
],
"platforms": [
{
"pos": [
0.0,
-256.0
],
"size": [
800.0,
16.0
]
}
],
"absorbing_filters": [],
"rotating_filters": [],
"texts": [
{
"pos": [
0.0,
128.0
],
"font_size": 48.0,
"text": "Thank you for playing!"
},
{
"pos": [
0.0,
0.0
],
"font_size": 32.0,
"text": "There is no more light to combine."
}
]
}
]
}

View file

@ -1,2 +1,3 @@
cargo build --release --target wasm32-unknown-unknown
wasm-bindgen --out-name bevyjam --out-dir target --target web target/wasm32-unknown-unknown/release/bevyjam.wasm
cargo build --release --target wasm32-unknown-unknown || exit 1
echo "==> wasm-bindgen..."
wasm-bindgen --out-name bevyjam --out-dir target --target web target/wasm32-unknown-unknown/release/bevyjam.wasm || exit 1

613
src/editor.rs Normal file
View file

@ -0,0 +1,613 @@
#![allow(clippy::type_complexity)]
use crate::{levels::stored::*, AppState};
use bevy::{
input::{keyboard::KeyCode, Input},
prelude::{
shape::{Circle, Quad},
*,
},
sprite::Mesh2dHandle,
};
use bevy_mod_picking::*;
pub struct EditorPlugin;
impl Plugin for EditorPlugin {
fn build(&self, app: &mut App) {
app.add_event::<DragEndEvent>()
.add_plugins(DefaultPickingPlugins)
.add_system_set(SystemSet::on_enter(AppState::Editor).with_system(setup))
.add_system_set(
SystemSet::on_update(AppState::Editor)
.with_system(move_system)
.with_system(input_control_system)
.with_system(follow_ends_system),
);
}
}
// Events
struct DragEndEvent(Entity);
// Resources
struct CharacterList(Vec<Entity>);
// Components
#[derive(Component)]
struct Platform;
#[derive(Component)]
struct Ends(Entity, Entity);
#[derive(Component)]
struct Draggable;
#[derive(Component)]
struct End(Entity);
#[derive(Component)]
struct CharacterColor(Color);
#[derive(Component)]
struct Size(Vec2);
#[derive(Component)]
struct RotatingFilterAngle(f32);
#[derive(Component)]
struct AbsorbingFilterColor(Color);
// Bundles
#[derive(Bundle)]
struct PlatformBundle {
#[bundle]
mesh: ColorMesh2dBundle,
size: Size,
platform: Platform,
}
#[derive(Bundle)]
struct EndBundle {
#[bundle]
mesh: ColorMesh2dBundle,
#[bundle]
pickable: PickableBundle,
draggable: Draggable,
end: End,
}
#[derive(Bundle)]
struct CharacterBundle {
#[bundle]
mesh: ColorMesh2dBundle,
color: CharacterColor,
#[bundle]
pickable: PickableBundle,
draggable: Draggable,
}
#[derive(Bundle)]
struct AbsorbingFilterBundle {
#[bundle]
mesh: ColorMesh2dBundle,
size: Size,
color: AbsorbingFilterColor,
}
#[derive(Bundle)]
struct RotatingFilterBundle {
#[bundle]
mesh: ColorMesh2dBundle,
angle: RotatingFilterAngle,
#[bundle]
pickable: PickableBundle,
draggable: Draggable,
}
// Functions
fn spawn_platform(
commands: &mut Commands,
meshes: &mut ResMut<Assets<Mesh>>,
materials: &mut ResMut<Assets<ColorMaterial>>,
transform: Transform,
size: Vec2,
) {
let platform = commands
.spawn_bundle(PlatformBundle {
mesh: ColorMesh2dBundle {
mesh: meshes.add(Mesh::from(Quad { size, flip: false })).into(),
material: materials.add(ColorMaterial::from(Color::GRAY)),
transform,
..default()
},
size: Size(size),
platform: Platform,
})
.id();
let ends = Ends(
commands
.spawn_bundle(EndBundle {
mesh: ColorMesh2dBundle {
mesh: meshes
.add(Mesh::from(Circle {
radius: 8.,
vertices: 12,
}))
.into(),
material: materials.add(ColorMaterial::from(Color::rgba(1., 1., 0., 0.7))),
transform: Transform::from_xyz(
transform.translation.x - size.x / 2.,
transform.translation.y - size.y / 2.,
0.5,
),
..default()
},
pickable: PickableBundle::default(),
draggable: Draggable,
end: End(platform),
})
.id(),
commands
.spawn_bundle(EndBundle {
mesh: ColorMesh2dBundle {
mesh: meshes
.add(Mesh::from(Circle {
radius: 8.,
vertices: 12,
}))
.into(),
material: materials.add(ColorMaterial::from(Color::rgba(1., 1., 0., 0.7))),
transform: Transform::from_xyz(
transform.translation.x + size.x / 2.,
transform.translation.y + size.y / 2.,
0.5,
),
..default()
},
pickable: PickableBundle::default(),
draggable: Draggable,
end: End(platform),
})
.id(),
);
commands.entity(platform).insert(ends);
}
fn spawn_character(
commands: &mut Commands,
meshes: &mut ResMut<Assets<Mesh>>,
materials: &mut ResMut<Assets<ColorMaterial>>,
asset_server: &Res<AssetServer>,
transform: Transform,
color: Color,
index: usize,
) -> Entity {
let font = asset_server.get_handle("UacariLegacy-Thin.ttf");
commands
.spawn_bundle(CharacterBundle {
mesh: ColorMesh2dBundle {
mesh: meshes
.add(Mesh::from(Quad {
size: Vec2 { x: 64., y: 64. },
flip: false,
}))
.into(),
material: materials.add(ColorMaterial::from(color)),
transform,
..default()
},
color: CharacterColor(color),
pickable: PickableBundle::default(),
draggable: Draggable,
})
.with_children(|c| {
c.spawn_bundle(Text2dBundle {
text: Text::from_section(
&index.to_string(),
TextStyle {
font: font.clone(),
font_size: 32.,
color: Color::WHITE,
},
)
.with_alignment(TextAlignment::CENTER),
transform: Transform::from_xyz(0., 0., 1.),
..Default::default()
});
})
.id()
}
fn spawn_absorbing_filter(
commands: &mut Commands,
meshes: &mut ResMut<Assets<Mesh>>,
materials: &mut ResMut<Assets<ColorMaterial>>,
transform: Transform,
size: Vec2,
color: Color,
) {
let absorbing_filter = commands
.spawn_bundle(AbsorbingFilterBundle {
mesh: ColorMesh2dBundle {
mesh: meshes.add(Mesh::from(Quad { size, flip: false })).into(),
material: materials.add(ColorMaterial::from(color)),
transform,
..default()
},
size: Size(size),
color: AbsorbingFilterColor(color),
})
.id();
let ends = Ends(
commands
.spawn_bundle(EndBundle {
mesh: ColorMesh2dBundle {
mesh: meshes
.add(Mesh::from(Circle {
radius: 8.,
vertices: 12,
}))
.into(),
material: materials.add(ColorMaterial::from(Color::rgba(1., 1., 0., 0.7))),
transform: Transform::from_xyz(
transform.translation.x - size.x / 2.,
transform.translation.y - size.y / 2.,
0.5,
),
..default()
},
pickable: PickableBundle::default(),
draggable: Draggable,
end: End(absorbing_filter),
})
.id(),
commands
.spawn_bundle(EndBundle {
mesh: ColorMesh2dBundle {
mesh: meshes
.add(Mesh::from(Circle {
radius: 8.,
vertices: 12,
}))
.into(),
material: materials.add(ColorMaterial::from(Color::rgba(1., 1., 0., 0.7))),
transform: Transform::from_xyz(
transform.translation.x + size.x / 2.,
transform.translation.y + size.y / 2.,
0.5,
),
..default()
},
pickable: PickableBundle::default(),
draggable: Draggable,
end: End(absorbing_filter),
})
.id(),
);
commands.entity(absorbing_filter).insert(ends);
}
fn spawn_rotating_filter(
commands: &mut Commands,
meshes: &mut ResMut<Assets<Mesh>>,
materials: &mut ResMut<Assets<ColorMaterial>>,
asset_server: &Res<AssetServer>,
transform: Transform,
angle: f32,
) -> Entity {
let font = asset_server.get_handle("UacariLegacy-Thin.ttf");
commands
.spawn_bundle(RotatingFilterBundle {
mesh: ColorMesh2dBundle {
mesh: meshes
.add(Mesh::from(Circle {
radius: 32.,
vertices: 36,
}))
.into(),
material: materials.add(ColorMaterial::from(Color::WHITE)),
transform,
..default()
},
angle: RotatingFilterAngle(angle),
pickable: PickableBundle::default(),
draggable: Draggable,
})
.with_children(|c| {
c.spawn_bundle(Text2dBundle {
text: Text::from_section(
&angle.to_string(),
TextStyle {
font: font.clone(),
font_size: 32.,
color: Color::RED,
},
)
.with_alignment(TextAlignment::CENTER),
transform: Transform::from_xyz(0., 0., 1.),
..Default::default()
});
})
.id()
}
fn spawn_stored_level(
commands: &mut Commands,
meshes: &mut ResMut<Assets<Mesh>>,
materials: &mut ResMut<Assets<ColorMaterial>>,
asset_server: &Res<AssetServer>,
stored_level: &StoredLevel,
) {
for platform in stored_level.platforms.iter() {
spawn_platform(
commands,
meshes,
materials,
Transform::from_xyz(platform.pos.x, platform.pos.y, 0.),
platform.size,
);
}
let mut character_list = Vec::new();
for (i, character) in stored_level.characters.iter().enumerate() {
character_list.push(spawn_character(
commands,
meshes,
materials,
asset_server,
Transform::from_xyz(character.pos.x, character.pos.y, 0.),
character.color.into(),
i,
));
}
commands.insert_resource(CharacterList(character_list));
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, 0.),
absorbing_filter.size,
absorbing_filter.color.into(),
);
}
for rotating_filter in stored_level.rotating_filters.iter() {
spawn_rotating_filter(
commands,
meshes,
materials,
asset_server,
Transform::from_xyz(rotating_filter.pos.x, rotating_filter.pos.y, 0.),
rotating_filter.angle,
);
}
}
fn save_level(
level_id: &Res<crate::game::FirstLevel>,
stored_levels_assets: &mut ResMut<Assets<StoredLevels>>,
stored_levels_handle: &Res<Handle<StoredLevels>>,
character_list: &Res<CharacterList>,
character_query: &Query<(&Transform, &CharacterColor), Without<Platform>>,
platform_query: &Query<(&Transform, &Size), With<Platform>>,
absorbing_filter_query: &Query<(&Transform, &Size, &AbsorbingFilterColor), Without<Platform>>,
rotating_filter_query: &Query<
(&Transform, &RotatingFilterAngle),
(Without<Platform>, Without<CharacterColor>),
>,
) {
let stored_levels = stored_levels_assets.get_mut(stored_levels_handle).unwrap();
if let Some(stored_level) = stored_levels.levels.get_mut(level_id.0 .0 as usize) {
stored_level.platforms.clear();
for (transform, size) in platform_query.iter() {
stored_level.platforms.push(StoredPlatform {
pos: Vec2 {
x: transform.translation.x,
y: transform.translation.y,
},
size: size.0,
})
}
stored_level.characters.clear();
for entity in character_list.0.iter() {
if let Ok((transform, color)) = character_query.get(*entity) {
stored_level.characters.push(StoredCharacter {
pos: Vec2 {
x: transform.translation.x,
y: transform.translation.y,
},
color: color.0.into(),
})
}
}
stored_level.absorbing_filters.clear();
for (transform, size, color) in absorbing_filter_query.iter() {
stored_level.absorbing_filters.push(StoredAbsorbingFilter {
pos: Vec2 {
x: transform.translation.x,
y: transform.translation.y,
},
size: size.0,
color: color.0.into(),
})
}
stored_level.rotating_filters.clear();
for (transform, angle) in rotating_filter_query.iter() {
stored_level.rotating_filters.push(StoredRotatingFilter {
pos: Vec2 {
x: transform.translation.x,
y: transform.translation.y,
},
angle: angle.0,
})
}
} else {
return;
}
match std::fs::OpenOptions::new()
.write(true)
.truncate(true)
.open("assets/game.levels.json")
{
Ok(mut file) => {
serde_json::to_writer_pretty(&mut file, stored_levels).unwrap();
println!("Saved!");
}
Err(e) => eprintln!("Error writing levels file: {:?}", e),
}
}
// Systems
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
camera_query: Query<Entity, With<Camera>>,
level_id: Res<crate::game::FirstLevel>,
stored_levels_assets: Res<Assets<StoredLevels>>,
stored_levels_handle: Res<Handle<StoredLevels>>,
asset_server: Res<AssetServer>,
) {
commands
.entity(camera_query.single())
.insert_bundle(PickingCameraBundle::default());
if let Some(stored_level) = stored_levels_assets
.get(&stored_levels_handle)
.unwrap()
.levels
.get(level_id.0 .0 as usize)
{
spawn_stored_level(
&mut commands,
&mut meshes,
&mut materials,
&asset_server,
stored_level,
);
}
}
fn input_control_system(
keyboard_input: Res<Input<KeyCode>>,
level_id: Res<crate::game::FirstLevel>,
mut stored_levels_assets: ResMut<Assets<StoredLevels>>,
stored_levels_handle: Res<Handle<StoredLevels>>,
character_list: Res<CharacterList>,
character_query: Query<(&Transform, &CharacterColor), Without<Platform>>,
platform_query: Query<(&Transform, &Size), With<Platform>>,
absorbing_filter_query: Query<(&Transform, &Size, &AbsorbingFilterColor), Without<Platform>>,
rotating_filter_query: Query<
(&Transform, &RotatingFilterAngle),
(Without<Platform>, Without<CharacterColor>),
>,
) {
if keyboard_input.just_released(KeyCode::S)
&& (keyboard_input.pressed(KeyCode::LControl) || keyboard_input.pressed(KeyCode::RControl))
{
save_level(
&level_id,
&mut stored_levels_assets,
&stored_levels_handle,
&character_list,
&character_query,
&platform_query,
&absorbing_filter_query,
&rotating_filter_query,
);
}
}
fn move_system(
keyboard_input: Res<Input<KeyCode>>,
mut camera_query: Query<&mut Transform, (With<Camera>, Without<Draggable>)>,
mut drag_query: Query<(&mut Transform, &Selection, Option<&End>), With<Draggable>>,
mut drag_end_event: EventWriter<DragEndEvent>,
) {
if keyboard_input.pressed(KeyCode::LControl) || keyboard_input.pressed(KeyCode::RControl) {
let mut transform = camera_query.single_mut();
let drag = Vec3 {
x: (keyboard_input.pressed(KeyCode::Right) as i8
- keyboard_input.pressed(KeyCode::Left) as i8) as _,
y: (keyboard_input.pressed(KeyCode::Up) as i8
- keyboard_input.pressed(KeyCode::Down) as i8) as _,
z: 0.,
} * 8.;
transform.translation += drag;
return;
}
let drag = if keyboard_input.pressed(KeyCode::LShift) || keyboard_input.pressed(KeyCode::RShift)
{
Vec3 {
x: (keyboard_input.pressed(KeyCode::Right) as i8
- keyboard_input.pressed(KeyCode::Left) as i8) as _,
y: (keyboard_input.pressed(KeyCode::Up) as i8
- keyboard_input.pressed(KeyCode::Down) as i8) as _,
z: 0.,
}
} else {
Vec3 {
x: (keyboard_input.just_pressed(KeyCode::Right) as i8
- keyboard_input.just_pressed(KeyCode::Left) as i8) as _,
y: (keyboard_input.just_pressed(KeyCode::Up) as i8
- keyboard_input.just_pressed(KeyCode::Down) as i8) as _,
z: 0.,
}
} * 8.;
if drag != Vec3::ZERO {
for (mut transform, selection, end) in drag_query.iter_mut() {
if selection.selected() {
transform.translation += drag;
if let Some(End(entity)) = end {
drag_end_event.send(DragEndEvent(*entity));
}
}
}
}
}
fn follow_ends_system(
mut meshes: ResMut<Assets<Mesh>>,
mut follower_query: Query<(&mut Transform, &mut Mesh2dHandle, &mut Size, &Ends)>,
end_query: Query<&Transform, Without<Ends>>,
mut drag_end_event: EventReader<DragEndEvent>,
) {
for DragEndEvent(entity) in drag_end_event.iter() {
if let Ok((mut transform, mut mesh, mut size, Ends(end1, end2))) =
follower_query.get_mut(*entity)
{
if let (Ok(end1), Ok(end2)) = (end_query.get(*end1), end_query.get(*end2)) {
transform.translation.x = (end1.translation.x + end2.translation.x) / 2.;
transform.translation.y = (end1.translation.y + end2.translation.y) / 2.;
size.0 = Vec2 {
x: (end2.translation.x - end1.translation.x).abs(),
y: (end2.translation.y - end1.translation.y).abs(),
};
*mesh = meshes
.add(Mesh::from(Quad {
size: size.0,
flip: false,
}))
.into();
}
}
}
}

View file

@ -471,14 +471,13 @@ fn move_camera(
let size: Vec2 = camera.logical_viewport_size().unwrap();
let half_height: f32 = size.y * 0.5;
let mut target_translation = character_transform.translation;
let mut target_translation = character_transform.translation;
// prevent camera from going too low
target_translation.y = target_translation.y.max(half_height - MARGIN);
camera_transform.translation = camera_transform.translation.lerp(
target_translation,
time.delta_seconds() * FOLLOW_SPEED,
);
camera_transform.translation = camera_transform
.translation
.lerp(target_translation, time.delta_seconds() * FOLLOW_SPEED);
// always make sure that camera is away from the object in order to render them
camera_transform.translation.z = 999.0;

View file

@ -1,5 +1,7 @@
#![allow(clippy::too_many_arguments)]
pub use stored::*;
use crate::game::*;
use bevy::{prelude::*, reflect::TypeUuid};
@ -55,60 +57,6 @@ pub fn post_setup_level(
}
}
#[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>,
@ -179,3 +127,61 @@ pub fn spawn_stored_level(
.insert(Level);
}
}
pub mod stored {
use super::*;
#[derive(Deserialize, Serialize, TypeUuid)]
#[uuid = "1fbba930-644b-0d62-2514-4b302b945327"]
pub struct StoredLevels {
pub 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,
}
}

View file

@ -1,5 +1,9 @@
#![allow(clippy::too_many_arguments)]
#[cfg(not(target_arch = "wasm32"))]
mod audio;
#[cfg(not(target_arch = "wasm32"))]
mod editor;
mod filters;
mod game;
mod levels;
@ -7,6 +11,7 @@ mod menu;
mod particle_effect;
use bevy::{
asset::{Asset, HandleId, LoadState},
prelude::*,
window::{WindowId, WindowMode},
};
@ -15,9 +20,22 @@ use bevy_rapier2d::prelude::*;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
enum AppState {
Loading,
Menu,
Game,
Win,
Editor,
}
struct UseEditor(bool);
struct LoadingAssets(Vec<HandleId>);
impl LoadingAssets {
fn add<T: Asset>(&mut self, handle: Handle<T>) -> Handle<T> {
self.0.push(handle.id);
handle
}
}
fn main() {
@ -28,32 +46,45 @@ fn main() {
std::thread::spawn(move || audio::setup(audio_event_receiver));
#[cfg(not(target_arch = "wasm32"))]
let first_level = game::LevelId(
std::env::args()
.nth(1)
.map_or(0, |s| s.parse().unwrap_or(0)),
);
let (first_level, use_editor) = {
let mut args = std::env::args().skip(1);
(
game::LevelId(args.next().map_or(0, |s| s.parse().unwrap_or(0))),
args.next().map_or(false, |s| s == "e"),
)
};
#[cfg(target_arch = "wasm32")]
let first_level = game::LevelId(0);
let (first_level, use_editor) = (game::LevelId(0), false);
App::new()
.insert_resource(Msaa { samples: 4 })
let mut app = App::new();
app.insert_resource(Msaa { samples: 4 })
.insert_resource(audio_event_sender)
.add_state(AppState::Menu)
.insert_resource(UseEditor(use_editor))
.add_state(AppState::Loading)
.insert_resource(game::FirstLevel(first_level))
.insert_resource(ClearColor(Color::BLACK))
.add_plugins(DefaultPlugins)
//.add_plugin(RapierDebugRenderPlugin::default())
//.add_plugin(bevy_inspector_egui::WorldInspectorPlugin::new())
.add_plugin(JsonAssetPlugin::<levels::StoredLevels>::new(&[
"levels.json",
]))
.add_plugin(RapierPhysicsPlugin::<NoUserData>::pixels_per_meter(64.0))
//.add_plugin(RapierDebugRenderPlugin::default())
.add_plugin(menu::MenuPlugin)
.add_plugin(game::GamePlugin)
.add_plugin(particle_effect::ParticleEffectPlugin)
//.add_plugin(bevy_inspector_egui::WorldInspectorPlugin::new())
.add_system(keyboard_util_system)
]));
if !use_editor {
app.add_plugin(RapierPhysicsPlugin::<NoUserData>::pixels_per_meter(64.0))
.add_plugin(menu::MenuPlugin)
.add_plugin(game::GamePlugin)
.add_plugin(particle_effect::ParticleEffectPlugin);
}
#[cfg(not(target_arch = "wasm32"))]
if use_editor {
app.add_plugin(editor::EditorPlugin);
}
app.add_system(keyboard_util_system)
.add_startup_system(setup)
.add_system_set(SystemSet::on_update(AppState::Loading).with_system(loading_system))
.run();
}
@ -63,9 +94,13 @@ fn setup(mut commands: Commands, mut windows: ResMut<Windows>, asset_server: Res
.unwrap()
.set_title(String::from("Bevyjam"));
commands.insert_resource(asset_server.load::<levels::StoredLevels, _>("game.levels.json"));
commands.insert_resource(asset_server.load::<Font, _>("UacariLegacy-Thin.ttf"));
commands.insert_resource(asset_server.load::<Image, _>("bevy.png"));
let mut assets = LoadingAssets(Vec::new());
commands.insert_resource(
assets.add(asset_server.load::<levels::StoredLevels, _>("game.levels.json")),
);
commands.insert_resource(assets.add(asset_server.load::<Font, _>("UacariLegacy-Thin.ttf")));
commands.insert_resource(assets.add(asset_server.load::<Image, _>("bevy.png")));
commands.insert_resource(assets);
commands.spawn_bundle(Camera2dBundle::default());
commands.insert_resource(AmbientLight {
@ -74,6 +109,23 @@ fn setup(mut commands: Commands, mut windows: ResMut<Windows>, asset_server: Res
});
}
fn loading_system(
asset_server: Res<AssetServer>,
use_editor: Res<UseEditor>,
assets: Res<LoadingAssets>,
mut app_state: ResMut<State<AppState>>,
) {
if asset_server.get_group_load_state(assets.0.iter().copied()) == LoadState::Loaded {
app_state
.replace(if use_editor.0 {
AppState::Editor
} else {
AppState::Menu
})
.ok();
}
}
fn keyboard_util_system(keyboard_input: Res<Input<KeyCode>>, mut windows: ResMut<Windows>) {
#[cfg(not(target_arch = "wasm32"))]
{

View file

@ -44,7 +44,6 @@ impl FromWorld for ParticleMesh {
}
}
#[derive(bevy_inspector_egui::Inspectable)]
pub struct ParticleEffectResource {
pub translation: Vec3,
pub prev_translation: Vec3,