Merge branch 'master' of https://git.txmn.tk/tuxmain/bevyjam into nixon-main

This commit is contained in:
Nixon 2022-08-23 23:14:51 +08:00
commit 05ef42d8c3
10 changed files with 571 additions and 223 deletions

150
src/audio.rs Normal file
View file

@ -0,0 +1,150 @@
// https://github.com/WeirdConstructor/HexoDSP/blob/master/examples/cpal_demo_node_api.rs
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use crossbeam_channel::Receiver;
use hexodsp::{matrix_repr::MatrixRepr, *};
use std::io::Read;
use ticktock::Clock;
pub enum AudioMsg {
Color([f32; 3]),
Jump,
}
pub fn setup(event_channel: Receiver<AudioMsg>) {
let mut buf = String::new();
std::fs::File::open("assets/init.hxy")
.unwrap()
.read_to_string(&mut buf)
.unwrap();
let matrix_repr: MatrixRepr = MatrixRepr::deserialize(&buf).unwrap();
let (node_conf, node_exec) = new_node_engine();
let mut matrix = Matrix::new(node_conf, 64, 64);
matrix.from_repr(&matrix_repr).unwrap();
start_backend(node_exec, move || {
let color_mix = NodeId::Mix3(0);
let color_mix_r_gain = color_mix.inp_param("gain1").unwrap();
let color_mix_g_gain = color_mix.inp_param("gain2").unwrap();
let color_mix_b_gain = color_mix.inp_param("gain3").unwrap();
let jump_ad = NodeId::Ad(0);
let jump_ad_trig = jump_ad.inp_param("trig").unwrap();
for (_tick, _now) in Clock::framerate(10.0).iter() {
matrix.set_param(jump_ad_trig, (0.0).into());
if let Ok(msg) = event_channel.try_recv() {
match msg {
AudioMsg::Color([r, g, b]) => {
matrix.set_param(color_mix_r_gain, r.into());
matrix.set_param(color_mix_g_gain, g.into());
matrix.set_param(color_mix_b_gain, b.into());
}
AudioMsg::Jump => matrix.set_param(jump_ad_trig, (1.0).into()),
}
}
}
});
}
pub fn run<T, F: FnMut()>(
device: &cpal::Device,
config: &cpal::StreamConfig,
mut node_exec: NodeExecutor,
mut frontend_loop: F,
) where
T: cpal::Sample,
{
let sample_rate = config.sample_rate.0 as f32;
let channels = config.channels as usize;
node_exec.set_sample_rate(sample_rate);
let input_bufs = [[0.0; hexodsp::dsp::MAX_BLOCK_SIZE]; 2];
let mut outputbufs = [[0.0; hexodsp::dsp::MAX_BLOCK_SIZE]; 2];
let err_fn = |err| eprintln!("an error occurred on stream: {}", err);
let stream = device
.build_output_stream(
config,
move |data: &mut [T], _: &cpal::OutputCallbackInfo| {
let mut frames_left = data.len() / channels;
let mut out_iter = data.chunks_mut(channels);
node_exec.process_graph_updates();
while frames_left > 0 {
let cur_nframes = if frames_left >= hexodsp::dsp::MAX_BLOCK_SIZE {
hexodsp::dsp::MAX_BLOCK_SIZE
} else {
frames_left
};
let input = &[
&input_bufs[0][0..cur_nframes],
&input_bufs[1][0..cur_nframes],
];
let split = outputbufs.split_at_mut(1);
let mut output = [
&mut ((split.0[0])[0..cur_nframes]),
&mut ((split.1[0])[0..cur_nframes]),
];
let mut context = Context {
nframes: cur_nframes,
output: &mut output[..],
input,
};
context.output[0].fill(0.0);
context.output[1].fill(0.0);
node_exec.process(&mut context);
for i in 0..cur_nframes {
if let Some(frame) = out_iter.next() {
let mut ctx_chan = 0;
for sample in frame.iter_mut() {
let value: T =
cpal::Sample::from::<f32>(&context.output[ctx_chan][i]);
*sample = value;
ctx_chan += 1;
if ctx_chan > context.output.len() {
ctx_chan = context.output.len() - 1;
}
}
}
}
frames_left -= cur_nframes;
}
},
err_fn,
)
.unwrap();
stream.play().unwrap();
frontend_loop();
}
fn start_backend<F: FnMut()>(node_exec: NodeExecutor, frontend_loop: F) {
let host = cpal::default_host();
let device = host
.default_output_device()
.expect("Finding useable audio device");
let config = device
.default_output_config()
.expect("A workable output config");
match config.sample_format() {
cpal::SampleFormat::F32 => run::<f32, F>(&device, &config.into(), node_exec, frontend_loop),
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),
};
}

View file

@ -1,6 +1,8 @@
#![allow(clippy::precedence)]
#![allow(clippy::too_many_arguments)]
pub use crate::audio::AudioMsg;
use crate::AppState;
use bevy::{
@ -8,7 +10,6 @@ use bevy::{
prelude::{shape::Quad, *},
sprite::Mesh2dHandle,
};
use bevy_fundsp::prelude::*;
use bevy_hanabi::*;
use bevy_rapier2d::prelude::*;
use std::collections::BTreeSet;
@ -104,6 +105,7 @@ pub fn spawn_character(
materials: &mut ResMut<Assets<ColorMaterial>>,
selected_character_id: &mut Mut<SelectedCharacterId>,
character_id_list: &mut Mut<CharacterIdList>,
audio: &Res<crossbeam_channel::Sender<AudioMsg>>,
transform: Transform,
color: Color,
) {
@ -187,6 +189,9 @@ pub fn spawn_character(
// If no character is selected, then select this one
if selected_character_id.0.is_none() {
selected_character_id.0 = Some(character_id);
audio
.send(AudioMsg::Color([color.r(), color.g(), color.b()]))
.ok();
}
}
@ -199,6 +204,7 @@ fn collision_event_system(
character_query: Query<(&CharacterId, &CharacterColor, &Transform)>,
mut level_query: Query<(&mut SelectedCharacterId, &mut CharacterIdList)>,
mut app_state: ResMut<State<AppState>>,
audio: Res<crossbeam_channel::Sender<AudioMsg>>,
) {
for collision_event in collision_events.iter() {
if let CollisionEvent::Started(e1, e2, flags) = collision_event {
@ -233,6 +239,7 @@ fn collision_event_system(
&mut materials,
&mut selected_character_id,
&mut character_id_list,
&audio,
*c1_transform,
new_color.into(),
);
@ -244,27 +251,18 @@ fn collision_event_system(
fn keyboard_input_system(
keyboard_input: Res<Input<KeyCode>>,
mut characters: Query<(
&CharacterId,
&mut Velocity,
&mut ExternalImpulse,
&mut ExternalForce,
&Children,
)>,
mut characters: Query<(&CharacterId, &mut Velocity, &CharacterColor, &Children)>,
mut level_query: Query<(&mut SelectedCharacterId, &CharacterIdList)>,
mut effect: Query<&mut ParticleEffect>,
dsp_assets: Res<DspAssets>,
audio: Res<Audio>,
mut app_state: ResMut<State<AppState>>,
audio: Res<crossbeam_channel::Sender<AudioMsg>>,
) {
if let Ok((mut selected_character_id, character_id_list)) = level_query.get_single_mut() {
if keyboard_input.just_pressed(KeyCode::Tab) {
audio.play(dsp_assets.graph(&sine_wave));
let selected = if let Some(selected_character_id) = &mut selected_character_id.0 {
if let Some((_character_id, _velocity, _impulse, _force, children)) = characters
if let Some((_character_id, _velocity, _color, children)) = characters
.iter_mut()
.find(|(character_id, _velocity, _impulse, _force, _children)| {
.find(|(character_id, _velocity, _color, _children)| {
*character_id == selected_character_id
}) {
effect
@ -286,17 +284,19 @@ fn keyboard_input_system(
CharacterId(0)
};
if let Some((_character_id, _velocity, _impulse, _force, children)) = characters
if let Some((_character_id, _velocity, color, children)) = characters
.iter_mut()
.find(|(character_id, _velocity, _impulse, _force, _children)| {
**character_id == selected
}) {
.find(|(character_id, _velocity, _color, _children)| **character_id == selected)
{
effect
.get_mut(children[0])
.unwrap()
.maybe_spawner()
.unwrap()
.set_active(true);
audio
.send(AudioMsg::Color([color.0.r(), color.0.g(), color.0.b()]))
.ok();
}
}
@ -306,14 +306,15 @@ fn keyboard_input_system(
keyboard_input.pressed(KeyCode::Left) || keyboard_input.pressed(KeyCode::A);
if let Some(selected_character_id) = &selected_character_id.0 {
if let Some((_character_id, mut velocity, _impulse, _force, _children)) = characters
if let Some((_character_id, mut velocity, _color, _children)) = characters
.iter_mut()
.find(|(character_id, _velocity, _impulse, _force, _children)| {
.find(|(character_id, _velocity, _color, _children)| {
*character_id == selected_character_id
}) {
velocity.linvel.x = 200. * (right_pressed as i8 - left_pressed as i8) as f32;
if keyboard_input.just_pressed(KeyCode::Space) {
audio.send(AudioMsg::Jump).ok();
velocity.linvel.y = 500.;
}
}
@ -342,9 +343,3 @@ fn win_setup(mut commands: Commands, asset_server: Res<AssetServer>) {
})
.insert(Level);
}
// Sounds
pub fn sine_wave() -> impl AudioUnit32 {
sine_hz(440.0) >> split::<U2>() * 0.2
}

View file

@ -43,6 +43,7 @@ pub fn post_setup_level(
mut level_query: Query<(&mut SelectedCharacterId, &mut CharacterIdList)>,
mut level_startup_event: EventReader<LevelStartupEvent>,
asset_server: Res<AssetServer>,
audio: Res<crossbeam_channel::Sender<AudioMsg>>,
) {
for LevelStartupEvent(level_entity) in level_startup_event.iter() {
if let Ok((mut selected_character_id, mut character_id_list)) =
@ -57,6 +58,7 @@ pub fn post_setup_level(
&mut materials,
&mut selected_character_id,
&mut character_id_list,
&audio,
),
1 => level1::setup(
&mut commands,
@ -65,6 +67,7 @@ pub fn post_setup_level(
&mut materials,
&mut selected_character_id,
&mut character_id_list,
&audio,
),
_ => game_over::setup(&mut commands, &asset_server),
}

View file

@ -11,6 +11,7 @@ pub fn setup(
materials: &mut ResMut<Assets<ColorMaterial>>,
selected_character_id: &mut Mut<SelectedCharacterId>,
character_id_list: &mut Mut<CharacterIdList>,
audio: &Res<crossbeam_channel::Sender<AudioMsg>>,
) {
commands
.spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, -256.0, 0.0)))
@ -24,6 +25,7 @@ pub fn setup(
materials,
selected_character_id,
character_id_list,
audio,
Transform::from_xyz(-128., -64., 0.),
Color::RED,
);
@ -34,6 +36,7 @@ pub fn setup(
materials,
selected_character_id,
character_id_list,
audio,
Transform::from_xyz(0., -64., 0.),
Color::GREEN,
);
@ -44,6 +47,7 @@ pub fn setup(
materials,
selected_character_id,
character_id_list,
audio,
Transform::from_xyz(128., -64., 0.),
Color::BLUE,
);

View file

@ -11,6 +11,7 @@ pub fn setup(
materials: &mut ResMut<Assets<ColorMaterial>>,
selected_character_id: &mut Mut<SelectedCharacterId>,
character_id_list: &mut Mut<CharacterIdList>,
audio: &Res<crossbeam_channel::Sender<AudioMsg>>,
) {
commands
.spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, -256.0, 0.0)))
@ -30,6 +31,7 @@ pub fn setup(
materials,
selected_character_id,
character_id_list,
audio,
Transform::from_xyz(128., 64., 0.),
Color::BLUE,
);
@ -40,6 +42,7 @@ pub fn setup(
materials,
selected_character_id,
character_id_list,
audio,
Transform::from_xyz(-128., -128., 0.),
Color::RED,
);
@ -50,6 +53,7 @@ pub fn setup(
materials,
selected_character_id,
character_id_list,
audio,
Transform::from_xyz(0., -128., 0.),
Color::GREEN,
);

View file

@ -1,3 +1,4 @@
mod audio;
mod game;
mod levels;
mod menu;
@ -8,7 +9,6 @@ use bevy::{
prelude::*,
render::settings::{WgpuFeatures, WgpuSettings},
};
use bevy_fundsp::prelude::*;
use bevy_hanabi::*;
use bevy_rapier2d::prelude::*;
@ -20,15 +20,19 @@ enum AppState {
}
fn main() {
let (audio_event_sender, audio_event_receiver) = crossbeam_channel::bounded(512);
std::thread::spawn(move || audio::setup(audio_event_receiver));
let mut options = WgpuSettings::default();
options
.features
.set(WgpuFeatures::VERTEX_WRITABLE_STORAGE, true);
App::new()
.insert_resource(options)
.insert_resource(audio_event_sender)
.add_state(AppState::Menu)
.add_plugins(DefaultPlugins)
.add_plugin(DspPlugin)
.add_plugin(HanabiPlugin)
.add_plugin(RapierPhysicsPlugin::<NoUserData>::pixels_per_meter(64.0))
.add_plugin(RapierDebugRenderPlugin::default())
@ -39,11 +43,7 @@ fn main() {
.run();
}
fn setup(
mut commands: Commands,
mut dsp_manager: ResMut<DspManager>,
asset_server: Res<AssetServer>,
) {
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
let font: Handle<Font> = asset_server.load("UacariLegacy-Thin.ttf");
commands.insert_resource(font);
@ -57,5 +57,4 @@ fn setup(
color: Color::WHITE,
brightness: 0.6,
});
dsp_manager.add_graph(game::sine_wave, 1.0);
}