Python Forum
[PyGame] positioning the laser
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[PyGame] positioning the laser
#1
Hello dear experts,

I have a last difficult problem to solve with my space invaders clone.
I need some help with this. It involves the boss's turret rotating on its own axis while aiming at the moving player.
The turret has 2 cannons.

I would like to work out a solution for the right cannon only first (I will try to apply the solution to the left cannon later).

The turret rotates and aims at the player, firing a laser every few seconds. I managed to get the laser to fire at the current angle of the right cannon.
The problem is the positioning of the laser when firing - its end should be at the end of the right cannon at the start of firing.

In the picture "positioning is not correct" you can see what is hapening - the laser is not at the right cannon's end.
You can see where the laser is placed when rotating the gun.

I tried to make a modell where the right cannon's end is (point(x,y)) when the gun rotates around the point(end_boss_centerx, end_boss_centery) with the angle alpha.

I made a drawing, which I used to get a formula for the x component and the y component of the gun's end.

The gun is orientated downwards on its picture. Because of this the angle in my code is angle = alpha - 90.

I isolated the problem of the firing end boss from my original code in a code snippet to can better focus on it.
And it is easier for someone how wants to help me too, I think.

I don't know how to make it right and can't go further.

It would be great if someone could help me to position the laser in the right place.

(I attached some pictures.)

Thanks a lot in advance!!

import pygame
from math import radians, sin, cos
import math

pygame.init()

screen_width = 800
screen_height = 600
fps = 60
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_laser.png"


# Create EndBoss class
class EndBoss(pygame.sprite.Sprite):
    def __init__(self, x, y, health):
        super().__init__()
        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 = (x, y)


# Create EndBossGun class
class EndBossGun(pygame.sprite.Sprite):
    def __init__(self, x, y, angle):
        super().__init__()
        self.x = x
        self.y = y
        self.angle = angle
        self.image = pygame.image.load(gun_image_path)
        self.gun_image = self.image.copy()
        self.rect = self.gun_image.get_rect()
        self.rect.center = (x, y)
        self.last_shot = pygame.time.get_ticks()

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


# Create EndBossLaser class
class EndBossLaser(pygame.sprite.Sprite):
    def __init__(self, angle):
        super().__init__()
        self.angle = angle
        self.image = pygame.image.load(laser_image_path)
        self.image = pygame.transform.rotate(self.image, angle - 90)
        self.rect = self.image.get_rect()
        l = math.sqrt(11 ** 2 + 70 ** 2)
        beta = math.asin(70 / l)
        xpos = l * cos(beta - angle)
        ypos = l * sin(beta - angle)
        self.rect.center = end_boss_centerx + xpos, end_boss_centery + ypos

    def update(self, speed, angle):
        pass

# Initialize objects
end_boss_centerx = screen_width / 2
end_boss_centery = 195

end_boss = EndBoss(end_boss_centerx, end_boss_centery, 300)
end_boss_group = pygame.sprite.Group()
end_boss_group.add(end_boss)
end_boss_gun = EndBossGun(end_boss_centerx, end_boss_centery, 0)
end_boss_gun_group = pygame.sprite.Group()
end_boss_gun_group.add(end_boss_gun)

end_boss_laser_group = pygame.sprite.Group()



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)

single_bullet_group = pygame.sprite.Group()
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_laser = EndBossLaser(angle)
                end_boss_laser_group.add(end_boss_laser)
                last_end_boss_shot = time_now

            if len(end_boss_group) == 0:
                game_over = 1

            if game_over == 0:
                end_boss_gun.update()
                spaceship.update()
                single_bullet_group.update()
                end_boss_group.update()
                end_boss_laser_group.update(1, angle)
                end_boss_gun_group.update()
                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 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)
        single_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
#2
Hi,
I got an idea what I could do...
I will present the new idea when I implemented it.

I just wanted to inform you...
Reply
#3
Let pygame do the math. Below I use a 2D vector to compute the location of the rotated part relative to the base object. For you the rotated part is the laser, and the base object is the turret.
import pygame
from PIL import Image


class Block(pygame.sprite.Sprite):
    """Simple sprite that creates a rectamgle image."""
    def __init__(self, color, width, height):
        super().__init__()
        pygame.sprite.Sprite.__init__(self)
        image = Image.new("RGBA", (width, height), color)
        self.image = pygame.image.fromstring(image.tobytes(), image.size, image.mode)
        self.clean_image = self.image.copy()
        self.rect = self.image.get_rect()

    @property
    def center(self):
        """Return center of image."""
        return self.rect.center

    @center.setter
    def center(self, coordinates):
        """Set center of image."""
        self.rect.center = coordinates

    def rotate(self, angle):
        """Rotate angle degreec CCW about the center."""
        center = self.center
        self.image = pygame.transform.rotate(self.clean_image, angle)
        self.rect = self.image.get_rect()
        self.center = center


class Component(Block):
    """A sprite that is part of a component sprite."""
    def __init__(self, color, width, height, offset):
        super().__init__(color, width, height)
        self.base = None
        self.offset = pygame.math.Vector2(offset)

    def rotate(self, angle):
        """Rotate angle degrees CCW about the base."""
        super().rotate(angle)
        self.center = self.base.center + self.offset.rotate(-angle)


class Composite:
    """A "sprite" made up of multiple sprites."""
    def __init__(self, base):
        self.base = base
        self.parts = []
        self.group = pygame.sprite.Group(base)
        self.angle = 0
        self.x, self.y = base.center

    def add_part(self, part):
        """Add a part that moves witht he base."""
        part.base = self.base
        self.parts.append(part)
        self.group.add(part)

    def move(self, dx=0):
        """Move the sprites forward or backward."""
        x, y = pygame.math.Vector2(dx, 0).rotate(-self.angle)
        self.x += x
        self.y += y
        self.base.center = self.x, self.y

    def rotate(self, angle):
        """Increment/decrement the rotation angle.."""
        self.angle = (self.angle + angle) % 360

    def draw(self, screen):
        """Update and draw all the sprites."""
        for sprite in self.group:
            sprite.rotate(self.angle)
        self.group.draw(screen)


pygame.init()
screen = pygame.display.set_mode((300, 300))
clock = pygame.time.Clock()

turret = Composite(Block("yellow", 30, 60))
turret.add_part(Component("blue", 60, 10, (45, 20)))
turret.add_part(Component("green", 80, 5, (55, 0)))
turret.add_part(Component("red", 60, 10, (45, -20)))
turret.add_part(Component("green", 10, 20, (100, 0)))

running = True
key_down = None
dx = 1
rx = 0.5
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN:
            key_down = event.key
        elif event.type == pygame.KEYUP:
            key_down = None

    if key_down == pygame.K_LEFT:
        turret.rotate(rx)
    elif key_down == pygame.K_RIGHT:
        turret.rotate(-rx)
    elif key_down == pygame.K_UP:
        turret.move(dx)
    elif key_down == pygame.K_DOWN:
        turret.move(-dx)

    screen.fill("black")
    turret.draw(screen)
    pygame.display.flip()
    clock.tick(60)

pygame.quit()
flash77 likes this post
Reply
#4
Hello, thank you very much for the detailed example!

I have been trying for a long time to adopt (adapt) things from it for my code, but unfortunately I still can't figure it out...

So I have to ask for help again. I can't get the offset to work.
I don't understand how "offset" is used here: "self.offset.rotate(-angle)".

I got problems with line 83 and line 136.

Perhaps you could please explain in more detail what I'm doing wrong using my code?
I would really appreciate it, as I'm stuck at the moment.

Thank you very much in advance!

import pygame
from math import radians, sin, cos
import math

pygame.init()

screen_width = 800
screen_height = 600
fps = 60
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_laser.png"


class EndBossGun(pygame.sprite.Sprite):
    def __init__(self, x, y, angle):
        super().__init__()
        self.x = x
        self.y = y
        self.angle = angle
        self.image = pygame.image.load(gun_image_path)
        self.gun_image = self.image.copy()
        self.rect = self.gun_image.get_rect()
        self.rect.center = (x, y)
        self.last_shot = pygame.time.get_ticks()

    def aim(self, p):
        # Rotate end_boss' gun
        x_dist = p[0] - self.x
        y_dist = self.y - (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=(self.x, self.y))
        return self.angle


# Create EndBoss class
class EndBoss(pygame.sprite.Sprite):
    def __init__(self, x, y, health):
        super().__init__()
        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 = (x, y)


# Initialize objects
end_boss_centerx = screen_width / 2
end_boss_centery = 195
end_boss_center = end_boss_centerx, end_boss_centery
end_boss = EndBoss(end_boss_centerx, end_boss_centery, 300)
end_boss_group = pygame.sprite.Group()
end_boss_group.add(end_boss)
end_boss_gun = EndBossGun(end_boss_centerx, end_boss_centery, 0)
end_boss_gun_group = pygame.sprite.Group()
end_boss_gun_group.add(end_boss_gun)

end_boss_laser_group = pygame.sprite.Group()


# Create EndBossLaser class
class EndBossLaser(pygame.sprite.Sprite):
    def __init__(self, x, y, offset):
        super().__init__()
        self.x = x
        self.y = y
        self.offset = pygame.math.Vector2(offset)
        self.image = pygame.image.load(laser_image_path)
        self.clean_image = self.image.copy()
        self.rect = self.clean_image.get_rect()
        self.rect.center = x, y

    def rotate(self, angle):
        # justify laser in the same angle of the gun
        self.image = end_boss_center + self.offset.rotate(self.clean_image, angle)


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)

single_bullet_group = pygame.sprite.Group()
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:
                # start position laser
                end_boss_laser = EndBossLaser(end_boss_centerx, end_boss_centery, pygame.math.Vector2(30.0, 70.0))
                end_boss_laser.rotate(angle)

                end_boss_laser_group.add(end_boss_laser)
                last_end_boss_shot = time_now

            if len(end_boss_group) == 0:
                game_over = 1

            if game_over == 0:
                end_boss_gun.update()
                spaceship.update()
                single_bullet_group.update()
                end_boss_group.update()
                end_boss_gun_group.update()
                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 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)
        single_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()
Error:
Traceback (most recent call last): File "D:\Daten\aktuell\lasertest\main.py", line 187, in <module> play_end_boss_level() File "D:\Daten\aktuell\lasertest\main.py", line 137, in play_end_boss_level end_boss_laser.rotate(angle) File "D:\Daten\aktuell\lasertest\main.py", line 83, in rotate self.image = end_boss_center + self.offset.rotate(self.clean_image, angle) TypeError: Vector2.rotate() takes exactly one argument (2 given) libpng warning: iCCP: known incorrect sRGB profile
Reply


Forum Jump:

User Panel Messages

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