Fallande fyrkanter

Screenshot

För att det ska hända lite mer i vårt spel är det dags att skapa lite action. Eftersom hjälten i vårt spel är en modig cirkel så får våra motståndare bli kantiga fyrkanter som faller ner från toppen av fönstret.

Implementering

Struct för former

För att hålla reda på vår cirkel och alla fyrkanter så skapar vi en struct som vi kan ge namnet Shape som innehåller storlek, hastighet samt x och y-koordinater.

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

Initiera slumpgenerator

Vi kommer använda oss av en slumpgenerator för att avgöra när nya fyrkanter ska komma in på skärmen. Därför behöver vi seeda slumpgeneratorn så att det inte blir samma slumptal varje gång. Detta görs i början av main-funktionen med metoden rand::srand() som vi skickar in nuvarande tid till som seed.

    rand::srand(miniquad::date::now() as u64);

Notera

Vi använder oss av metoden miniquad::date::now() från det underliggande grafikramverket Miniquad för att få den aktuella tiden.

Vektor med fyrkanter

I början av main-funktionen skapar vi en vektor squares som kommer innehålla alla fyrkanter som ska visas på skärmen. Den nya variabeln circle får representera vår hjälte, den fantastiska cirkeln. Hastigheten använder konstanten MOVEMENT_SPEED och x och y-fälten sätts till mitten av skärmen.

    let mut squares = vec![];
    let mut circle = Shape {
        size: 32.0,
        speed: MOVEMENT_SPEED,
        x: screen_width() / 2.0,
        y: screen_height() / 2.0,
    };

Börja med att ändra programmet så att circle används i stället för variablerna x, och y och bekräfta att allt fungerar som förut innan du börjar skapa fiendefyrkanter.

Note

Rust-kompilatorn kan ge varningen “type annotations needed” på raden där vektorn skapas. Detta kommer försvinna när vi lägger till en fyrkant i vektorn i avsnittet nedan.

Skapa nya fyrkanter

Nu är det dags att starta invasionen av fyrkanter. Här delar vi som tidigare upp förflyttningen och utritningen av fyrkanterna. Det gör att förflyttningen inte behöver vara beroende av uppdateringsfrekvensen av skärmen, och vi kan se till att alla förändringar har skett innan vi börjar rita upp något på skärmen.

Först använder vi oss av funktionen rand::gen_range() för att avgöra om vi ska lägga till en ny fyrkant. Den tar två argument, ett lägsta värde och ett högsta värde, och returnerar sedan ett slumpat tal mellan dom två värdena. Om värdet är tillräckligt högt så skapar vi en ny Shape och lägger till i vektorn squares. För att få lite variation använder vi även rand::gen_range() för att få olika storlek, hastighet och startposition på alla fyrkanter.

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

Notera

Rektanglar ritas ut med början från övre vänstra hörnet. Därför subtraherar vi halva fyrkantens storlek när vi räknar ut X-positionen. Y-positionen börjar på negativt av fyrkantens storlek, så att den börjar helt utanför skärmen.

Uppdatera fyrkanters position

Nu kan vi gå igenom hela vektorn med en for-loop och uppdatera y-positionen med hjälp av fyrkantens hastighet och variabeln delta_time. Detta gör att fyrkanterna kommer åka neråt över skärmen.

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

Rensa bort fyrkanter som inte syns

Därefter måste vi rensa upp alla fyrkanter som har hamnat utanför skärmen då det är onödigt att rita ut saker som inte syns. Vi använder oss av metoden retain() på vektorn som tar en funktion som avgör om elementen ska behållas. Vi kollar att fyrkantens y-värde fortfarande är mindre än höjden på fönstret plus storleken på fyrkanten.

        squares.retain(|square| square.y < screen_height() + square.size);

Rita ut fyrkanterna

Till sist lägger vi till en for-loop som går igenom vektorn squares och använder funktionen draw_rectangle() för att rita ut en rektangel på den uppdaterade positionen och med rätt storlek. Eftersom rektanglar ritas ut med x och y från hörnet längst upp till vänster och våra koordinater utgår från center av fyrkanten så använder vi lite matematik för att räkna ut var dom ska placeras. Storleken används två gånger, en gång för fyrkantens bredd och en gång för fyrkantens höjd. Vi sätter färgen till GREEN så att alla fyrkanter blir gröna.

Notera

Det finns även funktionen draw_rectangle_ex() som tar structen DrawTextureParams istället för en färg. Med den kan man förutom färg även sätta rotation och offset på rektangeln.

        for square in &squares {
            draw_rectangle(
                square.x - square.size / 2.0,
                square.y - square.size / 2.0,
                square.size,
                square.size,
                GREEN,
            );
        }

Utmaning

Försök att ge olika färger till fyrkanterna genom att använda metoden choose() på vektorer från Macroquads ChooseRandom trait som returnerar ett slumpmässigt valt element från vektorn.

Komplett källkod

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

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

#[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 circle = Shape {
        size: 32.0,
        speed: MOVEMENT_SPEED,
        x: screen_width() / 2.0,
        y: screen_height() / 2.0,
    };

    loop {
        clear_background(DARKPURPLE);

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

        // 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,
            });
        }

        // Move squares
        for square in &mut squares {
            square.y += square.speed * delta_time;
        }

        // Remove squares below bottom of screen
        squares.retain(|square| square.y < screen_height() + square.size);

        // Draw everything
        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,
            );
        }

        next_frame().await
    }
}

Quiz

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

Agical