Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor of camera control/movement and tower shooting #2

Closed
kristianmandrup opened this issue Nov 12, 2022 · 8 comments
Closed

Refactor of camera control/movement and tower shooting #2

kristianmandrup opened this issue Nov 12, 2022 · 8 comments

Comments

@kristianmandrup
Copy link

kristianmandrup commented Nov 12, 2022

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 a camera_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 press w and camera.translation and translation change each time...

I'm pretty sure it is due to not using Mut<Transform> in let mut camera: Transform = *movement.camera; but I couldn't make it compile otherwise.

I also tried to use type aliases for Keyboard and Camera types but ran into similar lifetime issues. Struggling a bit with how to use the Bevy Mut type correctly.

struct CameraMovement<'a> {
    camera: &'a Transform,
    forward: Vec3,
    left: Vec3,
    speed: f32,
    rotate_speed: f32

}
impl CameraMovement<'_> {
    fn new(camera: &'_ Transform) -> CameraMovement {
        CameraMovement {
            camera,
            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 camera = camera_query.single_mut();
    let movement = CameraMovement::new(&camera);

    camera_keyboard_control(&movement, keyboard, time);
}
fn camera_keyboard_control(movement: &CameraMovement, keyboard: Res<Input<KeyCode>>, time: Res<Time>) {
    let mut camera: Transform = *movement.camera;
    // print!("keyboard control");
    if keyboard.pressed(KeyCode::W) {
        println!("pressed W");
        let translation = movement.forward * time.delta_seconds() * movement.speed;
        camera.translation += translation;
        println!("cam translation {}, translation {}", camera.translation, translation);
    }
    if keyboard.pressed(KeyCode::S) {
        camera.translation -= movement.forward * time.delta_seconds() * movement.speed;
    }
    if keyboard.pressed(KeyCode::A) {
        camera.translation += movement.left * time.delta_seconds() * movement.speed;
    }
    if keyboard.pressed(KeyCode::D) {
        camera.translation -= movement.left * time.delta_seconds() * movement.speed;
    }
    if keyboard.pressed(KeyCode::Q) {
        camera.rotate_axis(Vec3::Y, movement.rotate_speed * time.delta_seconds())
    }
    if keyboard.pressed(KeyCode::E) {
        camera.rotate_axis(Vec3::Y, -movement.rotate_speed * time.delta_seconds())
    }
    
}
@kristianmandrup
Copy link
Author

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())
    }    
}

@kristianmandrup
Copy link
Author

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 ;)

@kristianmandrup
Copy link
Author

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

image

@kristianmandrup
Copy link
Author

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`

@kristianmandrup
Copy link
Author

kristianmandrup commented Nov 13, 2022

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

@kristianmandrup kristianmandrup changed the title Refactor of camera control and movement Refactor of camera control/movement and tower shooting Nov 13, 2022
@kristianmandrup
Copy link
Author

kristianmandrup commented Nov 13, 2022

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)
        }
    }
}

@kristianmandrup
Copy link
Author

kristianmandrup commented Nov 13, 2022

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 - bevyengine/bevy#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...

@mwbryant
Copy link
Owner

Resolved via discord discussions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants