Moving a Sprite towards player in Pygame (using pygame vectors)

I'm making a game and an opponent is supposed to shoot bullets at the player.

I want the bullets to go in the direction of where the player is when the bullet shoots (the player may move but the bullet goes in a constant direction.)

But the bullet just flickers on the opponent.

I use pygame's Vector2 to control the movement of the bullets.

Here's a bullet-spell example:

bulletspell = Spell(
    pygame.image.load("Sprites/lightblue-glowey.png"),
    ((0, pygame.Vector2(-0.5, 1) * 4), #these vectors show the bullet shooting pattern
     (0, pygame.Vector2(0, 1) * 4),
     (0, pygame.Vector2(0.5, 1) * 4)),
    10, 8, 340
    )

The vector I tried was (player.image.get_rect(topleft=(player.rect.x, player.rect.y)).x, 1)

I'm not looking for the whole code to be revised, I've had too many revisions and the code I have works, I just need help figuring out the vector.

Here's the code (just for reference):

import pygame

class Player(pygame.sprite.Sprite):
    sprite = pygame.image.load("Sprites/player.png")

    def __init__(self, *groups):
        super().__init__(*groups)
        self.image = Player.sprite
        self.rect = self.image.get_rect(topleft=(445, 550))
        self.pos = pygame.Vector2(self.rect.topleft)

    def update(self):
        key = pygame.key.get_pressed()
        dist = 3
        if key[pygame.K_DOWN]:
            self.rect.y += dist
        elif key[pygame.K_UP]:
            self.rect.y -= dist
        if key[pygame.K_RIGHT]:
            self.rect.x += dist
        elif key[pygame.K_LEFT]:
            self.rect.x -= dist
class Spell:

    def __init__(self, bullet, pattern, speed, loop, tick_delay):
        self.bullet = bullet
        self.pattern = pattern
        self.speed = speed
        self.loop = loop
        self.tick_delay = tick_delay


class Opponent(pygame.sprite.Sprite):

    def __init__(self, sprite, sequence, *groups):
        super().__init__(*groups)
        self.image = sprite
        self.rect = self.image.get_rect(topleft=(425, 30))
        self.start_time = pygame.time.get_ticks()
        self.sequence = sequence
        self.spellno = 0
        self.currentspell = sequence[self.spellno]

    def update(self):
        time_gone = pygame.time.get_ticks() - self.start_time

        if self.currentspell is not None and time_gone > self.currentspell.tick_delay:
            self.start_time = pygame.time.get_ticks()
            for bullet in self.currentspell.pattern:
                if bullet[0] <= time_gone:
                    Bullet(self.rect.center, bullet[1], self.currentspell.bullet, sprites, bullets)

            self.currentspell.loop -= 1
            if self.currentspell.loop <= 0:
                self.spellno += 1
                if self.spellno >= len(self.sequence):
                    self.currentspell = None
                else:
                    self.currentspell = self.sequence[self.spellno]


class Bullet(pygame.sprite.Sprite):

    def __init__(self, pos, direction, image, *groups):
        super().__init__(*groups)
        self.image = image
        self.rect = self.image.get_rect(topleft=pos)
        self.direction = direction
        self.pos = pygame.Vector2(self.rect.topleft)

    def update(self):
        self.pos += self.direction
        self.rect.topleft = (self.pos.x, self.pos.y)
        if not screen.get_rect().colliderect(self.rect):
            self.kill()


sprites = pygame.sprite.Group()
bullets = pygame.sprite.Group()

opponentgroup = pygame.sprite.Group()


img3 = pygame.image.load("Sprites/minty.png")
player = Player(sprites)

#I tried:    
mi3 = Spell(
    pygame.image.load("Sprites/purple-glowey.png"),
    ((0, pygame.Vector2(player.image.get_rect(topleft=(player.rect.x, player.rect.y)).x, 1) * 4),
    ), 4, 8, 340)

minty_spells = [mi1, mi3]

Minty = Opponent(img3, minty_spells, opponentgroup)
sprites.add(Minty)

pygame.init()
SCREENWIDTH = 1000
SCREENHEIGHT = 650
screen = pygame.display.set_mode([SCREENWIDTH, SCREENHEIGHT])
screen.fill((255, 123, 67))
pygame.draw.rect(screen, (0, 255, 188), (50, 50, 900, 575), 0)
background = screen.copy()
clock = pygame.time.Clock()

#main loop goes here

Any help would be appreciated

Thanks :)

Upvotes: 9

Views: 1060

Answers (2)

skrx
skrx

Reputation: 20438

I recommend changing the code structure a bit and turn the spells into subclasses of Spell (take a look at the state pattern). Move more of the shooting related code into the Spell class and give it an update method where the timer is updated and a shoot method in which the bullets are created.

The shoot method of the spell that should target the player can be overridden, so that it aims at the position of the player instead of using one of the predefined directions.

In the update method of the Opponent, you just need to check if the current spell is done (when loop is 0), then switch the spell by creating a new instance and call its update method each frame.

import pygame


class Spell:

    def __init__(self, bullet_img, speed, loop, delay):
        self.bullet_img = bullet_img
        self.pattern = None
        self.speed = speed
        self.loop = loop
        self.delay = delay
        self.start_time = pygame.time.get_ticks()

    def shoot(self, pos, target_pos):
        for pattern in self.pattern:
            Bullet(pos, pattern, self.bullet_img, sprites, bullets)

    def update(self, pos, target_pos):
        time_gone = pygame.time.get_ticks() - self.start_time
        if time_gone > self.delay:
            self.start_time = pygame.time.get_ticks()
            self.shoot(pos, target_pos)
            self.loop -= 1


class Spell1(Spell):

    def __init__(self):
        super().__init__(img, 10, 3, 340)
        self.pattern = (pygame.Vector2(-2, 4), pygame.Vector2(0, 4),
                        pygame.Vector2(2, 4))


class Spell2(Spell):

    def __init__(self):
        super().__init__(img2, 4, 2, 340)
        self.pattern = (pygame.Vector2(4, 4), pygame.Vector2(-4, 4))


class Spell3(Spell):

    def __init__(self):
        super().__init__(img3, 4, 6, 340)

    # Override the shoot method to aim at the player position.
    def shoot(self, pos, target_pos):
        direction = (pygame.Vector2(target_pos) - pos).normalize() * 4
        Bullet(pos, direction, self.bullet_img, sprites, bullets)


class Opponent(pygame.sprite.Sprite):

    def __init__(self, pos, sprite, spells, *groups):
        super().__init__(*groups)
        self.image = sprite
        self.rect = self.image.get_rect(topleft=pos)
        self.start_time = pygame.time.get_ticks()
        self.spells = spells
        self.spellno = 0
        self.currentspell = spells[self.spellno]()  # Create the instance here.

    def update(self):
        if self.spellno < len(self.spells):
            # You can pass the player position instead of the mouse pos here.
            self.currentspell.update(self.rect.center, pygame.mouse.get_pos())
            # Decrement the loop attribute of the current spell and
            # switch to the next spell when it's <= 0. When all spells
            # are done, set self.currentspell to None to stop shooting.
            if self.currentspell.loop <= 0:
                self.spellno += 1
                if self.spellno < len(self.spells):
                    self.currentspell = self.spells[self.spellno]()


class Bullet(pygame.sprite.Sprite):

    def __init__(self, pos, direction, image, *groups):
        super().__init__(*groups)
        self.image = image
        self.rect = self.image.get_rect(center=pos)
        self.direction = direction
        self.pos = pygame.Vector2(self.rect.center)

    def update(self):
        self.pos += self.direction
        self.rect.center = self.pos
        if not screen.get_rect().colliderect(self.rect):
            self.kill()


sprites = pygame.sprite.Group()
bullets = pygame.sprite.Group()
opponentgroup = pygame.sprite.Group()

img = pygame.Surface((30, 40))
img.fill((0, 100, 200))
img2 = pygame.Surface((30, 30))
img2.fill((110, 0, 220))
img3 = pygame.Surface((30, 50))
img3.fill((255, 170, 0))
minty_spells = [Spell1, Spell2, Spell3]
minty = Opponent((425, 30), img3, minty_spells, opponentgroup)
minty2 = Opponent((225, 30), img3, [Spell2, Spell3, Spell1], opponentgroup)
sprites.add(minty, minty2)

pygame.init()
screen = pygame.display.set_mode([1000, 650])
screen.fill((255, 123, 67))
pygame.draw.rect(screen, (0, 255, 188), (50, 50, 900, 575), 0)
background = screen.copy()
clock = pygame.time.Clock()


def main():
    while True:
        for events in pygame.event.get():
            if events.type == pygame.QUIT:
                pygame.quit()
                return

        sprites.update()

        screen.blit(background, (0, 0))
        sprites.draw(screen)
        pygame.display.update()

        clock.tick(100)

if __name__ == '__main__':
    main()

Upvotes: 4

westgarthw
westgarthw

Reputation: 181

At the moment a bullet is instantiated its direction needs to be:

(Player.pos - Opponent.pos).normalise()

This will give you a unit vector (a vector of length 1) pointing from Opponent to Player at the moment the bullet is fired. You'll need to add this to the bullet's position at each update.

Upvotes: 7

Related Questions