Compare commits
12 commits
nixon-main
...
master
Author | SHA1 | Date | |
---|---|---|---|
Pascal Engélibert | c203be7b2a | ||
Pascal Engélibert | e8bd4a9c81 | ||
Pascal Engélibert | 14eb48dcf8 | ||
Pascal Engélibert | 160e881ba7 | ||
Pascal Engélibert | da1a8351ff | ||
Pascal Engélibert | d03308136e | ||
Pascal Engélibert | 53bf4b1aef | ||
Nixon | 32c67d46d5 | ||
Nixon | 492c4238cb | ||
Nixon | 5b22e84952 | ||
Nixon | 7d28ccfb88 | ||
Nixon | dd34c412dd |
680
Cargo.lock
generated
680
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
12
Cargo.toml
12
Cargo.toml
|
@ -6,22 +6,22 @@ license = "AGPL-3.0-only"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bevy = "0.8.1"
|
||||
bevy = { version = "0.8.1", default-features = false, features = ["bevy_asset", "bevy_audio", "bevy_gilrs", "bevy_winit", "render", "png", "vorbis", "x11"] }
|
||||
bevy_common_assets = { version = "0.3.0", features = ["json"] }
|
||||
bevy_rapier2d = "0.16.2"
|
||||
crossbeam-channel = "0.5.6"
|
||||
#crossbeam-channel = "0.5.6"
|
||||
rand = "0.8.5"
|
||||
rand_distr = "0.4.3"
|
||||
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-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 }
|
||||
# cpal = "0.14.0"
|
||||
# hexodsp = { git = "https://github.com/WeirdConstructor/HexoDSP", default-features = false }
|
||||
serde_json = "1.0.85"
|
||||
ticktock = "0.8.0"
|
||||
#ticktock = "0.8.0"
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies]
|
||||
#cpal = { version = "0.14.0", features = ["wasm-bindgen"] }
|
||||
|
|
20
README.md
20
README.md
|
@ -1,7 +1,15 @@
|
|||
# Lux synthesĕ
|
||||
|
||||
Combine colored lights to synthesize white light!
|
||||
|
||||
[Play in browser](https://txmn.tk/projects/lux-synthese/)
|
||||
|
||||
Download: [Linux x86_64](https://txmn.tk/projects/lux-synthese/linux-x86_64.zip), [Windows x86_64](https://txmn.tk/projects/lux-synthese/windows-x86_64.zip)
|
||||
|
||||
In latin, _lux synthesĕ_ means "light by the means of synthesis".
|
||||
|
||||
This game was developped for the [Bevy Jam #2](https://itch.io/jam/bevy-jam-2).
|
||||
|
||||
## Controls
|
||||
|
||||
* **Move**: arrows
|
||||
|
@ -14,12 +22,10 @@
|
|||
## TODO
|
||||
|
||||
* more filters
|
||||
* despawn black characters
|
||||
* despawn character when too far
|
||||
* more levels
|
||||
* (?) multiplayer
|
||||
* more audio
|
||||
* bug: in level2, move the blue character to win, then reset. The characters are lighter than expected. (also level 4)
|
||||
* bug: when reset after win, character colors are wrong
|
||||
* redshift warning
|
||||
|
||||
## Build
|
||||
|
@ -30,8 +36,6 @@ cargo build --release
|
|||
|
||||
### WASM
|
||||
|
||||
**Audio does not work in WASM!**
|
||||
|
||||
```bash
|
||||
rustup target add wasm32-unknown-unknown
|
||||
cargo install wasm-bindgen-cli
|
||||
|
@ -44,16 +48,20 @@ python3 -m http.server
|
|||
|
||||
## Audio
|
||||
|
||||
This game uses [HexoDSP](https://github.com/WeirdConstructor/HexoDSP) for audio synthesis.
|
||||
This game used [HexoDSP](https://github.com/WeirdConstructor/HexoDSP) for audio synthesis.
|
||||
|
||||
The synthetizer matrix can be edited using [HexoSynth](https://github.com/WeirdConstructor/HexoSynth) visual editor.
|
||||
|
||||
However we could not get this working in WASM, hence we used simple playback audio instead for the jam. The concept of procedural audio is not abandonned but it will be for another time.
|
||||
|
||||
## Develop
|
||||
|
||||
Skip to level `N: u32` with the command `bevyjam <N>`.
|
||||
|
||||
Edit the level `N: u32` with the command `bevyjam <N> e`.
|
||||
|
||||
Editor is not available in the WASM build.
|
||||
|
||||
### Editor controls
|
||||
|
||||
* **Select**: left click to select, click in void to deselect, CTRL+click to select many, CTRL+A to select all
|
||||
|
|
|
@ -301,12 +301,12 @@
|
|||
0.0
|
||||
],
|
||||
"font_size": 32.0,
|
||||
"text": "Let's rotate the hue!"
|
||||
"text": "Let's rotate the hue!\nPress Space to jump."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"comment": "Melting platform tutorial",
|
||||
"comment": "Melty platform tutorial",
|
||||
"characters": [
|
||||
{
|
||||
"pos": [
|
||||
|
@ -384,6 +384,389 @@
|
|||
},
|
||||
{
|
||||
"comment": "First puzzle",
|
||||
"characters": [
|
||||
{
|
||||
"pos": [
|
||||
-96.0,
|
||||
472.0
|
||||
],
|
||||
"color": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
-264.0,
|
||||
472.0
|
||||
],
|
||||
"color": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
648.0,
|
||||
600.0
|
||||
],
|
||||
"color": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.7,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
648.0,
|
||||
-8.0
|
||||
],
|
||||
"color": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.3,
|
||||
1.0
|
||||
]
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
{
|
||||
"pos": [
|
||||
-168.0,
|
||||
424.0
|
||||
],
|
||||
"size": [
|
||||
464.0,
|
||||
16.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
432.0,
|
||||
424.0
|
||||
],
|
||||
"size": [
|
||||
352.0,
|
||||
16.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
156.0,
|
||||
152.0
|
||||
],
|
||||
"size": [
|
||||
312.0,
|
||||
16.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
352.0,
|
||||
-208.0
|
||||
],
|
||||
"size": [
|
||||
176.0,
|
||||
16.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
484.0,
|
||||
-56.0
|
||||
],
|
||||
"size": [
|
||||
104.0,
|
||||
16.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
360.0,
|
||||
64.0
|
||||
],
|
||||
"size": [
|
||||
96.0,
|
||||
16.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
64.0,
|
||||
-272.0
|
||||
],
|
||||
"size": [
|
||||
320.0,
|
||||
16.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
600.0,
|
||||
716.0
|
||||
],
|
||||
"size": [
|
||||
16.0,
|
||||
312.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
696.0,
|
||||
412.0
|
||||
],
|
||||
"size": [
|
||||
16.0,
|
||||
920.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
600.0,
|
||||
232.0
|
||||
],
|
||||
"size": [
|
||||
16.0,
|
||||
368.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
600.0,
|
||||
-8.0
|
||||
],
|
||||
"size": [
|
||||
16.0,
|
||||
80.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
576.0,
|
||||
56.0
|
||||
],
|
||||
"size": [
|
||||
32.0,
|
||||
16.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
564.0,
|
||||
40.0
|
||||
],
|
||||
"size": [
|
||||
8.0,
|
||||
16.0
|
||||
]
|
||||
}
|
||||
],
|
||||
"absorbing_filters": [
|
||||
{
|
||||
"pos": [
|
||||
56.0,
|
||||
492.0
|
||||
],
|
||||
"size": [
|
||||
16.0,
|
||||
120.0
|
||||
],
|
||||
"color": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
56.0,
|
||||
628.0
|
||||
],
|
||||
"size": [
|
||||
16.0,
|
||||
152.0
|
||||
],
|
||||
"color": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
264.0,
|
||||
492.0
|
||||
],
|
||||
"size": [
|
||||
16.0,
|
||||
120.0
|
||||
],
|
||||
"color": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
264.0,
|
||||
628.0
|
||||
],
|
||||
"size": [
|
||||
16.0,
|
||||
152.0
|
||||
],
|
||||
"color": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
-48.0,
|
||||
152.0
|
||||
],
|
||||
"size": [
|
||||
96.0,
|
||||
16.0
|
||||
],
|
||||
"color": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
600.0,
|
||||
488.0
|
||||
],
|
||||
"size": [
|
||||
16.0,
|
||||
112.0
|
||||
],
|
||||
"color": [
|
||||
0.0,
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
524.0,
|
||||
556.0
|
||||
],
|
||||
"size": [
|
||||
56.0,
|
||||
8.0
|
||||
],
|
||||
"color": [
|
||||
0.0,
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
]
|
||||
}
|
||||
],
|
||||
"rotating_filters": [
|
||||
{
|
||||
"pos": [
|
||||
-48.0,
|
||||
40.0
|
||||
],
|
||||
"angle": -120.0
|
||||
}
|
||||
],
|
||||
"melty_platforms": [
|
||||
{
|
||||
"pos": [
|
||||
112.0,
|
||||
424.0
|
||||
],
|
||||
"color": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
208.0,
|
||||
424.0
|
||||
],
|
||||
"color": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
208.0,
|
||||
288.0
|
||||
],
|
||||
"color": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
600.0,
|
||||
552.0
|
||||
],
|
||||
"color": [
|
||||
0.0,
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
616.0,
|
||||
40.0
|
||||
],
|
||||
"color": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
584.0,
|
||||
-56.0
|
||||
],
|
||||
"color": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
}
|
||||
],
|
||||
"texts": []
|
||||
},
|
||||
{
|
||||
"comment": "Second puzzle",
|
||||
"characters": [
|
||||
{
|
||||
"pos": [
|
||||
|
|
BIN
assets/silence.ogg
Normal file
BIN
assets/silence.ogg
Normal file
Binary file not shown.
BIN
assets/sound/notes/a5.ogg
Normal file
BIN
assets/sound/notes/a5.ogg
Normal file
Binary file not shown.
BIN
assets/sound/notes/cs6.ogg
Normal file
BIN
assets/sound/notes/cs6.ogg
Normal file
Binary file not shown.
BIN
assets/sound/notes/e6.ogg
Normal file
BIN
assets/sound/notes/e6.ogg
Normal file
Binary file not shown.
BIN
assets/sound/reverb_notes/a5.ogg
Normal file
BIN
assets/sound/reverb_notes/a5.ogg
Normal file
Binary file not shown.
BIN
assets/sound/reverb_notes/cs6.ogg
Normal file
BIN
assets/sound/reverb_notes/cs6.ogg
Normal file
Binary file not shown.
BIN
assets/sound/reverb_notes/e6.ogg
Normal file
BIN
assets/sound/reverb_notes/e6.ogg
Normal file
Binary file not shown.
BIN
assets/sound/warp_notes/a5.ogg
Normal file
BIN
assets/sound/warp_notes/a5.ogg
Normal file
Binary file not shown.
BIN
assets/sound/warp_notes/cs6.ogg
Normal file
BIN
assets/sound/warp_notes/cs6.ogg
Normal file
Binary file not shown.
BIN
assets/sound/warp_notes/e6.ogg
Normal file
BIN
assets/sound/warp_notes/e6.ogg
Normal file
Binary file not shown.
|
@ -1,9 +1,11 @@
|
|||
# WASM
|
||||
|
||||
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 -r 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/
|
||||
|
|
10
index.html
10
index.html
|
@ -57,10 +57,12 @@ a, a:visited {
|
|||
import init from './target/lux-synthese.js'
|
||||
init()
|
||||
</script>
|
||||
<!-- workaround to be sure the browser will request permission to autoplay audio -->
|
||||
<audio autoplay src="assets/silence.ogg"></audio>
|
||||
<div id="readme">
|
||||
<h1>Lux synthesĕ</h1>
|
||||
<p>
|
||||
<strong>Note</strong>: audio does not work in the WASM build.
|
||||
<strong>If audio does not work, please allow media autoplay in browser, then refresh this page.</strong>
|
||||
</p>
|
||||
<h2>Controls</h2>
|
||||
<ul>
|
||||
|
@ -69,6 +71,12 @@ init()
|
|||
<li><strong>Level up</strong>: Enter</li>
|
||||
<li><strong>Reset</strong>: R</li>
|
||||
</ul>
|
||||
<h2>Download</h2>
|
||||
Download an executable release:
|
||||
<ul>
|
||||
<li><a href="https://txmn.tk/projects/lux-synthese/linux-x86_64.zip">Linux x86_64</a></li>
|
||||
<li><a href="https://txmn.tk/projects/lux-synthese/windows-x86_64.zip">Windows x86_64</a></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>.
|
||||
|
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
BIN
res/notes.flp
Normal file
BIN
res/notes.flp
Normal file
Binary file not shown.
|
@ -1,5 +1,6 @@
|
|||
// https://github.com/WeirdConstructor/HexoDSP/blob/master/examples/cpal_demo_node_api.rs
|
||||
|
||||
// use bevy::{prelude::*, asset::HandleId};
|
||||
use crate::game::AudioMsg;
|
||||
|
||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||
|
@ -163,4 +164,4 @@ fn start_backend<F: FnMut()>(node_exec: NodeExecutor, frontend_loop: F) {
|
|||
cpal::SampleFormat::I16 => run::<i16, F>(&device, &config.into(), node_exec, frontend_loop),
|
||||
cpal::SampleFormat::U16 => run::<u16, F>(&device, &config.into(), node_exec, frontend_loop),
|
||||
};
|
||||
}
|
||||
}
|
48
src/audio_system.rs
Normal file
48
src/audio_system.rs
Normal file
|
@ -0,0 +1,48 @@
|
|||
use bevy::prelude::*;
|
||||
|
||||
pub struct AudioSystemPlugin;
|
||||
|
||||
impl Plugin for AudioSystemPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_resource::<AudioAssets>()
|
||||
.add_startup_system(load_audio);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AudioAssets {
|
||||
pub notes: [Handle<AudioSource>; 3],
|
||||
pub reverb_notes: [Handle<AudioSource>; 3],
|
||||
pub warp_notes: [Handle<AudioSource>; 3],
|
||||
}
|
||||
|
||||
pub fn play_audio(
|
||||
audio_set: &[Handle<AudioSource>; 3],
|
||||
audio: &Res<Audio>,
|
||||
color: Vec4,
|
||||
volume: f32,
|
||||
) {
|
||||
for i in 0..3 {
|
||||
audio.play_with_settings(
|
||||
audio_set[i].clone(),
|
||||
PlaybackSettings::ONCE.with_volume(color[i] * volume),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn load_audio(mut audio_assets: ResMut<AudioAssets>, asset_server: Res<AssetServer>) {
|
||||
audio_assets.notes[0] = asset_server.load::<AudioSource, &str>("sound/notes/a5.ogg");
|
||||
audio_assets.notes[1] = asset_server.load::<AudioSource, &str>("sound/notes/cs6.ogg");
|
||||
audio_assets.notes[2] = asset_server.load::<AudioSource, &str>("sound/notes/e6.ogg");
|
||||
|
||||
audio_assets.reverb_notes[0] =
|
||||
asset_server.load::<AudioSource, &str>("sound/reverb_notes/a5.ogg");
|
||||
audio_assets.reverb_notes[1] =
|
||||
asset_server.load::<AudioSource, &str>("sound/reverb_notes/cs6.ogg");
|
||||
audio_assets.reverb_notes[2] =
|
||||
asset_server.load::<AudioSource, &str>("sound/reverb_notes/e6.ogg");
|
||||
|
||||
audio_assets.warp_notes[0] = asset_server.load::<AudioSource, &str>("sound/warp_notes/a5.ogg");
|
||||
audio_assets.warp_notes[1] = asset_server.load::<AudioSource, &str>("sound/warp_notes/cs6.ogg");
|
||||
audio_assets.warp_notes[2] = asset_server.load::<AudioSource, &str>("sound/warp_notes/e6.ogg");
|
||||
}
|
|
@ -23,6 +23,10 @@ impl PassThroughFilter {
|
|||
PassThroughFilter::Rotating(filter_angle) => {
|
||||
let mut hsla = color.as_hsla_f32();
|
||||
hsla[0] = (hsla[0] + filter_angle) % 360.;
|
||||
// floating rem is not modulo!
|
||||
if hsla[0] < 0. {
|
||||
hsla[0] += 360.;
|
||||
}
|
||||
Color::hsla(hsla[0], hsla[1], hsla[2], hsla[3])
|
||||
}
|
||||
}
|
||||
|
|
141
src/game.rs
141
src/game.rs
|
@ -2,8 +2,8 @@
|
|||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
pub use crate::filters::*;
|
||||
|
||||
use crate::AppState;
|
||||
use crate::levels;
|
||||
use crate::{audio_system, AppState};
|
||||
|
||||
use bevy::{
|
||||
ecs::system::EntityCommands,
|
||||
|
@ -15,13 +15,6 @@ use bevy_rapier2d::prelude::*;
|
|||
use rapier2d::geometry::CollisionEventFlags;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
pub enum AudioMsg {
|
||||
Color([f32; 3]),
|
||||
Fusion,
|
||||
Jump,
|
||||
Switch,
|
||||
}
|
||||
|
||||
pub struct FirstLevel(pub LevelId);
|
||||
|
||||
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
|
||||
|
@ -32,9 +25,11 @@ pub struct GamePlugin;
|
|||
impl Plugin for GamePlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_event::<LevelStartupEvent>()
|
||||
.add_event::<ChangeCharacterEvent>()
|
||||
.init_resource::<CharacterMeshes>()
|
||||
.insert_resource(CurrentLevel(None))
|
||||
.init_resource::<CharacterList>()
|
||||
.init_resource::<levels::ZoomTimer>()
|
||||
.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(
|
||||
|
@ -46,14 +41,15 @@ impl Plugin for GamePlugin {
|
|||
.with_system(change_character_system)
|
||||
.with_system(player_movement_system)
|
||||
.with_system(level_keyboard_system)
|
||||
.with_system(move_camera)
|
||||
.with_system(character_particle_effect_system),
|
||||
.with_system(camera_system)
|
||||
.with_system(character_particle_effect_system)
|
||||
.with_system(kill_character_system),
|
||||
)
|
||||
.add_system_set(
|
||||
SystemSet::on_update(AppState::Win)
|
||||
.with_system(player_movement_system)
|
||||
.with_system(level_keyboard_system)
|
||||
.with_system(move_camera)
|
||||
.with_system(camera_system)
|
||||
.with_system(character_particle_effect_system)
|
||||
.with_system(move_win_text_system),
|
||||
)
|
||||
|
@ -71,6 +67,8 @@ impl Plugin for GamePlugin {
|
|||
|
||||
pub struct LevelStartupEvent;
|
||||
|
||||
pub struct ChangeCharacterEvent;
|
||||
|
||||
// Resources
|
||||
|
||||
pub struct CurrentLevel(pub Option<LevelId>);
|
||||
|
@ -144,19 +142,19 @@ fn setup(
|
|||
mut current_level: ResMut<CurrentLevel>,
|
||||
mut level_startup_event: EventWriter<LevelStartupEvent>,
|
||||
mut camera_query: Query<&mut Transform, With<Camera>>,
|
||||
mut zoom_timer: ResMut<levels::ZoomTimer>,
|
||||
) {
|
||||
if current_level.0.is_none() {
|
||||
current_level.0 = Some(first_level.0);
|
||||
}
|
||||
|
||||
crate::levels::setup_level(&mut level_startup_event, &mut camera_query);
|
||||
crate::levels::setup_level(&mut level_startup_event, &mut camera_query, &mut zoom_timer);
|
||||
}
|
||||
|
||||
pub fn spawn_characters<I: IntoIterator<Item = (Transform, Color)>>(
|
||||
commands: &mut Commands,
|
||||
character_meshes: &Res<CharacterMeshes>,
|
||||
materials: &mut ResMut<Assets<ColorMaterial>>,
|
||||
audio: &Res<crossbeam_channel::Sender<AudioMsg>>,
|
||||
character_list: &mut ResMut<CharacterList>,
|
||||
|
||||
characters: I,
|
||||
|
@ -166,7 +164,6 @@ pub fn spawn_characters<I: IntoIterator<Item = (Transform, Color)>>(
|
|||
commands,
|
||||
character_meshes,
|
||||
materials,
|
||||
audio,
|
||||
character_list,
|
||||
transform,
|
||||
color,
|
||||
|
@ -179,7 +176,6 @@ pub fn spawn_character(
|
|||
commands: &mut Commands,
|
||||
character_meshes: &Res<CharacterMeshes>,
|
||||
materials: &mut ResMut<Assets<ColorMaterial>>,
|
||||
audio: &Res<crossbeam_channel::Sender<AudioMsg>>,
|
||||
character_list: &mut ResMut<CharacterList>,
|
||||
mut transform: Transform,
|
||||
color: Color,
|
||||
|
@ -224,9 +220,6 @@ pub fn spawn_character(
|
|||
|
||||
if is_player {
|
||||
entity_commands.insert(Player);
|
||||
audio
|
||||
.send(AudioMsg::Color([color.r(), color.g(), color.b()]))
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -305,8 +298,9 @@ fn char_char_collision_event_system(
|
|||
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>>,
|
||||
audio_assets: Res<audio_system::AudioAssets>,
|
||||
audio: Res<Audio>,
|
||||
) {
|
||||
for collision_event in collision_events.iter() {
|
||||
if let CollisionEvent::Started(e1, e2, _flags) = collision_event {
|
||||
|
@ -333,7 +327,6 @@ fn char_char_collision_event_system(
|
|||
&mut commands,
|
||||
&character_meshes,
|
||||
&mut materials,
|
||||
&audio,
|
||||
&mut character_list,
|
||||
if c1_player.is_some() {
|
||||
*c1_transform
|
||||
|
@ -348,7 +341,7 @@ fn char_char_collision_event_system(
|
|||
c1_player.is_some() || c2_player.is_some(),
|
||||
);
|
||||
|
||||
audio.send(AudioMsg::Fusion).ok();
|
||||
audio_system::play_audio(&audio_assets.warp_notes, &audio, new_color, 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -405,7 +398,8 @@ fn collision_event_system(
|
|||
)>,
|
||||
pass_through_filter_query: Query<&PassThroughFilter>,
|
||||
melty_query: Query<&Melty>,
|
||||
audio: Res<crossbeam_channel::Sender<AudioMsg>>,
|
||||
mut character_list: ResMut<CharacterList>,
|
||||
mut change_character_event: EventWriter<ChangeCharacterEvent>,
|
||||
) {
|
||||
for collision_event in collision_events.iter() {
|
||||
if let CollisionEvent::Started(e1, e2, flags) = collision_event {
|
||||
|
@ -429,18 +423,14 @@ fn collision_event_system(
|
|||
pass_through_filter_query.get(*e2),
|
||||
) {
|
||||
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();
|
||||
if c_color.0.as_hsla_f32()[2] < 0.1 {
|
||||
commands.entity(*e1).despawn_recursive();
|
||||
character_list.0.remove(e1);
|
||||
if c_player.is_some() {
|
||||
change_character_event.send(ChangeCharacterEvent);
|
||||
}
|
||||
}
|
||||
*c_material = materials.add(ColorMaterial::from(c_color.0));
|
||||
} else if let (
|
||||
Ok((mut c_color, _c_transform, mut c_material, c_player)),
|
||||
Ok(filter),
|
||||
|
@ -449,18 +439,14 @@ fn collision_event_system(
|
|||
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();
|
||||
if c_color.0.as_hsla_f32()[2] < 0.1 {
|
||||
commands.entity(*e2).despawn_recursive();
|
||||
character_list.0.remove(e2);
|
||||
if c_player.is_some() {
|
||||
change_character_event.send(ChangeCharacterEvent);
|
||||
}
|
||||
}
|
||||
*c_material = materials.add(ColorMaterial::from(c_color.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -469,13 +455,14 @@ fn collision_event_system(
|
|||
|
||||
fn change_character_system(
|
||||
mut commands: Commands,
|
||||
|
||||
keyboard_input: Res<Input<KeyCode>>,
|
||||
characters: Query<(Entity, &CharacterColor, Option<&Player>)>,
|
||||
audio: Res<crossbeam_channel::Sender<AudioMsg>>,
|
||||
character_list: Res<CharacterList>,
|
||||
audio_assets: Res<audio_system::AudioAssets>,
|
||||
audio: Res<Audio>,
|
||||
change_character_event: EventReader<ChangeCharacterEvent>,
|
||||
) {
|
||||
if !keyboard_input.just_pressed(KeyCode::Tab) {
|
||||
if !keyboard_input.just_pressed(KeyCode::Tab) && change_character_event.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -493,10 +480,7 @@ fn change_character_system(
|
|||
{
|
||||
commands.entity(*new_player_entity).insert(Player);
|
||||
if let Ok((_entity, color, _player)) = characters.get(*new_player_entity) {
|
||||
audio
|
||||
.send(AudioMsg::Color([color.0.r(), color.0.g(), color.0.b()]))
|
||||
.ok();
|
||||
audio.send(AudioMsg::Switch).ok();
|
||||
audio_system::play_audio(&audio_assets.reverb_notes, &audio, color.0.into(), 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -504,22 +488,23 @@ fn change_character_system(
|
|||
|
||||
fn player_movement_system(
|
||||
keyboard_input: Res<Input<KeyCode>>,
|
||||
mut characters: Query<(&mut Velocity, &Children), With<Player>>,
|
||||
mut characters: Query<(&mut Velocity, &Children, &CharacterColor), With<Player>>,
|
||||
mut platform_count_query: Query<&mut PlatformCount>,
|
||||
audio: Res<crossbeam_channel::Sender<AudioMsg>>,
|
||||
audio_assets: Res<audio_system::AudioAssets>,
|
||||
audio: Res<Audio>,
|
||||
) {
|
||||
let right_pressed: bool =
|
||||
keyboard_input.pressed(KeyCode::Right) || keyboard_input.pressed(KeyCode::D);
|
||||
let left_pressed: bool =
|
||||
keyboard_input.pressed(KeyCode::Left) || keyboard_input.pressed(KeyCode::A);
|
||||
|
||||
for (mut velocity, children) in characters.iter_mut() {
|
||||
for (mut velocity, children, color) in characters.iter_mut() {
|
||||
velocity.linvel.x = 200. * (right_pressed as i8 - left_pressed as i8) as f32;
|
||||
|
||||
let mut platform_count: Mut<PlatformCount> =
|
||||
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_system::play_audio(&audio_assets.notes, &audio, color.0.into(), 0.5);
|
||||
velocity.linvel.y = 700.;
|
||||
platform_count.reset();
|
||||
}
|
||||
|
@ -575,16 +560,22 @@ fn win_setup(
|
|||
.insert(WinText);
|
||||
}
|
||||
|
||||
fn move_camera(
|
||||
mut camera_query: Query<(&Camera, &mut Transform)>,
|
||||
fn camera_system(
|
||||
mut camera_query: Query<(&Camera, &mut OrthographicProjection, &mut Transform)>,
|
||||
characters: Query<&Transform, (Without<Camera>, With<Player>)>,
|
||||
time: Res<Time>,
|
||||
mut zoom_timer: ResMut<levels::ZoomTimer>,
|
||||
) {
|
||||
fn lerp(x: f32, y: f32, t: f32) -> f32 {
|
||||
((y - x) * t) + x
|
||||
}
|
||||
|
||||
const MARGIN: f32 = 300.0;
|
||||
const FOLLOW_SPEED: f32 = std::f32::consts::PI;
|
||||
const ZOOM_SPEED: f32 = std::f32::consts::E;
|
||||
|
||||
for character_transform in characters.iter() {
|
||||
let (camera, mut camera_transform) = camera_query.single_mut();
|
||||
let (camera, mut projection, mut camera_transform) = camera_query.single_mut();
|
||||
|
||||
let size: Vec2 = camera.logical_viewport_size().unwrap();
|
||||
let half_height: f32 = size.y * 0.5;
|
||||
|
@ -596,6 +587,14 @@ fn move_camera(
|
|||
.translation
|
||||
.lerp(target_translation, time.delta_seconds() * FOLLOW_SPEED);
|
||||
|
||||
if zoom_timer.0.finished() {
|
||||
projection.scale = lerp(projection.scale, 1.0, time.delta_seconds() * ZOOM_SPEED);
|
||||
} else {
|
||||
projection.scale = 3.0;
|
||||
camera_transform.translation = Vec3::ZERO;
|
||||
zoom_timer.0.tick(time.delta());
|
||||
}
|
||||
|
||||
// always make sure that camera is away from the object in order to render them
|
||||
camera_transform.translation.z = 999.0;
|
||||
}
|
||||
|
@ -621,6 +620,7 @@ fn level_keyboard_system(
|
|||
mut character_list: ResMut<CharacterList>,
|
||||
level_query: Query<Entity, With<Level>>,
|
||||
mut app_state: ResMut<State<AppState>>,
|
||||
mut zoom_timer: ResMut<levels::ZoomTimer>,
|
||||
) {
|
||||
if app_state.current() == &AppState::Win && keyboard_input.just_pressed(KeyCode::Return) {
|
||||
current_level.0 = Some(LevelId(
|
||||
|
@ -635,7 +635,28 @@ fn level_keyboard_system(
|
|||
commands.entity(entity).despawn_recursive();
|
||||
}
|
||||
if app_state.replace(AppState::Game).is_err() {
|
||||
crate::levels::setup_level(&mut level_startup_event, &mut camera_query);
|
||||
crate::levels::setup_level(
|
||||
&mut level_startup_event,
|
||||
&mut camera_query,
|
||||
&mut zoom_timer,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn kill_character_system(
|
||||
mut commands: Commands,
|
||||
character_query: Query<(Entity, &Transform, Option<&Player>), With<CharacterColor>>,
|
||||
mut character_list: ResMut<CharacterList>,
|
||||
mut change_character_event: EventWriter<ChangeCharacterEvent>,
|
||||
) {
|
||||
for (entity, transform, player) in character_query.iter() {
|
||||
if transform.translation.y < -512. {
|
||||
commands.entity(entity).despawn_recursive();
|
||||
character_list.0.remove(&entity);
|
||||
if player.is_some() {
|
||||
change_character_event.send(ChangeCharacterEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
pub use stored::*;
|
||||
|
||||
use crate::game::*;
|
||||
|
@ -7,11 +9,24 @@ use crate::game::*;
|
|||
use bevy::{prelude::*, reflect::TypeUuid};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct ZoomTimer(pub Timer);
|
||||
|
||||
impl Default for ZoomTimer {
|
||||
fn default() -> Self {
|
||||
Self(Timer::new(Duration::new(2, 0), false))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_level(
|
||||
level_startup_event: &mut EventWriter<LevelStartupEvent>,
|
||||
camera_query: &mut Query<&mut Transform, With<Camera>>,
|
||||
zoom_timer: &mut ResMut<ZoomTimer>,
|
||||
) {
|
||||
camera_query.single_mut().translation = Default::default();
|
||||
let mut transform = camera_query.single_mut();
|
||||
transform.translation = Default::default();
|
||||
|
||||
zoom_timer.0.reset();
|
||||
|
||||
level_startup_event.send(LevelStartupEvent);
|
||||
}
|
||||
|
@ -36,7 +51,6 @@ pub fn post_setup_level(
|
|||
current_level: Res<CurrentLevel>,
|
||||
mut level_startup_event: EventReader<LevelStartupEvent>,
|
||||
asset_server: Res<AssetServer>,
|
||||
audio: Res<crossbeam_channel::Sender<AudioMsg>>,
|
||||
mut character_list: ResMut<CharacterList>,
|
||||
stored_levels_assets: Res<Assets<StoredLevels>>,
|
||||
stored_levels_handle: Res<Handle<StoredLevels>>,
|
||||
|
@ -55,7 +69,6 @@ pub fn post_setup_level(
|
|||
&mut meshes,
|
||||
&mut materials,
|
||||
&asset_server,
|
||||
&audio,
|
||||
&mut character_list,
|
||||
stored_level,
|
||||
);
|
||||
|
@ -70,7 +83,6 @@ pub fn spawn_stored_level(
|
|||
meshes: &mut ResMut<Assets<Mesh>>,
|
||||
materials: &mut ResMut<Assets<ColorMaterial>>,
|
||||
asset_server: &Res<AssetServer>,
|
||||
audio: &Res<crossbeam_channel::Sender<AudioMsg>>,
|
||||
character_list: &mut ResMut<CharacterList>,
|
||||
|
||||
stored_level: &StoredLevel,
|
||||
|
@ -91,7 +103,6 @@ pub fn spawn_stored_level(
|
|||
commands,
|
||||
character_meshes,
|
||||
materials,
|
||||
audio,
|
||||
character_list,
|
||||
stored_level.characters.iter().map(|character| {
|
||||
(
|
||||
|
|
14
src/main.rs
14
src/main.rs
|
@ -1,9 +1,9 @@
|
|||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod audio;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod editor;
|
||||
|
||||
mod audio_system;
|
||||
mod filters;
|
||||
mod game;
|
||||
mod levels;
|
||||
|
@ -39,12 +39,6 @@ impl LoadingAssets {
|
|||
}
|
||||
|
||||
fn main() {
|
||||
let (audio_event_sender, audio_event_receiver) =
|
||||
crossbeam_channel::bounded::<game::AudioMsg>(512);
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
std::thread::spawn(move || audio::setup(audio_event_receiver));
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let (first_level, use_editor) = {
|
||||
let mut args = std::env::args().skip(1);
|
||||
|
@ -71,14 +65,14 @@ fn main() {
|
|||
title: "Lux synthesĕ".into(),
|
||||
..Default::default()
|
||||
})
|
||||
.insert_resource(audio_event_sender)
|
||||
.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(audio_system::AudioSystemPlugin)
|
||||
//.add_plugin(RapierDebugRenderPlugin::default())
|
||||
//.add_plugin(bevy_inspector_egui::WorldInspectorPlugin::new())
|
||||
// .add_plugin(bevy_inspector_egui::WorldInspectorPlugin::new())
|
||||
.add_plugin(JsonAssetPlugin::<levels::StoredLevels>::new(&[
|
||||
"levels.json",
|
||||
]));
|
||||
|
|
16
src/menu.rs
16
src/menu.rs
|
@ -20,22 +20,6 @@ impl Plugin for MenuPlugin {
|
|||
|
||||
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
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
|
||||
.spawn_bundle(Text2dBundle {
|
||||
text: Text::from_section(
|
||||
|
|
Loading…
Reference in a new issue