5

I have a sprite which shoots bullets. Sometimes bullets also shoot out of invisible shooters.

The switch from opponent to shooter mid-program works but when I want the bullets to shoot in a certain way, with delays between each shot, the bullets seem to become a single line (the purple thing in the image is the bullets):

Screenshot with long purple line starting from circle

The mechanic works when it's the opponent shooting, but not when it's the invisible square.

Why is this happening? Is there a small bug I need to fix?

Here's my code:

import pygame
import time
import itertools
import os

pygame.init()
SCREENWIDTH = 1000
SCREENHEIGHT = 650
screen = pygame.display.set_mode([SCREENWIDTH, SCREENHEIGHT])
screen.fill((255, 123, 67))
pygame.draw.rect(screen, (230, 179, 204), (0, 50, 1000, 650), 0)

background = screen.copy()
clock = pygame.time.Clock()
stageon = True

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 Shooter(pygame.sprite.Sprite):

    def __init__(self, spell, pos, *groups):
        super().__init__(*groups)
        self.image = pygame.image.load("Sprites/transparent.jpg")
        self.rect = self.image.get_rect(topleft=(pos))
        self.pos = pygame.Vector2(pos)
        self.start_time = pygame.time.get_ticks()
        self.currentspell = spell
        self.speed = 3
        self.ticks = 1000

    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.currentspell = None
                    self.kill()

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.pos = pygame.Vector2(self.rect.topleft)
        self.start_time = pygame.time.get_ticks()
        self.sequence = sequence
        self.spellno = 0
        self.currentspell = sequence[self.spellno]
        self.speed = 3
        self.ticks = 1000
        self.shooters = 0
    def update(self):
        time_gone = pygame.time.get_ticks() - self.start_time
        if type(self.currentspell) != Spell:
           Shooter(self.currentspell[0], self.currentspell[1], sprites) 
           self.shooters += 1
        if self.shooters != 0:
           return
        else:
            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]

sprites = pygame.sprite.Group()

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

bullets = pygame.sprite.Group()

opponentgroup = pygame.sprite.Group()

img4 = pygame.image.load("Sprites/utd.png")

ut1 = Spell(pygame.image.load("Sprites/purple-glowey.png"),((0, pygame.Vector2(-1, 1) * 4),
                                                            (0, pygame.Vector2(-0.5, 1) * 4.5),
                                                            (0, pygame.Vector2(0, 1) * 5),
                                                            (0, pygame.Vector2(0.5, 1) * 4.5),
                                                            (0, pygame.Vector2(1, 1) * 4),),4, 1, 400)

ut2 = Spell(pygame.image.load("Sprites/purple-glowey.png"),((0, pygame.Vector2(1, 0) * 5),),4, 8, 400)

op_spells = [ut1, (ut2, (10, 395))]

OP = Opponent(img4, op_spells, opponentgroup)
sprites.add(OP)



def main():
    while stageon:
        for events in pygame.event.get():
            if events.type == pygame.QUIT or stageon == False:
                time.sleep(1)
                pygame.quit()
                return        
        sprites.update()
        screen.blit(background, (0, 0))
        sprites.draw(screen)
        pygame.display.update()

        clock.tick(100)
        if stageon == False:
            return
if __name__ == '__main__':
    main()
  • For your transparent images, try using [convert_alpha](https://www.pygame.org/docs/ref/surface.html#pygame.Surface.convert_alpha) when loading. It seems like your background is not retaining its initial state, maybe there's some issue with using `surface.copy` in that way. I'd try skipping that and just call `screen.fill()` and `pygame.draw.rect()` every loop. – import random Feb 17 '19 at 21:15
  • It didn't work so I resorted to make a 1-pixel picture in ms paint. – Eleeza the Other World Wizard Feb 17 '19 at 21:23
  • The bullet thing still doesn't work – Eleeza the Other World Wizard Feb 17 '19 at 21:24
  • I've noticed that your sprite image is in jpeg format, so you'll have to use `set_colorkey` as it doesn't include transparency in the file, see this [answer](https://stackoverflow.com/a/13078080/7675174). Note also that it's difficult to help without a [mcve] that shows your error. – import random Feb 17 '19 at 21:30
  • Ah, I was thinking it might have something to do with the format... also there’s no trace back which makes the debugging harder (at least to me) and this version of the code is simplified so it can run, but I’ll post a version that just shows the “problem areas” in the code as well. – Eleeza the Other World Wizard Feb 17 '19 at 21:33
  • You can invoke the [python debugger](https://docs.python.org/3/library/pdb.html) by including `import pdb; pdb.set_trace()` where you need break execution to find out what's going on. – import random Feb 17 '19 at 21:41
  • So that finds out where the problem is? Sorry I’m a novice at this whole programmer thing. Googling it right now – Eleeza the Other World Wizard Feb 17 '19 at 21:42
  • It looks like your logic for creating new `Bullet` objects in `Shooter.update()` is responsible for your laser effect. You will see this more clearly if you limit the number of bullets, e.g. by adding an additional check `if bullet[0] <= time_gone and len(bullets) < 10:` – import random Feb 18 '19 at 02:02
  • So `len(bullets) < 10` makes it so only 10 bullets can be on screen at any given time. But I used exactly the same logic for the Opponent object which seems fine. – Eleeza the Other World Wizard Feb 18 '19 at 09:53
  • Can you share your sprites? – Chris Larson Feb 20 '19 at 02:15
  • Can you please explain the logic around `if bullet[0] <= time_gone:` and then referencing `bullet[1]` when creating a new `Bullet`. I spent a while playing with your code, and found this aspect of it quite confusing. It seems to be looping through the bullet-trajectory definitions from the spell. I guess maybe if the loop-variable was named `pattern` it would be better, but that doesn't explain the reason for using `[0]` and `[1]`. – Kingsley Feb 20 '19 at 02:16
  • @Kingsley `bullet` is a tuple made when you make a `Spell` object. `bullet[0]` is the time to wait before shooting, `bullet[1]` is the vector the bullet moves in. The tuples are put into a multidimensional tuple which is iterated through so those bullets are shot at the same time, creating a pattern, or a "Spell". `if bullet[0] <= time_gone` basically means "is it time to shoot the bullet yet?" – Eleeza the Other World Wizard Feb 20 '19 at 08:59

1 Answers1

4

You problem is this the update function of the Opponent class:

def update(self):
    time_gone = pygame.time.get_ticks() - self.start_time
    if type(self.currentspell) != Spell:
       Shooter(self.currentspell[0], self.currentspell[1], sprites) 
       self.shooters += 1
    ...

You use the list op_spells = [ut1, (ut2, (10, 395))], and as you can see, the second element is not an instance of Spell. So the if condition if type(self.currentspell) != Spell will be True and a new Shooter instance will be created.

But the creation of the Shooter instace happens every frame, so you create an infinite number of Shooter instances, which in turn create an infinite number of Bullet instances.

You could change your update function to this:

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

    if self.currentspell is not None:
        spell = self.currentspell if type(self.currentspell) == Spell else self.currentspell[0]
        shooterpos = None if type(self.currentspell) == Spell else self.currentspell[1]
        if time_gone > spell.tick_delay:
            if shooterpos:
                # we have a position, so create a shooter
                Shooter(spell, shooterpos, sprites) 
            else:
                # we don't have a position, so create the Bullets ourself
                self.start_time = pygame.time.get_ticks()
                for bullet in spell.pattern:
                    if bullet[0] <= time_gone:
                        Bullet(self.rect.center, bullet[1], spell.bullet, sprites, bullets)
                spell.loop -= 1

            # if we have a position or the spell loop is done, go to the next spell
            if shooterpos or spell.loop <= 0:
                self.spellno += 1
                if self.spellno >= len(self.sequence):
                    self.currentspell = None
                else:
                    self.currentspell = self.sequence[self.spellno]
sloth
  • 99,095
  • 21
  • 171
  • 219
  • I can see where this is coming from, but I got this traceback after running the code `self.currentspell = sequence[self.spellno] TypeError: 'Spell' object does not support indexing` which doesn't make sense because it just says "which one in the sequence array is the spell we want?" – Eleeza the Other World Wizard Feb 20 '19 at 09:09
  • It seems like `self.sequence` contains a `Spell` instance instead of a list. Maybe you passed a single `Spell` instance to `Opponent`'s `__init__` function instead of a list? – sloth Feb 20 '19 at 09:12
  • Well, `sequence` has always been passed in as a list of spells, and it worked on the first spell in the list, when it came to the one with the external shooter I got the error... – Eleeza the Other World Wizard Feb 20 '19 at 09:17
  • Never mind, I made a weird change to my code after asking this, which caused the problem. I reverted back to the original question's code and added this answer in. It works now. So here's a bounty for you (which it says I have to wait 9 hours to award) – Eleeza the Other World Wizard Feb 20 '19 at 09:47