2

I have Particle and Explosion classes I am drawing using pygame.
An Explosion represents a bunch of flying Particles. A Particle fades eventually; when its ttl property becomes 0 it should not be visible and should be removed from the Explosion.particles. I want the program to delete dead particles so that they are not updated.

The issue I am having is with the Explosion.update() method. It seems it's not removing any Particles. I am experiencing a bug where supposedly 'dead' Particles are still drawn but their movement is not updated.
I've experimented on lists in python and verified that the premise of iterating through a 'dead' list to remove from the other list works.
Any suggestions on where the fault lies in this code would be greatly appreciated.

Edit: I've attached and shortened both source files for better context.

explosion.py

import sys
import pygame
from particleac import *

class App:
    def __init__(self):
        pygame.init()
        self.screen = pygame.display.set_mode(SCREEN_SIZE, pygame.HWSURFACE|pygame.DOUBLEBUF)                    
        self.clock = pygame.time.Clock()

        self.running = True
        self.explosions = []
        self.frame_no = 0

    def check_input(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.running = False
            if event.type == pygame.MOUSEBUTTONDOWN or event.type == pygame.MOUSEMOTION:
                x, y = pygame.mouse.get_pos()
                self.explosions.append(Explosion(x, y))

    def update_screen(self):
        self.screen.fill(BLACK)
        dead_explosions = []
        # remove explosion if particles all gone
        for e in self.explosions:
            if len(e.particles) == 0:
                dead_explosions.append(e)
            else:
                for p in e.particles:
                    pygame.draw.circle(self.screen, p.colour, [p.x, p.y], p.size)
                    p.update()

        for e in dead_explosions:
            self.explosions.remove(e)

        pygame.display.flip()
        self.frame_no += 1
        self.frame_no %= 60
        self.clock.tick(FRAMERATE)

    def run(self):
        while self.running:
            self.check_input()
            self.update_screen()

        pygame.quit()       

app = App()
app.run()

particleac.py

import random
import sys

BLACK = [  0,   0,   0]
WHITE = [255, 255, 255]
BLUE = [0, 0, 255]
SCREEN_WIDTH = 600
SCREEN_HEIGHT = 400
SCREEN_CENTRE = [SCREEN_WIDTH/2, SCREEN_HEIGHT/2]
FRAMERATE = 40
SCREEN_SIZE = [SCREEN_WIDTH, SCREEN_HEIGHT]

GRAVITY = 1
TERMINAL_VELOCITY = 10

class Particle:
    def __init__(self, x=SCREEN_CENTRE[0], y=SCREEN_CENTRE[1], colour=WHITE):
        self.x = x
        self.y = y
        self.colour = colour
        self.brightness = 255
        self.size = 2
        self.x_velocity = random.randrange(-4, 4)
        self.y_velocity = random.randrange(-8, 3)
        self.ttl = random.randrange(50, 200)
        self.decay = (self.ttl / FRAMERATE) * 2

    def update(self):            
        if self.ttl > 0:
            self.ttl -= 1

            self.brightness -= self.decay
            if self.brightness < 0:
                self.brightness = 0

            self.colour[0] = self.colour[1] = self.colour[2] = self.brightness 

            self.y_velocity += GRAVITY
            self.y += self.y_velocity
            self.x += self.x_velocity

            if self.y > SCREEN_HEIGHT:
                self.y -= SCREEN_HEIGHT

class Explosion:
    MAX_NUM_PARTICLES = 2

    def __init__(self, x=0, y=0, colour=WHITE):
        self.x = x
        self.y = y
        self.colour = colour
        self.x_velocity = random.randrange(-4, 4)
        self.y_velocity = random.randrange(-6, -1)
        self.num_particles = random.randrange(1, self.MAX_NUM_PARTICLES)
        self.particles = []

        for i in range(self.MAX_NUM_PARTICLES):
            p = Particle(self.x, self.y)
            p.colour = self.colour
            p.x_velocity += self.x_velocity
            p.y_velocity += self.y_velocity

            self.particles.append(p)

    def update(self):
        for p in self.particles:
            p.update()
        self.particles = [p for p in self.particles if p.ttl > 0]
        sys.stdout.write("len(self.particles) == {}".format(len(self.particles)))
        sys.stdout.flush()
user1330734
  • 390
  • 6
  • 21

3 Answers3

1

The problem may come from the fact that self.particles is defined at the class level. This may cause some problems elsewhere in your code of you create more than one instance of the Explosion class. Try moving the self.particle in the constructor, and see what happens.

(As a suggestion) Since you build a list anyways, why not copy the ones that are alive?

def update(self):
  particles_alive = []
  for p in self.particles:
    p.update()
    sys.out.write("p.ttl == {}\n".format(p.ttl))
    if p.ttl == 0:
      sys.out.write("Particle died.\n")
    else:
      particles_alive.append(p)
  self.particles = particles_alive
Sci Prog
  • 2,651
  • 1
  • 10
  • 18
  • Thanks @Sci Prog, I incorporated particles into the constructor because I do have multiple Explosion instances and the program runs better but I still have issues with 'dead' particles drawing. Kindly see my Edit. – user1330734 Apr 01 '16 at 04:24
1

Your code works for me, once sys.out.write() is changed to sys.stdout.write(). Since there is that error in your code, are you sure that the code in your post the same as the code that is failing?

You can simplify Explosion.update() to this:

    def update(self):
        for p in self.particles:
            p.update()
        self.particles = [p for p in self.particles if p.ttl > 0]

Possibly this change might fix the problem because you are now dealing with only one list, but I believe that your code should work.

mhawke
  • 84,695
  • 9
  • 117
  • 138
  • Thanks @mhwake, I incorporated the change but I still have issues with 'dead' particles still drawing. Please see my Edit when you can. – user1330734 Apr 01 '16 at 04:22
0

Solution: I was never calling e.update() !

Therefore Explosions weren't updating while iterating through them, which meant the dead Particles weren't being removed. Aaargh!

I added e.update() to app.update_screen() and removed the incorrect p.update() (which is called for each Particle in the e.update()).

def update_screen(self):
    self.screen.fill(BLACK)
    dead_explosions = []
    for e in self.explosions:
        if len(e.particles) == 0:
            dead_explosions.append(e)
        else:
            e.update()
            for p in e.particles:
                pygame.draw.circle(self.screen, p.colour, [p.x, p.y], p.size)

    for e in dead_explosions:
        self.explosions.remove(e)

    pygame.display.flip()
    self.clock.tick(FRAMERATE)

I now know to give better context when posting up code. Thank you kindly for your trouble guys!

user1330734
  • 390
  • 6
  • 21