3
votes

My game has a sprite which can move left and right and shoot bullets. However, if I shoot out too many bullets (about 50 or so), my game starts lagging a lot. My sprite doesn't move fluently anymore and the game doesn't work well. So I think the problem to this is that all the bullets created are continuing to be run outside my screen. That's why I would like to know how to set a range limit to my bullet. Once past that limit it disappears and out of the program so my game won't lag. However, I am open to other suggestions like if there is a way to make my game no lag without setting a range limit. I have separated my game into 2 .py files a main.py and a Sprite1.py which I import to my main. Here's my Sprite1.py file:

import pygame
import sys
import os
import time
from pygame import mixer
from pygame.locals import *

def showStartScreen(surface):
    show = True
    while (show == True):
        background = pygame.image.load(os.path.join('images', 'Starting_scr.png'))
        # rect = surface.get_rect()
        surface.blit(background, (0,0))
        pygame.display.flip()
        for event in pygame.event.get():
            if event.type == pygame.MOUSEBUTTONDOWN:
                show = False

class Player(pygame.sprite.Sprite):
    def __init__(self, all_sprites):
        pygame.sprite.Sprite.__init__(self)
        self.movex = 0
        self.movey = 0
        self.frame = 0
        self.images = []
        self.imagesleft = []
        self.imagesright = []
        self.direction = "right"
        # self.rect = self.image.get_rect(center=pos)
        self.alpha = (0,0,0)
        self.ani = 4 # animation cycles
        self.all_sprites = all_sprites
        self.add(self.all_sprites)
        self.bullet_timer = .1

        for i in range(1,5):
            img = pygame.image.load(os.path.join('images','hero' + str(i) + '.png')).convert()
            img.convert_alpha()
            img.set_colorkey(self.alpha)
            self.imagesright.append(img)
            self.image = self.imagesright[0]
            self.rect  = self.image.get_rect()

        for i in range(1,5):
            img = pygame.image.load(os.path.join('images','hero' + str(i) + '.png')).convert()
            img = pygame.transform.flip(img, True, False)
            img.convert_alpha()
            img.set_colorkey(self.alpha)
            self.imagesleft.append(img)
            self.image = self.imagesleft[0]
            self.rect  = self.image.get_rect()

    def control(self,x,y):
        '''
        control player movement
        '''
        self.movex += x
        self.movey -= y

    def update(self, dt):
        '''
        Update sprite position
        '''
        self.rect.x = self.rect.x + self.movex
        self.rect.y = self.rect.y + self.movey

        # moving left
        if self.movex < 0:
            self.frame += 1
            if self.frame > 3*self.ani:
                self.frame = 0
            self.image = self.imagesleft[self.frame//self.ani]
            self.direction = "left"

        # moving right
        if self.movex > 0:
            self.frame += 1
            if self.frame > 3*self.ani:
                self.frame = 0
            self.image = self.imagesright[self.frame//self.ani]
            self.direction = "right"

        # self.rect.center = pygame.mouse.get_pos()


        keys = pygame.key.get_pressed()
        if keys[pygame.K_SPACE]:
            self.bullet_timer -= dt  # Subtract the time since the last tick.
        if self.bullet_timer <= 0:
            self.bullet_timer = 0  # Bullet ready.
            if keys:  # Left mouse button.
                # Create a new bullet instance and add it to the groups.
                if self.direction == "right":
                    Bullet([self.rect.x + self.image.get_width(), self.rect.y + self.image.get_height()/2], self.direction, self.all_sprites)
                else:
                    Bullet([self.rect.x, self.rect.y + self.image.get_height()/2], self.direction, self.all_sprites)
                self.bullet_timer = .1  # Reset the timer.

class Bullet(pygame.sprite.Sprite):

    def __init__(self, pos, direction, *sprite_groups):
        super().__init__(*sprite_groups)

        self.image = pygame.image.load(os.path.join('images','fireball.png'))
        self.rect  = self.image.get_rect()
        self.sound = pygame.mixer.music.play()
        self.pos = pygame.math.Vector2(pos)
        self.vel = pygame.math.Vector2(750, 0)
        self.direction = direction

    def update(self, dt):
        # Add the velocity to the position vector to move the sprite
        if self.direction == "right":
            #self.vel = pygame.math.Vector2(750, 0)
            self.image = pygame.image.load(os.path.join('images','fireball.png'))
            self.rect  = self.image.get_rect()
            self.pos += self.vel * dt
        else:
            #self.vel = pygame.math.Vector2(-750, 0)
            BULLET_IMG = pygame.image.load(os.path.join('images','fireball.png'))
            self.image = pygame.transform.flip(BULLET_IMG, True, False)
            self.rect  = self.image.get_rect()
            self.pos -= self.vel * dt

        #print(self.pos)
        self.rect.center = self.pos  # Update the rect pos.
        if self.rect.bottom <= 0:
            self.kill()

And this is my main.py file:


import pygame
import os
import sys
import time
from pygame import mixer
import Sprite1

'''
Setup
'''
pygame.init()
width = 960
height = 720
fps = 40        # frame rate
#ani = 4        # animation cycles
clock = pygame.time.Clock()
pygame.display.set_caption('B.S.G.!!!')
surface = pygame.display.set_mode((width, height))
#pygame.mixer.music.load('.\\sounds\\Fairy.mp3')
#pygame.mixer.music.play(-1, 0.0)

pygame.mixer.music.load('.\\sounds\\Fireball.wav')

#direction = "right"

all_sprites = pygame.sprite.Group()
player = Sprite1.Player(all_sprites)

player.rect.x = 50
player.rect.y = 500

steps = 10      # how fast to move

Sprite1.showStartScreen(surface)

'''
Main loop
'''
main = True

while main == True:

    background = pygame.image.load(os.path.join('images', 'Bg.png'))
    surface.blit(background, (0,0))


    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
            main = False

        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT or event.key == ord('a'):
                player.control(-steps,0)
                #direction = "left"
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                player.control(steps,0)
                #direction = "right"
            if event.key == pygame.K_UP or event.key == ord('w'):
                player.rect.y -= 100
                #player.rect.y -= 10
                #player.movey == 10
                #player.movey == -10
                #time.sleep(1)
                #player.control(0,-steps)

        if event.type == pygame.KEYUP:
            if event.key == pygame.K_LEFT or event.key == ord('a'):
                player.control(steps,0)
                #direction = "left"
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                player.control(-steps,0)
                #direction = "right"
            if event.key == pygame.K_UP or event.key == ord('w'):
                player.rect.y += 100
                #player.movey == -10
            if event.key == ord('q'):
                pygame.quit()
                sys.exit()
                main = False

    # dt = time since last tick in milliseconds.
    dt = clock.tick(60) / 1000
    all_sprites.update(dt)
    player.update(dt)
    all_sprites.draw(surface) #refresh player position
    pygame.display.flip()

P.S.: don't mind how my sprite jumps, I just wrote it that way for now. I will be making him actually jump later on with gravity. Thanks beforehand.

2

2 Answers

1
votes

You can check if the bullet is in the screen and remove it if it isn't.

for bullet in bullets[:]:
    if not surface.get_rect().collidepoint(bullet.pos):
        #remove the bullet

Alternatively, you can remove it after a set amount of time. Under your __init__ method of the bullet class, you can add start = time.time() to record when it spawns.

class Bullet(pygame.sprite.Sprite):

    def __init__(self, pos, direction, *sprite_groups):
        super().__init__(*sprite_groups)

        self.image = pygame.image.load(os.path.join('images','fireball.png'))
        self.rect  = self.image.get_rect()
        self.sound = pygame.mixer.music.play()
        self.pos = pygame.math.Vector2(pos)
        self.vel = pygame.math.Vector2(750, 0)
        self.direction = direction
        self.start = time.time()

Then in your main loop make a variable now = time.time() to keep track of the current time. You can determine how long you bullet has been around by taking away spawn time, which is self.start from now

def kill(self, now, lifetime):
    if now - self.start > lifetime:
        #remove bullet
1
votes

You already check if the bullets leave the screen at the top:

if self.rect.bottom <= 0:
    self.kill()

You could change it to

if not pygame.display.get_surface().get_rect().colliderect(self.rect):
    self.kill()

to kill the sprite if it is not at the screen at all.

But your problem is actually this:

def update(self, dt):
    # Add the velocity to the position vector to move the sprite
    if self.direction == "right":
        #self.vel = pygame.math.Vector2(750, 0)
        self.image = pygame.image.load(os.path.join('images','fireball.png'))
        self.rect  = self.image.get_rect()
        self.pos += self.vel * dt
    else:
        #self.vel = pygame.math.Vector2(-750, 0)
        BULLET_IMG = pygame.image.load(os.path.join('images','fireball.png'))
        self.image = pygame.transform.flip(BULLET_IMG, True, False)
        self.rect  = self.image.get_rect()
        self.pos -= self.vel * dt
    ...

Here you're loading the fireball.png image once for every instance of the Bullet class every frame. You aim for 60 fps, so when there are 50 Bullet instances you try to load the file 300 times per second from your disk.

Instead, you should load the image once at startup.

Here's how it could look like:

class Bullet(pygame.sprite.Sprite):

    IMAGE = None
    FLIPPED_IMAGE = None

    def __init__(self, pos, direction, *sprite_groups):
        super().__init__(*sprite_groups)

        # cache images
        if not Bullet.IMAGE:
            Bullet.IMAGE = pygame.image.load(os.path.join('images','fireball.png'))
            Bullet.FLIPPED_IMAGE = pygame.transform.flip(Bullet.IMAGE, True, False)

        if direction == "right":
            self.vel = pygame.math.Vector2(750, 0)
            self.image = Bullet.IMAGE 
        else:
            self.vel = pygame.math.Vector2(-750, 0)
            self.image = Bullet.FLIPPED_IMAGE 

        # suspicious... Should use the Sound class instead
        # self.sound = pygame.mixer.music.play()
        self.pos = pygame.math.Vector2(pos)
        self.rect  = self.image.get_rect(center=pos)

    def update(self, dt):
        # Add the velocity to the position vector to move the sprite
        self.pos += self.vel * dt
        self.rect.center = self.pos  # Update the rect pos.
        if not pygame.display.get_surface().get_rect().colliderect(self.rect):
            self.kill()

Also, you should only use pygame.mixer.music.play() for playing background music (since you can only play one file at once with music.play()). For sound effects, better use the Sound class.