Bomber och granater

Screenshot

Det känns lite orättvist att vår stackars cirkel inte kan försvara sig mot de läskiga fyrkanterna. Därför är det dags att implementera skott som cirkeln kan skjuta ner fyrkanterna med.

Implementering

Känner sig träffade

För att hålla reda på vilka fyrkanter som har blivit träffade av kulor så lägger vi till ett nytt fält collided av typen bool i structen Shape.

struct Shape {
    size: f32,
    speed: f32,
    x: f32,
    y: f32,
    collided: bool,
}

Vektor för kulor

Vi måste ha en ny vektor som håller reda på alla kulor som har skjutits. Vi kallar den bullets och skapar den efter vektorn med squares. Här anger vi vilken typ vektorn ska innehålla eftersom Rust-kompilatorn måste veta vilken typ det är innan vi har tilldelat den något värde. Vi använder structen Shape även för kulorna för enkelhetens skull.

    let mut bullets: Vec<Shape> = vec![];

Skjut kulor

Efter cirkeln har förflyttats så lägger vi till en kontroll om spelaren har tryckt på mellanslag, och lägger till en kula i vektorn med kulor. Kulans x- och y-koordinater sätts till samma som cirkeln, och hastigheten till dubbla cirkelns hastighet.

            if is_key_pressed(KeyCode::Space) {
                bullets.push(Shape {
                    x: circle.x,
                    y: circle.y,
                    speed: circle.speed * 2.0,
                    size: 5.0,
                    collided: false,
                });
            }

Notera

Notera att vi använder funktionen is_key_pressed() som bara är sann under den första bildrutan som tangenten trycks ned.

Eftersom vi har lagt till ett fält på structen Shape måste vi lägga till den när vi skapar en fyrkant.

                squares.push(Shape {
                    size,
                    speed: rand::gen_range(50.0, 150.0),
                    x: rand::gen_range(size / 2.0, screen_width() - size / 2.0),
                    y: -size,
                    collided: false,
                });

Flytta kulor

För att kulorna inte ska bli stillastående minor så måste vi loopa över alla kulor och flytta dom i Y-led. Lägg till följande kod efter förflyttningen av fyrkanterna.

            for square in &mut squares {
                square.y += square.speed * delta_time;
            }
            for bullet in &mut bullets {
                bullet.y -= bullet.speed * delta_time;
            }

Ta bort kulor och fyrkanter

Även kulorna behöver tas bort om de hamnar utanför skärmen.

            bullets.retain(|bullet| bullet.y > 0.0 - bullet.size / 2.0);

Nu är det dags att ta bort alla fyrkanter och kulor som har kolliderat med något. Det gör vi enkelt med retain-metoden och behåller alla som inte har collided satt till true. Vi gör detta på båda vektorerna för squares och bullets.

            squares.retain(|square| !square.collided);
            bullets.retain(|bullet| !bullet.collided);

Kollidering

Efter vi har kollat om cirkeln har kolliderat med en fyrkant lägger vi till en kontroll om någon fyrkant blir träffad av en kula. Både kulan och fyrkanten uppdateras och fältet collided sätts till true så att vi kan ta bort dem längre ned i koden.

        for square in squares.iter_mut() {
            for bullet in bullets.iter_mut() {
                if bullet.collides_with(square) {
                    bullet.collided = true;
                    square.collided = true;
                }
            }
        }

Rensa kulor

Om det har blivit game over måste vi även rensa vektorn bullets så att kulorna försvinner när ett nytt spel påbörjas.

        if gameover && is_key_pressed(KeyCode::Space) {
            squares.clear();
            bullets.clear();
            circle.x = screen_width() / 2.0;
            circle.y = screen_height() / 2.0;
            gameover = false;
        }

Rita ut kulor

Innan vi ritar ut cirkeln så ritar vi ut alla kulor, så att de ritas ut under övriga former.

        for bullet in &bullets {
            draw_circle(bullet.x, bullet.y, bullet.size / 2.0, RED);
        }

Info

Det finns även en funktion som heter draw_circle_lines() som används för att rita ut en cirkel som inte är ifylld.

Det var allt för att kunna skjuta sönder fyrkanter.

Utmaning

För att öka svårighetsgraden går det att lägga till en begränsning så att det måste gå en viss tid mellan varje skott. Använd funktionen get_time() för att spara undan när varje skott skjuts och jämför aktuella tiden med detta värde.

Kompletta källkoden

Klicka för att visa hela källkoden
use macroquad::prelude::*;

struct Shape {
    size: f32,
    speed: f32,
    x: f32,
    y: f32,
    collided: bool,
}

impl Shape {
    fn collides_with(&self, other: &Self) -> bool {
        self.rect().overlaps(&other.rect())
    }

    fn rect(&self) -> Rect {
        Rect {
            x: self.x - self.size / 2.0,
            y: self.y - self.size / 2.0,
            w: self.size,
            h: self.size,
        }
    }
}

#[macroquad::main("My game")]
async fn main() {
    const MOVEMENT_SPEED: f32 = 200.0;

    rand::srand(miniquad::date::now() as u64);
    let mut squares = vec![];
    let mut bullets: Vec<Shape> = vec![];
    let mut circle = Shape {
        size: 32.0,
        speed: MOVEMENT_SPEED,
        x: screen_width() / 2.0,
        y: screen_height() / 2.0,
        collided: false,
    };
    let mut gameover = false;

    loop {
        clear_background(DARKPURPLE);

        if !gameover {
            let delta_time = get_frame_time();
            if is_key_down(KeyCode::Right) {
                circle.x += MOVEMENT_SPEED * delta_time;
            }
            if is_key_down(KeyCode::Left) {
                circle.x -= MOVEMENT_SPEED * delta_time;
            }
            if is_key_down(KeyCode::Down) {
                circle.y += MOVEMENT_SPEED * delta_time;
            }
            if is_key_down(KeyCode::Up) {
                circle.y -= MOVEMENT_SPEED * delta_time;
            }
            if is_key_pressed(KeyCode::Space) {
                bullets.push(Shape {
                    x: circle.x,
                    y: circle.y,
                    speed: circle.speed * 2.0,
                    size: 5.0,
                    collided: false,
                });
            }

            // Clamp X and Y to be within the screen
            circle.x = clamp(circle.x, 0.0, screen_width());
            circle.y = clamp(circle.y, 0.0, screen_height());

            // Generate a new square
            if rand::gen_range(0, 99) >= 95 {
                let size = rand::gen_range(16.0, 64.0);
                squares.push(Shape {
                    size,
                    speed: rand::gen_range(50.0, 150.0),
                    x: rand::gen_range(size / 2.0, screen_width() - size / 2.0),
                    y: -size,
                    collided: false,
                });
            }

            // Movement
            for square in &mut squares {
                square.y += square.speed * delta_time;
            }
            for bullet in &mut bullets {
                bullet.y -= bullet.speed * delta_time;
            }

            // Remove shapes outside of screen
            squares.retain(|square| square.y < screen_height() + square.size);
            bullets.retain(|bullet| bullet.y > 0.0 - bullet.size / 2.0);

            // Remove collided shapes
            squares.retain(|square| !square.collided);
            bullets.retain(|bullet| !bullet.collided);
        }

        // Check for collisions
        if squares.iter().any(|square| circle.collides_with(square)) {
            gameover = true;
        }
        for square in squares.iter_mut() {
            for bullet in bullets.iter_mut() {
                if bullet.collides_with(square) {
                    bullet.collided = true;
                    square.collided = true;
                }
            }
        }

        if gameover && is_key_pressed(KeyCode::Space) {
            squares.clear();
            bullets.clear();
            circle.x = screen_width() / 2.0;
            circle.y = screen_height() / 2.0;
            gameover = false;
        }

        // Draw everything
        for bullet in &bullets {
            draw_circle(bullet.x, bullet.y, bullet.size / 2.0, RED);
        }
        draw_circle(circle.x, circle.y, circle.size / 2.0, YELLOW);
        for square in &squares {
            draw_rectangle(
                square.x - square.size / 2.0,
                square.y - square.size / 2.0,
                square.size,
                square.size,
                GREEN,
            );
        }
        if gameover {
            let text = "GAME OVER!";
            let text_dimensions = measure_text(text, None, 60, 1.0);
            draw_text(
                text,
                screen_width() / 2.0 - text_dimensions.width / 2.0,
                screen_height() / 2.0,
                50.0,
                RED,
            );
        }

        next_frame().await
    }
}

Quiz

Testa dina nya kunskaper genom att svara på följande quiz innan du går vidare.

Agical