Compare commits

..

11 commits

11 changed files with 763 additions and 63 deletions

18
Cargo.lock generated
View file

@ -3754,14 +3754,13 @@ checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb"
[[package]] [[package]]
name = "wgpu" name = "wgpu"
version = "0.13.1" version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/mockersf/wgpu/?branch=unconditional-clear-workaround#a703a78644bc277f8b93870297bb3734e25f2be9"
checksum = "277e967bf8b7820a76852645a6bce8bbd31c32fda2042e82d8e3ea75fda8892d"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"js-sys", "js-sys",
"log", "log",
"naga", "naga",
"parking_lot 0.12.1", "parking_lot 0.11.2",
"raw-window-handle 0.4.3", "raw-window-handle 0.4.3",
"smallvec", "smallvec",
"wasm-bindgen", "wasm-bindgen",
@ -3775,8 +3774,7 @@ dependencies = [
[[package]] [[package]]
name = "wgpu-core" name = "wgpu-core"
version = "0.13.2" version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/mockersf/wgpu/?branch=unconditional-clear-workaround#a703a78644bc277f8b93870297bb3734e25f2be9"
checksum = "89b92788dec9d0c1bed849a1b83f01b2ee12819bf04a79c90f68e4173f7b5ba2"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"bit-vec", "bit-vec",
@ -3787,7 +3785,7 @@ dependencies = [
"fxhash", "fxhash",
"log", "log",
"naga", "naga",
"parking_lot 0.12.1", "parking_lot 0.11.2",
"profiling", "profiling",
"raw-window-handle 0.4.3", "raw-window-handle 0.4.3",
"smallvec", "smallvec",
@ -3800,8 +3798,7 @@ dependencies = [
[[package]] [[package]]
name = "wgpu-hal" name = "wgpu-hal"
version = "0.13.2" version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/mockersf/wgpu/?branch=unconditional-clear-workaround#a703a78644bc277f8b93870297bb3734e25f2be9"
checksum = "20cbdfc3d0637dba3d5536b93adef3d26023a0b96f0e1ee5ee9560a401d9f646"
dependencies = [ dependencies = [
"android_system_properties", "android_system_properties",
"arrayvec", "arrayvec",
@ -3824,7 +3821,7 @@ dependencies = [
"metal", "metal",
"naga", "naga",
"objc", "objc",
"parking_lot 0.12.1", "parking_lot 0.11.2",
"profiling", "profiling",
"range-alloc", "range-alloc",
"raw-window-handle 0.4.3", "raw-window-handle 0.4.3",
@ -3839,8 +3836,7 @@ dependencies = [
[[package]] [[package]]
name = "wgpu-types" name = "wgpu-types"
version = "0.13.2" version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/mockersf/wgpu/?branch=unconditional-clear-workaround#a703a78644bc277f8b93870297bb3734e25f2be9"
checksum = "1f762cbc08e1a51389859cf9c199c7aef544789cf3510889aab12c607f701604"
dependencies = [ dependencies = [
"bitflags", "bitflags",
] ]

View file

@ -28,3 +28,6 @@ cpal = { version = "0.14.0", features = ["wasm-bindgen"] }
[profile.dev.package."*"] [profile.dev.package."*"]
opt-level = 3 opt-level = 3
[patch.crates-io]
wgpu = { git = "https://github.com/mockersf/wgpu/", branch = "unconditional-clear-workaround" }

View file

@ -13,11 +13,13 @@
* more filters * more filters
* despawn black characters * despawn black characters
* despawn character when too far * despawn character when too far
* level design * more levels
* (?) multiplayer * (?) multiplayer
* more audio * more audio
* "jumpable" component to avoid jumping on sensors * "jumpable" component to avoid jumping on sensors
* bug: in level2, move the blue character to win, then reset. The characters are lighter than expected. * bug: in level2, move the blue character to win, then reset. The characters are lighter than expected. (also level 4)
* redshift warning
* itchio test
## Build ## Build
@ -55,6 +57,8 @@ Edit the level `N: u32` with the command `bevyjam <N> e`.
* **Select**: left click to select, click in void to deselect, CTRL+click to select many, CTRL+A to select all * **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 selection**: arrows to move one step, Shift+arrows to move continuously
* **Delete selection**: delete
* **Add objects**: P=platform, C=character, A=absorbing filter, R=rotating filter, M=melty platform
* **Move camera**: CTRL+arrows * **Move camera**: CTRL+arrows
* **Save**: CTRL+S * **Save**: CTRL+S

View file

@ -54,6 +54,7 @@
], ],
"absorbing_filters": [], "absorbing_filters": [],
"rotating_filters": [], "rotating_filters": [],
"melty_platforms": [],
"texts": [ "texts": [
{ {
"pos": [ "pos": [
@ -129,6 +130,7 @@
], ],
"absorbing_filters": [], "absorbing_filters": [],
"rotating_filters": [], "rotating_filters": [],
"melty_platforms": [],
"texts": [ "texts": [
{ {
"pos": [ "pos": [
@ -145,7 +147,7 @@
"characters": [ "characters": [
{ {
"pos": [ "pos": [
-128.0, -160.0,
-192.0 -192.0
], ],
"color": [ "color": [
@ -157,7 +159,7 @@
}, },
{ {
"pos": [ "pos": [
128.0, 160.0,
-192.0 -192.0
], ],
"color": [ "color": [
@ -209,6 +211,7 @@
} }
], ],
"rotating_filters": [], "rotating_filters": [],
"melty_platforms": [],
"texts": [ "texts": [
{ {
"pos": [ "pos": [
@ -216,6 +219,14 @@
0.0 0.0
], ],
"font_size": 32.0, "font_size": 32.0,
"text": "This filter absorbs light."
},
{
"pos": [
0.0,
-64.0
],
"font_size": 32.0,
"text": "Press R to reset." "text": "Press R to reset."
} }
] ]
@ -279,9 +290,10 @@
0.0, 0.0,
-64.0 -64.0
], ],
"angle": 45.0 "angle": 120.0
} }
], ],
"melty_platforms": [],
"texts": [ "texts": [
{ {
"pos": [ "pos": [
@ -293,6 +305,352 @@
} }
] ]
}, },
{
"comment": "Melting platform tutorial",
"characters": [
{
"pos": [
-304.0,
-208.0
],
"color": [
0.7,
0.7,
0.7,
1.0
]
},
{
"pos": [
304.0,
-208.0
],
"color": [
0.3,
0.3,
0.3,
1.0
]
}
],
"platforms": [
{
"pos": [
-304.0,
-256.0
],
"size": [
192.0,
16.0
]
},
{
"pos": [
304.0,
-256.0
],
"size": [
192.0,
16.0
]
}
],
"absorbing_filters": [],
"rotating_filters": [],
"melty_platforms": [
{
"pos": [
0.0,
-256.0
],
"color": [
0.5,
0.5,
0.5,
1.0
]
}
],
"texts": [
{
"pos": [
0.0,
-64.0
],
"font_size": 32.0,
"text": "Too much light\ncause some platforms to melt."
}
]
},
{
"comment": "First puzzle",
"characters": [
{
"pos": [
184.0,
168.0
],
"color": [
0.85,
0.5,
0.0,
1.0
]
},
{
"pos": [
-184.0,
168.0
],
"color": [
0.0,
0.5,
0.1,
1.0
]
},
{
"pos": [
-1376.0,
-184.0
],
"color": [
1.0,
0.0,
0.0,
1.0
]
},
{
"pos": [
-1512.0,
-184.0
],
"color": [
0.0,
0.0,
0.9,
1.0
]
},
{
"pos": [
0.0,
368.0
],
"color": [
0.15,
0.0,
0.5,
1.0
]
}
],
"platforms": [
{
"pos": [
-12.0,
-264.0
],
"size": [
456.0,
16.0
]
},
{
"pos": [
-148.0,
120.0
],
"size": [
200.0,
16.0
]
},
{
"pos": [
148.0,
120.0
],
"size": [
200.0,
16.0
]
},
{
"pos": [
-1336.0,
-256.0
],
"size": [
576.0,
16.0
]
},
{
"pos": [
-240.0,
292.0
],
"size": [
16.0,
328.0
]
},
{
"pos": [
240.0,
292.0
],
"size": [
16.0,
328.0
]
},
{
"pos": [
0.0,
20.0
],
"size": [
176.0,
24.0
]
},
{
"pos": [
-200.0,
60.0
],
"size": [
16.0,
104.0
]
},
{
"pos": [
200.0,
60.0
],
"size": [
16.0,
104.0
]
},
{
"pos": [
0.0,
320.0
],
"size": [
96.0,
16.0
]
}
],
"absorbing_filters": [
{
"pos": [
-1176.0,
-96.0
],
"size": [
16.0,
304.0
],
"color": [
0.0,
0.5,
0.5,
1.0
]
},
{
"pos": [
-140.0,
16.0
],
"size": [
104.0,
16.0
],
"color": [
0.6,
0.0,
0.0,
1.0
]
},
{
"pos": [
140.0,
16.0
],
"size": [
104.0,
16.0
],
"color": [
0.0,
1.0,
0.0,
1.0
]
}
],
"rotating_filters": [],
"melty_platforms": [
{
"pos": [
0.0,
120.0
],
"color": [
0.7,
0.7,
0.0,
1.0
]
},
{
"pos": [
-616.0,
-256.0
],
"color": [
0.45,
0.0,
0.0,
1.0
]
},
{
"pos": [
-856.0,
-256.0
],
"color": [
0.0,
0.0,
0.5,
1.0
]
},
{
"pos": [
-400.0,
-256.0
],
"color": [
0.0,
0.0,
0.6,
1.0
]
}
],
"texts": []
},
{ {
"comment": "Game over", "comment": "Game over",
"characters": [ "characters": [
@ -323,6 +681,7 @@
], ],
"absorbing_filters": [], "absorbing_filters": [],
"rotating_filters": [], "rotating_filters": [],
"melty_platforms": [],
"texts": [ "texts": [
{ {
"pos": [ "pos": [

BIN
assets/melty.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -22,7 +22,9 @@ impl Plugin for EditorPlugin {
SystemSet::on_update(AppState::Editor) SystemSet::on_update(AppState::Editor)
.with_system(move_system) .with_system(move_system)
.with_system(input_control_system) .with_system(input_control_system)
.with_system(follow_ends_system), .with_system(follow_ends_system)
.with_system(remove_system)
.with_system(spawn_system),
); );
} }
} }
@ -61,6 +63,12 @@ struct RotatingFilterAngle(f32);
#[derive(Component)] #[derive(Component)]
struct AbsorbingFilterColor(Color); struct AbsorbingFilterColor(Color);
#[derive(Component)]
struct MeltyPlatformColor(Color);
#[derive(Component)]
struct Removable;
// Bundles // Bundles
#[derive(Bundle)] #[derive(Bundle)]
@ -69,6 +77,9 @@ struct PlatformBundle {
mesh: ColorMesh2dBundle, mesh: ColorMesh2dBundle,
size: Size, size: Size,
platform: Platform, platform: Platform,
#[bundle]
pickable: PickableBundle,
removable: Removable,
} }
#[derive(Bundle)] #[derive(Bundle)]
@ -89,6 +100,7 @@ struct CharacterBundle {
#[bundle] #[bundle]
pickable: PickableBundle, pickable: PickableBundle,
draggable: Draggable, draggable: Draggable,
removable: Removable,
} }
#[derive(Bundle)] #[derive(Bundle)]
@ -97,6 +109,9 @@ struct AbsorbingFilterBundle {
mesh: ColorMesh2dBundle, mesh: ColorMesh2dBundle,
size: Size, size: Size,
color: AbsorbingFilterColor, color: AbsorbingFilterColor,
#[bundle]
pickable: PickableBundle,
removable: Removable,
} }
#[derive(Bundle)] #[derive(Bundle)]
@ -107,6 +122,18 @@ struct RotatingFilterBundle {
#[bundle] #[bundle]
pickable: PickableBundle, pickable: PickableBundle,
draggable: Draggable, draggable: Draggable,
removable: Removable,
}
#[derive(Bundle)]
struct MeltyPlatformBundle {
#[bundle]
mesh: ColorMesh2dBundle,
color: MeltyPlatformColor,
#[bundle]
pickable: PickableBundle,
draggable: Draggable,
removable: Removable,
} }
// Functions // Functions
@ -129,6 +156,8 @@ fn spawn_platform(
}, },
size: Size(size), size: Size(size),
platform: Platform, platform: Platform,
pickable: PickableBundle::default(),
removable: Removable,
}) })
.id(); .id();
let ends = Ends( let ends = Ends(
@ -207,6 +236,7 @@ fn spawn_character(
color: CharacterColor(color), color: CharacterColor(color),
pickable: PickableBundle::default(), pickable: PickableBundle::default(),
draggable: Draggable, draggable: Draggable,
removable: Removable,
}) })
.with_children(|c| { .with_children(|c| {
c.spawn_bundle(Text2dBundle { c.spawn_bundle(Text2dBundle {
@ -245,6 +275,8 @@ fn spawn_absorbing_filter(
}, },
size: Size(size), size: Size(size),
color: AbsorbingFilterColor(color), color: AbsorbingFilterColor(color),
pickable: PickableBundle::default(),
removable: Removable,
}) })
.id(); .id();
let ends = Ends( let ends = Ends(
@ -322,6 +354,7 @@ fn spawn_rotating_filter(
angle: RotatingFilterAngle(angle), angle: RotatingFilterAngle(angle),
pickable: PickableBundle::default(), pickable: PickableBundle::default(),
draggable: Draggable, draggable: Draggable,
removable: Removable,
}) })
.with_children(|c| { .with_children(|c| {
c.spawn_bundle(Text2dBundle { c.spawn_bundle(Text2dBundle {
@ -341,6 +374,52 @@ fn spawn_rotating_filter(
.id() .id()
} }
fn spawn_melty_platform(
commands: &mut Commands,
meshes: &mut ResMut<Assets<Mesh>>,
materials: &mut ResMut<Assets<ColorMaterial>>,
asset_server: &Res<AssetServer>,
transform: Transform,
color: Color,
) -> Entity {
let font = asset_server.get_handle("UacariLegacy-Thin.ttf");
commands
.spawn_bundle(MeltyPlatformBundle {
mesh: ColorMesh2dBundle {
mesh: meshes
.add(Mesh::from(Quad {
size: Vec2 { x: 96., y: 16. },
flip: false,
}))
.into(),
material: materials.add(ColorMaterial::from(color)),
transform,
..default()
},
color: MeltyPlatformColor(color),
pickable: PickableBundle::default(),
draggable: Draggable,
removable: Removable,
})
.with_children(|c| {
c.spawn_bundle(Text2dBundle {
text: Text::from_section(
"MELTY",
TextStyle {
font: font.clone(),
font_size: 16.,
color: Color::BLACK,
},
)
.with_alignment(TextAlignment::CENTER),
transform: Transform::from_xyz(0., 0., 1.),
..Default::default()
});
})
.id()
}
fn spawn_stored_level( fn spawn_stored_level(
commands: &mut Commands, commands: &mut Commands,
meshes: &mut ResMut<Assets<Mesh>>, meshes: &mut ResMut<Assets<Mesh>>,
@ -394,6 +473,17 @@ fn spawn_stored_level(
rotating_filter.angle, rotating_filter.angle,
); );
} }
for melty_platform in stored_level.melty_platforms.iter() {
spawn_melty_platform(
commands,
meshes,
materials,
asset_server,
Transform::from_xyz(melty_platform.pos.x, melty_platform.pos.y, 0.),
melty_platform.color.into(),
);
}
} }
fn save_level( fn save_level(
@ -408,6 +498,10 @@ fn save_level(
(&Transform, &RotatingFilterAngle), (&Transform, &RotatingFilterAngle),
(Without<Platform>, Without<CharacterColor>), (Without<Platform>, Without<CharacterColor>),
>, >,
melty_platform_query: &Query<
(&Transform, &MeltyPlatformColor),
(Without<Platform>, Without<CharacterColor>),
>,
) { ) {
let stored_levels = stored_levels_assets.get_mut(stored_levels_handle).unwrap(); 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) { if let Some(stored_level) = stored_levels.levels.get_mut(level_id.0 .0 as usize) {
@ -457,6 +551,17 @@ fn save_level(
angle: angle.0, angle: angle.0,
}) })
} }
stored_level.melty_platforms.clear();
for (transform, color) in melty_platform_query.iter() {
stored_level.melty_platforms.push(StoredMeltyPlatform {
pos: Vec2 {
x: transform.translation.x,
y: transform.translation.y,
},
color: color.0.into(),
})
}
} else { } else {
return; return;
} }
@ -518,8 +623,12 @@ fn input_control_system(
(&Transform, &RotatingFilterAngle), (&Transform, &RotatingFilterAngle),
(Without<Platform>, Without<CharacterColor>), (Without<Platform>, Without<CharacterColor>),
>, >,
melty_platform_query: Query<
(&Transform, &MeltyPlatformColor),
(Without<Platform>, Without<CharacterColor>),
>,
) { ) {
if keyboard_input.just_released(KeyCode::S) if keyboard_input.just_pressed(KeyCode::S)
&& (keyboard_input.pressed(KeyCode::LControl) || keyboard_input.pressed(KeyCode::RControl)) && (keyboard_input.pressed(KeyCode::LControl) || keyboard_input.pressed(KeyCode::RControl))
{ {
save_level( save_level(
@ -531,6 +640,7 @@ fn input_control_system(
&platform_query, &platform_query,
&absorbing_filter_query, &absorbing_filter_query,
&rotating_filter_query, &rotating_filter_query,
&melty_platform_query,
); );
} }
} }
@ -584,6 +694,96 @@ fn move_system(
} }
} }
fn remove_system(
mut commands: Commands,
keyboard_input: Res<Input<KeyCode>>,
mut removable_query: Query<(Entity, &Selection, Option<&Ends>), With<Removable>>,
) {
if keyboard_input.just_released(KeyCode::Delete) {
for (entity, selection, ends) in removable_query.iter_mut() {
if selection.selected() {
commands.entity(entity).despawn_recursive();
if let Some(ends) = ends {
commands.entity(ends.0).despawn_recursive();
commands.entity(ends.1).despawn_recursive();
}
}
}
}
}
fn spawn_system(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
asset_server: Res<AssetServer>,
camera_query: Query<&Transform, With<Camera>>,
keyboard_input: Res<Input<KeyCode>>,
mut character_list: ResMut<CharacterList>,
) {
if keyboard_input.pressed(KeyCode::LControl)
|| keyboard_input.pressed(KeyCode::RControl)
|| keyboard_input.pressed(KeyCode::LAlt)
|| keyboard_input.pressed(KeyCode::RAlt)
{
return;
}
let camera_pos = camera_query.single().translation;
if keyboard_input.just_released(KeyCode::P) {
spawn_platform(
&mut commands,
&mut meshes,
&mut materials,
Transform::from_xyz(camera_pos.x, camera_pos.y, 0.),
Vec2 { x: 96., y: 16. },
);
}
if keyboard_input.just_released(KeyCode::C) {
let index = character_list.0.len();
character_list.0.push(spawn_character(
&mut commands,
&mut meshes,
&mut materials,
&asset_server,
Transform::from_xyz(camera_pos.x, camera_pos.y, 0.),
Color::RED,
index,
));
}
if keyboard_input.just_released(KeyCode::A) {
spawn_absorbing_filter(
&mut commands,
&mut meshes,
&mut materials,
Transform::from_xyz(camera_pos.x, camera_pos.y, 0.),
Vec2 { x: 16., y: 96. },
Color::RED,
);
}
if keyboard_input.just_released(KeyCode::R) {
spawn_rotating_filter(
&mut commands,
&mut meshes,
&mut materials,
&asset_server,
Transform::from_xyz(camera_pos.x, camera_pos.y, 0.),
90.,
);
}
if keyboard_input.just_released(KeyCode::M) {
spawn_melty_platform(
&mut commands,
&mut meshes,
&mut materials,
&asset_server,
Transform::from_xyz(camera_pos.x, camera_pos.y, 0.),
Color::RED,
);
}
}
fn follow_ends_system( fn follow_ends_system(
mut meshes: ResMut<Assets<Mesh>>, mut meshes: ResMut<Assets<Mesh>>,
mut follower_query: Query<(&mut Transform, &mut Mesh2dHandle, &mut Size, &Ends)>, mut follower_query: Query<(&mut Transform, &mut Mesh2dHandle, &mut Size, &Ends)>,

View file

@ -13,6 +13,7 @@ use bevy::{
}; };
use bevy_rapier2d::prelude::*; use bevy_rapier2d::prelude::*;
use rapier2d::geometry::CollisionEventFlags; use rapier2d::geometry::CollisionEventFlags;
use std::collections::BTreeSet;
pub enum AudioMsg { pub enum AudioMsg {
Color([f32; 3]), Color([f32; 3]),
@ -33,6 +34,7 @@ impl Plugin for GamePlugin {
app.add_event::<LevelStartupEvent>() app.add_event::<LevelStartupEvent>()
.init_resource::<CharacterMeshes>() .init_resource::<CharacterMeshes>()
.insert_resource(CurrentLevel(None)) .insert_resource(CurrentLevel(None))
.init_resource::<CharacterList>()
.add_system_set(SystemSet::on_enter(AppState::Game).with_system(setup)) .add_system_set(SystemSet::on_enter(AppState::Game).with_system(setup))
.add_system_set(SystemSet::on_enter(AppState::Win).with_system(win_setup)) .add_system_set(SystemSet::on_enter(AppState::Win).with_system(win_setup))
.add_system_set( .add_system_set(
@ -52,7 +54,8 @@ impl Plugin for GamePlugin {
.with_system(player_movement_system) .with_system(player_movement_system)
.with_system(level_keyboard_system) .with_system(level_keyboard_system)
.with_system(move_camera) .with_system(move_camera)
.with_system(character_particle_effect_system), .with_system(character_particle_effect_system)
.with_system(move_win_text_system),
) )
.add_system_to_stage(CoreStage::PostUpdate, collision_event_system); .add_system_to_stage(CoreStage::PostUpdate, collision_event_system);
} }
@ -66,6 +69,9 @@ pub struct LevelStartupEvent;
pub struct CurrentLevel(pub Option<LevelId>); pub struct CurrentLevel(pub Option<LevelId>);
#[derive(Default)]
pub struct CharacterList(pub BTreeSet<Entity>);
pub struct CharacterMeshes { pub struct CharacterMeshes {
square: Mesh2dHandle, square: Mesh2dHandle,
} }
@ -98,6 +104,12 @@ pub struct Player;
#[derive(Component)] #[derive(Component)]
pub struct CollisionCount(usize); pub struct CollisionCount(usize);
#[derive(Component)]
pub struct Melty(pub Color);
#[derive(Component)]
pub struct WinText;
// Systems // Systems
fn setup( fn setup(
@ -118,19 +130,29 @@ pub fn spawn_characters<I: IntoIterator<Item = (Transform, Color)>>(
character_meshes: &Res<CharacterMeshes>, character_meshes: &Res<CharacterMeshes>,
materials: &mut ResMut<Assets<ColorMaterial>>, materials: &mut ResMut<Assets<ColorMaterial>>,
audio: &Res<crossbeam_channel::Sender<AudioMsg>>, audio: &Res<crossbeam_channel::Sender<AudioMsg>>,
character_list: &mut ResMut<CharacterList>,
characters: I, characters: I,
) { ) {
const Z_INCREMENT: f32 = 0.01;
let mut curr_z: f32 = Z_INCREMENT;
for (i, (transform, color)) in characters.into_iter().enumerate() { for (i, (transform, color)) in characters.into_iter().enumerate() {
spawn_character( spawn_character(
commands, commands,
character_meshes, character_meshes,
materials, materials,
audio, audio,
transform, character_list,
{
let mut new_transform: Transform = transform;
new_transform.translation.z = curr_z;
new_transform
},
color, color,
i == 0, i == 0,
); );
curr_z += Z_INCREMENT;
} }
} }
@ -139,6 +161,7 @@ pub fn spawn_character(
character_meshes: &Res<CharacterMeshes>, character_meshes: &Res<CharacterMeshes>,
materials: &mut ResMut<Assets<ColorMaterial>>, materials: &mut ResMut<Assets<ColorMaterial>>,
audio: &Res<crossbeam_channel::Sender<AudioMsg>>, audio: &Res<crossbeam_channel::Sender<AudioMsg>>,
character_list: &mut ResMut<CharacterList>,
mut transform: Transform, mut transform: Transform,
color: Color, color: Color,
is_player: bool, is_player: bool,
@ -178,6 +201,8 @@ pub fn spawn_character(
.insert(CollisionCount(0)); .insert(CollisionCount(0));
}); });
character_list.0.insert(entity_commands.id());
if is_player { if is_player {
entity_commands.insert(Player); entity_commands.insert(Player);
audio audio
@ -217,6 +242,39 @@ pub fn spawn_platform(
.insert(Level); .insert(Level);
} }
pub fn spawn_melty_platform(
commands: &mut Commands,
meshes: &mut ResMut<Assets<Mesh>>,
materials: &mut ResMut<Assets<ColorMaterial>>,
asset_server: &Res<AssetServer>,
transform: Transform,
color: Color,
) {
commands
.spawn_bundle(ColorMesh2dBundle {
mesh: meshes
.add(Mesh::from(Quad {
size: Vec2 { x: 96., y: 16. },
flip: false,
}))
.into(),
material: materials.add(ColorMaterial::from(color)),
transform,
..default()
})
.insert(Collider::cuboid(48., 8.))
.insert(Melty(color))
.insert(Level)
.with_children(|c| {
c.spawn_bundle(SpriteBundle {
texture: asset_server.get_handle("melty.png"),
transform: Transform::from_xyz(0., 0., 0.5),
..default()
});
});
}
fn collision_event_system( fn collision_event_system(
mut commands: Commands, mut commands: Commands,
character_meshes: Res<CharacterMeshes>, character_meshes: Res<CharacterMeshes>,
@ -229,9 +287,11 @@ fn collision_event_system(
Option<&Player>, Option<&Player>,
)>, )>,
pass_through_filter_query: Query<&PassThroughFilter>, pass_through_filter_query: Query<&PassThroughFilter>,
melty_query: Query<&Melty>,
mut collision_counter_query: Query<&mut CollisionCount>, mut collision_counter_query: Query<&mut CollisionCount>,
mut app_state: ResMut<State<AppState>>, mut app_state: ResMut<State<AppState>>,
audio: Res<crossbeam_channel::Sender<AudioMsg>>, audio: Res<crossbeam_channel::Sender<AudioMsg>>,
mut character_list: ResMut<CharacterList>,
) { ) {
for collision_event in collision_events.iter() { for collision_event in collision_events.iter() {
match collision_event { match collision_event {
@ -242,6 +302,8 @@ fn collision_event_system(
Ok((c2_color, c2_transform, _c2_material, c2_player)), Ok((c2_color, c2_transform, _c2_material, c2_player)),
) = (character_query.get(*e1), character_query.get(*e2)) ) = (character_query.get(*e1), character_query.get(*e2))
{ {
character_list.0.remove(e1);
character_list.0.remove(e2);
commands.entity(*e1).despawn_recursive(); commands.entity(*e1).despawn_recursive();
commands.entity(*e2).despawn_recursive(); commands.entity(*e2).despawn_recursive();
@ -249,8 +311,7 @@ fn collision_event_system(
.clamp(Vec4::ZERO, Vec4::ONE); .clamp(Vec4::ZERO, Vec4::ONE);
// If color approximately white // If color approximately white
if app_state.current() == &AppState::Game if app_state.current() == &AppState::Game && new_color.min_element() >= 0.9
&& 4. - new_color.length_squared() < 0.1
{ {
app_state.replace(AppState::Win).ok(); app_state.replace(AppState::Win).ok();
} }
@ -261,6 +322,7 @@ fn collision_event_system(
&character_meshes, &character_meshes,
&mut materials, &mut materials,
&audio, &audio,
&mut character_list,
if c1_player.is_some() { if c1_player.is_some() {
*c1_transform *c1_transform
} else if c2_player.is_some() { } else if c2_player.is_some() {
@ -275,6 +337,18 @@ fn collision_event_system(
); );
audio.send(AudioMsg::Fusion).ok(); audio.send(AudioMsg::Fusion).ok();
} else if let (Ok((c_color, _c_transform, _c_material, _c_player)), Ok(melty)) =
(character_query.get_mut(*e1), melty_query.get(*e2))
{
if (Vec4::from(melty.0) - Vec4::from(c_color.0)).max_element() <= 0. {
commands.entity(*e2).despawn_recursive();
}
} else if let (Ok((c_color, _c_transform, _c_material, _c_player)), Ok(melty)) =
(character_query.get_mut(*e2), melty_query.get(*e1))
{
if (Vec4::from(melty.0) - Vec4::from(c_color.0)).max_element() <= 0. {
commands.entity(*e1).despawn_recursive();
}
} }
} else if *flags == CollisionEventFlags::SENSOR { } else if *flags == CollisionEventFlags::SENSOR {
if let (Ok((mut c_color, _c_transform, mut c_material, c_player)), Ok(filter)) = ( if let (Ok((mut c_color, _c_transform, mut c_material, c_player)), Ok(filter)) = (
@ -292,6 +366,7 @@ fn collision_event_system(
c_color.0.b(), c_color.0.b(),
])) ]))
.ok(); .ok();
audio.send(AudioMsg::Switch).ok();
} }
} else if let ( } else if let (
Ok((mut c_color, _c_transform, mut c_material, c_player)), Ok((mut c_color, _c_transform, mut c_material, c_player)),
@ -311,6 +386,7 @@ fn collision_event_system(
c_color.0.b(), c_color.0.b(),
])) ]))
.ok(); .ok();
audio.send(AudioMsg::Switch).ok();
} }
} else if let (Ok(mut collision_count), Err(_)) = ( } else if let (Ok(mut collision_count), Err(_)) = (
collision_counter_query.get_mut(*e1), collision_counter_query.get_mut(*e1),
@ -350,41 +426,32 @@ fn change_character_system(
keyboard_input: Res<Input<KeyCode>>, keyboard_input: Res<Input<KeyCode>>,
characters: Query<(Entity, &CharacterColor, Option<&Player>)>, characters: Query<(Entity, &CharacterColor, Option<&Player>)>,
audio: Res<crossbeam_channel::Sender<AudioMsg>>, audio: Res<crossbeam_channel::Sender<AudioMsg>>,
character_list: Res<CharacterList>,
) { ) {
if !keyboard_input.just_pressed(KeyCode::Tab) { if !keyboard_input.just_pressed(KeyCode::Tab) {
return; return;
} }
let mut player_idx: usize = 0; if let Some((player_entity, _color, _)) = characters
let mut player_count: usize = 0; .iter()
.find(|(_entity, _color, player)| player.is_some())
// find player idx .or_else(|| characters.iter().next())
for (_entity, _color, player) in characters.iter() { {
if player.is_some() { commands.entity(player_entity).remove::<Player>();
player_idx = player_count; if let Some(new_player_entity) = character_list
} .0
player_count += 1; .range(player_entity..)
} .nth(1)
.or_else(|| character_list.0.iter().next())
// calculate next player index {
let next_player_idx = (player_idx + 1) % player_count; commands.entity(*new_player_entity).insert(Player);
player_count = 0; if let Ok((_entity, color, _player)) = characters.get(*new_player_entity) {
// exchange `Player` component from old `player_idx` to new `next_player_idx`
for (entity, color, _player) in characters.iter() {
if player_count == player_idx {
commands.entity(entity).remove::<Player>();
}
if player_count == next_player_idx {
commands.entity(entity).insert(Player);
audio audio
.send(AudioMsg::Color([color.0.r(), color.0.g(), color.0.b()])) .send(AudioMsg::Color([color.0.r(), color.0.g(), color.0.b()]))
.ok(); .ok();
audio.send(AudioMsg::Switch).ok(); audio.send(AudioMsg::Switch).ok();
} }
}
player_count += 1;
} }
} }
@ -440,7 +507,8 @@ fn win_setup(
transform: Transform::from_xyz(0., 0., 3.), transform: Transform::from_xyz(0., 0., 3.),
..default() ..default()
}) })
.insert(Level); .insert(Level)
.insert(WinText);
commands commands
.spawn_bundle(Text2dBundle { .spawn_bundle(Text2dBundle {
text: Text::from_section( text: Text::from_section(
@ -455,7 +523,8 @@ fn win_setup(
transform: Transform::from_xyz(0., 0., 4.), transform: Transform::from_xyz(0., 0., 4.),
..Default::default() ..Default::default()
}) })
.insert(Level); .insert(Level)
.insert(WinText);
} }
fn move_camera( fn move_camera(
@ -484,12 +553,24 @@ fn move_camera(
} }
} }
fn move_win_text_system(
camera_query: Query<&Transform, With<Camera>>,
mut win_text_query: Query<&mut Transform, (With<WinText>, Without<Camera>)>,
) {
let camera_pos = camera_query.single();
for mut pos in win_text_query.iter_mut() {
pos.translation.x = camera_pos.translation.x;
pos.translation.y = camera_pos.translation.y;
}
}
fn level_keyboard_system( fn level_keyboard_system(
mut commands: Commands, mut commands: Commands,
mut current_level: ResMut<CurrentLevel>, mut current_level: ResMut<CurrentLevel>,
mut level_startup_event: EventWriter<LevelStartupEvent>, mut level_startup_event: EventWriter<LevelStartupEvent>,
mut camera_query: Query<&mut Transform, With<Camera>>, mut camera_query: Query<&mut Transform, With<Camera>>,
keyboard_input: Res<Input<KeyCode>>, keyboard_input: Res<Input<KeyCode>>,
mut character_list: ResMut<CharacterList>,
level_query: Query<Entity, With<Level>>, level_query: Query<Entity, With<Level>>,
mut app_state: ResMut<State<AppState>>, mut app_state: ResMut<State<AppState>>,
) { ) {
@ -501,6 +582,7 @@ fn level_keyboard_system(
} }
if keyboard_input.just_pressed(KeyCode::R) { if keyboard_input.just_pressed(KeyCode::R) {
character_list.0.clear();
for entity in level_query.iter() { for entity in level_query.iter() {
commands.entity(entity).despawn_recursive(); commands.entity(entity).despawn_recursive();
} }

View file

@ -16,7 +16,12 @@ pub fn setup_level(
level_startup_event.send(LevelStartupEvent); level_startup_event.send(LevelStartupEvent);
} }
pub fn despawn_level(mut commands: Commands, level_query: Query<Entity, With<Level>>) { pub fn despawn_level(
mut commands: Commands,
mut character_list: ResMut<CharacterList>,
level_query: Query<Entity, With<Level>>,
) {
character_list.0.clear();
for entity in level_query.iter() { for entity in level_query.iter() {
commands.entity(entity).despawn_recursive(); commands.entity(entity).despawn_recursive();
} }
@ -32,6 +37,7 @@ 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>>,
mut character_list: ResMut<CharacterList>,
stored_levels_assets: Res<Assets<StoredLevels>>, stored_levels_assets: Res<Assets<StoredLevels>>,
stored_levels_handle: Res<Handle<StoredLevels>>, stored_levels_handle: Res<Handle<StoredLevels>>,
) { ) {
@ -50,6 +56,7 @@ pub fn post_setup_level(
&mut materials, &mut materials,
&asset_server, &asset_server,
&audio, &audio,
&mut character_list,
stored_level, stored_level,
); );
} }
@ -64,6 +71,7 @@ pub fn spawn_stored_level(
materials: &mut ResMut<Assets<ColorMaterial>>, materials: &mut ResMut<Assets<ColorMaterial>>,
asset_server: &Res<AssetServer>, asset_server: &Res<AssetServer>,
audio: &Res<crossbeam_channel::Sender<AudioMsg>>, audio: &Res<crossbeam_channel::Sender<AudioMsg>>,
character_list: &mut ResMut<CharacterList>,
stored_level: &StoredLevel, stored_level: &StoredLevel,
) { ) {
@ -84,6 +92,7 @@ pub fn spawn_stored_level(
character_meshes, character_meshes,
materials, materials,
audio, audio,
character_list,
stored_level.characters.iter().map(|character| { stored_level.characters.iter().map(|character| {
( (
Transform::from_xyz(character.pos.x, character.pos.y, 0.), Transform::from_xyz(character.pos.x, character.pos.y, 0.),
@ -109,6 +118,16 @@ pub fn spawn_stored_level(
rotating_filter.angle, rotating_filter.angle,
); );
} }
for melty_platform in stored_level.melty_platforms.iter() {
spawn_melty_platform(
commands,
meshes,
materials,
asset_server,
Transform::from_xyz(melty_platform.pos.x, melty_platform.pos.y, 2.),
melty_platform.color.into(),
);
}
for text in stored_level.texts.iter() { for text in stored_level.texts.iter() {
commands commands
.spawn_bundle(Text2dBundle { .spawn_bundle(Text2dBundle {
@ -145,6 +164,7 @@ pub mod stored {
pub platforms: Vec<StoredPlatform>, pub platforms: Vec<StoredPlatform>,
pub absorbing_filters: Vec<StoredAbsorbingFilter>, pub absorbing_filters: Vec<StoredAbsorbingFilter>,
pub rotating_filters: Vec<StoredRotatingFilter>, pub rotating_filters: Vec<StoredRotatingFilter>,
pub melty_platforms: Vec<StoredMeltyPlatform>,
pub texts: Vec<StoredText>, pub texts: Vec<StoredText>,
} }
@ -177,6 +197,13 @@ pub mod stored {
pub angle: f32, pub angle: f32,
} }
#[derive(Deserialize, Serialize, TypeUuid)]
#[uuid = "cb0773ef-eca6-9b96-dcba-f4240ebdcf40"]
pub struct StoredMeltyPlatform {
pub pos: Vec2,
pub color: Vec4,
}
#[derive(Deserialize, Serialize, TypeUuid)] #[derive(Deserialize, Serialize, TypeUuid)]
#[uuid = "72f6321a-f01f-6eea-9b17-3159837a2fd3"] #[uuid = "72f6321a-f01f-6eea-9b17-3159837a2fd3"]
pub struct StoredText { pub struct StoredText {

View file

@ -99,7 +99,10 @@ fn setup(mut commands: Commands, mut windows: ResMut<Windows>, asset_server: Res
assets.add(asset_server.load::<levels::StoredLevels, _>("game.levels.json")), 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::<Font, _>("UacariLegacy-Thin.ttf")));
commands.insert_resource(assets.add(asset_server.load::<Image, _>("bevy.png"))); commands.insert_resource([
assets.add(asset_server.load::<Image, _>("bevy.png")),
assets.add(asset_server.load("melty.png")),
]);
commands.insert_resource(assets); commands.insert_resource(assets);
commands.spawn_bundle(Camera2dBundle::default()); commands.spawn_bundle(Camera2dBundle::default());

View file

@ -20,6 +20,22 @@ impl Plugin for MenuPlugin {
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
let font = asset_server.get_handle("UacariLegacy-Thin.ttf"); let font = asset_server.get_handle("UacariLegacy-Thin.ttf");
#[cfg(target_arch = "wasm32")]
commands
.spawn_bundle(Text2dBundle {
text: Text::from_section(
"Note:\nAudio is NOT available in the WASM build.",
TextStyle {
font: font.clone(),
font_size: 24.0,
color: Color::rgba(1., 0.4, 0.4, 1.),
},
)
.with_alignment(TextAlignment::CENTER),
transform: Transform::from_xyz(0., -128.0, 0.),
..Default::default()
})
.insert(Menu);
commands commands
.spawn_bundle(Text2dBundle { .spawn_bundle(Text2dBundle {
text: Text::from_section( text: Text::from_section(

View file

@ -1,5 +1,5 @@
use bevy::{prelude::*, sprite::Mesh2dHandle}; use bevy::{prelude::*, sprite::Mesh2dHandle};
use rand::Rng; use rand::{rngs::ThreadRng, Rng};
use rand_distr::{Distribution, UnitCircle}; use rand_distr::{Distribution, UnitCircle};
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
@ -68,15 +68,20 @@ pub struct ParticleComponent {
} }
impl ParticleComponent { impl ParticleComponent {
pub fn new() -> Self { pub fn new(rng: &mut ThreadRng) -> Self {
let mut particle_component: Self = Self::default(); let mut particle_component: Self = Self::default();
particle_component.randomize_velocity(MIN_VELOCITY, MAX_VELOCITY); particle_component.randomize_velocity(rng, MIN_VELOCITY, MAX_VELOCITY);
particle_component particle_component
} }
pub fn randomize_velocity(&mut self, min_velocity: f32, max_velocity: f32) { pub fn randomize_velocity(
let random_direction: [f32; 2] = UnitCircle.sample(&mut rand::thread_rng()); &mut self,
let random_magnitude: f32 = rand::thread_rng().gen_range(min_velocity..max_velocity); rng: &mut ThreadRng,
min_velocity: f32,
max_velocity: f32,
) {
let random_direction: [f32; 2] = UnitCircle.sample(rng);
let random_magnitude: f32 = rng.gen_range(min_velocity..max_velocity);
self.velocity = Vec3::new(random_direction[0], random_direction[1], 0.0) * random_magnitude; self.velocity = Vec3::new(random_direction[0], random_direction[1], 0.0) * random_magnitude;
} }
} }
@ -87,6 +92,8 @@ fn particle_effect_startup(
particle_mesh: Res<ParticleMesh>, particle_mesh: Res<ParticleMesh>,
mut materials: ResMut<Assets<ColorMaterial>>, mut materials: ResMut<Assets<ColorMaterial>>,
) { ) {
let mut rng = rand::thread_rng();
for _p in 0..POOL_COUNT { for _p in 0..POOL_COUNT {
let color_mesh = ColorMesh2dBundle { let color_mesh = ColorMesh2dBundle {
mesh: particle_mesh.square.clone(), mesh: particle_mesh.square.clone(),
@ -96,7 +103,7 @@ fn particle_effect_startup(
commands commands
.spawn_bundle(color_mesh) .spawn_bundle(color_mesh)
.insert(ParticleComponent::new()); .insert(ParticleComponent::new(&mut rng));
} }
} }
@ -110,6 +117,8 @@ fn particle_effect_system(
mut particle_effect: ResMut<ParticleEffectResource>, mut particle_effect: ResMut<ParticleEffectResource>,
time: Res<Time>, time: Res<Time>,
) { ) {
let mut rng = rand::thread_rng();
let delta_seconds: f32 = f32::max(0.001, time.delta_seconds()); let delta_seconds: f32 = f32::max(0.001, time.delta_seconds());
let delta_position: Vec3 = particle_effect.translation - particle_effect.prev_translation; let delta_position: Vec3 = particle_effect.translation - particle_effect.prev_translation;
// let overall_velocity: Vec3 = delta_position / delta_seconds; // let overall_velocity: Vec3 = delta_position / delta_seconds;
@ -124,7 +133,7 @@ fn particle_effect_system(
.distance_squared(particle_effect.translation); .distance_squared(particle_effect.translation);
if squared_distance > particle_effect.radius_squared { if squared_distance > particle_effect.radius_squared {
transform.translation = particle_effect.translation; transform.translation = particle_effect.translation;
particle_component.randomize_velocity(MIN_VELOCITY, MAX_VELOCITY); particle_component.randomize_velocity(&mut rng, MIN_VELOCITY, MAX_VELOCITY);
} }
if let Some(material) = materials.get_mut(color_material) { if let Some(material) = materials.get_mut(color_material) {
@ -134,5 +143,6 @@ fn particle_effect_system(
/ particle_effect.radius_squared, / particle_effect.radius_squared,
); );
} }
transform.translation.z = 0.005;
} }
} }