Game Dev Diary

Day 3: Two circles #

I check on A’s progress and independently she created 3 new systems and hooked them up and they work nicely!

fn move_system_up(input: Res<Input<KeyCode>>, mut query: Query<&mut Transform, With<Balloon>>) {
    for mut transform in query.iter_mut() {
        if input.pressed(KeyCode::Up) {
            transform.translation.y += 1.0
        }
    }
}
fn move_system_down(input: Res<Input<KeyCode>>, mut query: Query<&mut Transform, With<Balloon>>) {
    if input.pressed(KeyCode::Down) {
        for mut transform in query.iter_mut() {
            transform.translation.y -= 1.0
        }
    }
}

fn move_system_right(input: Res<Input<KeyCode>>, mut query: Query<&mut Transform, With<Balloon>>) {
    if input.pressed(KeyCode::Right) {
        for mut transform in query.iter_mut() {
            transform.translation.x += 1.0
        }
    }
}

fn move_system_left(input: Res<Input<KeyCode>>, mut query: Query<&mut Transform, With<Balloon>>) {
    if input.pressed(KeyCode::Left) {
        for mut transform in query.iter_mut() {
            transform.translation.x -= 1.0
        }
    }
}

Not bad at all! A bit repetitive but I decide no need to refactor this right now there will be future opportunities to refactor this. Very proud of her for getting these done all by herself!

Next up she suggests implementing an “enemy” type that chases the player, cool. She really likes bullet heavens and who can blame her. This is indeed the kinda building block you want to get to a bullet heavens so let’s go!

I leave her in the driving seat with minimal directions as she creates a new Enemy marker component and setup a new circle similar to the Balloon one but with Enemy instead of Balloon marker.

Then I start this rough drawing

An extremely rough drawing of a 2-dimensional table with 3 columns: transform, balloon and enemy. And 3 rows: 1, 2, 3. All rows have a transform. Only the first row has balloon check marked. Only the second row has enemy check marked. The rest of enemies and balloon cells are completely empty.

I use this to demo some of the ideas around ECS queries, explaining that we want to get access to the second row and the first row but we don’t care about the third row. We only care about the first row because it is marked by balloon. And we only care about the second row because it is marked by enemy, if there were other enemies we’d also want access to them!

And then I give her a chance to formulate that into a query, in words. I nudge a little bit here and there and we eventually reach it! “We want all the Transforms for all the things that implement Balloon immutably. And we want all the Transforms for the all the things that implement Enemy mutably.”

And then I show her how to write that query, we copy the same query as the movement system and remove the input as well as mutable access to the Balloon then we duplicate the query and I let her give each query a distinct name and then we change With<Balloon> to With<Enemy> and we make sure it’s a mutable Transform access:

fn enemy_movement(
    mut query1: Query<&mut Transform, With<Enemy>>,
    query2: Query<&Transform, With<Balloon>>,
)

I then tell her this is a great opportunity for us to try get_single() which we saw in the docs when we were looking at Query a while back, and I show her the if let Rust syntax:

if let Ok(balloon) = query2.get_single() {

And inside of that we can get access to all the transforms exactly how we did before we had access to get_single()


for (mut enemy, mut movement) in query1.iter_mut() {

And inside we can for now just hardcode some direction and we finally get:

fn enemy_movement(
    mut query1: Query<&mut Transform, With<Enemy>>,
    query2: Query<&Transform, With<Balloon>>,
) {
    if let Ok(balloon) = query2.get_single() {
        for mut transform in query1.iter_mut() {
            transform.translation.x -= 1.0
        }
    }
}

Neat! And let’s try to run it! And a big surprising error, one which I was half expecting to me be there. I knew Bevy conflict detection changed a bunch over the releases and I was not sure what was its current form but yep, this is it:

thread 'main' panicked at 'error[B0001]: Query<&bevy_transform::components::transform::Transform, bevy_ecs::query::filter::With<diary::Balloon>> in system learning::enemy_movement accesses component(s) bevy_transform::components::transform::Transform in a way that conflicts with a previous system parameter. Consider using `Without<T>` to create disjoint Queries or merging conflicting Queries into a `ParamSet`.', /Users/mathspy/.cargo/registry/src/github.com-1ecc6299db9ec823/bevy_ecs-0.8.1/src/system/system_param.rs:205:5

It’s a bit of a long winded error but I explain to her that if we look at our table, there’s nothing stopping the someone from eventually adding Balloon component to the one of the Enemys and that would not work and cause confusing behavior because we would need to be able to access the same Transform twice, once mutably and once immutable. The error also guides us in the right direction and tells us which system is causing us the trouble: enemy_movement. It also tells us how we can fix it: `Without<T>`. And I mention briefly that this is one of the few errors Bevy detects at the startup of the application instead of rejecting to compile for it.

We add Without<Balloon> to query1:

mut query1: Query<&mut Transform, (With<Enemy>, Without<Balloon>)>,

And finally I explain that the next part to make the Enemy move towards the Balloon involves a little bit of Math. ”There be dragons; but they are tamed dragons.” I explain, “for you see, game development does have some Math. But it’s not like the Math in school. It’s not mental Math. And it’s definitely not memorization “Math”. Rarely is it here’s how to do Math. The computer does the Math for us, the only things we need to know is what Math we need to perform to get a specific result and why does that Math work that way. Sometimes it’s nice to have a better grasp of how the Math is working behind the scenes, but that’s the extend of Math in game development.”

Okay I admit it wasn’t this elegant when I said it the first time because I wasn’t fully prepared also because I kept getting distracted or interrupted and starting over and I might have said “There be dragons” a few too many times…

I also point out that I am not the bestest at explaining these topics and that I’d rather she watches Coding Train’s Nature of Code playlist and I can be there to ask some questions and makes sure she understands.

Finally we swap some of the hard coded movement speeds for both the Enemy and the Balloon with nicely named constants.

Onwards to another day!