Hadex02
Hadex02

Reputation: 31

How to make my bullets animate after they hit something

I want to make it so that when my bullet reaches a certain distance, it goes through an explosion animation and then removes itself. I am able to have it remove itself, but I don't know how to animate it properly.

        enemyShotList = pygame.sprite.spritecollide(bullet, enemyGroup, False)

        for enemy in enemyShotList:
            enemyGroup.remove(enemy)
            bulletGroup.remove(bullet)

        currentBulletTime = pygame.time.get_ticks()
        currentbulletx = bullet.rect.x
        currentbullety = bullet.rect.y
        if currentBulletTime - bullet.originalBulletTime > 700:
            bulletGroup.remove(bullet)
            for i in range(0,15):
                screen.blit(bulletpopList[i], [currentbulletx,currentbullety])

The problem with the above code is that it runs through the animation too fast. I don't know how to animate it so that it is slower.

Upvotes: 1

Views: 189

Answers (1)

sloth
sloth

Reputation: 101042

You seem to use the Sprite class already, so it's easy to keep track of time inside your sprites.

What you should do is to use the so called delta time. Basically, you measure the time it takes to render a frame, and pass that value to every part of the game that "moves through time". So when the last frame took X ms, a sprite that moves with a speed of S moves S*X pixels. If the next frame takes Y ms, that same sprite will move S*Y pixels.

In pygame, you usually use a Clock to limit the framerate calling its tick method. This method will also return how many milliseconds have passed since the previous call. So this value is our delta time that we pass to the update method of our sprites.

So since now every sprites knows each frame how much time passed, we can do a slower animation by creating a instance variable (let's call it timer), set its value to the amount of milliseconds we want to wait, and substract the delta time (let's call it dt) each frame. Once we hit <= 0, we reset timer and change the image of the sprite.

Here's a running example I hacked together. Shoot a red rocket by pressing any key and try to hit the blue targets. Watch the awesome explosions!

import pygame
import random

TILESIZE = 32

# Since our sprites are very generic, we want to be able
# to set some attributes while creating them.
# Here we create a set of allowed options.
ACTOR_OPTIONS = set(['can_be_destroyed'])

# guess what
def random_color():
    return random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)

# A generic Sprite class. 
# It takes a position, a velocity, a color, a function that
# controls its behaviour, and keyworded arguments.
class Actor(pygame.sprite.Sprite):
    def __init__(self, pos, vel, color, handler, all_sprites, **kwargs):
        super().__init__(all_sprites)
        # A timer and counter attribute to handle time based stuff.
        self.timer = 0
        self.counter = 0
        # Update the sprite attributes with the keyworded arguments. 
        # We only allow the keys defined in ACTOR_OPTIONS.
        self.__dict__.update(kwargs)
        self.__dict__.update((key, False) for key in ACTOR_OPTIONS)
        self.__dict__.update((key, value) for key, value in kwargs.items() if key in ACTOR_OPTIONS)
        # We keep a reference to a sprite groups that contains all sprites
        # so we can access them easily
        self.all_sprites = all_sprites
        # Simple non-colored image 
        self.image = pygame.Surface((TILESIZE, TILESIZE))
        self.image.fill(pygame.Color(color))
        self.rect = self.image.get_rect(center=pos)
        # Position and velocity as Vectors for easier math
        self.pos = pygame.Vector2(pos)
        self.vel = pygame.Vector2(vel)
        # The handler function that we're going to call each frame
        self.handler = handler

    def update(self, dt, events):
        # Let the handler function to their thing
        self.handler(self, dt, events)
        # Move the sprite
        self.pos += (self.vel * dt/10)
        self.rect.center = [int(x) for x in self.pos]

# Our sprites are very generic, so here we create some functions
# that will control the behaviour of the sprites.
# We could have created new classes and implement the update-function instead,
# but this function-approach allows us to change the behaviour of a sprite
# at runtime by setting the handler attribute

def target_handler(actor, dt, events):
    screen_rect = pygame.display.get_surface().get_rect()

    # A target does nothing but changing its direction when hittin the screen edges.
    # Note that this implementation does not handle edge cases like a huge delta time.
    if not screen_rect.contains(actor.rect):
        actor.vel *= -1

def rocket_handler(actor, dt, events):
    # A rocket will disappear when leaving the screen
    screen_rect = pygame.display.get_surface().get_rect()
    if not screen_rect.colliderect(actor.rect):
        actor.kill()

    # but when it hits a sprite with the can_be_destroyed-flag...
    collisions = pygame.sprite.spritecollide(actor, actor.all_sprites, False)
    for c in collisions:
        if c.can_be_destroyed:
            # ...we remove both sprites 
            actor.kill()
            c.kill()
            # ...and create to explosion sprites
            Actor(actor.pos, ( 0, 0), 'yellow', explosion_handler, actor.all_sprites, timer=100)
            Actor(c.pos, ( 0, 0), 'orange', explosion_handler, actor.all_sprites, timer=50)

def explosion_handler(actor, dt, events):
    # An explosion will change its color 15 times
    # every 100 milliseconds.
    if actor.timer:
        actor.timer -= dt
        if actor.timer <= 0:
            actor.image.fill(random_color())
            actor.timer = 100
            actor.counter += 1
    if actor.counter >= 15:
        actor.kill()

# Standard pygame main loop
def main():
    width, height = 640, 480
    pygame.init()
    screen = pygame.display.set_mode((width, height))
    clock = pygame.time.Clock()
    sprites = pygame.sprite.Group()
    dt = 0

    # We create three targets
    Actor((20, 20), ( 2,  0), 'dodgerblue', target_handler, sprites, can_be_destroyed=True)
    Actor((120, 120), ( 2,  0), 'dodgerblue', target_handler, sprites, can_be_destroyed=True)
    Actor((320, 220), ( 2,  0), 'dodgerblue', target_handler, sprites, can_be_destroyed=True)

    while True:
        events = pygame.event.get()
        for e in events:
            if e.type == pygame.QUIT:
                return
            if e.type == pygame.KEYDOWN:
                # Fire a rocket when we press a key
                Actor((320, 479), ( 0, -4), 'red', rocket_handler, sprites)

        sprites.update(dt, events)
        screen.fill((20, 20, 20))
        sprites.draw(screen)
        pygame.display.flip()
        dt = clock.tick(60)

if __name__ == '__main__':
    main()

enter image description here

Upvotes: 1

Related Questions