144 lines
3.8 KiB
Rust
144 lines
3.8 KiB
Rust
use crate::{
|
|
canvas::CanvasPlugin,
|
|
fruit::FruitPlugin,
|
|
grid::{SEGMENT_SIZE, SIZE},
|
|
level::{LevelPlugin, LevelState},
|
|
snake::SnakePlugin,
|
|
ui::UiPlugin,
|
|
};
|
|
use bevy::{prelude::*, render::camera::ScalingMode, window::WindowResolution};
|
|
use bevy_asset_loader::prelude::*;
|
|
use bevy_tweening::TweeningPlugin;
|
|
use std::time::Duration;
|
|
|
|
#[cfg(debug_assertions)]
|
|
use bevy_editor_pls::prelude::*;
|
|
|
|
mod audio;
|
|
mod canvas;
|
|
mod fruit;
|
|
mod grid;
|
|
mod level;
|
|
mod snake;
|
|
mod ui;
|
|
|
|
const ASPECT_RATIO: f32 = 16. / 9.;
|
|
const WINDOW_WIDTH: f32 = WINDOW_HEIGHT * ASPECT_RATIO;
|
|
const WINDOW_HEIGHT: f32 = 720.;
|
|
const TICK_PERIOD: Duration = Duration::from_millis(125);
|
|
|
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, States)]
|
|
enum AppState {
|
|
MainMenu,
|
|
InGame(LevelState),
|
|
}
|
|
|
|
impl Default for AppState {
|
|
fn default() -> Self {
|
|
if cfg!(debug_assertions) {
|
|
Self::InGame(LevelState::Begin)
|
|
} else {
|
|
Self::MainMenu
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct AppStateIter {
|
|
current: Option<AppState>,
|
|
}
|
|
|
|
impl Iterator for AppStateIter {
|
|
type Item = AppState;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
let next_state = match self.current {
|
|
None => Some(AppState::MainMenu),
|
|
Some(AppState::MainMenu) => Some(AppState::InGame(LevelState::Begin)),
|
|
Some(AppState::InGame(level_state)) => match level_state {
|
|
LevelState::Begin => Some(AppState::InGame(LevelState::InGame)),
|
|
LevelState::InGame => Some(AppState::InGame(LevelState::End)),
|
|
LevelState::End => None,
|
|
},
|
|
};
|
|
|
|
if let Some(next_state) = next_state {
|
|
self.current = Some(next_state);
|
|
}
|
|
|
|
next_state
|
|
}
|
|
}
|
|
|
|
#[derive(Resource)]
|
|
struct Score(u32);
|
|
|
|
fn main() {
|
|
let mut app = App::new();
|
|
|
|
app.insert_resource(ClearColor(Color::BLACK))
|
|
.insert_resource(Time::<Fixed>::from_duration(TICK_PERIOD))
|
|
.insert_resource(Score(0))
|
|
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
|
primary_window: Some(Window {
|
|
title: "Bevy-Snake".into(),
|
|
resizable: true,
|
|
resolution: WindowResolution::new(WINDOW_WIDTH, WINDOW_HEIGHT),
|
|
..Default::default()
|
|
}),
|
|
..Default::default()
|
|
}))
|
|
.init_collection::<audio::Assets>()
|
|
.init_state::<AppState>()
|
|
.add_plugins((
|
|
TweeningPlugin,
|
|
SnakePlugin,
|
|
FruitPlugin,
|
|
CanvasPlugin,
|
|
UiPlugin,
|
|
LevelPlugin,
|
|
))
|
|
.add_systems(Startup, setup_camera_system)
|
|
.add_systems(Update, update_score_system);
|
|
|
|
#[cfg(debug_assertions)]
|
|
{
|
|
app.add_plugins(EditorPlugin::new())
|
|
.add_systems(Update, (bevy::window::close_on_esc, pause_system));
|
|
}
|
|
|
|
app.run();
|
|
}
|
|
|
|
fn setup_camera_system(mut commands: Commands) {
|
|
let grid_dimensions = f32::from(SIZE) * SEGMENT_SIZE * 1.2;
|
|
|
|
commands
|
|
.spawn(Camera2dBundle {
|
|
projection: OrthographicProjection {
|
|
scaling_mode: ScalingMode::AutoMin {
|
|
min_width: grid_dimensions,
|
|
min_height: grid_dimensions,
|
|
},
|
|
near: -1000.,
|
|
..Default::default()
|
|
},
|
|
..Default::default()
|
|
})
|
|
.insert(Name::new("Orthographic Camera"));
|
|
}
|
|
|
|
fn update_score_system(mut score: ResMut<Score>, mut eaten_event: EventReader<fruit::EatenEvent>) {
|
|
score.0 += eaten_event.read().count() as u32;
|
|
}
|
|
|
|
fn pause_system(keypress: Res<ButtonInput<KeyCode>>, mut time: ResMut<Time<Virtual>>) {
|
|
if keypress.just_pressed(KeyCode::KeyP) {
|
|
if time.is_paused() {
|
|
time.unpause()
|
|
} else {
|
|
time.pause()
|
|
}
|
|
}
|
|
}
|