1use ponko::*;
11
12struct Vel(Vec2);
14struct Life(f32);
16
17struct Settings {
19 rng: u64,
20 crab: Texture,
21}
22
23impl Settings {
24 fn random(&mut self) -> f32 {
25 self.rng ^= self.rng << 13;
26 self.rng ^= self.rng >> 7;
27 self.rng ^= self.rng << 17;
28 (self.rng >> 40) as f32 / (1u64 << 24) as f32
29 }
30}
31
32const LIFETIME: f32 = 8.0;
33
34fn spawn_crab(ctx: &mut Ctx, at: Vec2) {
35 let s = ctx.world.resource_mut::<Settings>();
36 let angle = s.random() * std::f32::consts::TAU;
37 let speed = 40.0 + s.random() * 120.0;
38 let tint = Color::rgb(0.5 + s.random() * 0.5, 0.5 + s.random() * 0.5, 0.5 + s.random() * 0.5);
39 let crab = s.crab;
40 ctx.world.spawn((
41 Transform2D::at(at),
42 Sprite::new(crab).with_color(tint),
43 Vel(Vec2::from_angle(angle) * speed),
44 Life(LIFETIME),
45 ));
46}
47
48fn movement(ctx: &mut Ctx) {
50 let dt = ctx.dt();
51 let bounds = ctx.window_size();
52 ctx.world.query::<(&mut Transform2D, &mut Vel)>().for_each(|(t, v)| {
53 t.position += v.0 * dt;
54 t.rotation = v.0.to_angle();
55 if t.position.x < 0.0 || t.position.x > bounds.x {
56 v.0.x = -v.0.x;
57 }
58 if t.position.y < 0.0 || t.position.y > bounds.y {
59 v.0.y = -v.0.y;
60 }
61 t.position = t.position.clamp(Vec2::ZERO, bounds);
62 });
63}
64
65fn aging(ctx: &mut Ctx) {
67 let dt = ctx.dt();
68 let commands = &mut ctx.commands;
69 ctx.world
70 .query::<(Entity, &mut Life, &mut Sprite)>()
71 .for_each(|(e, life, sprite)| {
72 life.0 -= dt;
73 sprite.color = sprite.color.with_alpha((life.0 / LIFETIME).clamp(0.0, 1.0));
74 if life.0 <= 0.0 {
75 commands.despawn(e);
76 }
77 });
78}
79
80fn controls(ctx: &mut Ctx) {
82 if ctx.key_pressed(Key::Space) {
83 if ctx.is_paused() { ctx.resume() } else { ctx.pause() }
84 }
85 if ctx.key_pressed(Key::Period) {
86 ctx.step();
87 }
88 for (key, speed) in [(Key::Num1, 1.0), (Key::Num2, 4.0), (Key::Num3, 16.0), (Key::Num4, 64.0)] {
89 if ctx.key_pressed(key) {
90 ctx.set_sim_speed(speed);
91 }
92 }
93 if ctx.mouse_down(MouseButton::Left) {
94 let at = ctx.mouse_pos();
95 for _ in 0..5 {
96 spawn_crab(ctx, at);
97 }
98 }
99 if ctx.key_pressed(Key::Escape) {
100 ctx.quit();
101 }
102}
103
104fn main() {
105 Game::new("Crabby Engine: ECS", 1280, 720)
106 .sim_system(movement)
107 .sim_system(aging)
108 .system(controls)
109 .run(|ctx| {
110 let crab = ctx.load_texture("crab.png");
111 ctx.world.insert_resource(Settings {
112 rng: 0x9E37_79B9_7F4A_7C15,
113 crab,
114 });
115 let center = ctx.window_size() / 2.0;
116 for _ in 0..500 {
117 spawn_crab(ctx, center);
118 }
119
120 let mut next_report = 1.0;
122 move |ctx: &mut Ctx| {
123 if ctx.time() >= next_report {
124 next_report += 1.0;
125 let sim = if ctx.is_paused() {
126 "paused".to_string()
127 } else {
128 format!("{}x", ctx.sim_speed())
129 };
130 println!(
131 "crabs: {:>6} fps: {:>5.1} sim: {sim:>6} tick: {}",
132 ctx.world.len(),
133 ctx.fps(),
134 ctx.sim_tick()
135 );
136 }
137 }
138 });
139}