Closed kristianmandrup closed 2 years ago
I figured it out in the end by using clone
no ideal I guess?
#[derive(Debug, Clone)]
struct CameraMovement {
forward: Vec3,
left: Vec3,
speed: f32,
rotate_speed: f32
}
impl CameraMovement {
fn new(camera: Transform) -> CameraMovement {
CameraMovement {
forward: CameraMovement::create_forward(camera),
left: CameraMovement::create_left(camera),
speed: 4.0,
rotate_speed: 0.4
}
}
fn create_left(camera: Transform) -> Vec3 {
let mut left = camera.left();
left.y = 0.0;
left = left.normalize();
left
}
fn create_forward(camera: Transform) -> Vec3 {
let mut forward = camera.forward();
forward.y = 0.0;
forward = forward.normalize();
forward
}
}
fn camera_controls(
keyboard: Res<Input<KeyCode>>,
mut camera_query: Query<&mut Transform, With<Camera3d>>,
time: Res<Time>,
) {
let mut camera = camera_query.single_mut();
let movement = CameraMovement::new(camera.clone());
camera_keyboard_control(&mut camera, movement, keyboard, time);
}
#[derive(Debug, Clone)]
struct Movement {
movement: CameraMovement,
time: Time
}
impl Movement {
fn new(movement: CameraMovement, time: Time) -> Self {
Movement {
movement,
time
}
}
fn delta(&self) -> f32 {
self.time.delta_seconds()
}
fn delta_speed(&self) -> f32 {
self.delta() * self.movement.speed
}
fn forward(&self) -> Vec3 {
self.movement.forward * self.delta_speed()
}
fn left(&self) -> Vec3 {
self.movement.left * self.delta_speed()
}
fn angle_rotate(&self) -> f32 {
self.movement.rotate_speed * self.delta()
}
}
fn camera_keyboard_control(camera: &mut Mut<Transform>, movement: CameraMovement, keyboard: Res<Input<KeyCode>>, time: Res<Time>) {
let mov: Movement = Movement::new(movement.clone(), time.clone());
if keyboard.pressed(KeyCode::W) {
camera.translation += mov.forward();
}
if keyboard.pressed(KeyCode::S) {
camera.translation -= mov.forward();
}
if keyboard.pressed(KeyCode::A) {
camera.translation += mov.left();
}
if keyboard.pressed(KeyCode::D) {
camera.translation -= mov.left();
}
if keyboard.pressed(KeyCode::Q) {
camera.rotate_axis(Vec3::Y, mov.angle_rotate())
}
if keyboard.pressed(KeyCode::E) {
camera.rotate_axis(Vec3::Y, -mov.angle_rotate())
}
}
I figured it out in the end...
#[derive(Debug, Clone)]
struct Movement<'a, 'b> {
movement: &'a CameraMovement,
time: &'b Time
}
impl<'a, 'b> Movement<'a, 'b> {
fn new(movement: &'a CameraMovement, time: &'b Time) -> Self {
Movement {
movement,
time
}
}
fn delta(&self) -> f32 {
self.time.delta_seconds()
}
fn delta_speed(&self) -> f32 {
self.delta() * self.movement.speed
}
fn forward(&self) -> Vec3 {
self.movement.forward * self.delta_speed()
}
fn left(&self) -> Vec3 {
self.movement.left * self.delta_speed()
}
fn angle_rotate(&self) -> f32 {
self.movement.rotate_speed * self.delta()
}
}
fn camera_keyboard_control(camera: &mut Mut<Transform>, movement: CameraMovement, keyboard: Res<Input<KeyCode>>, time: Res<Time>) {
// let m2 = M2::new(&movement, &time);
let mov: Movement = Movement::new(&movement, &time);
if keyboard.pressed(KeyCode::W) {
camera.translation += mov.forward();
}
if keyboard.pressed(KeyCode::S) {
camera.translation -= mov.forward();
}
if keyboard.pressed(KeyCode::A) {
camera.translation += mov.left();
}
if keyboard.pressed(KeyCode::D) {
camera.translation -= mov.left();
}
if keyboard.pressed(KeyCode::Q) {
camera.rotate_axis(Vec3::Y, mov.angle_rotate())
}
if keyboard.pressed(KeyCode::E) {
camera.rotate_axis(Vec3::Y, -mov.angle_rotate())
}
}
Helped to watch a few tutorials on it like this one: https://www.youtube.com/watch?v=rAl-9HwD858
Still not sure about how to properly use Res
, Mut
, ResMut
etc. however. Please do a tutorial on that ;)
What is the best approach to structure Bevy ECS functionality in general?
I'm running into loads of issues when trying to break up your complex functions such as tower_shooting
I almost solved it and reduced the complexity:
impl Plugin for TowerPlugin {
fn build<'a>(&self, app: &mut App) {
app.register_type::<Tower>()
.add_system(tower_shooting)
.add_system(tower_button_clicked)
.add_system(create_ui_on_selection);
}
fn name(&self) -> &str {
std::any::type_name::<Self>()
}
}
#[derive(Copy, Clone)]
struct TowerCtx<'a> {
tower_ent: Entity,
tower: &'a Tower,
tower_type: &'a TowerType,
transform: &'a GlobalTransform
}
impl<'a> TowerCtx<'a> {
fn new(tower_ent: Entity,
tower: &'a Tower,
tower_type: &'a TowerType,
transform: &'a GlobalTransform) -> Self {
TowerCtx {
tower_ent,
tower,
tower_type,
transform
}
}
}
struct TowerShooter<'a> {
commands: &'a mut Commands<'a, 'a>,
time: &'a Time,
ctx: Option<TowerCtx<'a>>
}
impl<'a> TowerShooter<'a> {
fn new(commands: &'a mut Commands<'a, 'a>, time: &'a Time, ) -> Self {
TowerShooter {
time,
commands,
ctx: None
}
}
fn setCtx(&mut self, ctx: &'a TowerCtx<'a>) {
self.ctx = Some(*ctx)
}
fn shoot_from(&self, tower: &Tower, commands: &mut Commands, direction: Option<Vec3>, bullet_assets: &GameAssets) {
if let Some(direction) = direction {
let ctx = self.ctx.unwrap();
let tower_type = ctx.tower_type;
let tower_ent = ctx.tower_ent;
let (model, bullet) = tower_type.get_bullet(direction, &bullet_assets);
commands.entity(tower_ent).with_children(|commands| {
commands
.spawn_bundle(SceneBundle {
scene: model,
transform: Transform::from_translation(tower.bullet_offset),
..Default::default()
})
.insert(Lifetime {
timer: Timer::from_seconds(10.0, false),
})
.insert(bullet)
.insert(Name::new("Bullet"));
});
}
else { return };
}
}
fn get_bullet_spawn(transform: &GlobalTransform, tower: &Tower) -> Vec3 {
transform.translation() + tower.bullet_offset
}
fn get_direction(targets: &Query<&GlobalTransform, With<Target>>, bullet_spawn: Vec3) -> Option<Vec3> {
targets
.iter()
.min_by_key(|target_transform| {
FloatOrd(Vec3::distance(target_transform.translation(), bullet_spawn))
})
.map(|closest_target| closest_target.translation() - bullet_spawn)
}
fn tower_shooting<'a>(
mut commands: Commands<'a, 'a>,
mut towers: Query<(Entity, &mut Tower, &TowerType, &GlobalTransform)>,
targets: Query<&GlobalTransform, With<Target>>,
bullet_assets: Res<GameAssets>,
time: Res<Time>,
) {
let tower_shooter = TowerShooter::new(&mut commands, &time);
for (tower_ent, tower, tower_type, transform) in &mut towers {
let ctx = TowerCtx::new(tower_ent, &tower, tower_type, transform);
let bullet_spawn = get_bullet_spawn(transform, &tower);
let direction = get_direction(&targets, bullet_spawn);
tower_shooter.setCtx(&ctx);
tower.as_ref().shooting_timer.tick(time.delta());
if tower.shooting_timer.just_finished() {
tower_shooter.shoot_from(&tower, &mut commands, direction, &bullet_assets)
}
}
}
Is this the right approach or is there a simpler way?
It only produces this single error which I don't understand:
--> src/tower.rs:28:25
|
28 | .add_system(tower_shooting)
| ---------- ^^^^^^^^^^^^^^ the trait `IntoSystem<(), (), _>` is not implemented for fn item `for<'a, 'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6, 't7> fn(bevy::prelude::Commands<'a, 'a>, bevy::prelude::Query<'r, 's, (bevy::prelude::Entity, &'t0 mut Tower, &'t1 tower::TowerType, &'t2 bevy::prelude::GlobalTransform)>, bevy::prelude::Query<'t3, 't4, &'t5 bevy::prelude::GlobalTransform, bevy::prelude::With<target::Target>>, bevy::prelude::Res<'t6, GameAssets>, bevy::prelude::Res<'t7, bevy::prelude::Time>) {tower_shooting}`
| |
| required by a bound introduced by this call
|
= help: the following other types implement trait `IntoSystemDescriptor<Params>`:
ExclusiveSystemCoerced
ExclusiveSystemDescriptor
ExclusiveSystemFn<F>
ParallelSystemDescriptor
SystemDescriptor
std::boxed::Box<(dyn bevy::prelude::System<Out = (), In = ()> + 'static)>
= note: required for `for<'a, 'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6, 't7> fn(bevy::prelude::Commands<'a, 'a>, bevy::prelude::Query<'r, 's, (bevy::prelude::Entity, &'t0 mut Tower, &'t1 tower::TowerType, &'t2 bevy::prelude::GlobalTransform)>, bevy::prelude::Query<'t3, 't4, &'t5 bevy::prelude::GlobalTransform, bevy::prelude::With<target::Target>>, bevy::prelude::Res<'t6, GameAssets>, bevy::prelude::Res<'t7, bevy::prelude::Time>) {tower_shooting}` to implement `IntoSystemDescriptor<_>`
note: required by a bound in `bevy::prelude::App::add_system`
--> /Users/kristian/.cargo/registry/src/github.com-1ecc6299db9ec823/bevy_app-0.8.0/src/app.rs:330:55
|
330 | pub fn add_system<Params>(&mut self, system: impl IntoSystemDescriptor<Params>) -> &mut Self {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `bevy::prelude::App::add_system`
For more information about this error, try `rustc --explain E0277`
I can remove the lifetime params for Commands<...> to simplify it to this, but then it complains that it expects 2 lifetime parameters
struct TowerShooter<'a> {
commands: &'a mut Commands,
time: &'a Time,
ctx: Option<TowerCtx<'a>>
}
impl<'a> TowerShooter<'a> {
fn new(commands: &'a mut Commands, time: &'a Time, ) -> Self {
fn tower_shooting(
mut commands: Commands,
mut towers: Query<(Entity, &mut Tower, &TowerType, &GlobalTransform)>,
targets: Query<&GlobalTransform, With<Target>>,
bullet_assets: Res<GameAssets>,
time: Res<Time>,
) {
Error
--> src/tower.rs:60:23
|
60 | commands: &'a mut Commands,
| ^^^^^^^^ expected 2 lifetime parameters
pub struct Commands<'w, 's> {
queue: &'s mut CommandQueue,
entities: &'w Entities,
}
impl<'w, 's> Commands<'w, 's> {
I've been through various lifetime tutorials but still doesn't quite make sense to me
I finally managed it :)
struct TowerShooter<'a> {
entity: Entity,
tower_type: &'a TowerType,
transform: &'a GlobalTransform
}
impl<'a> TowerShooter<'a> {
fn new(entity: Entity, tower_type: &'a TowerType, transform: &'a GlobalTransform) -> Self {
TowerShooter {
entity,
tower_type,
transform
}
}
fn get_bullet_spawn(&self, tower: &Tower) -> Vec3 {
self.transform.translation() + tower.bullet_offset
}
fn get_direction(&self, tower: &Tower, targets: &Query<&GlobalTransform, With<Target>>) -> Option<Vec3> {
let bullet_spawn: Vec3 = self.get_bullet_spawn(tower);
targets
.iter()
.min_by_key(|target_transform| {
FloatOrd(Vec3::distance(target_transform.translation(), bullet_spawn))
})
.map(|closest_target| closest_target.translation() - bullet_spawn)
}
fn shoot_from(&self, commands: &mut Commands, tower: &Tower, targets: &Query<&GlobalTransform, With<Target>>, bullet_assets: &GameAssets) {
if let Some(direction) = self.get_direction(tower, targets) {
self.shoot_direction(commands, tower, direction, bullet_assets)
}
else { return };
}
fn shoot_direction(&self, commands: &mut Commands, tower: &Tower, direction: Vec3, bullet_assets: &GameAssets) {
let (model, bullet) = self.tower_type.get_bullet(direction, &bullet_assets);
self.spawn_bullet(commands, tower, model, bullet)
}
fn spawn_bullet(&self, commands: &mut Commands, tower: &Tower, model: Handle<Scene>, bullet: Bullet) {
commands.entity(self.entity).with_children(|commands| {
commands
.spawn_bundle(SceneBundle {
scene: model,
transform: Transform::from_translation(tower.bullet_offset),
..Default::default()
})
.insert(Lifetime {
timer: Timer::from_seconds(10.0, false),
})
.insert(bullet)
.insert(Name::new("Bullet"));
});
}
}
fn tower_shooting(
mut commands: Commands,
mut towers: Query<(Entity, &mut Tower, &TowerType, &GlobalTransform)>,
targets: Query<&GlobalTransform, With<Target>>,
bullet_assets: Res<GameAssets>,
time: Res<Time>,
) {
for (entity, mut tower, tower_type, transform) in &mut towers {
let tower_shooter = TowerShooter::new( entity, &tower_type, &transform);
tower.shooting_timer.tick(time.delta());
if tower.shooting_timer.just_finished() {
tower_shooter.shoot_from( &mut commands, &tower, &targets, &bullet_assets)
}
}
}
My only frustration is that I was not able to set mutable Commands or Tower on the TowerShooter instance, so I have to pass these around to each method instead. Would love to know a better approach.
I thought I could still find a way to have multiple references to commands, but discovered it was Sync
used across multiple threads and that even Arc/Mutex were removed for Commands - https://github.com/bevyengine/bevy/issues/795
I believe the correct approach you hinted at would be to create a new TowerShooter
component in each loop iteration and then a new system with a Query
to select TowerShooter
components and handle each and set up spawning of bullets. I could then add a Bullet
component and system as well, turtles all the way down...
Resolved via discord discussions
Hi,
Thank you so much for your youtube tutorial series and code. I've been trying to work on the survival game and refactor it to be encapsulated in a
CameraMovement
struct and acamera_keyboard_control
function for the keyboard control. I initially got some lifetime errors, but now it compiles just fine. However the functionality is broken, pressing keyboard controls doesn't move the camera. I can see it printing each time I pressw
andcamera.translation
andtranslation
change each time...I'm pretty sure it is due to not using
Mut<Transform>
inlet mut camera: Transform = *movement.camera;
but I couldn't make it compile otherwise.I also tried to use type aliases for
Keyboard
andCamera
types but ran into similar lifetime issues. Struggling a bit with how to use the BevyMut
type correctly.