-
-
Notifications
You must be signed in to change notification settings - Fork 63
/
Copy pathecs.rs
192 lines (156 loc) · 5.22 KB
/
ecs.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
//! This example demonstrates how an ECS library can be integrated into Tetra,
//! by porting the logic from the 'bunnymark' example into systems.
//!
//! The below code uses Legion, but other libraries (such as Specs and Hecs)
//! should work too.
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 {
// A common issue people run into when trying to use ECS libraries (and multi-threading
// in general) with Tetra is how to handle the fact that the Context cannot be
// accessed from other threads.
//
// One way to deal with this is to decouple your game logic from the raw input. In this
// example, we store the state of the player's input into a resource, and then read
// that in the systems. This has the additional benefit of making it very easy to
// change input bindings/add input configuration later on!
//
// Similarly, if you want to access things like the delta time inside a system, write
// them into a resource before running your system scheduler.
{
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));
// Similar to the above issue with input, rendering can also be a bit tricky
// when using an ECS library, due to the relevant types not being thread-safe.
//
// The easiest way to work around this is to just render outside of the ECS
// systems, where you have full access to the Context.
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)
}