Python Forum
[PyGame] Particle movement mystery
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[PyGame] Particle movement mystery
#1
I have an Explosion class that spawns a bunch of Particles. But when it does, all the particles zip off in a clump, not in random directions.
class Explosion():
    def __init__(self, game, pos):
        self.game = game
        self.pos = pos
        for p in range(10):
            rot = random.randrange(360)
            dir = vec(1, 0).rotate(rot) * random.randrange(3, 6)
            print(dir)  #<------ see below
            p = Particle(self.game, self.pos, dir)
            self.game.effects.append(p)
class Particle():
    def __init__(self, game, pos, dir):
        self.pos = pos
        self.game = game
        self.dir = dir
        self.image = pg.Surface((25, 25), pg.SRCALPHA)
        self.image.fill((200, 100, 100))
        self.rect = self.image.get_rect()
        self.rect.center = self.pos
        self.alpha = 255

    def update(self):
        self.alpha -= 4
        if self.alpha < 0:
            self.game.effects.remove(self)
        self.image.set_alpha(self.alpha)
        self.pos += self.dir
        self.rect.center = self.pos


#### then...

for e in self.effects:
            e.update()

for particle in self.effects:
                screen.blit(particle.image, (vec(particle.rect.x, particle.rect.y) - self.camera.pos))
And the print(dir) produces:
Output:
[-4.89074, 1.03956] [0.899804, -3.89748] [-0.927051, -2.85317] [-0.261467, -2.98858] [3.99318, -3.00908] [1.69047, 3.62523] [0.348623, -3.98478] [-3.1466, 3.88573] [-0.174497, 4.99695] [-0.572427, 2.94488] [-1.23607, -3.80423] [-1.49843, 3.70874] [-3.47329, 3.5967]
If I print the particle.dir from the update I get the same kind of information. They all get different vectors to move with, yet sail off together. But NOT perfectly. It's a clump, like they got a random vector for the first frame, then all moved together after that (total speculation). They fade and die like I want.

I'm certain this approach worded for me in the past, but if someone could point to any obvious error I would really appreciate the help.

Thank you all.
Reply
#2
First I would not use a keyword to store variables. _dir over dir.
Second print self.pos, self.dir out during update. Make sure data is right.

Other improvements.

pygame.Vector2 where made for this. Understanding the basic we take care of a lot math. They are always floats.
vector = pygame.Vector2()
vector.from_polar((1, angle))
To give more control over random. Create different list. Then use random choice.
explosion_data = [list(range(1, 30)), list(range(32, 60))]
# in explosion
for particle in explosion_data:
    angle = random.choice(particle)
    vector = pygame.Vector2()
    vector.from_polar((1, angle))
pygame.sprite.Spirte and pygame.sprite.Group can handle your particles. They are a built in framework. They also work faster. I give example later.
99 percent of computer problems exists between chair and keyboard.
Reply
#3
(Jul-05-2022, 01:29 AM)Windspar Wrote: First I would not use a keyword to store variables. _dir over dir.
Second print self.pos, self.dir out during update. Make sure data is right.

Other improvements.

pygame.Vector2 where made for this. Understanding the basic we take care of a lot math. They are always floats.
vector = pygame.Vector2()
vector.from_polar((1, angle))
To give more control over random. Create different list. Then use random choice.
explosion_data = [list(range(1, 30)), list(range(32, 60))]
# in explosion
for particle in explosion_data:
    angle = random.choice(particle)
    vector = pygame.Vector2()
    vector.from_polar((1, angle))
pygame.sprite.Spirte and pygame.sprite.Group can handle your particles. They are a built in framework. They also work faster. I give example later.

For some reason, I thought pygame groups were more resource intensive, I'll certainly change that. The .pos I use is already a pygame vector2, as is vec(), I just imported it as vec for brevity. I also really like the idea of storing the random data as a list rather than calculating it a million times.

I'll implement your suggestions later today and see what I find. Thank you.
Reply
#4
Here an example.
import random
import pygame

class Scene:
    def __init__(self, engine):
        self.engine = engine

    def on_draw(self, surface):
        pass

    def on_event(self, event):
        pass

    def on_update(self, delta, ticks):
        pass

    def on_quit(self):
        self.engine.running = False

class DisplayEngine:
    def __init__(self, caption, width, height, flags=0):
        pygame.display.set_caption(caption)
        self.surface = pygame.display.set_mode((width, height), flags)
        self.rect = self.surface.get_rect()
        self.clock = pygame.time.Clock()
        self.running = False
        self.gamespeed = 1
        self.delta = 0
        self.fps = 60

        self._scene = Scene(self)
        self._next_scene = None

    def main_loop(self, scene=None):
        if scene:
            self._scene = scene

        self.running = True
        while self.running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    self._scene.on_quit()
                else:
                    self._scene.on_event(event)

            ticks = pygame.time.get_ticks()
            self._scene.on_draw(self.surface)
            self._scene.on_update(self.delta, ticks)

            pygame.display.flip()
            self.delta = self.clock.tick(self.fps) * 0.001

class Images:
    def __init__(self):
        self.particle = pygame.Surface((20, 20), pygame.SRCALPHA)
        self.particle.fill((200, 100, 100))

class Particle(pygame.sprite.Sprite):
    def __init__(self, image, pos, vector):
        super().__init__()
        self.center = pos
        self.image = image.copy()
        self.rect = self.image.get_rect(center=pos)
        self.alpha = 255
        self.interval = 40
        self.vector = vector
        self.next_tick = pygame.time.get_ticks() + self.interval
        self.speed = random.randrange(30, 170) * 0.01

    def update(self, ticks):
        if ticks > self.next_tick:
            if self.alpha > 0:
                self.next_tick += self.interval
                self.alpha -= 4
                self.center += self.vector * self.speed
                self.rect.center = self.center
                self.image.set_alpha(self.alpha)
            else:
                self.kill()

class Game(Scene):
    def __init__(self, engine):
        super().__init__(engine)
        self.particles = pygame.sprite.Group()
        self.images = Images()
        self.explode = []
        for x in range(0, 320, 45):
            self.explode.append(list(range(x, x + 45)))

    def mouse_push(self, mouse_pos):
        for part in self.explode:
            angle = random.choice(part)
            vector = pygame.Vector2()
            vector.from_polar((1, angle))
            particle = Particle(self.images.particle, mouse_pos, vector)
            self.particles.add(particle)

    def on_draw(self, surface):
        surface.fill('black')
        self.particles.draw(surface)

    def on_event(self, event):
        if event.type == pygame.MOUSEBUTTONDOWN:
            if event.button == 1:
                self.mouse_push(event.pos)

    def on_update(self, delta, ticks):
        self.particles.update(ticks)


if __name__ == "__main__":
    pygame.init()
    engine = DisplayEngine("Particles", 800, 600)
    game = Game(engine)
    engine.main_loop(game)
    pygame.quit()
BashBedlam likes this post
99 percent of computer problems exists between chair and keyboard.
Reply
#5
(Jul-05-2022, 10:47 PM)Windspar Wrote: Here an example.

Your example is much more elegant than what I had. So I replaced the code I was using with yours, but then the same thing happens! Wall

So here is my whole code. The state machine was lifted from the forum here, and remains basically unchanged. Same with what I use for tiling the background. The rest I just pretty much cobbled together. I took it out to make your changes, but I was also giving the enemies the ability to shoot, and that was messed up. They would shoot, and then the next frame zip away at a million miles per hour (I tracked their .pos). I don't know if that is part of the same mistake, because I couldn't tell why that was happening either. It's not in this code I'm posting, because I set it aside to fix the explosion problem, but alas....

This is a working example. The mistake must be in the Game()? I'm lost. lol

import pygame as pg
import sys
from pygame import Vector2 as vec
import random
from os import path
pg.init()


class Images:
    def __init__(self):
        self.particle =pg.Surface((20, 20),pg.SRCALPHA)
        self.particle.fill((200, 100, 100))

class Particle(pg.sprite.Sprite):
    def __init__(self, image, pos, vector):
        super().__init__()
        self.center = pos
        self.image = image.copy()
        self.rect = self.image.get_rect(center=pos)
        self.alpha = 255
        self.interval = 40
        self.vector = vector
        self.next_tick = pg.time.get_ticks() + self.interval
        self.speed = random.randrange(30, 170) * 0.01


    def update(self, ticks):
        if ticks > self.next_tick:
            if self.alpha > 0:
                self.next_tick += self.interval
                self.alpha -= 4
                self.center += self.vector * self.speed
                self.rect.center = self.center
                self.image.set_alpha(self.alpha)
            else:
                self.kill()


class Mini_map():
    def __init__(self, game, pos):
        self.game = game
        self.pos = pos
        self.background = pg.Surface((200, 200), pg.SRCALPHA)
        self.bg = pg.Surface((200, 200), pg.SRCALPHA)
        self.bg.set_alpha(80)
        self.bg.fill((10, 250, 40))
        self.background.blit(self.bg, (0,0))
        pg.draw.rect(self.background, (10, 200, 40), (0,0, 200, 200), 2)
        self.image = self.background.copy()

    def update(self):
        self.image = self.background.copy()
        for sprite in self.game.all_sprites:
            pg.draw.rect(self.image, (220, 20, 40), (sprite.pos.x/10 - self.game.camera.pos.x/10 + 60, sprite.pos.y/10 - self.game.camera.pos.y/10 + 60, 2, 2))

class Enemy(pg.sprite.Sprite):
    def __init__(self, game, pos):
        pg.sprite.Sprite.__init__(self)
        self.game = game
        self.original_img = pg.Surface((12, 30), pg.SRCALPHA)
        self.original_img.fill((100, 255, 100))
        self.rect = self.original_img.get_rect()
        self.image = pg.transform.rotate(self.original_img, self.game.player.rot)
        self.vel = vec(0,0)
        self.rot = random.randrange(365)
        self.state = "normal"
        self.pos = vec(pos)
        self.rect.center = self.pos
        self.spawn_time = pg.time.get_ticks()
        self.life_span = 1000
        self.hit_points = 20
    def update(self, ticks):

        if self.hit_points <= 0:
            self.kill()
        self.rot = (self.game.player.pos - self.pos).angle_to(vec(1, 0))
        self.image = pg.transform.rotate(self.original_img, self.rot)
        self.acc = vec(1, 0).rotate(-self.rot)
        self.avoid_mobs()
        self.avoid_player()
        self.vel *= .95
        self.vel += self.acc/2
        self.pos += self.vel
        self.rect = self.image.get_rect()
        self.image.set_colorkey((255, 255, 255))
        self.rect.center = self.pos
        # role = random.randrange(100)
        # if role < 5:
        #     b = Enemy_Blaster(self, self.game, self.rot)
        #     self.game.all_sprites.add(b)
        # print(self.pos)
    def avoid_mobs(self):
        for mob in self.game.enemies:
            if mob != self:
                dist = self.pos - mob.pos
                if 0 < dist.length() < 60:
                    self.acc += dist.normalize()
    def avoid_player(self):
        dist = self.pos - self.game.player.pos
        if 0 < dist.length() < 200:
            self.acc += dist.normalize()
    def hit(self):
        self.hit_points -= 5
        if self.hit_points <= 0:
            for part in self.game.explode:
                angle = random.choice(part)
                vector =pg.Vector2()
                vector.from_polar((1, angle))
                particle = Particle(self.game.images.particle, self.pos, vector)
                self.game.particles.add(particle)
            self.kill()

class Camera():
    def __init__(self, game):
        self.game = game
        self.pos = vec(0, 0)# vec(self.game.player.pos.x / 2, self.game.player.pos.y / 2)
        self.state = "follow"

    def update(self):
        self.pos = vec(self.game.player.pos.x - 400, self.game.player.pos.y - 400)




def tile_image(game, surface, image):

    width, height = 800, 800
    i_width, i_height = image.get_size()
    camera_x = int(game.camera.pos.x % i_width)
    camera_y = int(game.camera.pos.y % i_height)
    for x in range(-camera_x, width, i_width):
        for y in range(-camera_y, height, i_height):
            surface.blit(image, (x, y))



class Enemy_Blaster(pg.sprite.Sprite):
    def __init__(self, source, game, dir):
        pg.sprite.Sprite.__init__(self)
        self.dir = vec(dir)*20 + source.vel
        self.game = game
        self.pos = source.pos
        self.rot = source.rot
        self.original_img = pg.Surface((7, 3), pg.SRCALPHA)
        self.original_img.fill((200, 55, 255))
        self.rect = self.original_img.get_rect()
        self.image = pg.transform.rotate(self.original_img, self.rot)
        self.vel = vec(1,0)
        self.state = "normal"
        self.rect.center = self.pos
        self.spawn_time = pg.time.get_ticks()
        self.life_span = 1000


    def update(self, ticks):
        now = pg.time.get_ticks()
        if now - self.spawn_time > self.life_span:
            self.kill()
        self.pos += self.dir
        self.rect.center = self.pos

        hit = self.rect.colliderect(self.game.player.rect)
        if hit:
            self.game.player.hit()
            print("hit")
            self.kill()

class Blaster(pg.sprite.Sprite):
    def __init__(self, game, pos, dir):
        pg.sprite.Sprite.__init__(self)
        self.dir = vec(dir)*20 + game.player.vel
        self.game = game
        self.original_img = pg.Surface((10, 4), pg.SRCALPHA)
        self.original_img.fill((200, 55, 10))
        self.rect = self.original_img.get_rect()
        self.image = pg.transform.rotate(self.original_img, self.game.player.rot)
        self.vel = vec(2,0)
        self.rot = 0
        self.state = "normal"
        self.pos = vec(pos)
        self.rect.center = self.pos
        self.spawn_time = pg.time.get_ticks()
        self.life_span = 1000
    def update(self, ticks):

        if ticks - self.spawn_time > self.life_span:
            self.kill()
        self.pos += self.dir
        self.rect.center = self.pos
        for sprite in self.game.enemies:
            hit = self.rect.colliderect(sprite.rect)
            if hit:

                sprite.hit()
                self.kill()


class Player(pg.sprite.Sprite):
    def __init__(self, game, pos):
        pg.sprite.Sprite.__init__(self)
        self.game = game
        self.game.player_img = pg.Surface((50, 50))
        self.rect = self.game.player_img.get_rect()
        self.image = self.game.player_img
        self.vel = vec(0,0)
        self.acc = vec(0,0)
        self.rot = 0
        self.angle = 0
        self.state = "normal"
        self.rot_speed = 4
        self.pos = vec(400, 400)
        self.firing_rate = 100
        self.last_shot = pg.time.get_ticks()
        self.blaster_offset = vec(0,0)
        self.thrust = .5

    def get_keys(self):

        self.acc = vec(0, 0)
        keys = pg.key.get_pressed()
        if keys[pg.K_LEFT] or keys[pg.K_a]:
             self.rot = (self.rot + self.rot_speed) % 360
        if keys[pg.K_RIGHT] or keys[pg.K_d]:
             self.rot = (self.rot - self.rot_speed) % 360
        if keys[pg.K_UP] or keys[pg.K_w]:
            self.acc = vec(self.thrust, 0).rotate(-self.rot)
        if keys[pg.K_DOWN] or keys[pg.K_s]:
            self.acc = vec(-self.thrust / 4, 0).rotate(-self.rot)
        if keys[pg.K_SPACE]:
            now = pg.time.get_ticks()
            if now - self.last_shot > self.firing_rate:
                self.last_shot = now
                dir = vec(1, 0).rotate(-self.rot)
                pos = self.rect.center #self.pos + self.blaster_offset.rotate(-self.rot)
                b = Blaster(self.game, pos, dir)
                self.game.all_sprites.add(b)

    def update(self, ticks):
        self.acc = vec(0, 0)
        self.get_keys()
        self.vel = self.vel * .99 + self.acc
        self.image = pg.transform.rotate(self.game.player_img, self.rot)
        self.rect = self.image.get_rect()
        self.image.set_colorkey((255, 255, 255))
        self.pos += self.vel
        if abs(self.vel.x) < 0.1:
            self.vel.x = 0
        self.rect.center = self.pos

    def hit(self):
        print("ouch!!")
class Control:
    def __init__(self):
        self.done = False
        self.fps = 60
        self.screen = pg.display.set_mode((800,800))
        self.screen_rect = self.screen.get_rect()
        self.clock = pg.time.Clock()
    def setup_states(self, state_dict, start_state):
        self.state_dict = state_dict
        self.state_name = start_state
        self.state = self.state_dict[self.state_name]
    def flip_state(self):
        self.state.done = False
        previous,self.state_name = self.state_name, self.state.next
        self.state.cleanup()
        self.state = self.state_dict[self.state_name]
        self.state.startup()
        self.state.previous = previous
    def update(self, dt):
        if self.state.quit:
            self.done = True
        elif self.state.done:
            self.flip_state()
        self.state.update(self.screen)
    def event_loop(self):
        for event in pg.event.get():
            if event.type == pg.QUIT:
                self.done = True
            self.state.get_event(event)
    def main_game_loop(self):
        while not self.done:
            delta_time = self.clock.tick(self.fps)/1000.0
            self.event_loop()
            self.update(delta_time)
            pg.display.update()

class MenuManager:
    def __init__(self):
        self.selected_index = 0
        self.last_option = None
        self.selected_color = (255,255,0)
        self.deselected_color = (255,255,255)

    def draw_menu(self, screen):
        '''handle drawing of the menu options'''
        for i,opt in enumerate(self.rendered["des"]):
            opt[1].center = (self.screen_rect.centerx, self.from_bottom+i*self.spacer)
            if i == self.selected_index:
                rend_img,rend_rect = self.rendered["sel"][i]
                rend_rect.center = opt[1].center
                screen.blit(rend_img,rend_rect)
            else:
                screen.blit(opt[0],opt[1])

    def update_menu(self):
        self.mouse_hover_sound()
        self.change_selected_option()

    def get_event_menu(self, event):
        if event.type == pg.KEYDOWN:
            '''select new index'''
            if event.key in [pg.K_UP, pg.K_w]:
                self.change_selected_option(-1)
            elif event.key in [pg.K_DOWN, pg.K_s]:
                self.change_selected_option(1)

            elif event.key == pg.K_RETURN:
                self.select_option(self.selected_index)
        self.mouse_menu_click(event)

    def mouse_hover_sound(self):
        '''play sound when selected option changes'''
        for i,opt in enumerate(self.rendered["des"]):
            if opt[1].collidepoint(pg.mouse.get_pos()):
                if self.last_option != opt:
                    self.last_option = opt
    def mouse_menu_click(self, event):
        '''select menu option '''
        if event.type == pg.MOUSEBUTTONDOWN and event.button == 1:
            for i,opt in enumerate(self.rendered["des"]):
                if opt[1].collidepoint(pg.mouse.get_pos()):
                    self.selected_index = i
                    self.select_option(i)
                    break
    def pre_render_options(self):
        '''setup render menu options based on selected or deselected'''
        font_deselect = pg.font.SysFont("arial", 50)
        font_selected = pg.font.SysFont("arial", 70)

        rendered_msg = {"des":[],"sel":[]}
        for option in self.options:
            d_rend = font_deselect.render(option, 1, self.deselected_color)
            d_rect = d_rend.get_rect()
            s_rend = font_selected.render(option, 1, self.selected_color)
            s_rect = s_rend.get_rect()
            rendered_msg["des"].append((d_rend,d_rect))
            rendered_msg["sel"].append((s_rend,s_rect))
        self.rendered = rendered_msg

    def select_option(self, i):
        '''select menu option via keys or mouse'''
        if i == len(self.next_list):
            self.quit = True
        else:
            self.next = self.next_list[i]
            self.done = True
            self.selected_index = 0

    def change_selected_option(self, op=0):
        '''change highlighted menu option'''
        for i,opt in enumerate(self.rendered["des"]):
            if opt[1].collidepoint(pg.mouse.get_pos()):
                self.selected_index = i
        if op:
            self.selected_index += op
            max_ind = len(self.rendered['des'])-1
            if self.selected_index < 0:
                self.selected_index = max_ind
            elif self.selected_index > max_ind:
                self.selected_index = 0

class States(Control):
    def __init__(self):
        Control.__init__(self)
        self.done = False
        self.next = None
        self.quit = False
        self.previous = None


class Menu(States, MenuManager):
    def __init__(self):
        States.__init__(self)
        MenuManager.__init__(self)
        self.next = 'game'
        self.options = ['Play', 'Options', 'Quit']
        self.next_list = ['game', 'options']
        self.pre_render_options()
        self.from_bottom = 200
        self.spacer = 75
    def cleanup(self):
        print('cleaning up Main Menu state stuff')
    def startup(self):
        print('starting Main Menu state stuff')
    def get_event(self, event):
        if event.type == pg.QUIT:
            self.quit = True
        self.get_event_menu(event)
    def update(self, screen, dt):
        self.update_menu()
        self.draw(screen)
    def draw(self, screen):
        screen.fill((255,0,0))
        self.draw_menu(screen)

class Options(States, MenuManager):
    def __init__(self):
        States.__init__(self)
        MenuManager.__init__(self)
        self.next = 'menu'
        self.options = ['Music', 'Sound', 'Graphics', 'Controls', 'Main Menu']
        self.next_list = ['options', 'options', 'options', 'options', 'menu']
        self.from_bottom = 200
        self.spacer = 75
        self.deselected_color = (150,150,150)
        self.selected_color = (0,0,0)
        self.pre_render_options()
    def cleanup(self):
        print('cleaning up Options state stuff')
    def startup(self):
        print('starting Options state stuff')
    def get_event(self, event):
        if event.type == pg.QUIT:
            self.quit = True
        self.get_event_menu(event)
    def update(self, screen, dt):
        self.update_menu()
        self.draw(screen)
    def draw(self, screen):
        screen.fill((255,0,0))
        self.draw_menu(screen)

class Game(States):
    def __init__(self):
        States.__init__(self)
        self.next = 'menu'
        self.effects = []
        self.all_sprites = pg.sprite.Group()
        self.enemies = pg.sprite.Group()
        self.player = Player(self, vec(400, 400))
        self.all_sprites.add(self.player)
        self.camera = Camera(self)
        self.bg = pg.Surface((800, 800))
        self.bg.fill(pg.Color('grey4'))
        for s in range(800):
            self.bg.set_at((random.randrange(800), random.randrange(800)), (250, 200, 200))

        self.img_dir = path.join(path.dirname(__file__), 'images')
        self.snd_dir = path.join(path.dirname(__file__), 'snd')
        self.load_images()
        for i in range(10):
            o = Enemy(self, (random.randrange(800), random.randrange(800)))
            self.all_sprites.add(o)
            self.enemies.add(o)
        self.mini_map = Mini_map(self, (600, 0))
        self.particles = pg.sprite.Group()
        self.images = Images()
        self.explode = []
        for x in range(0, 320, 45):
            self.explode.append(list(range(x, x + 45)))
    def load_images(self):
        img = pg.image.load(path.join(self.img_dir, "ship1.png")).convert()
        self.player_img = pg.transform.scale(self.player_img, (40, 50))
        self.player_img.set_colorkey((255, 255, 255))
        self.player_img.fill((200, 101, 120))
    def cleanup(self):
        print('cleaning up Game state stuff')
    def startup(self):
        print('starting Game state stuff')
    def get_event(self, event):
        if event.type == pg.MOUSEBUTTONDOWN:
            self.done = True
    def update(self, screen):
        ticks = pg.time.get_ticks()
        self.all_sprites.update(ticks)
        self.particles.update(ticks)
        self.camera.update()
        self.mini_map.update()
        self.draw(screen)
    def draw(self, screen):
        if self.camera.state == "follow":

            tile_image(self, self.screen, self.bg)
            for sprite in self.all_sprites:
                screen.blit(sprite.image, (vec(sprite.rect.x, sprite.rect.y) - self.camera.pos))
            for particle in self.particles:
                screen.blit(particle.image, (vec(particle.rect.x, particle.rect.y) - self.camera.pos))
        self.screen.blit(self.mini_map.image, self.mini_map.pos)
app = Control()
state_dict = {
    'menu': Menu(),
    'game': Game(),
    'options':Options()
}
app.setup_states(state_dict, 'game')
app.main_game_loop()
pg.quit()
sys.exit()
Reply
#6
It will help knowing your images sizes. For I can replace them in your code. Then I can see what going on.
Here part of my code updated. It is better to use delta time for really smooth movement. I use ticks last time.
class Particle(pygame.sprite.Sprite):
    def __init__(self, image, pos, vector):
        super().__init__()
        self.center = pos
        self.image = image.copy()
        self.rect = self.image.get_rect(center=pos)
        self.alpha = 255
        self.interval = 40
        self.vector = vector
        self.next_tick = pygame.time.get_ticks() + self.interval
        self.speed = random.randrange(8, 80)

    def update(self, delta, ticks):
        if ticks > self.next_tick:
            if self.alpha > 0:
                self.next_tick += self.interval
                self.alpha -= 4
                self.image.set_alpha(self.alpha)
            else:
                self.kill()

        self.center += self.vector * self.speed * delta
        self.rect.center = self.center

class Game(Scene):
    def __init__(self, engine):
        super().__init__(engine)
        self.particles = pygame.sprite.Group()
        self.images = Images()
        self.explode = []
        for x in range(0, 320, 45):
            self.explode.append(list(range(x, x + 45)))

    def mouse_push(self, mouse_pos):
        for part in self.explode:
            angle = random.choice(part)
            vector = pygame.Vector2()
            vector.from_polar((1, angle))
            particle = Particle(self.images.particle, mouse_pos, vector)
            self.particles.add(particle)

    def on_draw(self, surface):
        surface.fill('black')
        self.particles.draw(surface)

    def on_event(self, event):
        if event.type == pygame.MOUSEBUTTONDOWN:
            if event.button == 1:
                self.mouse_push(event.pos)

    def on_update(self, delta, ticks):
        self.particles.update(delta, ticks)
99 percent of computer problems exists between chair and keyboard.
Reply
#7
(Jul-06-2022, 08:11 PM)Windspar Wrote: It will help knowing your images sizes. For I can replace them in your code.

Sorry, the image ship1.png isn't used. I forgot to comment it out so it would run. Take that line out and it should work... I think.
Reply
#8
Okay I get to that soon. Here I refactor my code more.
import random
import pygame

class Scene:
    def __init__(self, engine):
        self.engine = engine

    def on_draw(self, surface):
        pass

    def on_event(self, event):
        pass

    def on_update(self, delta, ticks):
        pass

    def on_quit(self):
        self.engine.running = False

class DisplayEngine:
    def __init__(self, caption, width, height, flags=0):
        pygame.display.set_caption(caption)
        self.surface = pygame.display.set_mode((width, height), flags)
        self.rect = self.surface.get_rect()
        self.clock = pygame.time.Clock()
        self.running = False
        self.gamespeed = 1
        self.delta = 0
        self.fps = 60

        self._scene = Scene(self)
        self._next_scene = None

    def main_loop(self, scene=None):
        if scene:
            self._scene = scene

        self.running = True
        while self.running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    self._scene.on_quit()
                else:
                    self._scene.on_event(event)

            ticks = pygame.time.get_ticks()
            self._scene.on_draw(self.surface)
            self._scene.on_update(self.delta, ticks)

            pygame.display.flip()
            self.delta = self.clock.tick(self.fps) * 0.001

class Images:
    def __init__(self):
        self.particle = pygame.Surface((20, 20), pygame.SRCALPHA)
        self.particle.fill((200, 100, 100))

class Ticker:
    def __init__(self, interval, next_tick=0):
        self.interval = interval
        if next_tick == 0:
            self.next_tick = pygame.time.get_ticks()
        else:
            self.next_tick = next_tick

    def tick(self, ticks):
        if ticks > self.next_tick:
            self.next_tick += self.interval
            return True
        else:
            return False

    def update(self, ticks=0):
        if ticks > 0:
            self.next_tick = ticks
        else:
            self.next_tick = pygame.time.get_ticks()

class Particle(pygame.sprite.Sprite):
    def __init__(self, image, pos, vector, interval):
        super().__init__()
        self.center = pos
        self.image = image.copy()
        self.rect = self.image.get_rect(center=pos)
        self.alpha = 255
        self.vector = vector
        self.speed = random.randrange(8, 80)
        self.ticker = Ticker(interval)

    def update(self, delta, ticks):
        if self.ticker.tick(ticks):
            if self.alpha > 0:
                self.alpha -= 4
                self.image.set_alpha(self.alpha)
            else:
                self.kill()

        self.center += self.vector * self.speed * delta
        self.rect.center = self.center

class ParticleEngine:
    def __init__(self, image):
        self.image = image
        self.particles = pygame.sprite.Group()
        self.explode = []
        for x in range(0, 320, 45):
            self.explode.append(list(range(x, x + 45)))

    def create(self, mouse_pos):
        for part in self.explode:
            angle = random.choice(part)
            vector = pygame.Vector2()
            vector.from_polar((1, angle))
            particle = Particle(self.image, mouse_pos, vector, 50)
            self.particles.add(particle)

class Game(Scene):
    def __init__(self, engine):
        super().__init__(engine)
        self.images = Images()
        self.particle_engine = ParticleEngine(self.images.particle)

    def on_draw(self, surface):
        surface.fill('black')
        self.particle_engine.particles.draw(surface)

    def on_event(self, event):
        if event.type == pygame.MOUSEBUTTONDOWN:
            if event.button == 1:
                self.particle_engine.create(event.pos)

    def on_update(self, delta, ticks):
        self.particle_engine.particles.update(delta, ticks)


if __name__ == "__main__":
    engine = DisplayEngine("Particles", 800, 600)
    game = Game(engine)
    engine.main_loop(game)
    pygame.quit()
99 percent of computer problems exists between chair and keyboard.
Reply
#9
I figure your problem out. It because you link the same vector. You can not pass the same vector without copying it.
particle = Particle(self.game.images.particle, vec(self.pos), vector)
99 percent of computer problems exists between chair and keyboard.
Reply
#10
(Jul-07-2022, 12:56 AM)Windspar Wrote: I figure your problem out. It because you link the same vector. You can not pass the same vector without copying it.
particle = Particle(self.game.images.particle, vec(self.pos), vector)

Amazing! Thank you, I would never, ever have figured that out.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Laggy Game Movement game_slayer_99 12 4,413 Oct-05-2022, 11:34 AM
Last Post: metulburr
  [PyGame] Isometric Movement on Tiled Map Josselin 0 2,378 Nov-02-2021, 06:56 AM
Last Post: Josselin
  [PyGame] object's movement to left leave shadow on the screen Zhaleh 3 3,164 Aug-02-2020, 09:59 PM
Last Post: nilamo
  Using classes for room movement (text game) Lawr3y 3 6,603 Aug-20-2019, 12:40 AM
Last Post: Lawr3y
  Problem with coding for movement keys Aresofthesea 3 3,486 Jul-05-2019, 07:05 PM
Last Post: nilamo
  Pygame Movement X/Y coord TheHumbleIdiot 2 3,532 Mar-19-2019, 02:21 PM
Last Post: TheHumbleIdiot
  Movement after KEYUP, only after pause func esteel 2 3,302 Aug-22-2018, 03:03 PM
Last Post: Windspar
  [PyGame] How to stop the sprite's movement when it touches the edges of the screen? mrmn 5 11,637 May-13-2018, 06:33 PM
Last Post: mrmn

Forum Jump:

User Panel Messages

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