Initial commit

This commit is contained in:
2022-08-06 23:41:20 +02:00
commit 967887456f
8 changed files with 3652 additions and 0 deletions

26
.cargo/config.toml Normal file
View File

@@ -0,0 +1,26 @@
# Add the contents of this file to `config.toml` to enable "fast build" configuration. Please read the notes below.
# NOTE: For maximum performance, build using a nightly compiler
# If you are using rust stable, remove the "-Zshare-generics=y" below.
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-Clink-arg=-fuse-ld=mold", "-Zshare-generics=y"]
# NOTE: you must manually install https://github.com/michaeleisel/zld on mac. you can easily do this with the "brew" package manager:
# `brew install michaeleisel/zld/zld`
[target.x86_64-apple-darwin]
rustflags = ["-C", "link-arg=-fuse-ld=/usr/local/bin/zld", "-Zshare-generics=y"]
[target.aarch64-apple-darwin]
rustflags = ["-C", "link-arg=-fuse-ld=/opt/homebrew/bin/zld", "-Zshare-generics=y"]
[target.x86_64-pc-windows-msvc]
linker = "rust-lld.exe"
rustflags = ["-Zshare-generics=n"]
# Optional: Uncommenting the following improves compile times, but reduces the amount of debug info to 'line number tables only'
# In most cases the gains are negligible, but if you are on macos and have slow compile times you should see significant gains.
#[profile.dev]
#debug = 1

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/target
.vscode

3246
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

19
Cargo.toml Normal file
View File

@@ -0,0 +1,19 @@
[package]
name = "bevy-snake"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
iyes_loopless = "0.7.0"
itertools = "0.10.3"
# bevy_editor_pls = { git = "https://github.com/jakobhellermann/bevy_editor_pls" }
[dependencies.bevy]
version = "0.8.0"
features = ["dynamic"]
[profile.dev.package."*"]
opt-level = 3

2
rust-toolchain.toml Normal file
View File

@@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"

65
src/grid.rs Normal file
View File

@@ -0,0 +1,65 @@
use bevy::prelude::*;
use std::ops::{Add, AddAssign};
type GridType = i16;
pub const GRID_SIZE: GridType = 21;
pub const GRID_SEGMENT_SIZE: f32 = 20.;
#[derive(Component, Clone, Copy, Default, Debug)]
pub struct GridCoordinate(pub GridType, pub GridType);
impl GridCoordinate {
pub fn splat(v: GridType) -> Self {
Self(v, v)
}
}
impl Add for GridCoordinate {
type Output = Self;
fn add(self, rhs: GridCoordinate) -> Self::Output {
GridCoordinate(self.0 + rhs.0, self.1 + rhs.1)
}
}
impl AddAssign for GridCoordinate {
fn add_assign(&mut self, rhs: Self) {
*self = Self(self.0 + rhs.0, self.1 + rhs.1)
}
}
impl From<GridCoordinate> for Transform {
fn from(grid_coordinate: GridCoordinate) -> Self {
(&grid_coordinate).into()
}
}
impl From<&GridCoordinate> for Transform {
fn from(grid_coordinate: &GridCoordinate) -> Self {
Transform::from_xyz(
(grid_coordinate.0 - GRID_SIZE / 2) as f32 * GRID_SEGMENT_SIZE,
(grid_coordinate.1 - GRID_SIZE / 2) as f32 * GRID_SEGMENT_SIZE,
0.,
)
}
}
pub struct GridPlugin;
impl Plugin for GridPlugin {
fn build(&self, app: &mut App) {
app.add_startup_system(spawn_background);
}
}
fn spawn_background(mut commands: Commands) {
commands.spawn_bundle(SpriteBundle {
sprite: Sprite {
color: Color::DARK_GRAY,
custom_size: Some(Vec2::splat(GRID_SEGMENT_SIZE * GRID_SIZE as f32)),
..Default::default()
},
..Default::default()
});
}

103
src/main.rs Normal file
View File

@@ -0,0 +1,103 @@
use bevy::{prelude::*, render::camera::ScalingMode};
// use bevy_editor_pls::prelude::*;
use grid::{GRID_SEGMENT_SIZE, GRID_SIZE};
use iyes_loopless::prelude::*;
mod grid;
mod snake;
const ASPECT_RATIO: f32 = 16. / 9.;
const WINDOW_WIDTH: f32 = 720. * ASPECT_RATIO;
const WINDOW_HEIGHT: f32 = 720.;
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
enum AppState {
Begin,
InGame,
Paused,
End,
}
fn main() {
App::new()
.insert_resource(ClearColor(Color::rgb_u8(12, 12, 12)))
.insert_resource(WindowDescriptor {
title: "Bevy-Snake".into(),
resizable: true,
width: WINDOW_WIDTH,
height: WINDOW_HEIGHT,
..Default::default()
})
.add_loopless_state(AppState::Begin)
.add_plugins(DefaultPlugins)
// .add_plugin(EditorPlugin)
.add_plugin(snake::SnakePlugin)
.add_plugin(grid::GridPlugin)
.add_startup_system(setup_system)
.add_system(camera_move_system)
.add_system(bevy::window::close_on_esc)
.add_system(pause_system)
.run();
}
fn setup_system(mut commands: Commands) {
let vertical_height = GRID_SIZE as f32 * GRID_SEGMENT_SIZE * 1.2;
commands
.spawn_bundle(Camera2dBundle {
projection: OrthographicProjection {
scaling_mode: ScalingMode::FixedVertical(vertical_height),
..Default::default()
},
..Default::default()
})
.insert(Name::new("Orthographic Camera"));
}
fn camera_move_system(
keypress: Res<Input<KeyCode>>,
mut query: Query<&mut Transform, With<Camera>>,
) {
if !keypress.pressed(KeyCode::LControl) {
return;
}
let delta = {
let mut delta = Vec3::ZERO;
if keypress.pressed(KeyCode::Up) {
delta += Vec3::new(0., 1., 0.);
}
if keypress.pressed(KeyCode::Down) {
delta += Vec3::new(0., -1., 0.);
}
if keypress.pressed(KeyCode::Left) {
delta += Vec3::new(-1., 0., 0.);
}
if keypress.pressed(KeyCode::Right) {
delta += Vec3::new(1., 0., 0.);
}
delta
};
query.for_each_mut(|mut transform| {
transform.translation += delta * 0.1;
});
}
fn pause_system(
mut commands: Commands,
keypress: Res<Input<KeyCode>>,
state: Res<CurrentState<AppState>>,
) {
if keypress.just_pressed(KeyCode::P) {
let next_state = match state.0 {
AppState::InGame => AppState::Paused,
AppState::Paused => AppState::InGame,
_ => return,
};
commands.insert_resource(NextState(next_state));
}
}

188
src/snake.rs Normal file
View File

@@ -0,0 +1,188 @@
use crate::{
grid::{GridCoordinate, GRID_SEGMENT_SIZE, GRID_SIZE},
AppState,
};
use bevy::prelude::*;
use itertools::Itertools;
use iyes_loopless::prelude::*;
use std::time::Duration;
#[derive(Debug, Clone, PartialEq, Eq, Hash, StageLabel)]
struct FixedTimeStage;
pub struct SnakePlugin;
impl Plugin for SnakePlugin {
fn build(&self, app: &mut App) {
let mut fixed_time_systems0 = SystemStage::parallel();
fixed_time_systems0.add_system(segments_movement_system.run_in_state(AppState::InGame));
let mut fixed_time_systems1 = SystemStage::parallel();
fixed_time_systems1.add_system(head_movement_system.run_in_state(AppState::InGame));
app.add_startup_system(setup_snake_system)
.add_stage_before(
CoreStage::Update,
FixedTimeStage,
FixedTimestepStage::new(Duration::from_millis(100))
.with_stage(fixed_time_systems0)
.with_stage(fixed_time_systems1),
)
.add_system(add_direction_system.run_in_state(AppState::Begin))
.add_system(direction_system.run_in_state(AppState::InGame))
.add_system(grid_transform_system)
.add_system(add_tail_system);
}
}
#[derive(Component)]
struct SnakeSegment;
#[derive(Component)]
struct SnakeHead;
#[derive(Component)]
enum Direction {
Up,
Down,
Left,
Right,
}
#[derive(Component)]
struct Snake;
#[derive(Component)]
struct SnakeSegments(Vec<Entity>);
impl SnakeSegments {
fn with_head(snake_head: Entity) -> Self {
Self(vec![snake_head])
}
}
impl Direction {
fn from_keypress(keypress: Res<Input<KeyCode>>) -> Option<Self> {
if keypress.pressed(KeyCode::Up) {
Some(Direction::Up)
} else if keypress.pressed(KeyCode::Down) {
Some(Direction::Down)
} else if keypress.pressed(KeyCode::Left) {
Some(Direction::Left)
} else if keypress.pressed(KeyCode::Right) {
Some(Direction::Right)
} else {
None
}
}
}
fn create_snake_segment(commands: &mut Commands, grid_position: GridCoordinate) -> Entity {
commands
.spawn_bundle(SpriteBundle {
sprite: Sprite {
color: Color::RED,
custom_size: Some(Vec2::splat(GRID_SEGMENT_SIZE)*0.9),
..Default::default()
},
transform: grid_position.into(),
..Default::default()
})
.insert(SnakeSegment)
.insert(grid_position)
.id()
}
fn setup_snake_system(mut commands: Commands) {
let snake_head = create_snake_segment(&mut commands, GridCoordinate::splat(GRID_SIZE / 2));
commands
.entity(snake_head)
.insert(Name::new("SnakeHead"))
.insert(SnakeHead);
commands
.spawn()
.insert(Snake)
.insert(SnakeSegments::with_head(snake_head))
.insert(Name::new("Snake"))
.insert_bundle(SpatialBundle::default())
.add_child(snake_head);
}
fn add_direction_system(
mut commands: Commands,
keypress: Res<Input<KeyCode>>,
mut query: Query<Entity, With<SnakeHead>>,
) {
if let Some(direction) = Direction::from_keypress(keypress) {
let snake_head = query.single_mut();
commands.entity(snake_head).insert(direction);
commands.insert_resource(NextState(AppState::InGame));
}
}
fn add_tail_system(
mut commands: Commands,
keypress: Res<Input<KeyCode>>,
mut snake_query: Query<(Entity, &mut SnakeSegments)>,
) {
if keypress.just_pressed(KeyCode::Space) {
let segment = create_snake_segment(&mut commands, GridCoordinate::splat(-1));
let (snake, mut snake_segments) = snake_query.single_mut();
snake_segments.0.push(segment);
commands.entity(snake).add_child(segment);
}
}
fn direction_system(
keypress: Res<Input<KeyCode>>,
mut query: Query<&mut Direction, With<SnakeHead>>,
) {
if let Some(new_direction) = Direction::from_keypress(keypress) {
let mut direction = query.single_mut();
match (direction.as_ref(), &new_direction) {
(Direction::Up, Direction::Down)
| (Direction::Down, Direction::Up)
| (Direction::Left, Direction::Right)
| (Direction::Right, Direction::Left) => return,
_ => {}
}
*direction = new_direction;
}
}
fn grid_transform_system(mut query: Query<(&mut Transform, &GridCoordinate), With<SnakeSegment>>) {
for (mut transform, grid_coordinate) in query.iter_mut() {
*transform = grid_coordinate.into();
}
}
fn head_movement_system(mut query: Query<(&mut GridCoordinate, &Direction), With<SnakeHead>>) {
let (mut grid_coordinate, direction) = query.single_mut();
match direction {
Direction::Up => *grid_coordinate += GridCoordinate(0, 1),
Direction::Down => *grid_coordinate += GridCoordinate(0, -1),
Direction::Left => *grid_coordinate += GridCoordinate(-1, 0),
Direction::Right => *grid_coordinate += GridCoordinate(1, 0),
};
}
fn segments_movement_system(
snake_segments_query: Query<&SnakeSegments>,
mut segment_query: Query<&mut GridCoordinate, With<SnakeSegment>>,
) {
for snake_segments in snake_segments_query.iter() {
for (&segment_entity, &previous_segment_entity) in
snake_segments.0.iter().rev().tuple_windows()
{
let previous_coordinate = *segment_query.get(previous_segment_entity).unwrap();
let mut coordinate = segment_query.get_mut(segment_entity).unwrap();
*coordinate = previous_coordinate;
}
}
}