Initial commit
This commit is contained in:
26
.cargo/config.toml
Normal file
26
.cargo/config.toml
Normal 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
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/target
|
||||
.vscode
|
||||
|
||||
3246
Cargo.lock
generated
Normal file
3246
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
19
Cargo.toml
Normal file
19
Cargo.toml
Normal 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
2
rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
65
src/grid.rs
Normal file
65
src/grid.rs
Normal 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
103
src/main.rs
Normal 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
188
src/snake.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user