use legion::systems::CommandBuffer;
use legion::*;
use rand::rngs::StdRng;
use rand::{self, Rng, SeedableRng};
use tetra::graphics::{self, Color, Texture};
use tetra::input::{self, Key, MouseButton};
use tetra::math::Vec2;
use tetra::time;
use tetra::window;
use tetra::{Context, ContextBuilder, State};
const INITIAL_BUNNIES: usize = 100;
const MAX_X: f32 = 1280.0 - 26.0;
const MAX_Y: f32 = 720.0 - 37.0;
const GRAVITY: f32 = 0.5;
struct Position(Vec2<f32>);
struct Velocity(Vec2<f32>);
struct Input {
clicked: bool,
auto_spawn: bool,
}
struct SpawnTimer(i32);
fn create_bunny(rng: &mut StdRng) -> (Position, Velocity) {
let x_vel = rng.gen::<f32>() * 5.0;
let y_vel = (rng.gen::<f32>() * 5.0) - 2.5;
(Position(Vec2::zero()), Velocity(Vec2::new(x_vel, y_vel)))
}
#[system]
fn spawn_bunnies(
cmd: &mut CommandBuffer,
#[resource] input: &Input,
#[resource] SpawnTimer(spawn_timer): &mut SpawnTimer,
#[resource] rng: &mut StdRng,
) {
if *spawn_timer > 0 {
*spawn_timer -= 1;
}
if *spawn_timer == 0 && (input.clicked || input.auto_spawn) {
for _ in 0..INITIAL_BUNNIES {
cmd.push(create_bunny(rng));
}
*spawn_timer = 10;
}
}
#[system(for_each)]
fn update_positions(
Position(position): &mut Position,
Velocity(velocity): &mut Velocity,
#[resource] rng: &mut StdRng,
) {
*position += *velocity;
velocity.y += GRAVITY;
if position.x > MAX_X {
velocity.x *= -1.0;
position.x = MAX_X;
} else if position.x < 0.0 {
velocity.x *= -1.0;
position.x = 0.0;
}
if position.y > MAX_Y {
velocity.y *= -0.8;
position.y = MAX_Y;
if rng.gen::<bool>() {
velocity.y -= 3.0 + (rng.gen::<f32>() * 4.0);
}
} else if position.y < 0.0 {
velocity.y = 0.0;
position.y = 0.0;
}
}
struct GameState {
world: World,
schedule: Schedule,
resources: Resources,
texture: Texture,
}
impl GameState {
fn new(ctx: &mut Context) -> tetra::Result<Self> {
let mut rng = StdRng::from_entropy();
let mut world = World::default();
for _ in 0..INITIAL_BUNNIES {
world.push(create_bunny(&mut rng));
}
let mut resources = Resources::default();
resources.insert(rng);
resources.insert(Input {
clicked: false,
auto_spawn: false,
});
resources.insert(SpawnTimer(0));
Ok(GameState {
world,
resources,
schedule: Schedule::builder()
.add_system(spawn_bunnies_system())
.add_system(update_positions_system())
.build(),
texture: Texture::new(ctx, "./examples/resources/wabbit_alpha.png")?,
})
}
}
impl State for GameState {
fn update(&mut self, ctx: &mut Context) -> tetra::Result {
{
let mut input = self.resources.get_mut::<Input>().unwrap();
input.clicked = input::is_mouse_button_down(ctx, MouseButton::Left);
if input::is_key_pressed(ctx, Key::A) {
input.auto_spawn = !input.auto_spawn;
}
}
self.schedule.execute(&mut self.world, &mut self.resources);
Ok(())
}
fn draw(&mut self, ctx: &mut Context) -> tetra::Result {
graphics::clear(ctx, Color::rgb(0.392, 0.584, 0.929));
let mut bunnies = <&Position>::query();
for position in bunnies.iter(&self.world) {
self.texture.draw(ctx, position.0);
}
window::set_title(
ctx,
&format!(
"ECS BunnyMark - {} bunnies - {:.0} FPS",
self.world.len(),
time::get_fps(ctx)
),
);
Ok(())
}
}
fn main() -> tetra::Result {
ContextBuilder::new("ECS BunnyMark", 1280, 720)
.quit_on_escape(true)
.build()?
.run(GameState::new)
}