Macroquad expandable 2d collision detection

Fellow rustaceans, I am trying to make expandable 2d collision detection with vectors in rust, I have succesfully made it, but when I made it it wasn't expandable, I want to know how to fill a vector with custom structs then cycle through it and perform collision detection. Here is my code, it has the names of everything and the struct you need to work with:

use macroquad::prelude::*;

#[derive(Debug, Clone, Copy)]
struct Rectangle {
    x: f32,
    y: f32,
    w: f32,
    h: f32,
    color: macroquad::prelude::Color,  // should be <T>, I don't wanna deal with the custom color type, but macroquad has some bugs
    speed: f32,  // should be <U> , I want users to have more customiazation over speed, but macroquad has some bugs with this...

}


#[macroquad::main("InputKeys")]
async fn main() {
    // let mut player_x: f32 = screen_width() / 2.0_f32;
    // let mut player_y: f32 = screen_width() / 2.0_f32;
    let gravity_speed: f32 = 3.0_f32;
    let mut collisions: bool = false;
    let mut player_rect = Rectangle { x: 150.0_f32, y: 10.0_f32, w: 13.0_f32, h: 13.0_f32, color: GREEN, speed: 3.5_f32 };
    let obstacle_rect = Rectangle { x: 150.0_f32, y: 500.0_f32, w: 120.0_f32, h: 23.0_f32, color: RED, speed: 0.0_f32 };
    let obstacle_rect2 = Rectangle { x: 250.0_f32, y: 600.0_f32, w: 80.0_f32, h: 17.0_f32, color: RED, speed: 0.0_f32 };
    // let mut obstacles_vector = vec![];

    loop {

        clear_background(GRAY);

       /* if is_key_down(KeyCode::W) {
                player_rect.y -= player_rect.speed;
        }*/
        if is_key_down(KeyCode::A) {
                player_rect.x -= player_rect.speed;
        }
        if is_key_down(KeyCode::D) {
                player_rect.x += player_rect.speed;
        }




        draw_rectangle(player_rect.x, player_rect.y, player_rect.w, player_rect.h, player_rect.color);

        draw_rectangle(obstacle_rect.x, obstacle_rect.y, obstacle_rect.w, obstacle_rect.h, obstacle_rect.color);


        rectangle_collisions(&player_rect, &obstacle_rect, &mut collisions);


       if collisions == true {
            player_rect.color = BLUE;
            if is_key_pressed(KeyCode::W) {
                player_rect.y -= player_rect.speed * 20.0_f32;
            }
        }
        if collisions == false {
            player_rect.color = GREEN;
            player_rect.y += gravity_speed;
        }

        next_frame().await;
    }

}

fn rectangle_collisions(rect1: &Rectangle, rect2: &Rectangle, true_or_false_variable: &mut bool) {
    if rect1.x < rect2.x + rect2.w && rect1.x + rect1.w > rect2.x && rect1.y < rect2.y + rect2.h && rect1.h + rect1.y > rect2.y {
        *true_or_false_variable = true;
    }
    else {
        *true_or_false_variable = false;
    }
}

I'm not clear what you mean by "expandable" here? You talk about having a collection, so do you mean a dynamic list of objects that can all collide with the player? Or also with each other? Or do you mean the object itself can get larger?

The first problem is solvable by putting the objects in a collection, say let obstacle_rects: Vec<Rectangle> = vec![];, filling up the collection, then looping over each object and running your collision logic with for obstacle in &obstacles { /* collision logic */ }.

The next problem, checking everything for colliding with each other, is solvable by brute force for small (less than 1000 for sure) numbers of objects by doing your current collision logic with every pair, which is just to test the first object against the second and third and so on, then the second object against the third and fourth and so on.... The code for this might look like this:

for (index, left) in obstacles.iter().enumerate() {
  for right in &obstacles[index+1..] {
    // collision logic
  }
}

But this is quite expensive as the number of objects gets larger. There's smarter ways to only check nearby things that requires a lot more complex code.

The last, the object itself expanding, I assume refers to handling the correction fit the intersection? A simple, general way to handle intersection is by having your collision check return how much and in what direction two objects collide, then you can move one or the other object by that distance in the opposite direction (and update velocities etc) in a general way. This is itself a very complex topic if you're needing to handle all the edge cases though.

1 Like

What I mean is there is a vector of structs, in the function, you input the player struct and the vector of structs (which dosen't include the player only the obstacles in the game), once you have done that, you need to iterate through the vector, and compare the data in each of the vector structs to the data of the regular player struct, using the collision detection algorithm found in the code above your post.

Sorry if that sounds overwhelming, it shouldn't be when you start doing it. I just don't know how to get data out of structs in a vector, so I can't do it.

Anyways, thanks for trying! :grinning:

Right, that's the first option. The Rust book has a chapter on using Vec, and in general is a really good source, but it doesn't specifically call out that when you iterate over a collection with for item in &collection that the item is a reference to the item, so on this case a Vec<Rectangle> will give you &Rectangle, so you can pass it directly to your collision function.

There's dozens of little footnotes you can bump into (what happens if you just for item in collection without the &, for example), but in general, if you read the error messages you should start to get an idea on what's going on.

If you get stuck, feel free to ask more questions!

My problems were not being able to get data out of a struct, that was inside of a vector, can you show me an example of iterating through a vector and pulling data out of a struct that was inside the vector and printing it. Showing me that would help a lot. - thanks!

So you already have this function:

fn rectangle_collisions(rect1: &Rectangle, rect2: &Rectangle, true_or_false_variable: &mut bool) {
    if rect1.x < rect2.x + rect2.w && rect1.x + rect1.w > rect2.x && rect1.y < rect2.y + rect2.h && rect1.h + rect1.y > rect2.y {
        *true_or_false_variable = true;
    }
    else {
        *true_or_false_variable = false;
    }
}

You see how you are accessing properties like x in the two arguments of type &Rectangle (a reference to a Rectangle) by using ., such as in rect1.x? Well when you loop over a collection with for some_name in &some_collection the name you use for some_name is set to a reference to each item in the collection when the body between the braces is run. So for a collection of type Vec<Rectangle> the item you get is a &Rectangle, the same as the arguments to your rectangle_collisions function. This means you can directly pass the item to this function, the same as the local you are currently passing as &obstacle_rect, but without needing the & to make a reference to the local as it is already a reference to the item.

If you're still having trouble with this, you have probably jumped ahead too far. Try starting with a simpler problem and building up, without copying code directly so you get a better idea what each part is doing. Use the foundational help available at Learn Rust - Rust Programming Language to help you figure out what each part is doing separately and putting them together.

I'm running into an error, where when I push into my obstacle_vec variable, it dosen't work because it "dosen't implement the copy trait" heres the code:


let mut obstacle_vec: Vec<Rectangle> = vec![];

let mut obstacle_rect = Rectangle { x: 30.0_f32, y: 420.0_f32, w: 1000.0_f32, h: 23.0_f32, color: RED, speed: 0.0_f32 };
let obstacle_rect2 = Rectangle { x: 350.0_f32, y: 380.0_f32, w: 12.0_f32, h: 12.0_f32, color: RED, speed: 0.0_f32 };

obstacle_vec.push(obstacle_rect);
obstacle_vec.push(obstacle_rect2);

Rust requires values implement Copy for you to move a value like in that push and still use it later. The error will point out the other location where you are using the value, probably the existing collision code.

You have a few options in general, you can:

  • Remove the other use or reorder them so the move is the last use of the value
  • implement Copy by adding Copy, Clone to the derive attribute, if everything in the type is also Copy, so a copy of the value is made instead of moved
  • implement only Clone, then use the value.clone() method it adds to the push call so you explicitly create a copy. The difference to Copy is that this is allowed to "run code", including allocations or keeping a count of uses.

As a general rule, most types should be Clone, and very simple ones can be Copy, but don't overuse it.

I have succesfully done it! Thanks for your response, but it didn't fit very good for my situation, it ended up working in the end though (with a lot of changes).

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.