Python project alien invasion: Aliens

Posted by Sgarissta on Tue, 08 Feb 2022 13:44:58 +0100

Python project alien invasion (II): Aliens

In this chapter, we mainly complete the following functions. First, add an alien at the edge of the screen, and then try to generate a group of aliens. We make these aliens move like two hundred years and below, and delete the aliens hit by the bullet. Finally, we show the number of ships the player has and end the game when the player runs out of ships. In this section, due to the need of display, the size of the screen in the Setting class is modified to 1200 * 700, the moving speed of the spacecraft is modified to 1.5, the bullet speed is modified to 1, and the code does not need to be modified elsewhere (mainly to make the number of Aliens displayed on the screen reasonable). The complete code is at the bottom. If you have any questions, you can ask them in the comment area. If you see them, you will reply:

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

    def __init__(self):
        """Initialize game 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
        self.ship_speed = 1.5  # Set the initial value of the speed

        # Bullet setting
        self.bullet_speed = 1     # Bullet speed
        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

Use Q to exit the game

Next, we set the shortcut key to exit the game. When we press Q from the keyboard, the game will exit automatically. Therefore, we need to modify the function check_keydown_events():

# game_function.py
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()

Create an alien

Preventing aliens on the screen is similar to placing spaceships on the screen. We will create a new Alien class in which we control the behavior of aliens. In order to simplify the program, we use bitmaps to represent aliens. We can not only obtain pictures of aliens from the Internet as in the previous chapter, but also directly use the following pictures. Please be sure to save the pictures in Alien_ In the images folder under the invasion folder.

First, we create the Alien class:

# alien.py
import pygame
from pygame.sprite import Sprite


class Alien(Sprite):
    def __init__(self, screen, ai_settings):
        """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)

Next, we need to instantiate the Alien class in the main function:

# 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 alien import Alien


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_length))  # Create a window with a size of 800 * 600
    pygame.display.set_caption("Alien invasion")  # Set screen name
    ship = Ship(ai_settings, screen)  # Create a spaceship
    bullets = Group()  # Create a group to store bullets
    alien = Alien(ai_settings, screen)    # Create an alien instance
    
    # Start the main cycle of the game
    while True:
        gf.check_events(ai_settings, screen, ship, bullets)
        ship.update()
        gf.update_bullets(bullets)
        gf.update_screen(ai_settings, screen, ship, alien, bullets)


run_game()  # Run program

Then we were at the game_function.py to modify the screen update function so that aliens can appear on the screen:

# game_function.py
def update_screen(ai_settings, screen, ship, alien, bullets):
    """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
    alien.blitme()  # Let aliens appear on the screen
    pygame.display.flip()  # Displays the most recently drawn screen

After that, when we run the main function, we can see an alien in the upper right corner of the screen. We draw bullets and spacecraft first to ensure that aliens are at the front.

Create a group of Aliens

Determine how many aliens a row can hold

To draw a group of aliens, we first need to determine how many aliens can be accommodated in a row. We need to first calculate the horizontal spacing to determine how many aliens can be accommodated in a row, and then calculate the vertical spacing to determine how many aliens can be accommodated in a column.

First, we calculate how much horizontal free space there is, and the width of the screen is stored in AI_ settings. screen. In width, because we need to leave a space of two alien widths on both sides of the screen, the actual available space in one line is:

available_space_x = ai_settings.screen_width - 2 * alien_width

At the same time, we also need to leave a certain space between aliens, that is, the width of aliens. Therefore, the space required by yo aliens is twice its width. One width is used to place aliens and the other width is the blank on the right of aliens. Therefore, we can divide the space by twice the width of aliens to determine how many aliens can be accommodated in a row:

number_alien_x = available_space_x / (2 * alien_width)

Create a line of Aliens

To create a row of aliens, we first need to create an empty group named aliens in the main function to store all aliens, and then call game_function.py function for creating alien populations:

# 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


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_length))  # Create a window with a size of 800 * 600
    pygame.display.set_caption("Alien invasion")  # Set screen name
    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, aliens)

    # Start the main cycle of the game
    while True:
        gf.check_events(ai_settings, screen, ship, bullets)
        ship.update()
        gf.update_bullets(bullets)
        gf.update_screen(ai_settings, screen, ship, aliens, bullets)


run_game()  # Run program

We are no longer in alien_invasion.py directly creates aliens, but creates an empty group in game_ function. Create of PY_ To generate a group of aliens in fleet (), we modified GF update_ The call in screen () passes the alien group to it. At the same time, we also need to modify the internal call of the function:

# game_function.py
def update_screen(ai_settings, screen, ship, aliens, bullets):
    """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
    pygame.display.flip()  # Displays the most recently drawn screen

When calling the draw() function to draw aliens on each screen, Python will automatically draw each element in the group, and the drawing position is determined by rect.

Then we can play in the game_ function. Write a new function create in PY_ Fleet() to generate a group of aliens. At the same time, because the basic parameters of aliens need to be used to generate aliens, we need to import Alien class here:

# game_function.py
def create_fleet(ai_settings, screen, aliens):
    """Create alien groups"""
    alien = Alien(ai_settings, screen)  # Create an alien
    alien_width = alien.rect.width  # Get the width of an alien

    # 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))

    # Create first row
    for alien_number in range(number_alien_x):
        # Create an alien and join the current line
        alien = Alien(ai_settings, screen)  # Create an alien
        alien.x = alien_width + 2 * alien_width * alien_number  # Set the initial position of each alien
        alien.rect.x = alien.x
        aliens.add(alien)   # Adding aliens to a group

In order to place aliens, we need to know their height and width. Therefore, before calculation, we created an alien, but did not put it into the group. Then we obtained the width attribute of aliens and calculated how many aliens can be accommodated in a row.

Next, we convert the calculated number into an integer, because we don't want an alien to display only a part. At the same time, the range() function also needs an integer, which returns a list of integers from 0 to the incoming number minus 1

In the main body of the loop, we create a new alien, add it to the current line by setting the x coordinate, and then set the distance between aliens.

After running, we will see a line of aliens.

Refactoring create_fleet()

Next, we refactor create_ The fleet () function decomposes its function into get that calculates how many aliens a line can hold_ number_ alien_ X () function, and create an alien function_ alien()

# game_function.py
def create_alien(ai_settings, screen, aliens, alien_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.x = alien_width + 2 * alien_width * alien_number  # Set the initial position of each alien
    alien.rect.x = alien.x
    aliens.add(alien)  # Adding aliens to a group


def create_fleet(ai_settings, screen, aliens):
    """Create alien groups"""
    alien = Alien(ai_settings, screen)  # Create an alien
    number_alien_x = get_number_alien_x(ai_settings, alien.rect.width)

    # Create first row
    for alien_number in range(number_alien_x):
        create_alien(ai_settings, screen, aliens, alien_number)

add rows

To create a swarm of aliens, you need to calculate how many lines the screen can hold and cycle the same number of times for creating a row of aliens. In order to calculate the number of rows that can be accommodated, we need to calculate the available vertical space and subtract the screen height from the height of the spacecraft, the height of aliens, and the distance between the initial spacecraft and aliens.

available_space_y = ai_settings.screen_height-3 * alien_height-ship_height

In this way, a certain blank area will be left at the top of each line to give players time to shoot aliens.

At the same time, a little space is left at the bottom of each row. In order to calculate the number of rows that can be accommodated, we divide the space between us by twice the height of aliens

number_rows = available_space_y / (2 * alien_height)

Once you know how many lines you can hold, you can repeat the code that generates one line:

# 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 - 3 * 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 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)

We're getting_ number_ Rows() implements the modification of the two formulas of the previous calculation line. Here, the int() function is also used to avoid incomplete alien lines. At the same time, the range() function needs to pass integers.

Finally, we use a nested loop. The inner loop is used to create a row of aliens, and the outer loop is used to count from 0 to the number of rows to create aliens.

After that, we need to modify create in the main function_ Modification of parameters of the fleet() function:

gf.create_fleet(ai_settings, screen, ship, aliens)

After running, we can display multiple aliens on the screen.

Let the alien crowd move

Next, we let the alien move to the right, hit the edge of the screen, move down a certain distance, and then move to the left. We will continue to move all aliens until all aliens are eliminated, an alien hits a spaceship, or an alien reaches the bottom of the screen

Let the aliens move to the right

Next, let the alien move to the right. First, we set the alien movement speed in the Settings class:

# settings.py        
    	# Alien settings
        self.alien_speed = 0.5  # Aliens move at a speed of 0.5

Then we use this speed to implement update()

# ailen.py  
    def update(self):
        """Move aliens to the right"""
        self.x += self.ai_settings.alien_speed
        self.rect.x = self.x

Borrow it. Let's go to game_function.py write a new function update_alien() to call this method:

# game_function.py
def update_aliens(aliens):
    """Update the location of Aliens"""
    aliens.update()

Finally, we call the location update function in the while loop of the main function. First, we update the location of the bullet and update the location of the alien.

# alien_invasion.py  
    # Start the main cycle of the game
    while True:
        gf.check_events(ai_settings, screen, ship, bullets)
        ship.update()
        gf.update_bullets(bullets)
        gf.update_aliens(aliens)
        gf.update_screen(ai_settings, screen, ship, aliens, bullets)

Create settings that represent alien movement

First, we set the flag for detecting alien collision screen and the speed of moving down in the Settings class:

# settings.py        
    	# Alien settings
        self.alien_speed = 0.5  # Aliens move at a speed of 1
        self.fleet_drop_speed = 10  # The speed at which aliens move down
        
        # fleet_ When direction is 1, it means to move right, and when direction is - 1, it means to move left
        self.fleet_direction = 1

Here, instead of using the left or right signs to mark the movement direction of aliens, we use 1 and - 1, because moving to the right increases the coordinate of x and moving to the left decreases the coordinate of x. This setting will be more convenient to calculate.

Detect whether aliens hit the edge of the screen

Now in alien Add function check in py module_ Edge() to detect whether aliens move to the edge of the screen. At the same time, we need to modify the update() function to ensure that each alien moves in the right direction:

# alien.py
    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

In the update() method, set the movement amount to alien speed and fleet_direction product, let aliens translate left and right. If fleet_ If the direction is 1, the alien will be moved to the left, if fleet_ If the direction is - 1, the alien will move to the right.

Move the Alien Swarm down and change direction

When an alien reaches the edge of the screen, you need to move the whole group of aliens down and change their moving direction. To do this, we need to modify the game_function.py, and create the function check_fleet_edges() is used to take certain measures and write change when aliens reach the edge of the screen_ fleet_ Direction () moves the aliens down and changes their direction, and updates them_ Aliens();

# game_function.py

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)
            # The reason for using break here is that calling it once will automatically move all grouping members down one line
            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 update_aliens(ai_settings, aliens):
    """Update the location of Aliens"""
    check_fleet_edges(ai_settings, aliens)
    aliens.update()

Check in function_ fleet_ In edges (), we traverse the alien colony and call check for each of them_ Edge(), if it is at the edge, it will return the True value. At this time, we know that aliens are at the edge of the screen and need to change the direction of the alien crowd, so we call change_fleet_direction() traverses all aliens and moves them down AI_ settings. fleet_ drop_ The value set in speed, and then set the flag to the opposite number of the previous flag.

Finally in alien_invasion.py, modify update_aliens(), add the formal parameter ai_settings:

    while True:
        gf.check_events(ai_settings, screen, ship, bullets)
        ship.update()
        gf.update_bullets(bullets)
        gf.update_aliens(ai_settings, aliens)
        gf.update_screen(ai_settings, screen, ship, aliens, bullets)

Then we can see the aliens moving back and forth on the screen and down after reaching the edge.

Shoot aliens

We have created spaceships and alien groups. When the odd bullet hits the alien, it will pass through the alien. In order to avoid this situation, we need to check the collision. In the game, the collision is the overlapping of game elements. In order to make the bullet shoot down the alien, we will use sprite Groupcollapse() detects collisions between two member groups.

Detect bullet and alien collisions

After the bullet hits the alien, we need to know immediately so that the alien can disappear in time after the collision, so we will detect the collision immediately after updating the location of the bullet.

Method sprite Groupcollapse () compares the rect of each bullet with the rect of each alien, and returns a dictionary that includes the colliding bullet and alien. Each key is the value of being shot by aliens in the dictionary.

# game_function.py
def update_bullets(aliens, bullets):
    """Update the location of bullets and delete bullets that have disappeared"""
    bullets.update()
    for bullet in bullets.copy():  # The number of lists or groups should not be modified in the for loop, which will lead to the loss of traversal
        if bullet.rect.bottom <= 0:
            bullets.remove(bullet)
            
    # Detect whether any bullets hit aliens. If so, delete bullets and aliens
    collision = pygame.sprite.groupcollide(bullets, aliens, True, True)

The last line of code traverses each bullet in the grouping bullets, and then traverses each alien in the grouping aliens. Whenever a bullet overlaps with an alien, groupcollide() adds a key value pair to the returned dictionary. The two arguments tell us to delete the bullet and alien in collision, because we call update_bullets() passes the argument aliens, so we need to modify the function call part:

# alien_invasion.py
    while True:
        gf.check_events(ai_settings, screen, ship, bullets)
        ship.update()
        gf.update_bullets(aliens, bullets)
        gf.update_aliens(ai_settings, aliens)
        gf.update_screen(ai_settings, screen, ship, aliens, bullets)

Now we can kill aliens by running the main function.

Create a bullet for the test

As long as you run this game, you can test many functions, but some functions are cumbersome under normal circumstances. For example, it takes a long time to shoot down all aliens when you want to test whether the code can correctly handle the empty formation of aliens. We can modify the width of the bullet in the Settings class to make it easier to hit aliens. For example, we can change the bullet_ The width is changed to 300.

Generate a new group of Aliens

Next, we need to write code to make a new group of aliens appear after an alien population is eliminated. To make a new group of aliens appear after aliens are eliminated, we first need to check whether the aliens group is empty. If it is empty, call create again_ The fleet () method, which we will use in update_bullets() to check, because aliens were destroyed here:

# update_bullets.py
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)

    # Detect whether a bullet hit the alien. If so, delete the bullet and alien
    collision = pygame.sprite.groupcollide(bullets, aliens, True, True)
    if len(aliens) == 0:
        # If all aliens are eliminated, delete the existing bullets and regenerate aliens
        bullets.empty()
        create_fleet(ai_settings, screen, ship, aliens)

Because we modified update_ The definition of bullets () contains formal parameters, so we need to update update in the main function_ Call of bullets():

# alien_invasion.py
    while True:
        gf.check_events(ai_settings, screen, ship, bullets)
        ship.update()
        gf.update_bullets(ai_settings, screen, ship, aliens, bullets)
        gf.update_aliens(ai_settings, aliens)
        gf.update_screen(ai_settings, screen, ship, aliens, bullets)

Now, after we eliminate the aliens on the screen, a new group of aliens will appear.

Refactoring update_bullets()

Next, we refactor update_bullets() function to make it no longer complete so many tasks. We move the code for detecting the collision between bullets and aliens to a separate function:

# game_function.py
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)
    # print(len(bullets)) # Show how many bullets are left. This is used to test. Deleting this statement at run time can reduce memory


def check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets):
    """Respond to the collision between bullets and aliens and generate a new group of aliens after all wasina people died"""
    # Detect whether a bullet hit the alien. If so, delete the bullet and alien
    collision = pygame.sprite.groupcollide(bullets, aliens, True, True)
    if len(aliens) == 0:
        # If all aliens are eliminated, delete the existing bullets and regenerate aliens
        bullets.empty()
        create_fleet(ai_settings, screen, ship, aliens)

End the game

If the player doesn't wipe out the whole group of aliens in enough time, and some aliens hit the ship, the ship will be destroyed. At the same time, we limit the number of ships available to the player. When the aliens reach the bottom of the screen, the ship will also be destroyed. After the player uses up the ship, the game will end.

Detect the collision between aliens and spacecraft

First of all, we check the collision between spacecraft and aliens. After moving the position of each alien, we will immediately detect the collision between spacecraft and Aliens:

# game_function
def update_aliens(ai_settings, ship, aliens):
    """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):
        print("Bang")

The method spritecollideany() accepts two arguments, one object and one group. It checks whether the members of the group collide with the object, stops traversing after finding the collided members, and outputs a prompt message. If there is no collision, the method will return to None. At this time, the if code block will not be executed. If the alien who has found the collision is found, the alien will be returned. At this time, the code behind the if code block will be executed.

In response to the collision between spacecraft and aliens

Now we need to determine what should be done when aliens collide with the spacecraft. Instead of destroying the existing ship instance and creating a new ship instance, we count how many times the spacecraft has been hit by tracking the game and counting the game information, which helps to count the scores in the third part.

Next, write a class to track game statistics, GameStates, and save it as a file game_states.py:

# game_stats.py
class GameStats:
    """Track game statistics"""

    def __init__(self, ai_settings):
        self.ai_settings = ai_settings
        self.reset_stats()

    def reset_stats(self):
        """Initialize information that may change during game operation"""
        
        # Count the number of non passes remaining in the game
        self.ships_left = self.ai_settings.ship_limit

During the game, we only need to reset some statistics every time we start the whole game_ Most statistics are initialized in the stats () method.

Currently, there is only one statistic, ship_left, YONGANLI counts the number of remaining available spacecraft

Then we add parameters in the Settings class:

# settings.py

        # Spacecraft setup
        self.ship_speed = 1.5  # Set the initial value of the speed
        self.ship_limit = 3 # Set the maximum number of ships for players

Next, we modify alien_invasion.py, create an instance of GameStats:

# alien_invasion.py
    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
    stats = GameStats(ai_settings)
    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, ship, bullets)
        ship.update()
        gf.update_bullets(ai_settings, screen, ship, aliens, bullets)
        gf.update_aliens(ai_settings, stats, screen,ship, aliens, bullets)
        gf.update_screen(ai_settings, screen, ship, aliens, bullets)

In the main loop, we modify update in advance_ Aliens parameter, and the parameters stats, screen and ship are added. After an alien collides with a spaceship, we will use these parameters to track how many spaceships the player has and create new aliens.

After an alien collides with a spaceship, we will reduce the number of remaining spaceships by one, create a group of new aliens, and place the spaceship in the bottom center (at the same time, we will pause the game for a period of time to buffer the players)

Next, put the code to realize these functions in ship_hit() and modify update_aliens() function:

from time import sleep

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

    # 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()  # Put the spacecraft in the center of the screen

    # suspend
    sleep(0.5)


def update_aliens(ai_settings, stats, 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, screen, ship, aliens, bullets)

First, import the function sleep() from the module time to pause the game and give the player buffer time, ship_ The hit() function responds when the spaceship hits aliens, reduces the number of available spaceships by 1, then empties the bullets and alien groups, regenerates a group of aliens, moves the spaceship back to the center of the screen, then pauses the game so that players can see the collision, then displays the new spaceship and aliens on the screen, and then writes center_ship() function, put the spaceship in the center of the screen:

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

An alien has reached the bottom of the screen

If an alien reaches the bottom of the screen, the response should be the same as that of an alien hitting a spacecraft. We will add a new function named check_aliens_bottom():

# game_function
def check_aliens_bottom(ai_settings, stats, 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, screen, ship, aliens, bullets)
            break


def update_aliens(ai_settings, stats, 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, screen, ship, aliens, bullets)
    check_aliens_bottom(ai_settings, stats, screen, ship, aliens, bullets)

End the game

At present, although collision has been detected, the game will never end_ The value of left will become negative, and a flag attribute game will be added in GameStats below_ Active so that players can end the game after using up the ship:

# game_function.py
    def __init__(self, ai_settings):
        self.ai_settings = ai_settings
        self.reset_stats()
        self.game_active = True  # The game has just started and is active

Then in ship_ After the game () function is used up, all players will increase the hit function_ Set active to False:

# 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

Determine the running part of the game

In alien_ invasion. In py, we need to determine which parts of the game run in any case and which only run in the active state:

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

In any case, we need to detect keyboard and mouse events and update the screen. The rest is as long as the game is active. Now we run the game and the game will stand still after using the spacecraft.

review

Next, we will paste all the files created by the game so far and the code in the file below:

# 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 time import sleep


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
    stats = GameStats(ai_settings)
    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, ship, bullets)
        if stats.game_active:
            ship.update()
            gf.update_bullets(ai_settings, screen, ship, aliens, bullets)
            gf.update_aliens(ai_settings, stats, screen, ship, aliens, bullets)
        gf.update_screen(ai_settings, screen, ship, aliens, bullets)


run_game()  # Run program
# game_stats.py
class GameStats:
    """Track game statistics"""

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

    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
# 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
class Settings:
    """Store all classes related to alien invasion"""

    def __init__(self):
        """Initialize game 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_speed = 1.5  # Set the initial value of the speed
        self.ship_limit = 3  # Set the maximum number of ships for players

        # Bullet setting
        self.bullet_speed = 1  # Bullet speed
        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.alien_speed = 0.5  # Aliens move at a speed of 0.5
        self.fleet_drop_speed = 10  # The speed at which aliens move down

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


class Ship:
    def __init__(self, ai_settings,screen):
        """Initialize the spacecraft and set its initial position"""
        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)      # Set a new property to store decimals

    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
# 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)

# game_function.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, 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)


def update_screen(ai_settings, screen, ship, aliens, bullets):
    """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
    pygame.display.flip()  # Displays the most recently drawn screen


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)
    # 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, ship, aliens, bullets):
    """Respond to the collision between bullets and aliens and generate a new group of aliens after all wasina people died"""
    # Detect whether a bullet hit the alien. If so, delete the bullet and alien
    collision = pygame.sprite.groupcollide(bullets, aliens, True, True)
    if len(aliens) == 0:
        # If all aliens are eliminated, delete the existing bullets and regenerate aliens
        bullets.empty()
        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 - 3 * 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, 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


def check_aliens_bottom(ai_settings, stats, 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, screen, ship, aliens, bullets)
            break


def update_aliens(ai_settings, stats, 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, screen, ship, aliens, bullets)
    check_aliens_bottom(ai_settings, stats, screen, ship, aliens, bullets)

Topics: Python Game Development pygame