Compare commits

..

8 commits

11 changed files with 253 additions and 158 deletions

41
Cargo.lock generated
View file

@ -923,26 +923,6 @@ dependencies = [
"winit", "winit",
] ]
[[package]]
name = "bevyjam"
version = "0.1.0"
dependencies = [
"bevy",
"bevy-inspector-egui",
"bevy_common_assets",
"bevy_mod_picking",
"bevy_rapier2d",
"cpal 0.14.0",
"crossbeam-channel",
"hexodsp",
"rand",
"rand_distr",
"rapier2d",
"serde",
"serde_json",
"ticktock",
]
[[package]] [[package]]
name = "bindgen" name = "bindgen"
version = "0.59.2" version = "0.59.2"
@ -1333,6 +1313,7 @@ dependencies = [
"parking_lot 0.12.1", "parking_lot 0.12.1",
"stdweb", "stdweb",
"thiserror", "thiserror",
"wasm-bindgen",
"web-sys", "web-sys",
"windows", "windows",
] ]
@ -2246,6 +2227,26 @@ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
] ]
[[package]]
name = "lux-synthese"
version = "0.1.0"
dependencies = [
"bevy",
"bevy-inspector-egui",
"bevy_common_assets",
"bevy_mod_picking",
"bevy_rapier2d",
"cpal 0.14.0",
"crossbeam-channel",
"hexodsp",
"rand",
"rand_distr",
"rapier2d",
"serde",
"serde_json",
"ticktock",
]
[[package]] [[package]]
name = "mach" name = "mach"
version = "0.3.2" version = "0.3.2"

View file

@ -1,5 +1,5 @@
[package] [package]
name = "bevyjam" name = "lux-synthese"
version = "0.1.0" version = "0.1.0"
authors = ["tuxmain <tuxmain@zettascript.org>"] authors = ["tuxmain <tuxmain@zettascript.org>"]
license = "AGPL-3.0-only" license = "AGPL-3.0-only"
@ -24,6 +24,7 @@ serde_json = "1.0.85"
ticktock = "0.8.0" ticktock = "0.8.0"
[target."cfg(target_arch = \"wasm32\")".dependencies] [target."cfg(target_arch = \"wasm32\")".dependencies]
cpal = { version = "0.14.0", features = ["wasm-bindgen"] }
[profile.dev.package."*"] [profile.dev.package."*"]
opt-level = 3 opt-level = 3

View file

@ -1,4 +1,4 @@
# Bevyjam # Lux synthesĕ
## Controls ## Controls
@ -9,17 +9,14 @@
## TODO ## TODO
* name
* more filters * more filters
* despawn black characters * despawn black characters
* despawn character when too far * despawn character when too far
* more levels * more levels
* (?) multiplayer * (?) multiplayer
* more audio * more audio
* "jumpable" component to avoid jumping on sensors
* bug: in level2, move the blue character to win, then reset. The characters are lighter than expected. (also level 4) * bug: in level2, move the blue character to win, then reset. The characters are lighter than expected. (also level 4)
* redshift warning * redshift warning
* itchio test
## Build ## Build
@ -66,6 +63,6 @@ Edit the level `N: u32` with the command `bevyjam <N> e`.
GNU AGPL v3, CopyLeft 2022 Pascal Engélibert, Nixon Cheng GNU AGPL v3, CopyLeft 2022 Pascal Engélibert, Nixon Cheng
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License. _Lux synthesĕ_ is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. _Lux synthesĕ_ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with this program. If not, see https://www.gnu.org/licenses/. You should have received a copy of the GNU Affero General Public License along with _Lux synthesĕ_. If not, see https://www.gnu.org/licenses/.

12
build-itchio.sh Normal file
View file

@ -0,0 +1,12 @@
sh build-wasm.sh || exit 1
mkdir -p target/itchio/wasm/target
mkdir -p target/itchio/wasm/assets
cp assets/* target/itchio/wasm/assets/
cp index.html target/itchio/wasm/
cp target/lux-synthese.js target/itchio/wasm/target/
cp target/lux-synthese_bg.wasm target/itchio/wasm/target/
cd target/itchio/wasm
zip -r ../wasm.zip .

View file

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

BIN
cover.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
cover.xcf Normal file

Binary file not shown.

View file

@ -2,12 +2,35 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"/> <meta charset="UTF-8"/>
<title>Bevyjam</title> <title>Lux synthesĕ</title>
</head> </head>
<body> <body>
<script type="module"> <script type="module">
import init from './target/bevyjam.js' import init from './target/bevyjam.js'
init() init()
</script> </script>
<div>
<h1>Lux synthesĕ</h1>
<p>
<strong>Note</strong>: audio does not work in the WASM build.
</p>
<h2>Controls</h2>
<ul>
<li><strong>Move</strong>: Arrows</li>
<li><strong>Switch</strong>: Tab</li>
<li><strong>Level up</strong>: Enter</li>
<li><strong>Reset</strong>: R</li>
</ul>
<h2>Source</h2>
<p>
The source code of this free software is available in our <a href="https://git.txmn.tk/tuxmain/bevyjam/">Git repository</a>.
</p>
<p>
GNU AGPL v3: CopyLeft 2022 Pascal Engélibert, Nixon Cheng<br/>
<em>Lux synthesĕ</em> is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.<br/>
<em>Lux synthesĕ</em> is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.<br/>
You should have received a copy of the GNU Affero General Public License along with <em>Lux synthesĕ</em>. If not, see <a href="https://www.gnu.org/licenses/">https://www.gnu.org/licenses/</a>.
</p>
</div>
</body> </body>
</html> </html>

View file

@ -57,7 +57,13 @@ impl Plugin for GamePlugin {
.with_system(character_particle_effect_system) .with_system(character_particle_effect_system)
.with_system(move_win_text_system), .with_system(move_win_text_system),
) )
.add_system_to_stage(CoreStage::PostUpdate, collision_event_system); .add_system_to_stage(CoreStage::PostUpdate, char_char_collision_event_system)
.add_system_to_stage(CoreStage::PostUpdate, char_platform_collision_event_system)
// collision event system might remove items, therefore, we should detect platforms first before removing them
.add_system_to_stage(
CoreStage::PostUpdate,
collision_event_system.after(char_platform_collision_event_system),
);
} }
} }
@ -102,7 +108,28 @@ pub struct CharacterColor(pub Color);
pub struct Player; pub struct Player;
#[derive(Component)] #[derive(Component)]
pub struct CollisionCount(usize); pub struct Platform;
#[derive(Component)]
pub struct PlatformCount(usize);
impl PlatformCount {
fn increment(&mut self) {
self.0 += 1;
}
fn decrement(&mut self) {
self.0 = self.0.saturating_sub(1);
}
fn reset(&mut self) {
self.0 = 0;
}
fn is_landed(&self) -> bool {
self.0 != 0
}
}
#[derive(Component)] #[derive(Component)]
pub struct Melty(pub Color); pub struct Melty(pub Color);
@ -190,7 +217,7 @@ pub fn spawn_character(
.insert(Sensor) .insert(Sensor)
.insert(Collider::cuboid(30., 0.5)) .insert(Collider::cuboid(30., 0.5))
.insert(ActiveEvents::COLLISION_EVENTS) .insert(ActiveEvents::COLLISION_EVENTS)
.insert(CollisionCount(0)); .insert(PlatformCount(0));
}); });
character_list.0.insert(entity_commands.id()); character_list.0.insert(entity_commands.id());
@ -231,7 +258,8 @@ pub fn spawn_platform(
..default() ..default()
}) })
.insert(Collider::cuboid(size.x / 2., size.y / 2.)) .insert(Collider::cuboid(size.x / 2., size.y / 2.))
.insert(Level); .insert(Level)
.insert(Platform);
} }
pub fn spawn_melty_platform( pub fn spawn_melty_platform(
@ -258,6 +286,7 @@ pub fn spawn_melty_platform(
.insert(Collider::cuboid(48., 8.)) .insert(Collider::cuboid(48., 8.))
.insert(Melty(color)) .insert(Melty(color))
.insert(Level) .insert(Level)
.insert(Platform)
.with_children(|c| { .with_children(|c| {
c.spawn_bundle(SpriteBundle { c.spawn_bundle(SpriteBundle {
texture: asset_server.get_handle("melty.png"), texture: asset_server.get_handle("melty.png"),
@ -267,9 +296,109 @@ pub fn spawn_melty_platform(
}); });
} }
fn char_char_collision_event_system(
mut commands: Commands,
mut collision_events: EventReader<CollisionEvent>,
character_query: Query<(
&CharacterColor,
&Transform,
Option<&Player>,
)>,
mut character_list: ResMut<CharacterList>,
mut app_state: ResMut<State<AppState>>,
character_meshes: Res<CharacterMeshes>,
audio: Res<crossbeam_channel::Sender<AudioMsg>>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
for collision_event in collision_events.iter() {
if let CollisionEvent::Started(e1, e2, _flags) = collision_event {
if let (
Ok((c1_color, c1_transform, c1_player)),
Ok((c2_color, c2_transform, c2_player)),
) = (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(*e2).despawn_recursive();
let new_color =
(Vec4::from(c1_color.0) + Vec4::from(c2_color.0)).clamp(Vec4::ZERO, Vec4::ONE);
// If color approximately white
if app_state.current() == &AppState::Game && new_color.min_element() >= 0.9 {
app_state.replace(AppState::Win).ok();
}
// position character based on current player location
spawn_character(
&mut commands,
&character_meshes,
&mut materials,
&audio,
&mut character_list,
if c1_player.is_some() {
*c1_transform
} else if c2_player.is_some() {
*c2_transform
} else {
Transform::identity().with_translation(
(c1_transform.translation + c2_transform.translation) * 0.5,
)
},
new_color.into(),
c1_player.is_some() || c2_player.is_some(),
);
audio.send(AudioMsg::Fusion).ok();
}
}
}
}
fn char_platform_collision_event_system(
mut collision_events: EventReader<CollisionEvent>,
mut platform_count_query: Query<&mut PlatformCount>,
platform_query: Query<&Platform>,
) {
// detect platform + player collisions only
for collision_event in collision_events.iter() {
match collision_event {
CollisionEvent::Started(e1, e2, flags) => {
if *flags == CollisionEventFlags::SENSOR {
if let (Ok(mut platform_count), Ok(_)) =
(platform_count_query.get_mut(*e1), platform_query.get(*e2))
{
platform_count.increment();
} else if let (Ok(mut platform_count), Ok(_)) =
(platform_count_query.get_mut(*e2), platform_query.get(*e1))
{
platform_count.increment();
}
}
}
CollisionEvent::Stopped(e1, e2, flags) => {
if *flags == CollisionEventFlags::SENSOR {
if let (Ok(mut platform_count), Ok(_)) =
(platform_count_query.get_mut(*e1), platform_query.get(*e2))
{
platform_count.decrement();
} else if let (Ok(mut platform_count), Ok(_)) =
(platform_count_query.get_mut(*e2), platform_query.get(*e1))
{
platform_count.decrement();
}
}
}
}
}
}
fn collision_event_system( fn collision_event_system(
mut commands: Commands, mut commands: Commands,
character_meshes: Res<CharacterMeshes>,
mut materials: ResMut<Assets<ColorMaterial>>, mut materials: ResMut<Assets<ColorMaterial>>,
mut collision_events: EventReader<CollisionEvent>, mut collision_events: EventReader<CollisionEvent>,
mut character_query: Query<( mut character_query: Query<(
@ -280,131 +409,61 @@ fn collision_event_system(
)>, )>,
pass_through_filter_query: Query<&PassThroughFilter>, pass_through_filter_query: Query<&PassThroughFilter>,
melty_query: Query<&Melty>, melty_query: Query<&Melty>,
mut collision_counter_query: Query<&mut CollisionCount>,
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 { if let CollisionEvent::Started(e1, e2, flags) = collision_event {
CollisionEvent::Started(e1, e2, flags) => { if flags.is_empty() {
if flags.is_empty() { if let (Ok((c_color, _c_transform, _c_material, _c_player)), Ok(melty)) =
if let ( (character_query.get_mut(*e1), melty_query.get(*e2))
Ok((c1_color, c1_transform, _c1_material, c1_player)), {
Ok((c2_color, c2_transform, _c2_material, c2_player)), if (Vec4::from(melty.0) - Vec4::from(c_color.0)).max_element() <= 0. {
) = (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(*e2).despawn_recursive(); commands.entity(*e2).despawn_recursive();
let new_color = (Vec4::from(c1_color.0) + Vec4::from(c2_color.0))
.clamp(Vec4::ZERO, Vec4::ONE);
// If color approximately white
if app_state.current() == &AppState::Game && new_color.min_element() >= 0.9
{
app_state.replace(AppState::Win).ok();
}
// position character based on current player location
spawn_character(
&mut commands,
&character_meshes,
&mut materials,
&audio,
&mut character_list,
if c1_player.is_some() {
*c1_transform
} else if c2_player.is_some() {
*c2_transform
} else {
Transform::identity().with_translation(
(c1_transform.translation + c2_transform.translation) / 2.,
)
},
new_color.into(),
c1_player.is_some() || c2_player.is_some(),
);
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 let (Ok((c_color, _c_transform, _c_material, _c_player)), Ok(melty)) =
if let (Ok((mut c_color, _c_transform, mut c_material, c_player)), Ok(filter)) = ( (character_query.get_mut(*e2), melty_query.get(*e1))
character_query.get_mut(*e1), {
pass_through_filter_query.get(*e2), if (Vec4::from(melty.0) - Vec4::from(c_color.0)).max_element() <= 0. {
) { commands.entity(*e1).despawn_recursive();
c_color.0 = filter.apply(c_color.0);
*c_material = materials.add(ColorMaterial::from(c_color.0));
if c_player.is_some() {
audio
.send(AudioMsg::Color([
c_color.0.r(),
c_color.0.g(),
c_color.0.b(),
]))
.ok();
audio.send(AudioMsg::Switch).ok();
}
} else if let (
Ok((mut c_color, _c_transform, mut c_material, c_player)),
Ok(filter),
) = (
character_query.get_mut(*e2),
pass_through_filter_query.get(*e1),
) {
c_color.0 = filter.apply(c_color.0);
*c_material = materials.add(ColorMaterial::from(c_color.0));
if c_player.is_some() {
audio
.send(AudioMsg::Color([
c_color.0.r(),
c_color.0.g(),
c_color.0.b(),
]))
.ok();
audio.send(AudioMsg::Switch).ok();
}
} else if let (Ok(mut collision_count), Err(_)) = (
collision_counter_query.get_mut(*e1),
character_query.get_mut(*e2),
) {
collision_count.0 += 1;
} else if let (Ok(mut collision_count), Err(_)) = (
collision_counter_query.get_mut(*e2),
character_query.get_mut(*e1),
) {
collision_count.0 += 1;
} }
} }
} } else if *flags == CollisionEventFlags::SENSOR {
CollisionEvent::Stopped(e1, e2, flags) => { if let (Ok((mut c_color, _c_transform, mut c_material, c_player)), Ok(filter)) = (
if *flags == CollisionEventFlags::SENSOR { character_query.get_mut(*e1),
if let (Ok(mut collision_count), Err(_)) = ( pass_through_filter_query.get(*e2),
collision_counter_query.get_mut(*e1), ) {
character_query.get_mut(*e2), c_color.0 = filter.apply(c_color.0);
) { *c_material = materials.add(ColorMaterial::from(c_color.0));
collision_count.0 = collision_count.0.saturating_sub(1);
} else if let (Ok(mut collision_count), Err(_)) = ( if c_player.is_some() {
collision_counter_query.get_mut(*e2), audio
character_query.get_mut(*e1), .send(AudioMsg::Color([
) { c_color.0.r(),
collision_count.0 = collision_count.0.saturating_sub(1); c_color.0.g(),
c_color.0.b(),
]))
.ok();
audio.send(AudioMsg::Switch).ok();
}
} else if let (
Ok((mut c_color, _c_transform, mut c_material, c_player)),
Ok(filter),
) = (
character_query.get_mut(*e2),
pass_through_filter_query.get(*e1),
) {
c_color.0 = filter.apply(c_color.0);
*c_material = materials.add(ColorMaterial::from(c_color.0));
if c_player.is_some() {
audio
.send(AudioMsg::Color([
c_color.0.r(),
c_color.0.g(),
c_color.0.b(),
]))
.ok();
audio.send(AudioMsg::Switch).ok();
} }
} }
} }
@ -450,7 +509,7 @@ fn change_character_system(
fn player_movement_system( fn player_movement_system(
keyboard_input: Res<Input<KeyCode>>, keyboard_input: Res<Input<KeyCode>>,
mut characters: Query<(&mut Velocity, &Children), With<Player>>, mut characters: Query<(&mut Velocity, &Children), With<Player>>,
collision_counter_query: Query<&CollisionCount>, mut platform_count_query: Query<&mut PlatformCount>,
audio: Res<crossbeam_channel::Sender<AudioMsg>>, audio: Res<crossbeam_channel::Sender<AudioMsg>>,
) { ) {
let right_pressed: bool = let right_pressed: bool =
@ -461,11 +520,12 @@ fn player_movement_system(
for (mut velocity, children) in characters.iter_mut() { for (mut velocity, children) in characters.iter_mut() {
velocity.linvel.x = 200. * (right_pressed as i8 - left_pressed as i8) as f32; velocity.linvel.x = 200. * (right_pressed as i8 - left_pressed as i8) as f32;
if keyboard_input.just_pressed(KeyCode::Space) let mut platform_count: Mut<PlatformCount> =
&& collision_counter_query.get(children[0]).unwrap().0 != 0 platform_count_query.get_mut(children[0]).unwrap();
{ if keyboard_input.just_pressed(KeyCode::Space) && platform_count.is_landed() {
audio.send(AudioMsg::Jump).ok(); audio.send(AudioMsg::Jump).ok();
velocity.linvel.y = 700.; velocity.linvel.y = 700.;
platform_count.reset();
} }
} }
} }

View file

@ -39,7 +39,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands commands
.spawn_bundle(Text2dBundle { .spawn_bundle(Text2dBundle {
text: Text::from_section( text: Text::from_section(
"BEVYJAM", "Lux synthesĕ",
TextStyle { TextStyle {
font: font.clone(), font: font.clone(),
font_size: 96.0, font_size: 96.0,

View file

@ -143,5 +143,6 @@ fn particle_effect_system(
/ particle_effect.radius_squared, / particle_effect.radius_squared,
); );
} }
transform.translation.z = 0.005;
} }
} }