Python project alien invasion (final) record score

Python project alien invasion (final) record score

In this chapter, we will end the development of the whole game project. We will add a play button to enable players to start the game by clicking or restart the game after the game is over. At the same time, we will also modify the game to speed up the pace of the game when the player level increases, and implement a scoring system. The -- snip -- in the code block represents the unchanged part of the code.

Before explaining this section, we first made the following modifications to make the game layout more reasonable:

# settings.py

        self.ship_speed = 1.5  # Set the initial value of spacecraft speed
        self.bullet_speed = 3  # Bullet speed
        self.alien_speed = 0.5  # Aliens move at a speed of 0.5

Modified the speed of spacecraft, bullets and aliens.

# game_function.py

def get_number_rows(ai_settings, ship_height, alien_height):
    """Calculate how many lines the screen can hold"""
    # Calculate how much space is left on the screen
    available_space_y = ai_settings.screen_height - 4 * alien_height - ship_height
    # How many lines are there on the calculation screen
    number_rows = int(available_space_y / (2 * alien_height))
    return number_rows

Modified the formula to calculate how much space is left on the screen, and changed the height of three aliens to four.

Add Play key

In this chapter, we will add a play button, which will appear before the beginning of the game and again after the end of the game, so that players can restart the game.

Make the game inactive

Currently, the game is running alien_invasion.py starts. Next, we need to make the game inactive. Only when we click the Play button can we enter the active state and start the game. Therefore, we need to modify the game_ stats. Parameters of Py:

class GameStats:
    """Track game statistics"""

    def __init__(self, ai_settings):
        self.ai_settings = ai_settings
        self.reset_stats()
        self.game_active = False  # The game has just started and is inactive

Create Button class

Since Pygame has no built-in method to create buttons, we need to create a Button class to create a solid rectangle with labels. We can use these codes to create any Button. First, we create a Python file and name it Button py:

# button.py
import pygame.ftfont    # The module can render text to the screen


class Button:

    # message is the text we want to display in the button
    def __init__(self, ai_settings, screen, message):
        """Initialize button properties"""
        self.screen = screen
        self.screen_rect = screen.get_rect()

        # Set the size and other properties of the button
        self.width, self.height = 200, 50
        self.button_color = (0, 255, 0) # The button is set to green
        self.text_color = (255, 255, 255)   # The text content is set to white
        self.font = pygame.font.SysFont(None, 48)   # None indicates the default font and 48 indicates the font size

        # Create a rect object for the button and center it
        self.rect = pygame.Rect(0, 0, self.width, self.height)
        self.rect.center = self.screen_rect.center

        # Create a label for the button
        self.prep_msg(message)	# Render the message as an image and center it in the button

The relevant interpretation of the emerging code is shown in the notes, where prep_ The code of MSG () is as follows. We use this function to render text as an image:

# button.py
    def prep_msg(self, message):
        """take message Render as an image and center it in the button"""
        self.msg_image = self.font.render(message, True, self.text_color, 
                                          self.button_color)	# Convert text to image
        self.msg_image_rect = self.msg_image.get_rect()
        self.msg_image_rect.center = self.rect.center

Method prep_msg(), receive the argument self and the text message to be rendered as an image. Call method font Render () converts the text stored in message into an image. This method receives a Boolean argument that specifies whether anti aliasing (anti aliasing makes the text edge smoother) is turned on or off.

Finally, we create the method draw_button() method, which can be called to display the button on the screen.

# button.py

    def draw_button(self):
        """Draw a button before you draw text"""
        self.screen.fill(self.button_color, self.rect)  # Draw button
        self.screen.blit(self.msg_image, self.msg_image_rect)   # Draw text

The fill() method fills the block with the specified color at the specified position

The blit() method draws the image at the specified place.

Draw button on screen

We use the Button class to create a Play Button. Since we only need one Play Button, we directly create a Play Button in alien_ invasion. Create it in py:

# alien_invasion.py
from button import Button
--snip--
	play_button = Button(ai_settings, screen, "Play")	# Create an instance of the Button class
--snip--
	gf.update_screen(ai_settings, screen, ship, aliens, bullets, play_button)
    
run_geme()

Here, we create an instance of the Button class, and then pass the instance to update_screen(), so that the Button can be displayed when the screen is updated. Next, we modify update_screen() function:

# game_function.py

def update_screen(ai_settings, screen, stats, ship, aliens, bullets, play_button):
    """Update the image on the screen and switch to a new screen"""
    screen.fill(ai_settings.bg_color)  # Fills the screen with the specified color
    for bullet in bullets:
        bullet.draw_bullet()
    ship.blitme()  # Display the spacecraft on the screen
    aliens.draw(screen)  # Let aliens appear on the screen
    if not stats.game_active:   # Print the start button if the game is inactive
        play_button.draw_button()
    pygame.display.flip()  # Displays the most recently drawn screen

In order to display the Play button on all other screen elements, we draw the button after drawing all other elements. Now we will see a Play button in the center of the screen after running the main function.

Use the button to start the game

Next, we need to modify the check_evernts() code, a new function check has been created_ play_ button():

# game_function.py

def check_events(ai_settings, screen, stats, play_button, ship, bullets):
    """Respond to keyboard and mouse events"""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:  # If the type of event is exit, it is equivalent to mouse click ×
            sys.exit()
        elif event.type == pygame.KEYDOWN:  # Determine whether a key is pressed
            check_keydown_events(event, ai_settings, screen, ship, bullets)
        elif event.type == pygame.KEYUP:  # Judge whether the key is released
            check_keyup_events(event, ship)
        elif event.type == pygame.MOUSEBUTTONDOWN:  # Determine whether the mouse is pressed
            mouse_x, mouse_y = pygame.mouse.get_pos()  # Returns the coordinates of the pressed point
            check_play_button(stats, play_button, mouse_x, mouse_y)


def check_play_button(stats, play_button, mouse_x, mouse_y):
    """Click on the player Play Start the game after"""
    if play_button.rect.collidepoint(mouse_x, mouse_y):  # Judge whether the position of the mouse click is within the key
        stats.game_active = True

No matter where the player clicks the screen, Pygame will detect a mousebuttown event, but we only want the game to respond when the player clicks the Play button. For this reason, we use Pygame mouse. get_ POS () function, which returns a tuple containing the coordinates of the click position, and then we pass the value to the function check_play_button() determines whether the clicked position is in the button. If so, start the game, otherwise nothing will happen.

Next, we modify check in the main function_ Events () function and pass the new arguments.

# alien_invasion.py

gf.check_events(ai_settings, screen, stats, play_button, ship, bullets)

Reset game

The code written above only deals with the situation when the player clicks the Play button for the first time, but does not deal with the situation at the end of the game, because the conditions leading to the end of the game are not reset. Next, we set up to reset the game every time the player clicks the Play button, including statistics, delete existing aliens, bullets, and create a new group to center the ship.

# game_function.py

def check_play_button(ai_settings, screen, stats, play_button, ship, aliens, bullets, mouse_x, mouse_y):
    """Click on the player Play Start the game after"""
    if play_button.rect.collidepoint(mouse_x, mouse_y):  # Judge whether the position of the mouse click is within the key
        # Reset game statistics
        stats.rest_stats()
        stats.game_active = True
        # Clear alien and bullet list
        aliens.empty()
        bullets.empty()
        # Create a new group of aliens and center it
        create_fleet(ai_settings, screen, ship, aliens)
        ship.center_ship()

Here we update the check_play_button() function, where we pass in new parameters. After clicking, we provide players with three new spaceships, delete existing aliens and bullet groups, create a new group, and finally center the spaceship.

Next, we need to modify the check_ The events () part of the code and make it pass in the parameters correctly.

# game_function.py

def check_events(ai_settings, screen, stats, play_button, ship, aliens, bullets):
    """Respond to keyboard and mouse events"""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:  # If the type of event is exit, it is equivalent to mouse click ×
            sys.exit()
        elif event.type == pygame.KEYDOWN:  # Determine whether a key is pressed
            check_keydown_events(event, ai_settings, screen, ship, bullets)
        elif event.type == pygame.KEYUP:  # Judge whether the key is released
            check_keyup_events(event, ship)
        elif event.type == pygame.MOUSEBUTTONDOWN:  # Determine whether the mouse is pressed
            mouse_x, mouse_y = pygame.mouse.get_pos()  # Returns the coordinates of the pressed point
            check_play_button(ai_settings, screen, stats, play_button, ship, aliens, bullets, mouse_x, mouse_y)

Meanwhile, alien_ invasion. The code of Py also needs to be modified:

# alien_invasion.py

gf.check_events(ai_settings, screen, stats, play_button, ship, aliens, bullets)

Now when we click the Play button, the game will be reset correctly. We can Play it as many times as we want.

Switch the Play button to inactive

At present, the Play button has the following problems: even if the Play button is not visible, the game will restart when the player clicks the original area. Next, we can solve this problem by setting the Play button to be effective when the game is inactive

def check_play_button(ai_settings, screen, stats, play_button, ship, aliens, bullets, mouse_x, mouse_y):
    """Click on the player Play Start the game after"""
    # Judge whether the position of the mouse click is within the key and whether the game state is active at this time
    if play_button.rect.collidepoint(mouse_x, mouse_y) and not stats.game_active:
        # Reset game statistics
        stats.reset_stats()
        stats.game_active = True
        # Clear alien and bullet list
        aliens.empty()
        bullets.empty()
        # Create a new group of aliens and center it
        create_fleet(ai_settings, screen, ship, aliens)
        ship.center_ship()

Hide game cursor

In order for the player to start the game, we need to make the cursor visible, but next we want the cursor to be invisible when the game is inactive:

# game_function.py

def check_play_button(ai_settings, screen, stats, play_button, ship, aliens, bullets, mouse_x, mouse_y):
    """Click on the player Play Start the game after"""
    # Judge whether the position of the mouse click is within the key and whether the game state is active at this time
    if play_button.rect.collidepoint(mouse_x, mouse_y) and not stats.game_active:
        # hide cursor
        pygame.mouse.set_visible(False)
        # Reset game statistics
        stats.reset_stats()
        stats.game_active = True
        # Clear alien and bullet list
        aliens.empty()
        bullets.empty()
        # Create a new group of aliens and center it
        create_fleet(ai_settings, screen, ship, aliens)
        ship.center_ship()

Through set_ The parameter in the visible() function is False. We can hide the mouse at the beginning of the game. Next, we need to re display the cursor after the game.

# game_function.py

def ship_hit(ai_settings, stats, screen, ship, aliens, bullets):
    """Responding to a spaceship hit by aliens"""

    if stats.ships_left > 0:
        # Reduce the number of ships by one
        stats.ships_left -= 1

        # Clear the list of bullets and aliens
        aliens.empty()
        bullets.empty()

        # Create a new group of aliens and put the spacecraft in the center of the screen
        create_fleet(ai_settings, screen, ship, aliens)  # Create new aliens
        ship.center_ship()  # Move the spacecraft to the center of the screen

        # suspend
        sleep(0.5)
    else:
        stats.game_active = False
        pygame.mouse.set_visible(True)

In the last line above, we are in set_ Pass in the parameter True in the visible() function to make the mouse appear again when the game is inactive, so as to operate with the player.

Continuously improve the level as the game progresses

At present, after the whole group of aliens are eliminated, the difficulty of the game has not changed. Next, we will increase the difficulty of the game: whenever the player eliminates the foreign person on the screen, speed up the rhythm of the game and make the game more difficult.

Modify speed settings

First, we reorganize the Settings class and divide the game Settings into static and dynamic parts. Static Settings refer to Settings that will not change with the progress of the game.

# settings.py
class Settings:
    """Store all classes related to alien invasion"""

    def __init__(self):
        """Initialize game static settings"""
        # screen setting
        self.screen_width = 1200  # Set window width
        self.screen_height = 700  # Set window height
        self.bg_color = (230, 230, 230)  # Set background color

        # Spacecraft setup
        self.ship_limit = 3  # Set the maximum number of ships for players

        # Bullet setting
        self.bullet_width = 3  # The width of the bullet
        self.bullet_height = 15  # Bullet height
        self.bullet_color = (60, 60, 60)  # Bullet color
        self.bullets_allowed = 3  # Limit the number of bullets that do not disappear to 3

        # Alien settings
        self.fleet_drop_speed = 10  # The speed at which aliens move down

        # What kind of speed to accelerate the pace of the game
        self.speed_up = 1.1

        self.initialize_dynamic_speed()

    def initialize_dynamic_speed(self):
        """The amount of dynamic change as the game progresses"""
        self.ship_speed = 1.5  # Sets the initial value of the speed
        self.bullet_speed = 3  # Bullet speed
        self.alien_speed = 1  # Aliens move at a speed of 0.5

        # fleet_ When direction is 1, it means to move right, and when direction is - 1, it means to move left
        self.fleet_direction = 1

We are__ init__ () initializes the static setting and adds the setting speed_up is used to control the acceleration of the game rhythm. At the same time, we put some quantities that will change with the progress of the game in the function initialize_ dynamic_ In speed (), we reset these variables whenever the game starts again.

Next, we define increase in it_ Speed() function to increase speed:

# settings.py

    def increase_speed(self):
        """Speed up"""
        self.ship_speed *= self.speed_up
        self.bullet_speed *= self.speed_up
        self.alien_speed *= self.speed_up

In this function, we increase the speed of spacecraft, bullets and aliens to the last speed every time_ When we finish killing a group of aliens, we will call this function to speed up the progress of the game, and then create a group of new Aliens:

def update_bullets(ai_settings, screen, ship, aliens, bullets):
    """Update the location of bullets and delete bullets that have disappeared"""
    bullets.update()

    # The number of lists or groups should not be modified in the for loop, which will lead to the loss of traversal
    for bullet in bullets.copy():
        if bullet.rect.bottom <= 0:
            bullets.remove(bullet)
    check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets)

Reset speed at the end of the game

Whenever players start a new game, we need to restore the changed settings to their original values.

# game_function.py

def check_play_button(ai_settings, screen, stats, play_button, ship, aliens, bullets, mouse_x, mouse_y):
    """Click on the player Play Start a new game after"""
    # Judge whether the position of the mouse click is within the key and whether the game state is active at this time
    if play_button.rect.collidepoint(mouse_x, mouse_y) and not stats.game_active:
        # Reset game speed settings
        ai_settings.initialize_dynamic_speed()
        # hide cursor
        pygame.mouse.set_visible(False)
        # Reset game statistics
        stats.reset_stats()
        stats.game_active = True
        # Clear alien and bullet list
        aliens.empty()
        bullets.empty()
        # Create a new group of aliens and center it
        create_fleet(ai_settings, screen, ship, aliens)
        ship.center_ship()

Now the game function of the game is basically developed. At present, we can design aliens through spacecraft and gradually increase the difficulty of the game. Next, we will develop a scoring system.

Score record

Note down that we will implement a scoring system to track players' scores in real time and display the highest score, current level and the number of remaining ships.

Because the score is a statistical information of the game, we add a score attribute in the GameStats class:

 # gamestats.py
    
    def reset_stats(self):
        """Initialize information that may change during game operation"""

        # Count the number of ships remaining in the game
        self.ships_left = self.ai_settings.ship_limit
        self.score = 0  # Count game scores

In order to reset the score at the beginning of the game every time, we are reset_ Reset score information in stats() function.

Show score

In order to display the score on the screen, we first create a new class ScoreBoard. At present, this class only displays scores, but later we also use it to display the highest score, level and the number of remaining ships.

# scoreboard.py

import pygame.ftfont


class Scoreboard:
    """Class that displays score information"""

    def __init__(self, ai_settings, screen, stats):
        """Initialize the attributes involved in displaying scores"""
        self.screen = screen
        self.screen_rect = screen.get_rect()
        self.ai_settings = ai_settings
        self.stats = stats
        
        # Font setting for displaying score information
        self.text_color = (30, 30, 30)
        self.font = pygame.font.SysFont(None, 48)
        
        # Prepare initial score image
        self.prep_score()

Because we need to display text on the screen, we need to import pyGame Ftfont, next we will use the method prep_ Convert text to image in score():

# scoreboard.py
    def prep_score(self):
        """Render scores as images"""
        score_str = str(self.stats.score)
        self.score_image = self.font.render(score_str, True, self.text_color, self.ai_settings.bg_color)
        
        # Displays the score in the upper right corner
        self.score_rect = self.score_image.get_rect()
        self.score_rect.rigtht = self.screen_rect.right - 20
        self.score_rect.top = 20

In the above method, we first convert the digital value into a string, and then pass the string to the render() function to render the image. In order to display the score more clearly, we pass the screen background color and text color to render().

Here, we put the score window in the upper right corner of the screen and extend it to the left when the score increases and the number widens.

Next, we create the method show_score() is used to better render the image:

# scoreboard.py

    def show_score(self):
        """Show score"""
        self.screen.blit(self.score_image, self.score_rect)

This method displays the score image to the specified position.

Create scoreboard

In order to show the score, we are in alien_invasion.py to create an instance of ScoreBoard:

# alien_invasion.py

		--snip--
    
from scoreboard import Scoreboard

		--snip--
    
    stats = GameStats(ai_settings)
    sb = Scoreboard(ai_settings, screen, stats)
    
    	--snip--
        
	gf.update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button)
    
    
run_game()
    

Next, we call the print scorecard function in the screen update function and pass the corresponding parameters:

# game_funcrion.py

def update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button):
    """Update the image on the screen and switch to a new screen"""
    screen.fill(ai_settings.bg_color)  # Fills the screen with the specified color
    for bullet in bullets:
        bullet.draw_bullet()
    ship.blitme()  # Display the spacecraft on the screen
    aliens.draw(screen)  # Let aliens appear on the screen
    sb.show_score() # Print scoring information
    if not stats.game_active:  # Print the start button if the game is inactive
        play_button.draw_button()
    pygame.display.flip()  # Displays the most recently drawn screen

Score points when aliens are eliminated

In order to display the score in real time on the screen, we will update stats whenever an alien is hit Score, and then call prep_score() updates the score image, but before that, we need to indicate how many points players can get for each alien shot down:

# settings.py

    def initialize_dynamic_speed(self):
        """The amount of dynamic change as the game progresses"""
        self.ship_speed = 1.5  # Set the initial value of spacecraft speed
        self.bullet_speed = 3  # Bullet speed
        self.alien_speed = 0.5  # Aliens move at a speed of 0.5
        self.alien_point = 50 # Points per alien defeated

        # fleet_ When direction is 1, it means to move right, and when direction is - 1, it means to move left
        self.fleet_direction = 1

As the game goes on, we will increase the points of each alien value. In order to ensure that this value will be reset before starting a new game, we will initialize_ dynamic_ Set it in speed().

Next, check_ bullet_ alien_ In collections (), whenever an alien is shot down, the score will be updated

# game_function.py

def check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets):
    """Respond to the collision between bullets and aliens, and generate a new group of aliens after all aliens die"""
    # Detect whether a bullet hit the alien. If so, delete the bullet and alien

    collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
    if collisions:  # Increase score when hitting aliens
        stats.score += ai_settings.alien_point
        sb.prep_score()
    if len(aliens) == 0:
        # If all aliens are eliminated, delete the existing bullets and regenerate aliens
        bullets.empty()
        ai_settings.increase_speed()
        create_fleet(ai_settings, screen, ship, aliens)

Here we update the check_ bullet_ alien_ The definition of collections () includes the formal parameters stats and sb. When the bullet hits an alien, Pygame will return a dictionary. We check whether the dictionary exists. If so, we will increase the score of an alien, and then we use prep_score() to create an image showing the latest score.

Next, we modify update_bullets(), ensuring that the correct arguments are passed between functions

# game_function.py
def update_bullets(ai_settings, screen, stats, sb, ship, aliens, bullets):
    """Update the location of bullets and delete bullets that have disappeared"""
    bullets.update()

    # The number of lists or groups should not be modified in the for loop, which will lead to the loss of traversal
    for bullet in bullets.copy():
        if bullet.rect.bottom <= 0:
            bullets.remove(bullet)
    check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets)

At the same time, we need to modify the code in the main loop that calls the functions mentioned above.

 while True:
        gf.check_events(ai_settings, screen, stats, play_button, ship, aliens, bullets)
        if stats.game_active:
            ship.update()
            gf.update_bullets(ai_settings, screen, stats, sb, ship, aliens, bullets)
            gf.update_aliens(ai_settings, stats, screen, ship, aliens, bullets)
        gf.update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button)

Every alien's point will be included in the point of elimination

At present, our code may miss some eliminated aliens. For example, two bullets hit aliens in a cycle, or because the bullets are wider and hit multiple aliens at the same time, players will only get one alien score at this time. In order to fix this problem, we adjust the way of collision detection:

# game_function.py

def check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets):
    """After all aliens collide with the new alien group, all aliens will respond"""
    # Detect whether a bullet hit the alien. If so, delete the bullet and alien

    collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
    if collisions:  # Increase score when hitting aliens
        for aliens in collisions.values():
            stats.score += ai_settings.alien_point * len(aliens)
            sb.prep_score()
    if len(aliens) == 0:
        # If all aliens are eliminated, delete the existing bullets and regenerate aliens
        bullets.empty()
        ai_settings.increase_speed()
        create_fleet(ai_settings, screen, ship, aliens)

In check_ bullet_ alien_ In collections (), each bullet colliding with aliens is a key in the dictionary, and the values related to bullets are a list. We traverse the list of values in the dictionary to ensure that each eliminated alien is included in the score.

Increase points

Since it becomes more difficult for players to destroy a group of aliens, the points of aliens should be higher at a higher level:

# settings.py

class Settings:
    """Store all classes related to alien invasion"""

    def __init__(self):
        
        --snip--
        self.speed_up = 1.1  # What kind of speed to accelerate the pace of the game

        self.score_scale = 1.5  # Speed up scores
        --snip
        
	def increase_speed(self):
        """Speed up"""
        self.ship_speed *= self.speed_up
        self.bullet_speed *= self.speed_up
        self.alien_speed *= self.speed_up
        self.alien_point = int(self.alien_point * self.score_scale)
        # print(self.alien_point)   # Print and display the score of the current alien

Here, we define that the fraction increases at a rate of 1.5 times. In order to make the calculated points an integer, we use the function int().

The last print statement is to enable us to see the score of the current alien in the terminal window every time we raise a level. At the last run time, we can comment out this line of code.

Round the score

Many shooting games will display scores as multiples of 10, so our small game will also follow this principle, and we will also set the format of numbers and add commas to larger numbers to divide the thousandths, so we are in prep_ Add the following code to the score () method

# scoreboard.py

    def prep_score(self):
        """Render scores as images"""
        rounded_score = int(round(self.stats.score, -1))
        score_str = "{:,}".format(rounded_score)
        self.score_image = self.font.render(score_str, True, self.text_color, self.ai_settings.bg_color)

The function round() is used to exact the decimal to the number of digits after the decimal point, but if a negative number is passed, it will be accurate to an integer multiple of 101001000, etc. At the same time, a string formatting instruction is used in the next line, which allows Python to insert a comma when converting a value into a string.

Highest score

Next, we track and record the highest score of the game:

# game_function.py

    def __init__(self, ai_settings):
        self.ai_settings = ai_settings
        self.reset_stats()
        self.game_active = False  # The game has just started and is active
        self.high_score = 0  # The highest score should not be reset at any time

We first create a new attribute high_score, since the highest score will not be reset under any circumstances, we__ init__ Function.

Next, we are at scoreboard Py adds a function to display the highest score and distinguish it from the current score.

# scoreboard.py

    def __init__(self, ai_settings, screen, stats):
        """Initialize the attributes involved in displaying scores"""
        
 		--snip--
        
        # Prepare initial score image
        self.prep_score()
        self.prep_high_score()

Next, we are in prep_ high_ The score () method renders the highest score as an image and uses the same output format as the current score

# scoreboard.py

    def prep_high_score(self):
        """Converts the highest score to a rendered image"""
        high_score = int(round(self.stats.high_score, -1))
        high_score_str = "{:,}".format((high_score))
        self.high_score_image = self.font.render(high_score_str, True, self.text_color, self.ai_settings.bg_color)
        
        # Place the highest score in the center of the screen
        self.high_score_rect = self.high_score_image.get_rect()
        self.high_score_rect.centerx = self.screen_rect.centerx
        self.high_score_rect.top = 20

The main function of this method is to place the highest score in the top center of the screen, and adopts the same rounding method as displaying the current score, which will not be repeated here.

Next, we're on show_ This method is invoked in the score () method to display the highest score on the screen.

# scoreboard.py
    def show_score(self):
        """Show score"""
        self.screen.blit(self.score_image, self.score_rect) # Displays the current score
        self.screen.blit(self.high_score_image, self.high_score_rect)   # Show highest score

In order to check whether there is a new highest score, we need to be in game_ function. Add a new function check in PY_ high_ score()

# game_function

def check_high_score(stats, sb):
    """Check whether the highest score is born"""
    if stats.score > stats.high_score:
        stats.high_score = stats.score
        sb.prep_high_score()

In this function, we compare the current score with the highest score. Once the current score exceeds the highest score, I Emei you will modify the value of the highest score. To do this, we need to call this function every time an alien is destroyed:

def check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets):
    """Respond to the collision between bullets and aliens, and generate a new group of aliens after all aliens die"""
    # Detect whether a bullet hit the alien. If so, delete the bullet and alien

    collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
    if collisions:  # Increase score when hitting aliens
        for aliens in collisions.values():
            stats.score += ai_settings.alien_point * len(aliens)
            sb.prep_score()
        check_high_score(stats, sb)
    if len(aliens) == 0:
        # If all aliens are eliminated, delete the existing bullets and regenerate aliens
        bullets.empty()
        ai_settings.increase_speed()
        create_fleet(ai_settings, screen, ship, aliens)

When the dictionary exists, we update the score according to the number of aliens eliminated, and then we call check_high_score() function to determine whether the highest score needs to be updated.

Display level

In order to display the player's level in the game, we need to add an attribute representing the current level to the GameStats class. At the same time, in order to cut thin, we can reset the level every time we start a new game_ Initialize this property in the stats() function.

# game_stats.py
    def reset_stats(self):
        """Initialize information that may change during game operation"""

        # Count the number of ships remaining in the game
        self.ships_left = self.ai_settings.ship_limit
        self.score = 0  # Count game scores
        self.level = 1  # Count the level information of the game

Next, in order to display the current level below the current score, we__ init__ A new method called prep_ is invoked in the method. Level () is used to render the game level as an image.

# scoreboard.py

class Scoreboard:
    """Class that displays score information"""

    def __init__(self, ai_settings, screen, stats):
        """Initialize the attributes involved in displaying scores"""

        --snip--

        # Prepare to display the image of the current score and the highest score
        self.prep_score()
        self.prep_high_score()
        self.prep_level()

Method prep_ The code of level() is as follows:

# scoreboard.py
    def prep_level(self):
        """Convert levels to images"""
        self.level_image = self.font.render(str(self.stats.level), True, self.text_color, self.ai_settings.bg_color)

        # Put the grade below the score
        self.level_rect = self.level_image.get_rect()
        self.level_rect.right = self.score_rect.right
        self.level_rect.top = self.score_rect.bottom + 10

This method renders the value of the level as an image and places the image below the current score.

Next, we update show_score() method to print the value of the grade:

# scoreboard.py
    def show_score(self):
        """Show score"""
        self.screen.blit(self.score_image, self.score_rect) # Displays the current score
        self.screen.blit(self.high_score_image, self.high_score_rect)   # Show highest score
        self.screen.blit(self.level_image, self.level_rect)

Next, we need to update the following functions to upgrade the current level one level every time we eliminate a group of Aliens:

# game-function
def check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets):
    """Respond to the collision between bullets and aliens, and generate a new group of aliens after all aliens die"""
    # Detect whether a bullet hit the alien. If so, delete the bullet and alien

    collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
    if collisions:  # Increase score when hitting aliens
        for aliens in collisions.values():
            stats.score += ai_settings.alien_point * len(aliens)
            sb.prep_score()
        check_high_score(stats, sb)
    if len(aliens) == 0:
        # If all aliens are eliminated, delete the existing bullets and regenerate aliens
        bullets.empty()
        ai_settings.increase_speed()
        # Raise the level
        stats.level += 1
        sb.prep_level()
        create_fleet(ai_settings, screen, ship, aliens)

In the above function, if we wipe out a group of aliens, we will add 1 to the value of level and call prep_level() method, so that the level can be displayed normally.

At the same time, in order to update the score and level image when we restart the game, we need to modify the code after we click the Play button:

# game_function.py
def check_play_button(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets, mouse_x, mouse_y):
    """Click on the player Play Start a new game after"""
    # Judge whether the position of the mouse click is within the key and whether the game state is active at this time
    if play_button.rect.collidepoint(mouse_x, mouse_y) and not stats.game_active:
        # Reset game settings
        ai_settings.initialize_dynamic_speed()
        # hide cursor
        pygame.mouse.set_visible(False)
        # Reset game statistics
        stats.reset_stats()
        stats.game_active = True
        # Reset scoreboard image
        sb.prep_score()
        sb.prep_high_score()
        sb.prep_level()
        # Clear alien and bullet list
        aliens.empty()
        bullets.empty()
        # Create a new group of aliens and center it
        create_fleet(ai_settings, screen, ship, aliens)
        ship.center_ship()

In the above code, we reset the scoreboard image after resetting the statistical information of the game, because we need to call scoreboard Py, so we need to pass in the instance of the module in the parameter.

At the same time, we need to modify the check_ Formal parameter of events(), to which an instance of scoreboard module is passed:

# game_function.py
def check_events(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets):
    """Respond to keyboard and mouse events"""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:  # If the type of event is exit, it is equivalent to mouse click ×
            sys.exit()
        elif event.type == pygame.KEYDOWN:  # Determine whether a key is pressed
            check_keydown_events(event, ai_settings, screen, ship, bullets)
        elif event.type == pygame.KEYUP:  # Judge whether the key is released
            check_keyup_events(event, ship)
        elif event.type == pygame.MOUSEBUTTONDOWN:  # Determine whether the mouse is pressed
            mouse_x, mouse_y = pygame.mouse.get_pos()  # Returns the coordinates of the pressed point
            check_play_button(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets, mouse_x, mouse_y)

After that, we need to modify the call to check_ in the main function. Formal parameters passed in the events () section

# alien.invasion.py 
    while True:
        gf.check_events(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets)
        if stats.game_active:
            ship.update()
            gf.update_bullets(ai_settings, screen, stats, sb, ship, aliens, bullets)
            gf.update_aliens(ai_settings, stats, screen, ship, aliens, bullets)
        gf.update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button)

Displays the number of ships remaining

Finally, let's show how many ships the player has left, but here we'll use pictures instead of numbers. To this end, we draw the number of spaceships in the upper left corner of the screen.

First, we let the Ship class inherit Sprite so that we can correctly create the Ship group.

# ship.py
class Ship(Sprite):
    def __init__(self, ai_settings, screen):
        """Initialize the spacecraft and set its initial position"""
        super(Ship, self).__init__()
        
        --snip--

First of all, we need to create and update a module in ScoreBoard for our initialization

# scoreboard.py
import pygame.ftfont
from pygame.sprite import Group
from ship import Ship


class Scoreboard:
    """Class that displays score information"""

    def __init__(self, ai_settings, screen, stats):
        """Initialize the attributes involved in displaying scores"""
        
        --snip--

        # Prepare to display the image of the current score and the highest score
        self.prep_score()
        self.prep_high_score()
        self.prep_level()
        self.prep_ships()

Next, we are in prep_ Create a ship group in the ship () method and use this method to show how many ships are left.

# scoreboard.py

    def prep_ships(self):
        """Show how many ships are left"""
        self.ships = Group()
        for ship_number in range(self.stats.ships_left):
            ship = Ship(self.ai_settings, self.screen)
            ship.rect.x = 10 + ship_number * ship.rect.width
            self.rect.y = 10
            self.ships.add(ship)

In this method, we create an empty group to store instances of ships. In order to fill this group, we cycle the corresponding times according to how many ships the player has. In this method, we create a spaceship, reset its coordinates, and then add it to the group.

Next we need to start drawing the spacecraft:

# scoreboard.py
    def show_score(self):
        """Show score"""
        self.screen.blit(self.score_image, self.score_rect) # Displays the current score
        self.screen.blit(self.high_score_image, self.high_score_rect)   # Show highest score
        self.screen.blit(self.level_image, self.level_rect)
        # Drawing spacecraft
        self.ships.draw(self.screen)

In order to let the player know how many ships he has at the beginning of the game, we call the above function at the beginning of the game

# game_function.py
def check_play_button(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets, mouse_x, mouse_y):
    """Click on the player Play Start a new game after"""

    --snip--
    
        stats.game_active = True
        # Reset scoreboard image
        sb.prep_score() # Reset current score
        sb.prep_high_score()    # Reset maximum score
        sb.prep_level() # Reset game level
        sb.prep_ships() # Reset available ships

Next, we need to call prep when the spacecraft is hit by aliens_ Ships() method

# game_function.py
def ship_hit(ai_settings, stats, sb, screen, ship, aliens, bullets):
    """Responding to a spaceship hit by aliens"""

    if stats.ships_left > 0:
        stats.ships_left -= 1  # Reduce the number of ships by one
        sb.prep_ships()  # Update scoreboard
        # Clear the list of bullets and aliens
        aliens.empty()
        bullets.empty()

        # Create a new group of aliens and put the spacecraft in the center of the screen
        create_fleet(ai_settings, screen, ship, aliens)  # Create new aliens
        ship.center_ship()  # Move the spacecraft to the center of the screen

        # suspend
        sleep(0.5)
    else:
        stats.game_active = False
        pygame.mouse.set_visible(True)

Here, we pass in the instance sb of the module scoreboard and call prep when the number of spacecraft decreases_ Ships() method

At the same time, we need to modify the following functions and the corresponding function calls to ensure that the parameters are passed correctly

def check_aliens_bottom(ai_settings, stats, sb, screen, ship, aliens, bullets):
    """Check if aliens have reached the bottom of the screen"""
    screen_rect = screen.get_rect()  # Read the matrix information of the screen
    for alien in aliens:

        # If the coordinates of the alien's bottom matrix are larger than the screen, the collision response is performed
        if alien.rect.bottom >= screen_rect.bottom:
            ship_hit(ai_settings, stats, sb, screen, ship, aliens, bullets)
            break


def update_aliens(ai_settings, stats, sb, screen, ship, aliens, bullets):
    """Update the location of Aliens"""
    check_fleet_edges(ai_settings, aliens)
    aliens.update()

    # Detect the collision between aliens and spacecraft
    if pygame.sprite.spritecollideany(ship, aliens):
        ship_hit(ai_settings, stats, sb, screen, ship, aliens, bullets)
    check_aliens_bottom(ai_settings, stats, sb, screen, ship, aliens, bullets)

Finally, we modify the code of the calling part in the main function and pass the argument sb to it

# alien.invasion.py

    while True:
        gf.check_events(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets)
        if stats.game_active:
            ship.update()
            gf.update_bullets(ai_settings, screen, stats, sb, ship, aliens, bullets)
            gf.update_aliens(ai_settings, stats, sb,screen, ship, aliens, bullets)
        gf.update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button)

The figure below shows the complete game scoring system, and the number of ships remaining is indicated in the upper left corner

summary

This is the end of the study of alien invasion in the project. We have learned in this project, such as hot drawing images on Pygame, how to respond to mouse and keyboard keys, creating visual buttons, etc., but there are still many imperfections in this project. For example, the highest score will be restored to 0 every time the project is re run, without other additional functions, For example, aliens can also launch bullets and then control the spacecraft to avoid. In the follow-up process, if there is a chance, the project will be optimized. Next, I mainly study the knowledge related to chart drawing and image processing.

All codes

The following is the code that can run successfully in the environment mentioned at the beginning as a reference, including a total of nine modules

alien_invasion.py

# Main module, alien_invasion.py
import pygame  # It contains the functions required for game development
from settings import Settings
from ship import Ship
import game_function as gf
from pygame.sprite import Group
from game_stats import GameStats
from button import Button
from scoreboard import Scoreboard


def run_game():
    """Initialize the game and create a screen object"""
    pygame.init()  # Initialize and check whether the toolkit is complete
    ai_settings = Settings()
    screen = pygame.display.set_mode(
        (ai_settings.screen_width, ai_settings.screen_height))  # Create a window with a size of 800 * 600
    pygame.display.set_caption("Alien invasion")  # Set screen name
    play_button = Button(ai_settings, screen, "Play")
    stats = GameStats(ai_settings)
    sb = Scoreboard(ai_settings, screen, stats)
    ship = Ship(ai_settings, screen)  # Create a spaceship
    bullets = Group()  # Create a group to store bullets
    aliens = Group()  # Create an alien group
    gf.create_fleet(ai_settings, screen, ship, aliens)  # Create alien groups
    # Start the main cycle of the game
    while True:
        gf.check_events(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets)
        if stats.game_active:
            ship.update()
            gf.update_bullets(ai_settings, screen, stats, sb, ship, aliens, bullets)
            gf.update_aliens(ai_settings, stats, sb,screen, ship, aliens, bullets)
        gf.update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button)


run_game()  # Run program

ship.py

# Initialize the relevant class of the spacecraft, ship py
import pygame
from pygame.sprite import Sprite


class Ship(Sprite):
    def __init__(self, ai_settings, screen):
        """Initialize the spacecraft and set its initial position"""
        super(Ship, self).__init__()
        self.screen = screen
        self.moving_right = False  # Can I move the sign to the right
        self.moving_left = False  # Can I move the sign to the left
        self.ai_settings = ai_settings

        # Load the ship image and obtain its circumscribed rectangle
        self.image = pygame.image.load("images/ship.bmp")
        self.rect = self.image.get_rect()  # Get the size attribute of the image and save it
        self.screen_rect = screen.get_rect()  # Get the size attribute of the screen and save it

        # Put each new ship in the center of the bottom
        self.rect.centerx = self.screen_rect.centerx  # Obtain the midpoint data of the x-axis of the screen and assign it to rect
        self.rect.bottom = self.screen_rect.bottom  # Get the bottom position data of the screen and assign it to rect

        # Store decimals in the attribute center of the spacecraft
        self.center = float(self.rect.centerx)  # A new decimal storage property

    def blitme(self):
        """Draw the spaceship at the specified location"""
        self.screen.blit(self.image, self.rect)

    def update(self):
        """Check the status of the flag, if the flag is True Just move the ship"""

        # Update the center value of the spacecraft instead of the rect value
        if self.moving_right and self.rect.right < self.screen_rect.right:
            self.center += self.ai_settings.ship_speed
        if self.moving_left and self.rect.left > 0:
            self.center -= self.ai_settings.ship_speed

        # According to self The value of center updates self Value of centerx
        self.rect.centerx = self.center

    def center_ship(self):
        """Center the ship on the screen"""
        self.center = self.screen_rect.centerx

game_function.py

# Functions about various functions of the game, game_fuction.py
import sys  # Use the sys module to exit the game
import pygame  # It contains the functions required for game development
from bullet import Bullet
from alien import Alien
from time import sleep


def check_keydown_events(event, ai_settings, screen, ship, bullets):
    """Operate in response to the key"""
    if event.key == pygame.K_RIGHT:  # Determine whether the right arrow is pressed
        ship.moving_right = True
    elif event.key == pygame.K_LEFT:  # Determine whether the left arrow is pressed
        ship.moving_left = True
    elif event.key == pygame.K_SPACE:  # Determine whether the space is pressed
        fire_bullet(ai_settings, screen, ship, bullets)
    elif event.key == pygame.K_q:  # Judge whether Q is pressed. If Q is pressed, quit the game
        sys.exit()


def check_keyup_events(event, ship):
    """Response release key"""
    if event.key == pygame.K_RIGHT:  # Right arrow when judging the released
        ship.moving_right = False
    elif event.key == pygame.K_LEFT:  # Judge whether there is an arrow loosened
        ship.moving_left = False


def check_events(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets):
    """Respond to keyboard and mouse events"""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:  # If the type of event is exit, it is equivalent to mouse click ×
            sys.exit()
        elif event.type == pygame.KEYDOWN:  # Determine whether a key is pressed
            check_keydown_events(event, ai_settings, screen, ship, bullets)
        elif event.type == pygame.KEYUP:  # Judge whether the key is released
            check_keyup_events(event, ship)
        elif event.type == pygame.MOUSEBUTTONDOWN:  # Determine whether the mouse is pressed
            mouse_x, mouse_y = pygame.mouse.get_pos()  # Returns the coordinates of the pressed point
            check_play_button(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets, mouse_x, mouse_y)


def check_play_button(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets, mouse_x, mouse_y):
    """Click on the player Play Start a new game after"""
    # Judge whether the position of the mouse click is within the key and whether the game state is active at this time
    if play_button.rect.collidepoint(mouse_x, mouse_y) and not stats.game_active:
        # Reset game settings
        ai_settings.initialize_dynamic_speed()
        # hide cursor
        pygame.mouse.set_visible(False)
        # Reset game statistics
        stats.reset_stats()
        stats.game_active = True
        # Reset scoreboard image
        sb.prep_score()  # Reset current score
        sb.prep_high_score()  # Reset maximum score
        sb.prep_level()  # Reset game level
        sb.prep_ships()  # Reset available ships
        # Clear alien and bullet list
        aliens.empty()
        bullets.empty()
        # Create a new group of aliens and center it
        create_fleet(ai_settings, screen, ship, aliens)
        ship.center_ship()


def update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button):
    """Update the image on the screen and switch to a new screen"""
    screen.fill(ai_settings.bg_color)  # Fills the screen with the specified color
    for bullet in bullets:
        bullet.draw_bullet()
    ship.blitme()  # Display the spacecraft on the screen
    aliens.draw(screen)  # Let aliens appear on the screen
    sb.show_score()  # Print scoring information
    if not stats.game_active:  # Print the start button if the game is inactive
        play_button.draw_button()
    pygame.display.flip()  # Displays the most recently drawn screen


def update_bullets(ai_settings, screen, stats, sb, ship, aliens, bullets):
    """Update the location of bullets and delete bullets that have disappeared"""
    bullets.update()

    # The number of lists or groups should not be modified in the for loop, which will lead to the loss of traversal
    for bullet in bullets.copy():
        if bullet.rect.bottom <= 0:
            bullets.remove(bullet)
    check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets)
    # print(len(bullets)) # Show how many bullets there are. This knowledge test. Deleting this statement at run time can reduce memory


def check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets):
    """Respond to the collision between bullets and aliens, and generate a new group of aliens after all aliens die"""
    # Detect whether a bullet hit the alien. If so, delete the bullet and alien

    collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
    if collisions:  # Increase score when hitting aliens
        for aliens in collisions.values():
            stats.score += ai_settings.alien_point * len(aliens)
            sb.prep_score()
        check_high_score(stats, sb)
    if len(aliens) == 0:
        # If all aliens are eliminated, delete the existing bullets and regenerate aliens
        bullets.empty()
        ai_settings.increase_speed()
        # Raise the level
        stats.level += 1
        sb.prep_level()
        create_fleet(ai_settings, screen, ship, aliens)


def fire_bullet(ai_settings, screen, ship, bullets):
    """If the firing limit is not reached, fire a bullet"""
    if len(bullets) < ai_settings.bullets_allowed:
        new_bullet = Bullet(ai_settings, screen, ship)  # Create an instance of the bullet
        bullets.add(new_bullet)  # Place bullet instances in groups


def get_number_rows(ai_settings, ship_height, alien_height):
    """Calculate how many lines the screen can hold"""
    # Calculate how much space is left on the screen
    available_space_y = ai_settings.screen_height - 4 * alien_height - ship_height
    # How many lines are there on the calculation screen
    number_rows = int(available_space_y / (2 * alien_height))
    return number_rows


def get_number_alien_x(ai_settings, alien_width):
    """Calculate how many aliens each row can hold"""
    # Calculate how many positions there are in a row
    available_space_x = ai_settings.screen_width - 2 * alien_width
    # Calculate how many aliens a row can hold
    number_alien_x = int(available_space_x / (2 * alien_width))
    return number_alien_x


def create_alien(ai_settings, screen, aliens, alien_number, row_number):
    """Create an alien and put it in the current line"""
    alien = Alien(ai_settings, screen)  # Create an alien
    alien_width = alien.rect.width  # Get the width of an alien
    alien_height = alien.rect.height
    alien.x = alien_width + 2 * alien_width * alien_number  # Set the initial position of each alien
    alien.rect.x = alien.x

    # Determine the vertical position of each alien
    alien.rect.y = alien_height + 2 * alien_height * row_number
    aliens.add(alien)  # Adding aliens to a group


def create_fleet(ai_settings, screen, ship, aliens):
    """Create alien groups"""
    alien = Alien(ai_settings, screen)  # Create an alien
    number_alien_x = get_number_alien_x(ai_settings, alien.rect.width)
    number_rows = get_number_rows(ai_settings, ship.rect.height, alien.rect.height)
    # Create first row
    for row_number in range(number_rows):
        for alien_number in range(number_alien_x):
            create_alien(ai_settings, screen, aliens, alien_number, row_number)


def check_fleet_edges(ai_settings, aliens):
    """When aliens reach the edge, take corresponding measures"""
    for alien in aliens.sprites():
        if alien.check_edge():
            change_fleet_direction(ai_settings, aliens)
            break


def change_fleet_direction(ai_settings, aliens):
    """Move all aliens down and change their direction"""
    for alien in aliens.sprites():
        alien.rect.y += ai_settings.fleet_drop_speed
    ai_settings.fleet_direction *= -1


def ship_hit(ai_settings, stats, sb, screen, ship, aliens, bullets):
    """Responding to a spaceship hit by aliens"""

    if stats.ships_left > 0:
        stats.ships_left -= 1  # Reduce the number of ships by one
        sb.prep_ships()  # Update scoreboard
        # Clear the list of bullets and aliens
        aliens.empty()
        bullets.empty()

        # Create a new group of aliens and put the spacecraft in the center of the screen
        create_fleet(ai_settings, screen, ship, aliens)  # Create new aliens
        ship.center_ship()  # Move the spacecraft to the center of the screen

        # suspend
        sleep(0.5)
    else:
        stats.game_active = False
        pygame.mouse.set_visible(True)


def check_aliens_bottom(ai_settings, stats, sb, screen, ship, aliens, bullets):
    """Check if aliens have reached the bottom of the screen"""
    screen_rect = screen.get_rect()  # Read the matrix information of the screen
    for alien in aliens:

        # If the coordinates of the alien's bottom matrix are larger than the screen, the collision response is performed
        if alien.rect.bottom >= screen_rect.bottom:
            ship_hit(ai_settings, stats, sb, screen, ship, aliens, bullets)
            break


def update_aliens(ai_settings, stats, sb, screen, ship, aliens, bullets):
    """Update the location of Aliens"""
    check_fleet_edges(ai_settings, aliens)
    aliens.update()

    # Detect the collision between aliens and spacecraft
    if pygame.sprite.spritecollideany(ship, aliens):
        ship_hit(ai_settings, stats, sb, screen, ship, aliens, bullets)
    check_aliens_bottom(ai_settings, stats, sb, screen, ship, aliens, bullets)


def check_high_score(stats, sb):
    """Check whether the highest score is born"""
    if stats.score > stats.high_score:
        stats.high_score = stats.score
        sb.prep_high_score()

alien.py

# Initialize alien related modules, alien py
import pygame
from pygame.sprite import Sprite


class Alien(Sprite):
    def __init__(self, ai_settings, screen):
        """Initialize the alien and set its starting position"""
        super().__init__()
        self.screen = screen
        self.ai_settings = ai_settings

        # Load the alien image and set its rect property
        self.image = pygame.image.load("images/alien.bmp")
        self.rect = self.image.get_rect()

        # Let each alien appear near the upper left corner of the screen
        self.rect.x = self.rect.width  # Left margin set to alien width
        self.rect.y = self.rect.height  # The top margin is set to alien height

        # Store the exact location of aliens
        self.x = float(self.rect.x)  # It is mainly used for back calculation

    def blitme(self):
        """Draw aliens at the specified location"""
        self.screen.blit(self.image, self.rect)

    def check_edge(self):
        """If the alien is on the edge of the screen, return True"""
        screen_rect = self.screen.get_rect()
        # If the edge of the alien is greater than or equal to the right edge of the screen
        if self.rect.right >= screen_rect.right:
            return True
        # If the edge of the alien is less than or equal to the left edge of the screen, i.e. coordinate 0
        elif self.rect.left <= 0:
            return True

    def update(self):
        """Move aliens to the right"""
        self.x += (self.ai_settings.alien_speed *
                   self.ai_settings.fleet_direction)
        self.rect.x = self.x

settings.py

# Store game related setting information, settings py
class Settings:
    """Store all classes related to alien invasion"""

    def __init__(self):
        """Initialize game static settings"""
        # screen setting
        self.screen_width = 1200  # Set window width
        self.screen_height = 700  # Set window height
        self.bg_color = (230, 230, 230)  # Set background color

        # Spacecraft setup
        self.ship_limit = 3  # Set the maximum number of ships for players

        # Bullet setting
        self.bullet_width = 300  # The width of the bullet
        self.bullet_height = 15  # Bullet height
        self.bullet_color = (60, 60, 60)  # Bullet color
        self.bullets_allowed = 3  # Limit the number of bullets that do not disappear to 3

        # Alien settings
        self.fleet_drop_speed = 10  # The speed at which aliens move down

        self.speed_up = 1.1  # What kind of speed to accelerate the pace of the game

        self.score_scale = 1.5  # Speed up scores

        self.initialize_dynamic_speed()

    def initialize_dynamic_speed(self):
        """The amount of dynamic change as the game progresses"""
        self.ship_speed = 1.5  # Set the initial value of spacecraft speed
        self.bullet_speed = 3  # Bullet speed
        self.alien_speed = 0.5  # Aliens move at a speed of 0.5
        self.alien_point = 50  # Points per alien defeated

        # fleet_ When direction is 1, it means to move right, and when direction is - 1, it means to move left
        self.fleet_direction = 1

    def increase_speed(self):
        """Speed up"""
        self.ship_speed *= self.speed_up
        self.bullet_speed *= self.speed_up
        self.alien_speed *= self.speed_up
        self.alien_point = int(self.alien_point * self.score_scale)
        # print(self.alien_point)   # Print and display the score of the current alien

game_stats.py

# Module for storing game statistics, game_stats.py
class GameStats:
    """Track game statistics"""

    def __init__(self, ai_settings):
        self.ai_settings = ai_settings
        self.reset_stats()
        self.game_active = False  # The game has just started and is active
        self.high_score = 0  # The highest score should not be reset at any time

    def reset_stats(self):
        """Initialize information that may change during game operation"""

        # Count the number of ships remaining in the game
        self.ships_left = self.ai_settings.ship_limit
        self.score = 0  # Count game scores
        self.level = 1  # Count the level information of the game

bullet.py

# Initialize the module of the bullet py
import pygame
from pygame.sprite import Sprite


class Bullet(Sprite):
    """A class used to manage the firing of bullets from a spacecraft"""

    def __init__(self, ai_settings, screen, ship):
        """Add a bullet object to the position of the ship"""
        super(Bullet, self).__init__()
        self.screen = screen

        # Create a rectangle representing the bullet at (0, 0) and set the correct position.
        self.rect = pygame.Rect(0, 0, ai_settings.bullet_width,
                                ai_settings.bullet_height)
        self.rect.centerx = ship.rect.centerx  # Set the centerx of the bullet to the centerx of the ship
        self.rect.top = ship.rect.top  # Set the top of the bullet to the top of the ship

        # Store bullet position in decimal
        self.y = float(self.rect.y)  # Store the y coordinate of the bullet in decimal places

        self.color = ai_settings.bullet_color  # Sets the color of the bullet
        self.speed = ai_settings.bullet_speed  # Set the speed of the bullet

    def update(self):
        """Move the bullet up"""
        self.y -= self.speed
        self.rect.y = self.y

    def draw_bullet(self):
        """Draw bullets on the screen"""
        pygame.draw.rect(self.screen, self.color, self.rect)

button.py

# For information about initializing game buttons, see button py
import pygame.ftfont  # The module can render text to the screen


class Button:

    # message is the text we want to display in the button
    def __init__(self, ai_settings, screen, message):
        """Initialize button properties"""
        self.screen = screen
        self.screen_rect = screen.get_rect()

        # Set the size and other properties of the button
        self.width, self.height = 200, 50
        self.button_color = (0, 255, 0)  # The button is set to green
        self.text_color = (255, 255, 255)  # The text content is set to white
        self.font = pygame.font.SysFont(None, 48)  # None indicates the default font and 48 indicates the font size

        # Create a rect object for the button and center it
        self.rect = pygame.Rect(0, 0, self.width, self.height)
        self.rect.center = self.screen_rect.center

        # Create a label for the button
        self.prep_msg(message)

    def prep_msg(self, message):
        """take message Render as an image and center it in the button"""
        self.msg_image = self.font.render(message, True, self.text_color,
                                          self.button_color)    # Convert text to image
        self.msg_image_rect = self.msg_image.get_rect()
        self.msg_image_rect.center = self.rect.center

    def draw_button(self):
        """Draw a button before you draw text"""
        self.screen.fill(self.button_color, self.rect)  # Draw button
        self.screen.blit(self.msg_image, self.msg_image_rect)   # Draw text

scoreboard.py

# Score statistics and rendering for image printing module, scoreboard py
import pygame.ftfont
from pygame.sprite import Group
from ship import Ship


class Scoreboard:
    """Class that displays score information"""

    def __init__(self, ai_settings, screen, stats):
        """Initialize the attributes involved in displaying scores"""
        self.screen = screen
        self.screen_rect = screen.get_rect()
        self.ai_settings = ai_settings
        self.stats = stats

        # Font setting for displaying score information
        self.text_color = (30, 30, 30)
        self.font = pygame.font.SysFont(None, 48)

        # Prepare to display the image of the current score and the highest score
        self.prep_score()
        self.prep_high_score()
        self.prep_level()
        self.prep_ships()

    def prep_score(self):
        """Render scores as images"""
        rounded_score = int(round(self.stats.score, -1))
        score_str = "{:,}".format(rounded_score)
        self.score_image = self.font.render(score_str, True, self.text_color, self.ai_settings.bg_color)

        # Displays the score in the upper right corner
        self.score_rect = self.score_image.get_rect()
        self.score_rect.right = self.screen_rect.right - 20
        self.score_rect.top = 20

    def prep_high_score(self):
        """Converts the highest score to a rendered image"""
        high_score = int(round(self.stats.high_score, -1))
        high_score_str = "{:,}".format((high_score))
        self.high_score_image = self.font.render(high_score_str, True, self.text_color, self.ai_settings.bg_color)

        # Place the highest score in the center of the screen
        self.high_score_rect = self.high_score_image.get_rect()
        self.high_score_rect.centerx = self.screen_rect.centerx
        self.high_score_rect.top = 20

    def prep_level(self):
        """Convert levels to images"""
        self.level_image = self.font.render(str(self.stats.level), True, self.text_color, self.ai_settings.bg_color)

        # Put the grade below the score
        self.level_rect = self.level_image.get_rect()
        self.level_rect.right = self.score_rect.right
        self.level_rect.top = self.score_rect.bottom + 10

    def prep_ships(self):
        """Show how many ships are left"""
        self.ships = Group()
        for ship_number in range(self.stats.ships_left):
            ship = Ship(self.ai_settings, self.screen)
            ship.rect.x = 10 + ship_number * ship.rect.width
            ship.rect.y = 10
            self.ships.add(ship)



    def show_score(self):
        """Show score"""
        self.screen.blit(self.score_image, self.score_rect) # Displays the current score
        self.screen.blit(self.high_score_image, self.high_score_rect)   # Show highest score
        self.screen.blit(self.level_image, self.level_rect)
        # Drawing spacecraft
        self.ships.draw(self.screen)

epilogue

If you have any questions in learning, please leave a message in the comment area. You will reply when you see it. Let's cheer together.

Keywords: Python Programming Game Development pygame

Added by davex on Tue, 08 Feb 2022 12:26:44 +0200