import random
import arcade
import _thread


class Fireball(arcade.Sprite):
    def update(self):
        move_rate = 10
        self.center_y += move_rate


class Bad_Guy(arcade.Sprite):
    def update(self):

        drop_rate = random.randrange(-3, 2)
        lateral_rate = random.randrange(-2, 3)

        if lateral_rate > 1:
            rotation = random.randrange(0, 3)
        else:
            rotation = random.randrange(-3, 0)

        # do the moves
        self.center_y += drop_rate
        self.center_x += lateral_rate
        self.angle += rotation


class MyGame(arcade.Window):
    """ Main application class. """

    def sound(self, selected_sound):

        print(selected_sound)
        if selected_sound == "fart":
            arcade.sound.play_sound(self.fart_sound)
        else:
            arcade.sound.play_sound(self.boink_sound)

    def __init__(self):
        """ Initializer """
        # Call the parent class initializer
        super().__init__(800, 600, "Shooter")

        # Variables that will hold sprite lists
        self.player_list = None
        self.enemies_list = None
        self.fireball_list = None

        # Set up the player info
        self.player_sprite = None
        self.score = 0

        # Sounds
        self.fart_sound = None
        self.boink_sound = None

        # Don't show the mouse cursor
        self.set_mouse_visible(False)

        # BG
        self.line_start = -1

        # BG
        arcade.set_background_color(arcade.color.BLACK)

    def setup(self):

        """ Set up the game and initialize the variables. """

        # Sprite lists
        self.player_list = arcade.SpriteList()
        self.enemies_list = arcade.SpriteList()
        self.fireball_list = arcade.SpriteList()

        # Set up the player
        self.score = 0

        # Sound Effects
        self.fart_sound = arcade.sound.load_sound("raspberry.wav")
        self.boink_sound = arcade.sound.load_sound("boink.wav")

        # Our hero
        self.player_sprite = arcade.Sprite("raspberry.png", 1)
        self.player_sprite.center_x = 400
        self.player_sprite.center_y = 50
        self.player_list.append(self.player_sprite)

        # Create the enemies
        for i in range(60):

            # Create a bad_guy
            bad_guy = Bad_Guy("bug.png", 1)

            # Position the bad_guy
            bad_guy.center_x = random.randrange(800)
            bad_guy.center_y = random.randrange(500, 1500)

            # Add the bad_guy to the lists
            self.enemies_list.append(bad_guy)

        # Set the background color
        arcade.set_background_color(arcade.color.BLACK)

    def on_draw(self):
        """
        Render the screen.
        """

        # This command has to happen before we start drawing
        arcade.start_render()

        # move the background
        if self.line_start < 49:
            self.line_start += 1
        else:
            self.line_start = 0

        # Draw vertical lines every 50 pixels
        for x in range(0, 800, 50):
            arcade.draw_line(x + self.line_start, 0, x + self.line_start, 600, arcade.color.BLUE_GRAY, 2)

        # Draw horizontal lines every 50 pixels
        for y in range(0, 600, 50):
            arcade.draw_line(0, y + self.line_start, 800, y + self.line_start, arcade.color.BLUEBERRY, 2)

        # Draw all the sprites.
        self.enemies_list.draw()
        self.fireball_list.draw()
        self.player_sprite.draw()

        # Draw the score box
        arcade.draw_rectangle_filled(590, 520, 300, 80, (0, 0, 0, 150))

        # Draw the score text
        arcade.draw_text(f"Score: {self.score}", 498, 498, arcade.color.DARK_RED, 36)
        arcade.draw_text(f"Score: {self.score}", 500, 500, arcade.color.ORANGE_RED, 36)

    def on_mouse_motion(self, x, y, dx, dy):
        """
        Called whenever the mouse moves.
        """
        self.player_sprite.center_x = x

    def on_mouse_press(self, x, y, button, modifiers):
        """
        Called whenever the mouse button is clicked.
        """

        # Create a fireball
        fireball = Fireball("fireball.png", 1)

        # play sound
        _thread.start_new_thread(self.sound, ("fart",))

        # rotate it.
        fireball.angle = random.randrange(-15, 15)

        # Position the fireball
        fireball.center_x = self.player_sprite.center_x
        fireball.bottom = self.player_sprite.top

        # Add the fireball to the appropriate lists
        self.fireball_list.append(fireball)

    def update(self, delta_time):
        """ Movement and game logic """

        # Call update on the enemies
        self.enemies_list.update()

        # Call update on fireball sprites
        self.fireball_list.update()

        # Loop through each fireball
        for fireball in self.fireball_list:

            # Check this fireball to see if it hit an enemy
            hit_list = arcade.check_for_collision_with_list(fireball, self.enemies_list)

            # If it did, get rid of the fireball
            if len(hit_list) > 0:
                # play sound
                _thread.start_new_thread(self.sound, ("boink",))

                # kill it
                fireball.kill()

            # For every enemy we hit, add to the score and remove the bad_guy
            for bad_guy in hit_list:
                bad_guy.kill()
                self.score += 1

            # If the fireball flies off-screen, remove it.
            if fireball.bottom > 600:
                fireball.kill()


def main():
    window = MyGame()
    window.setup()
    arcade.run()


if __name__ == "__main__":
    main()

Вы заметите, что я разделил звуковые эффекты на новые потоки — если мы этого не сделаем, в настоящее время игра останавливается, пока воспроизводится звук. Я связался с разработчиком аркадного модуля в Reddit, и он работает над этим, но я рассказал ему о своем решении для многопоточности в качестве опции.

Многопоточность — это действительно круто, я, скорее всего, вернусь с учебником именно об этом.