Game state
Innan vi lägger till någon ny funktionalitet i vårt spel så är det dags för
lite refaktorisering. För att det ska bli enklare att hantera spelets
tillstånd så inför vi en enum vid namn GameState
som håller reda på om
spelet pågår eller om det har blivit game over. Tack vare detta kan vi ta bort
vår gameover
variabel, och lägga till tillstånd för en meny och pausa
spelet.
Implementering
Enum för game state
Börja med att lägga till en enum kallad GameState
under implementationen av
Shape
. Den innehåller alla fyra tillstånd som spelet kan vara i.
enum GameState {
MainMenu,
Playing,
Paused,
GameOver,
}
Variabel för GameState
Ersätt raden som deklarerar variabeln gameover
med en deklarering av en ny
game_state
variabel. Till att börja med sätter vi den till tillståndet
GameState::MainMenu
så att vi väntar på att spelaren trycker mellanslag
innan spelet börjar.
let mut game_state = GameState::MainMenu;
Matcha på GameState
Koden inne i spelloopen ska nu ersättas med en matchning på variabeln
game_state
. Den måste hantera alla tillstånd i enumen. Senare ska vi införa
koden från tidigare steg inne i de olika blocken. Behåll anropet till rensning
av skärmen i början av loopen, och anropet till next_frame().await
i slutet.
clear_background(DARKPURPLE);
match game_state {
GameState::MainMenu => {
...
}
GameState::Playing => {
...
}
GameState::Paused => {
...
}
GameState::GameOver => {
...
}
}
next_frame().await
Huvudmeny
Nu ska vi lägga till kod i varje block i matchningen för att hantera varje
tillstånd. När spelet börjar kommer spelet vara i tillståndet
GameState::MainMenu
. Vi börjar med att kolla om Escape
är nedtryckt så kan
vi avsluta spelet. Om spelaren trycker på mellanslagstangenten tilldelar vi
det nya tillståndet GameState::Playing
till variabeln game_state
. Vi
passar även på att nollställa alla spelvariabler. Till sist skriver ut texten
“Press space” i mitten av skärmen.
GameState::MainMenu => {
if is_key_pressed(KeyCode::Escape) {
std::process::exit(0);
}
if is_key_pressed(KeyCode::Space) {
squares.clear();
bullets.clear();
circle.x = screen_width() / 2.0;
circle.y = screen_height() / 2.0;
score = 0;
game_state = GameState::Playing;
}
let text = "Press space";
let text_dimensions = measure_text(text, None, 50, 1.0);
draw_text(
text,
screen_width() / 2.0 - text_dimensions.width / 2.0,
screen_height() / 2.0,
50.0,
WHITE,
);
},
Pågående spel
Nu ska vi lägga tillbaka koden för spelet, det är samma som större delen av
spelloopen från förra kapitlet. Dock ska inte koden som hanterar game over vara
med då vi kommer lägga in det nedan i tillståndet för GameState::Playing
. Vi
lägger också till en kontroll om spelaren tryckt på Escape
och byter
tillstånd till GameState::Paused
.
GameState::Playing => {
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,
});
}
if is_key_pressed(KeyCode::Escape) {
game_state = GameState::Paused;
}
// 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)) {
if score == high_score {
fs::write("highscore.dat", high_score.to_string()).ok();
}
game_state = GameState::GameOver;
}
for square in squares.iter_mut() {
for bullet in bullets.iter_mut() {
if bullet.collides_with(square) {
bullet.collided = true;
square.collided = true;
score += square.size.round() as u32;
high_score = high_score.max(score);
}
}
}
// 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,
);
}
draw_text(
format!("Score: {}", score).as_str(),
10.0,
35.0,
25.0,
WHITE,
);
let highscore_text = format!("High score: {}", high_score);
let text_dimensions = measure_text(highscore_text.as_str(), None, 25, 1.0);
draw_text(
highscore_text.as_str(),
screen_width() - text_dimensions.width - 10.0,
35.0,
25.0,
WHITE,
);
},
Pausa spelet
Många spel har en möjlighet att pausa, så vi passar på att lägga in stöd för
det även i vårat spel. I pausat läge kollar vi om spelaren trycker på
Escape
, om så är fallet så sätter vi tillståndet till GameState::Playing
så att spelet kan fortsätta igen. Sen skriver vi ut en text på skärmen om att
spelet är pausat.
GameState::Paused => {
if is_key_pressed(KeyCode::Space) {
game_state = GameState::Playing;
}
let text = "Paused";
let text_dimensions = measure_text(text, None, 50, 1.0);
draw_text(
text,
screen_width() / 2.0 - text_dimensions.width / 2.0,
screen_height() / 2.0,
50.0,
WHITE,
);
},
Game Over
Till sist ska vi hantera vad som händer när det blir game over. Om spelaren
trycker på mellanslag så byter vi tillstånd till GameState::MainMenu
så att
spelaren kan börja ett nytt spel eller avsluta spelet. Sen skriver vi ut
texten på skärmen som tidigare.
GameState::GameOver => {
if is_key_pressed(KeyCode::Space) {
game_state = GameState::MainMenu;
}
let text = "GAME OVER!";
let text_dimensions = measure_text(text, None, 50, 1.0);
draw_text(
text,
screen_width() / 2.0 - text_dimensions.width / 2.0,
screen_height() / 2.0,
50.0,
RED,
);
},
Eftersom tillstånden för Playing
och GameOver
är separerade nu så visas
inte någonting från spelet när det är game over.
Nu när det finns en startmeny så kan du hitta på ett namn på ditt spel och
skriva ut det med stor text på övre delen av skärmen i tillståndet för
GameState::MainMenu
.
Quiz
Testa dina nya kunskaper genom att svara på följande quiz innan du går vidare.