#![allow(clippy::type_complexity)]
mod camera;
mod collision;
mod cutscene;
mod debug;
mod fundsp_kira;
mod movement;
mod movement_pointer;
mod player;
mod post_process;
mod sfxr;
mod simple_bt;
use bevy::{prelude::*, sprite::Anchor};
use bevy_asset_loader::prelude::*;
use bevy_ecs_ldtk::prelude::*;
use bevy_egui::EguiPlugin;
use bevy_kira_audio::prelude::*;
use bevy_prototype_lyon::prelude::*;
use bevy_smooth_pixel_camera::PixelCameraPlugin;
use bevy_tweening::TweeningPlugin;
use iyes_progress::prelude::*;
use leafwing_input_manager::prelude::*;
use moonshine_spawn::SpawnPlugin;
const LOGICAL_WIDTH: u32 = 800;
const LOGICAL_HEIGHT: u32 = 600;
#[cfg(target_arch = "wasm32")]
fn app() -> App {
let mut app = App::new();
app.insert_resource(bevy::asset::AssetMetaCheck::Never);
app
}
#[cfg(not(target_arch = "wasm32"))]
fn app() -> App {
App::new()
}
fn main() {
app()
.add_plugins((
DefaultPlugins
.set(ImagePlugin::default_nearest())
.set(WindowPlugin {
primary_window: Some(Window {
canvas: Some("#game-canvas".into()),
..default()
}),
..default()
})
.set(AssetPlugin {
..default()
}),
InputManagerPlugin::<player::PlayerAction>::default(),
AudioPlugin,
TweeningPlugin,
PixelCameraPlugin,
ShapePlugin,
EguiPlugin,
LdtkPlugin,
SpawnPlugin,
))
.add_plugins((
camera::CameraPlugin,
debug::DebugPlugin,
sfxr::SfxrPlugin,
movement::MovementPlugin,
collision::CollisionPlugin,
movement_pointer::MovementPointerPlugin,
fundsp_kira::FundspAudioPlugin,
))
.add_plugins(AcerolaGame0)
.run()
}
pub(crate) mod utils {
use bevy::prelude::Color;
pub fn lerp_mix(a: Color, b: Color, alpha: f32) -> Color {
use palette::{
blend::{BlendWith, PreAlpha},
LinSrgb, Srgba,
};
type PreRgba = PreAlpha<LinSrgb<f32>>;
let alpha = alpha.clamp(0.0, 1.0);
let a_rgb = Srgba::from(a.as_rgba_f32()).into_linear();
let b_rgb = Srgba::from(b.as_rgba_f32()).into_linear();
let mixed = a_rgb.blend_with(b_rgb, |a: PreRgba, b: PreRgba| {
PreAlpha {
color: LinSrgb::new(
a.red + (b.red - a.red) * alpha,
a.green + (b.green - a.green) * alpha,
a.blue + (b.blue - a.blue) * alpha,
),
alpha: a.alpha + (b.alpha - a.alpha) * alpha,
}
});
Color::rgba(mixed.red, mixed.green, mixed.blue, mixed.alpha)
}
}
struct AcerolaGame0;
impl Plugin for AcerolaGame0 {
fn build(&self, app: &mut App) {
app.insert_resource(ClearColor(Color::rgb(0.015, 0.01, 0.01)))
.init_state::<GameState>()
.insert_resource(LevelSelection::index(0))
.init_resource::<You>()
.register_ldtk_entity::<player::PlayerBlueprint>("PlayerStart")
.add_plugins(ProgressPlugin::new(GameState::Booting).continue_to(GameState::StartRun))
.add_plugins(ProgressPlugin::new(GameState::StartRun).continue_to(GameState::InRun))
.add_loading_state(
LoadingState::new(GameState::Booting)
.load_collection::<PlayerAssets>()
.load_collection::<AudioLoadTest>()
.on_failure_continue_to_state(GameState::FailBoot),
)
.add_systems(OnEnter(GameState::FailBoot), fail_boot)
.add_systems(OnEnter(GameState::StartRun), new_run_new_you)
.add_systems(OnEnter(GameState::InRun), setup_level)
.add_systems(
Update,
(loading_screen_progress,)
.run_if(in_state(GameState::Booting))
.after(LoadingStateSet(GameState::Booting)),
)
.add_systems(OnEnter(GameState::Booting), create_loading_screen_bar)
.add_systems(OnExit(GameState::Booting), clean_loading_screen_bar)
.add_systems(
Update,
(
player::process_player_start,
player::handle_dashing,
player::move_player,
)
.run_if(in_state(GameState::InRun)),
)
.add_systems(Update, update_settings.run_if(in_state(GameState::InRun)))
.add_systems(OnEnter(GameState::InRun), test_audio_loop)
.insert_resource(Time::<Fixed>::from_seconds(60.0f64.recip()));
}
}
#[derive(Component)]
struct LoadingScreenBar;
fn create_loading_screen_bar(mut commands: Commands) {
commands
.spawn((
LoadingScreenBar,
ShapeBundle {
path: GeometryBuilder::build_as(&shapes::Rectangle {
origin: shapes::RectangleOrigin::TopLeft,
extents: Vec2::new(LOGICAL_WIDTH as f32, 40.0),
}),
spatial: SpatialBundle {
transform: Transform::default()
.with_translation(Vec3::new(
LOGICAL_WIDTH as f32 / -2.0,
LOGICAL_HEIGHT as f32 / -2.0 + 80.0 * 1.5,
-1.0,
))
.with_scale(Vec3::ONE - Vec3::X),
..default()
},
..default()
},
Fill::color(Color::ANTIQUE_WHITE),
))
.with_children(|cb| {
cb.spawn(Text2dBundle {
text: Text::from_section(
"Loading...",
TextStyle {
font_size: 30.0,
color: Color::BLACK,
..default()
},
),
text_anchor: Anchor::CenterLeft,
transform: Transform::from_translation(Vec3::Z + Vec3::NEG_Y * 20.0),
..default()
});
});
}
fn clean_loading_screen_bar(mut commands: Commands, bar: Query<Entity, With<LoadingScreenBar>>) {
let entity = bar.single();
commands.entity(entity).despawn_recursive();
}
fn loading_screen_progress(
progress: Option<Res<ProgressCounter>>,
mut last_done: Local<u32>,
mut lb: Query<&mut Transform, With<LoadingScreenBar>>,
) {
if let Some(progress) = progress.map(|counter| counter.progress()) {
if progress.done > *last_done {
let mut lbt = lb.single_mut();
*last_done = progress.done;
info!("Changed progress: {:?}", progress);
lbt.scale.x = progress.done as f32 / progress.total as f32;
}
}
}
fn fail_boot(mut commands: Commands) {
commands.spawn(Text2dBundle {
text: Text::from_section(
"Something failed to load",
TextStyle {
font_size: 30.0,
color: Color::RED,
..default()
},
),
..default()
});
}
#[derive(Resource, AssetCollection)]
struct AudioLoadTest {
#[asset(path = "mfvart 2023-12-19 2023-12-19 1344.flac")]
test_audio: Handle<AudioSource>,
}
fn test_audio_loop(audio: Res<Audio>, audio_load_test: Res<AudioLoadTest>) {
audio
.play(audio_load_test.test_audio.clone())
.with_volume(0.3)
.looped();
}
#[derive(Clone, PartialEq, Eq, Debug, Hash, Default, States)]
enum GameState {
#[default]
Booting,
FailBoot,
StartRun,
InRun,
}
#[derive(Resource, Default)]
struct You(Option<ulid::Ulid>);
impl You {
pub fn new() -> Self {
Self(Some(ulid::Ulid::new()))
}
}
fn new_run_new_you(mut you: ResMut<You>) {
*you = You::new();
}
#[derive(AssetCollection, Resource)]
struct PlayerAssets {
#[asset(path = "dum_player.png")]
texture: Handle<Image>,
#[asset(texture_atlas_layout(tile_size_x = 32., tile_size_y = 48., columns = 4, rows = 8))]
layout: Handle<TextureAtlasLayout>,
}
fn update_settings(
mut settings: Query<&mut post_process::ChromaticAberattionSettings>,
time: Res<Time>,
) {
for mut setting in &mut settings {
let mut intensity = time.elapsed_seconds().sin();
intensity = intensity.sin();
intensity = intensity * 0.5 + 0.5;
intensity *= 0.015;
setting.intensity = intensity;
let angle = Vec2::from_angle(std::f32::consts::FRAC_PI_3 * time.delta_seconds());
setting.red_offset = angle.rotate(setting.red_offset);
}
}
pub(crate) fn setup_level(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(LdtkWorldBundle {
ldtk_handle: asset_server.load("test_map.ldtk"),
..Default::default()
});
}