Python Forum
[PyGame] positioning the laser
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[PyGame] positioning the laser
#11
Hi,
I ended up with the following code.

I set angle=0 while instantiation of the gun and experimented with the algebraic sign of angle.
And I tried what was happening if I remove "rotate()" from "self.unit_vector".

On both pictures you can see a small inaccuracy, how the laser leaves the gun.

But I accept this because of the following reason:

The player has to permanently sidestep the endboss' laser. Because of this the gun is permanently rotating aiming at the player.
So you won't notice the inaccuracy.

Thank you very much for your patient help!!
import pygame
from math import radians, sin, cos
import math
from PIL import Image
from pygame import Vector2

pygame.init()

screen_width = 800
screen_height = 600
fps = 60
red = (255, 0, 0)
green = (0, 255, 0)
end_boss_cooldown = 3000  # bullet cooldown in milliseconds

screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("End Boss Example")

ship_image_path = "ship_4.png"
gun_image_path = "gun4.png"
laser_image_path = "endboss_laser2.png"
end_boss_gun_length = 70
# Initialize objects
end_boss_centerx = screen_width / 2
end_boss_centery = 220
end_boss_center = end_boss_centerx, end_boss_centery
bullet_group = pygame.sprite.Group()


# how to entry
# pygame.transform.rotate(degrees)
# math.cos(radians)


# Create EndBoss class
class EndBoss(pygame.sprite.Sprite):
    def __init__(self, center, health):
        super().__init__()
        self.center = center
        # self.x = x
        # self.y = y
        self.health_start = health
        self.health_remaining = health
        self.image = pygame.image.load(ship_image_path)
        self.rect = self.image.get_rect()
        self.rect.center = center

    def update(self):
        if pygame.sprite.spritecollide(self, bullet_group, False, pygame.sprite.collide_mask):
            # reduce spaceship health
            end_boss.health_remaining -= 1
            # explosion = Explosion(self.rect.centerx, self.rect.centery, 1, "yellow")
            # explosion_group.add(explosion)
        # draw health bar
        pygame.draw.rect(screen, red, (self.rect.x, (self.rect.top - 15), self.rect.width, 15))
        if self.health_remaining > 0:
            pygame.draw.rect(screen, green, (
                self.rect.x, (self.rect.top - 15),
                int(self.rect.width * (self.health_remaining / self.health_start)),
                15))
        elif self.health_remaining <= 0:
            # explosion = Explosion(self.rect.centerx, self.rect.centery, 3, "yellow")
            # explosion_group.add(explosion)
            self.kill()
        #     game_over = 1
        # return game_over


end_boss = EndBoss(end_boss_center, 300)
end_boss_group = pygame.sprite.Group()
end_boss_group.add(end_boss)


class EndBossLaser(pygame.sprite.Sprite):
    """A laser shot from the spaceship."""

    def __init__(self, angle, origin):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load("endboss_laser.png")
        clean_laser_image = self.image.copy()
        self.rect = clean_laser_image.get_rect()
        """Fire laser.
        angle : angle in degrees the gun is pointing.
        origin : origin of my path (end of gun).
        """
        self.angle = angle
        self.image = pygame.transform.rotate(clean_laser_image, angle)
        self.origin = origin
        self.rect.center = origin
        self.unit_vector = pygame.math.Vector2(math.sin(self.angle / 180 * math.pi),
                                               math.cos(self.angle / 180 * math.pi))
        self.distance = 23

    def update(self):
        """Update position."""
        self.distance += 1
        if self.distance > 500:
            self.kill()
        else:
            self.rect.center = self.origin + self.unit_vector * self.distance


end_boss_laser_group = pygame.sprite.Group()


class EndBossGun(pygame.sprite.Sprite):
    def __init__(self, center, angle=0):
        super().__init__()
        self.image = pygame.image.load(gun_image_path)
        self.gun_image = self.image.copy()
        self.rect = self.gun_image.get_rect()
        self.center = center
        self.rect.center = self.center
        # guns is (x, y) position of gun relative to center of spaceship.
        self.guns = [pygame.math.Vector2(12, 70), pygame.math.Vector2(-12, 70)]
        self.lasers = pygame.sprite.Group()
        self.angle = angle
        self.last_shot = pygame.time.get_ticks()

    def aim(self, p):
        # Rotate end_boss' gun
        x_dist = p[0] - end_boss_centerx
        y_dist = end_boss_centery - (screen_height - 100)
        self.angle = math.degrees(math.atan2(y_dist, x_dist)) + 90
        self.image = pygame.transform.rotate(self.gun_image, self.angle)
        self.rect = self.image.get_rect(center=(end_boss_centerx, end_boss_centery))
        return self.angle

    def fire(self, angle):
        """Fire a laser."""
        for gun in self.guns:
            origin = pygame.math.Vector2(end_boss_center) + gun.rotate(-angle)
            end_boss_laser = EndBossLaser(angle, origin)
            end_boss_laser_group.add(end_boss_laser)


end_boss_gun = EndBossGun(end_boss_center)
end_boss_gun_group = pygame.sprite.Group()
end_boss_gun_group.add(end_boss_gun)


def draw_bg():
    screen.fill((0, 0, 0))


# Dummy spaceship class for testing
class Spaceship(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = pygame.Surface((50, 50))
        self.image.fill((255, 255, 255))
        self.rect = self.image.get_rect()
        self.rect.center = (screen_width // 2, screen_height - 50)

    def update(self):
        pass

    def move(self, x):
        self.rect.centerx = x


spaceship = Spaceship()
spaceship_group = pygame.sprite.Group()
spaceship_group.add(spaceship)

explosion_group = pygame.sprite.Group()

font40 = pygame.font.Font(None, 40)


def draw_text(text, font, color, x, y):
    img = font.render(text, True, color)
    screen.blit(img, (x, y))


def play_end_boss_level():
    game_over = 0
    last_count = pygame.time.get_ticks()
    last_end_boss_shot = pygame.time.get_ticks()
    countdown = 3
    run = True
    while run:
        p = pygame.mouse.get_pos()
        clock.tick(fps)
        draw_bg()
        if countdown == 0:
            angle = end_boss_gun.aim(p)
            time_now = pygame.time.get_ticks()
            if time_now - last_end_boss_shot > end_boss_cooldown and len(end_boss_group) > 0:
                end_boss_gun.fire(angle)
                last_end_boss_shot = time_now
            if len(end_boss_group) == 0:
                game_over = 1

            if game_over == 0:
                spaceship.update()
                bullet_group.update()
                end_boss_group.update()
                end_boss_gun_group.update()
                explosion_group.update()
                end_boss_laser_group.update()
            else:
                if game_over == -1:
                    draw_text('GAME OVER!', font40, (255, 255, 255), int(screen_width / 2 - 100),
                              int(screen_height / 2 + 50))
                if game_over == 1:
                    draw_text('YOU WIN!', font40, (255, 255, 255), int(screen_width / 2 - 100),
                              int(screen_height / 2 + 50))
        if countdown > 0:
            draw_text('GET READY!', font40, (255, 255, 255), int(screen_width / 2 - 110), int(screen_height / 2 + 50))
            draw_text(str(countdown), font40, (255, 255, 255), int(screen_width / 2 - 10), int(screen_height / 2 + 100))
            count_timer = pygame.time.get_ticks()
            if count_timer - last_count > 1000:
                countdown -= 1
                last_count = count_timer

        spaceship_group.draw(screen)
        bullet_group.draw(screen)
        explosion_group.draw(screen)
        end_boss_group.draw(screen)
        end_boss_gun_group.draw(screen)
        end_boss_laser_group.draw(screen)

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False

        spaceship.move(p[0])
        pygame.display.flip()
    pygame.quit()


clock = pygame.time.Clock()

if __name__ == "__main__":
    play_end_boss_level()

Attached Files

Thumbnail(s)
       
Reply
#12
For your laser image the unit vector = pygame.math.Vector2(0, 1).rotate(-angle). This is because the laser image is vertical at 0 degrees instead of horizontal.

You can see that your sin/cos math gives the same results as the rotate using a vertical unit vector.
import pygame
import math

for angle in range(0, 360, 90):
    print(
        f"{angle:3}  [{math.sin(math.radians(angle)):0.0f}, {math.cos(math.radians(angle)):0.0f}] {pygame.math.Vector2(0, 1).rotate(-angle)}"
    )
Output:
0 [0, 1] [0, 1] 90 [1, 0] [1, -0] 180 [0, -1] [-0, -1] 270 [-1, -0] [-1, 0]
Since you only rotate the laser image once, you don't need a "clean image". The purpose of the clean image is to prevent the image growing ever larger when it is rotated multiple times.
lass EndBossLaser(pygame.sprite.Sprite):
    """A laser shot from the spaceship."""
 
    def __init__(self, angle, origin):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.transform.rotate(
            pygame.image.load("endboss_laser.png"), angle
        )
        self.unit_vector = pygame.math.Vector2(0, 1).rotate(-angle)
        self.origin = origin
        self.distance = 22
        self.update()
 
    def update(self):
        """Update position."""
        self.distance += 1
        if self.distance > 500:
            self.kill()
        else:
            self.rect.center = self.origin + self.unit_vector * self.distance
I was also thinking that if you always fire two lasers together, you can simplify things by making a double laser image. Half as many sprites to update.

There is no reason for having a boss group and a boss gun group. Make it one group, but add sprites in the order to be drawn.
.
Reply
#13
Hi,
now I have the opportunity to work on the game again.

It is playable now (you can find the full code below) - but 2 errors still occur when closing the pygame window:
1) libpng warning: iCCP: known incorrect sRGB profile
2) see error message

Regarding 1):
I tried to fix 1) with Photoshop because I found the following note online:
Open your .png file.
"File -> Save As and in the dialog that opens up uncheck "ICC Profile: sRGB IEC61966-2.1"
Uncheck "As a Copy".
Save over your original .png."

Unfortunately that didn't help...

Regarding 2):
I experimented with pygame.init() and pygame.quit() guessing that these could have influence on the error.

Unfortunately I was unsuccessful...

I would like to ask for your help with both problems.

Many thanks in advance!!
Thank you very much for your help in the past!!
Error:
File "D:\Daten\aktuell\space_invaders_organized2\main.py", line 857, in <module> level = play_breakout(level, move_direction) File "D:\Daten\aktuell\space_invaders_organized2\main.py", line 590, in play_breakout paddle = Paddle(screen_width / 2, screen_height - 100, 3) File "D:\Daten\aktuell\space_invaders_organized2\main.py", line 454, in __init__ super().__init__(create_image(PADDLE_IMAGE, PADDLE_COLOR), self.group) File "D:\Daten\aktuell\space_invaders_organized2\main.py", line 373, in create_image return image.convert() pygame.error: cannot convert without pygame.display initialized
import pygame
from PIL import Image
import random, math
from itertools import product

pygame.init()
clock = pygame.time.Clock()
fps = 60
screen_width = 1280
screen_height = 720
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Space Invaders")

# define fonts
font30 = pygame.font.SysFont('Constantia', 30)
font40 = pygame.font.SysFont('Constantia', 40)

alien_cooldown = 1000  # bullet cooldown in milliseconds
last_count = pygame.time.get_ticks()
last_alien_shot = pygame.time.get_ticks()

# define colours
red = (255, 0, 0)
green = (0, 255, 0)
white = (255, 255, 255)

# load image
bg = pygame.image.load("bg.png")
screen_rect = bg.get_rect()


def draw_bg():
    screen.blit(bg, (0, 0))


# define function for creating text
def draw_text(text, font, text_col, x, y):
    img = font.render(text, True, text_col)
    screen.blit(img, (x, y))


################ space invaders things ###############################################################################
# create spaceship class
class Spaceship(pygame.sprite.Sprite):
    def __init__(self, x, y, health):
        pygame.sprite.Sprite.__init__(self)
        self.x = x
        self.y = y
        self.health_start = health
        self.health_remaining = health
        self.image = pygame.image.load("ship.png")
        self.rect = self.image.get_rect()
        self.rect.center = x, y
        self.xmin = self.rect.width // 2  # Compute Spaceship x range.
        self.xmax = screen_width - self.xmin
        self.last_shot = pygame.time.get_ticks()

    def move(self, xpos):
        self.xpos = xpos
        self.rect.centerx = max(self.xmin, min(self.xmax, xpos))

    def update(self):
        # set a cooldown variable
        cooldown = 500  # milliseconds
        game_over = 0
        # record current time
        time_now = pygame.time.get_ticks()
        # shoot, get key press
        key = pygame.key.get_pressed()
        if key[pygame.K_SPACE] and time_now - self.last_shot > cooldown:
            weapon_list = ["bullet.png", "flash.png", "rocket.png"]
            chosen = random.choice(weapon_list)
            if level == 1:
                # single bullet
                bullet = Bullets(self.rect.centerx, self.rect.top, chosen)
                bullet_group.add(bullet)
                self.last_shot = time_now
            else:
                # double bullets
                bullet_1 = Bullets(self.rect.centerx - 43, self.rect.top, chosen)
                bullet_2 = Bullets(self.rect.centerx + 43, self.rect.top, chosen)
                bullet_group.add(bullet_1, bullet_2)
                self.last_shot = time_now

        # update mask
        self.mask = pygame.mask.from_surface(self.image)

        # draw health bar
        pygame.draw.rect(screen, red, (self.rect.x, (self.rect.bottom + 10), self.rect.width, 15))
        if self.health_remaining > 0:
            pygame.draw.rect(screen, green, (
                self.rect.x, (self.rect.bottom + 10),
                int(self.rect.width * (self.health_remaining / self.health_start)),
                15))
        elif self.health_remaining <= 0:
            explosion = Explosion(self.rect.centerx, self.rect.centery, 3, "yellow")
            explosion_group.add(explosion)
            self.kill()
            game_over = -1
        return game_over


# create player
spaceship = Spaceship(screen_width / 2, screen_height - 100, 3)
paddle_group = pygame.sprite.Group()
ball_group = pygame.sprite.Group()


# create Bullets class
class Bullets(pygame.sprite.Sprite):
    def __init__(self, x, y, chosen):
        pygame.sprite.Sprite.__init__(self)
        self.x = x
        self.y = y
        self.chosen = chosen
        self.image = pygame.image.load(chosen)
        self.rect = self.image.get_rect()
        self.rect.center = x, y

    def hit_endboss(self):
        x = self.rect.centerx
        y = self.rect.centery
        return x, y

    def update(self):
        self.rect.y -= 5
        if self.rect.bottom < 0:
            self.kill()
        hits = pygame.sprite.spritecollide(self, alien_group, True, pygame.sprite.collide_mask)
        if hits:
            self.kill()
            for alien in hits:
                x, y = alien.hit()
                if self.chosen == "rocket.png":
                    explosion = Explosion(x, y, 4, "yellow")
                    explosion_group.add(explosion)
                    hits_rocket_expl_and_aliens = pygame.sprite.spritecollide(explosion, alien_group, True,
                                                                              pygame.sprite.collide_mask)
                    if hits_rocket_expl_and_aliens:
                        for alien in hits_rocket_expl_and_aliens:
                            x, y = alien.hit()
                            explosion_red = Explosion(x, y, 2, "red")
                            explosion_group.add(explosion_red)
                else:
                    explosion = Explosion(x, y, 2, "yellow")
                    explosion_group.add(explosion)


# create Aliens class
class Aliens(pygame.sprite.Sprite):
    def __init__(self, x, y, move_direction):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load("alien" + str(random.randint(1, 6)) + ".png")
        self.x = x
        self.y = y
        self.move_direction = move_direction
        self.rect = self.image.get_rect()
        self.rect.center = x, y

    def hit(self):
        x = self.rect.centerx
        y = self.rect.centery
        self.kill()
        return x, y

    def update(self, move_direction):
        self.rect.x += self.move_direction
        if self.rect.right >= screen_width or self.rect.left <= 0:
            self.move_direction = -self.move_direction
            self.rect.y += 20
        if pygame.sprite.spritecollide(self, spaceship_group, False, pygame.sprite.collide_mask):
            spaceship.health_remaining = -5
        if pygame.sprite.spritecollide(self, paddle_group, False, pygame.sprite.collide_mask):
            draw_text('GAME OVER!', font40, white, int(screen_width / 2 - 100),
                      int(screen_height / 2 + 50))


# create Alien Bullets class
class Alien_Bullets(pygame.sprite.Sprite):
    def __init__(self, x, y):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load("alien_bullet.png")
        self.rect = self.image.get_rect()
        self.rect.center = x, y

    def update(self):
        self.rect.y += 2
        if self.rect.top > screen_height:
            self.kill()
        if pygame.sprite.spritecollide(self, spaceship_group, False, pygame.sprite.collide_mask):
            self.kill()
            # reduce spaceship health
            spaceship.health_remaining -= 1
            explosion = Explosion(self.rect.centerx, self.rect.centery, 1, "yellow")
            explosion_group.add(explosion)


# create Explosion class
class Explosion(pygame.sprite.Sprite):
    def __init__(self, x, y, size, color):
        pygame.sprite.Sprite.__init__(self)
        self.images = []
        if color == "yellow":
            for num in range(1, 8):
                img = pygame.image.load(f"explosion{num}.png")
                # ship is hit
                if size == 1:
                    img = pygame.transform.scale(img, (20, 20))
                # alien is hit
                if size == 2:
                    img = pygame.transform.scale(img, (100, 100))
                # ship gameover
                if size == 3:
                    img = pygame.transform.scale(img, (160, 160))
                # rocket hits alien
                if size == 4:
                    img = pygame.transform.scale(img, (500, 500))
                # add the image to the list
                self.images.append(img)
                self.index = 0
                self.image = self.images[self.index]
                self.rect = self.image.get_rect()
                self.rect.center = x, y
                self.counter = 0
                # add the image to the list
        if color == "red":
            # rocketExplosion hits alien, it occurs a red explosion
            for num1 in range(1, 8):
                img = pygame.image.load(f"explosion2_{num1}.png")
                img = pygame.transform.scale(img, (100, 100))
                # add the image to the list
                self.images.append(img)
                self.index = 0
                self.image = self.images[self.index]
                self.rect = self.image.get_rect()
                self.rect.center = x, y
                self.counter = 0

    def update(self):
        explosion_speed = 3
        # update explosion animation
        self.counter += 1

        if self.counter >= explosion_speed and self.index < len(self.images) - 1:
            self.counter = 0
            self.index += 1
            self.image = self.images[self.index]

        # if the animation is complete, delete explosion
        if self.index >= len(self.images) - 1 and self.counter >= explosion_speed:
            self.kill()


# create sprite groups
spaceship_group = pygame.sprite.Group()
spaceship_group.add(spaceship)
bullet_group = pygame.sprite.Group()
alien_group = pygame.sprite.Group()
alien_bullet_group = pygame.sprite.Group()
explosion_group = pygame.sprite.Group()
# a different red explosion
explosion_red_group = pygame.sprite.Group()


def create_aliens(rows, cols, move_direction):
    # generate aliens
    for row in range(rows):
        for item in range(cols):
            alien = Aliens(100 + item * 100, 100 + row * 100, move_direction)
            alien_group.add(alien)


def play_space_invaders(level, move_direction):
    game_over = 0
    last_count = pygame.time.get_ticks()
    last_alien_shot = pygame.time.get_ticks()
    create_aliens(4, 10, move_direction)
    countdown = 3
    run = True
    while run:
        clock.tick(fps)
        # draw background
        draw_bg()
        # space invaders single bullets level
        if countdown == 0:
            # create random alien bullets
            # record current time
            time_now = pygame.time.get_ticks()
            # shoot
            if time_now - last_alien_shot > alien_cooldown and len(alien_bullet_group) < 5 and len(
                    alien_group) > 0:
                attacking_alien = random.choice(alien_group.sprites())
                alien_bullet = Alien_Bullets(attacking_alien.rect.centerx, attacking_alien.rect.bottom)
                alien_bullet_group.add(alien_bullet)
                last_alien_shot = time_now

            # check if all the aliens have been killed
            if len(alien_group) == 0:
                game_over = 1
                level += 1
            if game_over == 0:
                # update spaceship
                game_over = spaceship.update()
                # update sprite groups
                bullet_group.update()
                alien_group.update(move_direction)
                alien_bullet_group.update()
                explosion_group.update()
                explosion_red_group.update()
            else:
                if game_over == -1:
                    draw_text('GAME OVER!', font40, white, int(screen_width / 2 - 100),
                              int(screen_height / 2 + 50))
                if game_over == 1:
                    draw_text('YOU WIN!', font40, white, int(screen_width / 2 - 100),
                              int(screen_height / 2 + 50))
                    return level

        if countdown > 0:
            draw_text('GET READY!', font40, white, int(screen_width / 2 - 110), int(screen_height / 2 + 50))
            draw_text(str(countdown), font40, white, int(screen_width / 2 - 10), int(screen_height / 2 + 100))
            count_timer = pygame.time.get_ticks()
            if count_timer - last_count > 1000:
                countdown -= 1
                last_count = count_timer

        # draw sprite groups
        spaceship_group.draw(screen)
        bullet_group.draw(screen)
        alien_group.draw(screen)
        alien_bullet_group.draw(screen)
        explosion_group.draw(screen)
        explosion_red_group.draw(screen)
        # event handlers
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False
            elif event.type == pygame.MOUSEMOTION:
                # space invaders level
                spaceship.move(event.pos[0])
        pygame.display.flip()
    pygame.quit()


# breakout things #############################################################
TEXT_COLOR = (255, 255, 255)
FOREGROUND = (0, 0, 0)  # Recolor image pixels that are this color
TRANSPARENT = (255, 255, 255)  # Make image pixels this color transparent
BALL_COLOR = (255, 255, 255)
PADDLE_COLOR = (255, 255, 255)
BALL_IMAGE = "ball.png"
PADDLE_IMAGE = "paddle.png"


def create_image(file, color=None):
    """
    Create image from a file.  If color is specified, replace all FOREGROUND
    pixels with color pixels.  Modify image so TRANSPARENT colored pixels are
    transparent.
    """
    if color:
        # Recolor the image
        image = Image.open(file).convert("RGB")
        for xy in product(range(image.width), range(image.height)):
            if image.getpixel(xy) == FOREGROUND:
                image.putpixel(xy, color)
        image = pygame.image.fromstring(image.tobytes(), image.size, "RGB")
    else:
        image = pygame.image.load(file)
    image.set_colorkey(TRANSPARENT)
    return image.convert()


class EnhancedSprite(pygame.sprite.Sprite):
    def __init__(self, image, group=None, **kwargs):
        super().__init__(**kwargs)
        self.image = image
        self.rect = image.get_rect()
        if group is not None:
            group.add(self)

    def at(self, x, y):
        """Convenience method for setting my position"""
        self.x = x
        self.y = y
        return self

    # Properties below expose properties of my rectangle so you can use
    # self.x = 10 or self.centery = 30 instead of self.rect.x = 10
    @property
    def x(self):
        return self.rect.x

    @x.setter
    def x(self, value):
        self.rect.x = value

    @property
    def y(self):
        return self.rect.y

    @y.setter
    def y(self, value):
        self.rect.y = value

    @property
    def centerx(self):
        return self.rect.centerx

    @centerx.setter
    def centerx(self, value):
        self.rect.centerx = value

    @property
    def centery(self):
        return self.rect.centery

    @centery.setter
    def centery(self, value):
        self.rect.centery = value

    @property
    def right(self):
        return self.rect.right

    @right.setter
    def right(self, value):
        self.rect.right = value

    @property
    def bottom(self):
        return self.rect.bottom

    @bottom.setter
    def bottom(self, value):
        self.rect.bottom = value

    @property
    def width(self):
        return self.rect.width

    @property
    def height(self):
        return self.rect.height


class Paddle(EnhancedSprite):
    """The sprite the player moves around to redirect the ball"""
    group = pygame.sprite.Group()

    def __init__(self, x, y, health):
        super().__init__(create_image(PADDLE_IMAGE, PADDLE_COLOR), self.group)
        self.x = x
        self.y = y
        self.health_start = health
        self.health_remaining = health
        self.image = pygame.image.load("paddle.png")
        self.rect = self.image.get_rect()
        self.rect.center = x, y
        self.xmin = self.rect.width // 2  # Compute Spaceship x range.
        self.xmax = screen_width - self.xmin
        self.last_shot = pygame.time.get_ticks()

    def move(self, xpos):
        """Move to follow the cursor.  Clamp to window bounds"""
        self.rect.centerx = max(self.xmin, min(self.xmax, xpos))


class LifeCounter():
    """Keep track of lives count.  Display lives remaining using ball image"""

    def __init__(self, x, y, count=5):
        self.x, self.y = x, y
        self.image = create_image(BALL_IMAGE, BALL_COLOR)
        self.spacing = self.image.get_width() + 5
        self.group = pygame.sprite.Group()
        self.reset(count)

    def reset(self, count):
        """Reset number of lives"""
        self.count = count
        for c in range(count - 1):
            EnhancedSprite(self.image, self.group).at(self.x + c * self.spacing, self.y)

    def __len__(self):
        """Return number of lives remaining"""
        return self.count

    def kill(self):
        """Reduce number of lives"""
        if self.count > 1:
            self.group.sprites()[-1].kill()
        self.count = max(0, self.count - 1)


class Ball(EnhancedSprite):
    """Ball bounces around colliding with walls, paddles and bricks"""
    group = pygame.sprite.Group()

    def __init__(self, paddle, lives, speed=5):
        super().__init__(create_image(BALL_IMAGE, BALL_COLOR), self.group)
        self.paddle = paddle
        self.lives = lives
        self.speed = speed
        self.dx = self.dy = 0
        self.xfloat = self.yfloat = 0
        self.xmax = screen_width - self.rect.width
        self.ymax = paddle.bottom - self.rect.height
        self.reset(0)

    def at(self, x, y):
        self.xfloat = x
        self.yfloat = y
        return super().at(x, y)

    def reset(self, score=None):
        """Reset for a new game"""
        self.active = False
        if score is not None:
            self.score = score

    def start(self):
        """Start moving the ball in a random direction"""
        angle = random.random() - 0.5  # Launch angle limited to about +/-60 degrees
        self.dx = self.speed * math.sin(angle)
        self.dy = -self.speed * math.cos(angle)
        self.active = True

    def move(self):
        """Update the ball position.  Check for collisions with bricks, walls and the paddle"""
        hit_status = 0
        if not self.active:
            # Sit on top of the paddle
            self.at(self.paddle.centerx - self.width // 2, self.paddle.y - self.height - 2)
            return self

        # Did I hit some bricks?  Update the bricks and the score
        x1, y1 = self.xfloat, self.yfloat
        x2, y2 = x1 + self.dx, y1 + self.dy
        if (xhits := pygame.sprite.spritecollide(self.at(x2, y1), alien_group, True, pygame.sprite.collide_mask)):
            self.dx = -self.dx
            hit_status += 1
        if (yhits := pygame.sprite.spritecollide(self.at(x1, y2), alien_group, True, pygame.sprite.collide_mask)):
            self.dy = -self.dy
            hit_status += 2
        # hits = pygame.sprite.spritecollide(self, alien_group, True, pygame.sprite.collide_mask)

        if xhits or yhits:
            for alien in xhits or yhits:
                x, y = alien.hit()
                explosion = Explosion(x, y, 2, "yellow")
                explosion_group.add(explosion)

        # Did I hit a wall?
        if x2 <= 0 or x2 >= self.xmax:
            self.dx = -self.dx
            hit_status += 4
        if y2 <= 0:
            self.dy = abs(self.dy)
            hit_status += 8

        # Did I get past the paddle?
        if (y2 >= self.paddle.y) and ((self.x > self.paddle.right) or (self.right < self.paddle.x)):
            self.lives.kill()
            self.active = False
        elif self.dy > 0 and pygame.Rect.colliderect(self.at(x2, y2).rect, self.paddle.rect):
            # I hit the paddle.  Compute angle of reflection
            bangle = math.atan2(-self.dx, self.dy)  # Ball angle of approach
            pangle = math.atan2(self.centerx - self.paddle.centerx, 30)  # Paddle angle
            rangle = (pangle - bangle) / 2  # Angle of reflection
            self.dx = math.sin(rangle) * self.speed
            self.dy = -math.cos(rangle) * self.speed
            hit_status += 16

        if hit_status > 0:
            self.at(x1, y1)
        else:
            self.at(x2, y2)


def play_breakout(level, move_direction):
    game_over = 0
    paddle = Paddle(screen_width / 2, screen_height - 100, 3)
    paddle_group.add(paddle)
    lives = LifeCounter(10, screen_height - 30)
    ball = Ball(paddle, lives)
    ball_group.add(ball)
    last_count = pygame.time.get_ticks()
    create_aliens(3, 10, move_direction)
    countdown = 3
    run = True
    while run:
        clock.tick(fps)
        # draw background
        draw_bg()
        # breakout level
        if countdown == 0:
            # create random alien bullets
            # record current time
            time_now = pygame.time.get_ticks()
            # in breakout levels aliens shouldn't shoot

            # check if all the aliens have been killed
            if len(alien_group) == 0:
                paddle.kill()
                ball.kill()
                # game_over = 1
                level += 1
                return level
            if game_over == 0:
                # update paddle
                game_over = paddle.move(event.pos[0])
                # update sprite groups
                alien_group.update(level)
            else:
                if game_over == -1:
                    draw_text('GAME OVER!', font40, white, int(screen_width / 2 - 100),
                              int(screen_height / 2 + 50))
                if game_over == 1:
                    draw_text('YOU WIN!', font40, white, int(screen_width / 2 - 100),
                              int(screen_height / 2 + 50))

        if countdown > 0:
            draw_text('GET READY!', font40, white, int(screen_width / 2 - 110), int(screen_height / 2 + 50))
            draw_text(str(countdown), font40, white, int(screen_width / 2 - 10), int(screen_height / 2 + 100))
            count_timer = pygame.time.get_ticks()
            if count_timer - last_count > 1000:
                countdown -= 1
                last_count = count_timer

        # update explosion group
        alien_group.update(move_direction)
        explosion_group.update()
        paddle_group.update()
        ball_group.update()
        # draw sprite groups
        alien_group.draw(screen)
        explosion_group.draw(screen)
        paddle_group.draw(screen)
        ball_group.draw(screen)
        # event handlers
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False
            elif event.type == pygame.MOUSEMOTION:
                # breakout level
                paddle.move(event.pos[0])
            elif event.type == pygame.MOUSEBUTTONUP:
                if not ball.active:
                    ball.start()
        ball.move()
        pygame.display.flip()
    pygame.quit()


##### end boss things ##############################################################
# level = 0
end_boss_cooldown = 4000  # bullet cooldown in milliseconds
end_boss_centerx = screen_width / 2
end_boss_centery = 220
end_boss_center = end_boss_centerx, end_boss_centery
bullet_group = pygame.sprite.Group()


# Create EndBoss class
class EndBoss(pygame.sprite.Sprite):
    def __init__(self, center, health):
        super().__init__()
        self.center = center
        self.health_start = health
        self.health_remaining = health
        self.image = pygame.image.load("ship_4.png")
        self.rect = self.image.get_rect()
        self.rect.center = center

    def update(self):
        if pygame.sprite.spritecollide(self, bullet_group, True, pygame.sprite.collide_mask):
            x, y = Bullets.hit_endboss(self)
            explosion = Explosion(x, y, 2, "yellow")
            end_boss_explosion_group.add(explosion)
            # reduce spaceship health
            end_boss.health_remaining -= 2

        # draw health bar
        pygame.draw.rect(screen, red, (self.rect.x, (self.rect.top - 15), self.rect.width, 15))
        if self.health_remaining > 0:
            pygame.draw.rect(screen, green, (
                self.rect.x, (self.rect.top - 15),
                int(self.rect.width * (self.health_remaining / self.health_start)),
                15))
        elif self.health_remaining <= 0:
            explosion = Explosion(self.rect.centerx, self.rect.centery, 4, "yellow")
            explosion_group.add(explosion)
            end_boss_gun.kill()
            self.kill()


end_boss_laser_group = pygame.sprite.Group()


class EndBossGun(pygame.sprite.Sprite):
    def __init__(self, center, angle=0):
        super().__init__()
        self.image = pygame.image.load("gun4.png")
        self.gun_image = self.image.copy()
        self.rect = self.gun_image.get_rect()
        self.center = center
        self.rect.center = self.center
        # guns is (x, y) position of gun relative to center of spaceship.
        self.guns = [pygame.math.Vector2(12, 70), pygame.math.Vector2(-12, 70)]
        self.lasers = pygame.sprite.Group()
        self.angle = angle
        self.last_shot = pygame.time.get_ticks()

    def aim(self, p):
        # Rotate end_boss' gun
        x_dist = p[0] - end_boss_centerx
        y_dist = end_boss_centery - (screen_height - 100)
        self.angle = math.degrees(math.atan2(y_dist, x_dist)) + 90
        self.image = pygame.transform.rotate(self.gun_image, self.angle)
        self.rect = self.image.get_rect(center=(end_boss_centerx, end_boss_centery))
        return self.angle

    def fire(self, angle):
        """Fire a laser."""
        for gun in self.guns:
            origin = pygame.math.Vector2(end_boss_center) + gun.rotate(-angle)
            end_boss_laser = EndBossLaser(angle, origin)
            end_boss_laser_group.add(end_boss_laser)


class EndBossLaser(pygame.sprite.Sprite):
    """A laser shot from the spaceship."""

    def __init__(self, angle, origin):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load("endbosslaser.png")
        clean_laser_image = self.image.copy()
        self.rect = clean_laser_image.get_rect()
        """Fire laser.
        angle : angle in degrees the gun is pointing.
        origin : origin of my path (end of gun).
        """
        self.angle = angle
        self.image = pygame.transform.rotate(clean_laser_image, angle)
        self.origin = origin
        self.rect.center = origin
        self.unit_vector = pygame.math.Vector2(math.sin(self.angle / 180 * math.pi),
                                               math.cos(self.angle / 180 * math.pi))
        self.distance = 23

    def update(self):
        """Update position."""
        self.distance += 1
        if self.distance > 500:
            self.kill()
        else:
            self.rect.center = self.origin + self.unit_vector * self.distance
        if pygame.sprite.spritecollide(self, spaceship_group, False, pygame.sprite.collide_mask):
            self.kill()
            # reduce spaceship health
            spaceship.health_remaining -= 0.5
            explosion = Explosion(self.rect.centerx, self.rect.centery, 1, "yellow")
            explosion_group.add(explosion)
            if spaceship.health_remaining <= 0:
                spaceship.kill()


end_boss_group = pygame.sprite.Group()
end_boss = EndBoss(end_boss_center, 300)
end_boss_gun = EndBossGun(end_boss_center, 0)
end_boss_group.add(end_boss)
end_boss_group.add(end_boss_gun)
end_boss_explosion_group = pygame.sprite.Group()


def play_end_boss_level():
    game_over = 0
    last_count = pygame.time.get_ticks()
    last_end_boss_shot = pygame.time.get_ticks()
    countdown = 3
    run = True
    while run:
        p = pygame.mouse.get_pos()
        clock.tick(fps)
        draw_bg()
        if countdown == 0:
            angle = end_boss_gun.aim(p)
            time_now = pygame.time.get_ticks()
            if time_now - last_end_boss_shot > end_boss_cooldown and len(end_boss_group) > 0:
                end_boss_gun.fire(angle)
                last_end_boss_shot = time_now
            if len(end_boss_group) == 0:
                end_boss_gun.kill()
                game_over = 1
            if len(spaceship_group) == 0:
                draw_text('GAME OVER!', font40, white, int(screen_width / 2 - 100),
                          int(screen_height / 2 + 50))
            if game_over == 0:
                spaceship.update()
                bullet_group.update()
                end_boss_group.update()
                # end_boss_gun_group.update()
                explosion_group.update()
                end_boss_laser_group.update()
                end_boss_explosion_group.update()
            else:
                if game_over == -1:
                    draw_text('GAME OVER!', font40, (255, 255, 255), int(screen_width / 2 - 100),
                              int(screen_height / 2 + 50))
                if game_over == 1:
                    draw_text('YOU SAVED THE EARTH!', font40, (255, 255, 255), int(screen_width / 2 - 100),
                              int(screen_height / 2 + 50))
        if countdown > 0:
            draw_text('GET READY!', font40, (255, 255, 255), int(screen_width / 2 - 110), int(screen_height / 2 + 50))
            draw_text(str(countdown), font40, (255, 255, 255), int(screen_width / 2 - 10), int(screen_height / 2 + 100))
            count_timer = pygame.time.get_ticks()
            if count_timer - last_count > 1000:
                countdown -= 1
                last_count = count_timer

        spaceship_group.draw(screen)
        bullet_group.draw(screen)
        explosion_group.draw(screen)
        end_boss_group.draw(screen)
        # end_boss_gun_group.draw(screen)
        end_boss_laser_group.draw(screen)
        end_boss_explosion_group.draw(screen)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False

        spaceship.move(p[0])
        pygame.display.flip()
    pygame.quit()


####################################################################################
# game_over = 0  # 0 is no game over, 1 means player has won, -1 means player has lost
space_invaders_levels = [1, 3, 5, 7]
breakout_levels = [2, 4, 6]
end_boss_level = [8]
level = 1
move_direction = 1
for i in range(1, 9):
    if level in space_invaders_levels:
        level = play_space_invaders(level, move_direction)
    else:
        level = play_breakout(level, move_direction)
    if level in end_boss_level:
        play_end_boss_level()
Reply
#14
Here's a shorter program that demonstrates the error.
import pygame

image = pygame.image.load("paddle.png")
image.convert()
This one doesn't have an error.
import pygame

pygame.init()
screen = pygame.display.set_mode((100, 100))
image = pygame.image.load("paddle.png")
image.convert()
And this one has the error again.
import pygame

pygame.init()
screen = pygame.display.set_mode((100, 100))
pygame.quit()
image = pygame.image.load("paddle.png")
image.convert()
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020