Seite 1 von 1

Spriteanimation (Explosion)

Verfasst: Mittwoch 24. November 2021, 21:21
von hell
hi, habe mal wieder ein Problem, im ff Code versuche ich nach einer Player-Gegner-Kollision eine Explosion zu animieren.
Wenn ich eine Spritegruppe verwende, so läuft die Animation mit korrekter Geschwindigkeit ab . Mein Ziel war jedoch, ohne Spritegruppe auszukommen.
Dann aber läuft die Animation sehr schnell ab. Vermute da muss irgendwo noch ein delay eingebaut werden ... ?
Vielleicht kann mir irgend eine Mensch aus dem Forum auf die Sprünge helfen , bitte?
Der Code - ohne Explosion -- stammt von metulburr aus dem englischsprachigen Forum.

Code: Alles auswählen

import pygame
import math

pygame.init()

screen = pygame.display.set_mode((800,600))
screen_rect = screen.get_rect()

BLACK = (0, 0, 0)
FPS  = 60
   
def enemy_image_load():
    image = pygame.image.load('data/enemy.png').convert()
    image.set_colorkey((255,0,255))
    transformed_image = pygame.transform.rotate(image, 180)
    orig_image = pygame.transform.scale(transformed_image, (40,80))
    mask = pygame.mask.from_surface(orig_image)
    return (orig_image, mask)
    

class Enemy:
    def __init__(self, images, screen_rect):
        self.screen_rect = screen_rect
        self.image = images[0]
        self.mask = images[1]
        start_buffer = 0
        self.rect = self.image.get_rect(
            center=(screen_rect.centerx, screen_rect.centery + start_buffer)
        )
        self.distance_above_player = 100 
        self.speed = 2
        self.bullet_color = (255,0,0)
        self.is_hit = False
        self.range_to_fire = False
        self.timer = 0.0
        self.bullets = [ ]
        self.dead = False
           
    def pos_towards_player(self, player_rect):
        c = math.sqrt((player_rect.x - self.rect.x) ** 2 + (player_rect.y - self.distance_above_player  - self.rect.y) ** 2)
        try:
            x = (player_rect.x - self.rect.x) / c
            y = ((player_rect.y - self.distance_above_player)  - self.rect.y) / c
        except ZeroDivisionError: 
            return False
        return (x,y)
           
    def update(self, dt, player):
        new_pos = self.pos_towards_player(player.rect)
        if new_pos: #if not ZeroDivisonError
            self.rect.x, self.rect.y = (self.rect.x + new_pos[0] * self.speed, self.rect.y + new_pos[1] * self.speed)
           
        self.check_attack_ability(player)
        if self.range_to_fire:  
            if pygame.time.get_ticks() - self.timer > 1500.0:
                self.timer = pygame.time.get_ticks()
                self.bullets.append(Laser(self.rect.center, self.bullet_color))
                   
        self.update_bullets(player)
                   
    def draw(self, surf):
        if self.bullets:
            for bullet in self.bullets:
                surf.blit(bullet.image, bullet.rect)
        surf.blit(self.image, self.rect)
           
    def check_attack_ability(self, player):
        #if player is lower than enemy
        if player.rect.y >= self.rect.y: 
            try:
                offset_x =  self.rect.x - player.rect.x
                offset_y =  self.rect.y - player.rect.y
                d = int(math.degrees(math.atan(offset_x / offset_y)))
            except ZeroDivisionError: #player is above enemy
                return
            #if player is within 15 degrees lower of enemy
            if math.fabs(d) <= 15: 
                self.range_to_fire = True
            else:
                self.range_to_fire = False
                   
    def update_bullets(self, player):
        if self.bullets:
            for obj in self.bullets[:]:
                obj.update('down')
                #check collision
                if obj.rect.colliderect(player.rect):
                    offset_x =  obj.rect.x - player.rect.x 
                    offset_y =  obj.rect.y - player.rect.y
                    if player.mask.overlap(obj.mask, (offset_x, offset_y)):
                        player.take_damage(1)
                        self.bullets.remove(obj)
           
class Laser:
    def __init__(self, loc, screen_rect):
        self.screen_rect = screen_rect
        self.image = pygame.Surface((5,40)).convert_alpha()
        #self.image.set_colorkey((255,0,255))
        self.mask = pygame.mask.from_surface(self.image)
        self.image.fill((255,255,0))
        self.rect = self.image.get_rect(center=loc)
        self.speed = 5
     
    def update(self,direction='up'):
        if direction == 'down':
            self.rect.y += self.speed
        else:
            self.rect.y -= self.speed
     
    def render(self, surf):
        surf.blit(self.image, self.rect)
     
class Player:
    def __init__(self, screen_rect):
        self.screen_rect = screen_rect
        self.image = pygame.image.load('data/spaceship.png').convert()
        self.image.set_colorkey((255,0,255))
        self.mask = pygame.mask.from_surface(self.image)
        self.transformed_image = pygame.transform.rotate(self.image, 180)
        self.transformed_image = pygame.transform.scale(self.transformed_image, (63,108))
        start_buffer = 300
        self.rect = self.image.get_rect(
            center=(screen_rect.centerx, screen_rect.centery + start_buffer)
        )
        self.dx = 300
        self.dy = 300
        self.lasers = []
        self.timer = 0.0
        self.laser_delay = 500
        self.add_laser = False
        self.damage = 10
         
    def take_damage(self, value):
        self.damage -= value
     
    def get_event(self, event):
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE:
                if self.add_laser:
                    self.lasers.append(Laser(self.rect.center, self.screen_rect))
                    self.add_laser = False
     
    def update(self, keys, dt, enemies):
        self.rect.clamp_ip(self.screen_rect)
        if keys[pygame.K_LEFT]:
            self.rect.x -= self.dx * dt
        if keys[pygame.K_RIGHT]:
            self.rect.x += self.dx * dt
        if keys[pygame.K_UP]:
            self.rect.y -= self.dy * dt
        if keys[pygame.K_DOWN]:
            self.rect.y += self.dy * dt
        if pygame.time.get_ticks()-self.timer > self.laser_delay:
            self.timer = pygame.time.get_ticks()
            self.add_laser = True
             
        self.check_laser_collision(enemies)
         
    def check_laser_collision(self, enemies):
        for laser in self.lasers[:]:
            laser.update()
            for e in enemies:
                if laser.rect.colliderect(e.rect):
                    offset_x =  laser.rect.x - e.rect.x 
                    offset_y =  laser.rect.y - e.rect.y
                    if e.mask.overlap(laser.mask, (offset_x, offset_y)):
                        # alternativ-Code ohne Spritegruppe
                        #expl = Explosion(e.rect.centerx, e.rect.centery)
                        #expl.update()
                        #expl.draw()
                        explosion = Explosion(e.rect.centerx, e.rect.centery)
                        explosion_group.add(explosion)
                        e.dead = True
                        self.lasers.remove(laser)
             
    def draw(self, surf):
        for laser in self.lasers:
            laser.render(surf)
        surf.blit(self.transformed_image, self.rect)
        
        
class Explosion(pygame.sprite.Sprite):
    def __init__(self, x, y):
        pygame.sprite.Sprite.__init__(self)
        self.images = []
        for num in range(1, 6):
            img = pygame.image.load(f"img/exp{num}.png")
            img = pygame.transform.scale(img, (120, 120))
            self.images.append(img)
        self.index = 0
        self.image = self.images[self.index]
        self.rect = self.image.get_rect()
        self.rect.center = [x, y]
        self.counter = 0

    def update(self):
        explosion_speed = 4
        #update explosion animation
        self.counter += 1

        if self.counter >= explosion_speed and self.index < len(self.images) - 1:
            self.counter = 0
            self.index += 1
            self.image = self.images[self.index]
        if self.index >= len(self.images) - 1 and self.counter >= explosion_speed:
            self.kill()

    def draw(self):
        screen.blit(self.image, self.rect)

screen = pygame.display.set_mode((800,600))
screen_rect = screen.get_rect()
player = Player(screen_rect)
ENEMY_IMAGE = enemy_image_load()
enemies = []
enemies.append(Enemy(ENEMY_IMAGE, screen_rect))
explosion_group = pygame.sprite.Group()

clock = pygame.time.Clock()
done = False

while not done:
    #clock.tick(FPS)
    keys = pygame.key.get_pressed()
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            done = True
        player.get_event(event)
    screen.fill((0,0,0))
    
    explosion_group.draw(screen)
    explosion_group.update()
    delta_time = clock.tick(60)/1000.0
    player.update(keys, delta_time, enemies)
    for e in enemies[:]:
        e.update(delta_time, player)
        if e.dead:
            enemies.remove(e) # remove: Listenmethode
        e.draw(screen)
    player.draw(screen)
    pygame.display.update()

Re: Spriteanimation (Explosion)

Verfasst: Mittwoch 24. November 2021, 22:22
von __deets__
Dein Delay ist in Frames. Und läuft bei 60 Frames mit 15 Animationsschritten/Sekunde ab, weil du alle 4 weiter stellst. Wenn das zu schnell ist, musst du das eben erhöhen. Und noch besser wär es, die wirklich verflossene Zeit zu nutzen, indem du delta_time in update gibst. Dann läuft das auch bei Variablen Framelängen immer gleich schnell ab. Delay hingegen ist keine Lösung. Denn bei 20 Explosionen ist dann 20 mal Delay.

Re: Spriteanimation (Explosion)

Verfasst: Donnerstag 25. November 2021, 15:56
von hell
hallo deets,
ich habe das mal mit delta_time umgeschrieben, allerdings bleibt das Problem dasselbe.

Code: Alles auswählen

class Explosion(pygame.sprite.Sprite):
    def __init__(self, x, y):
        pygame.sprite.Sprite.__init__(self)
        self.images = []
        for num in range(1, 6):
            img = pygame.image.load(f"img/exp{num}.png")
            img = pygame.transform.scale(img, (120, 120))
            self.images.append(img)
        
        self.index = 0
        self.image = self.images[self.index]
        self.rect = self.image.get_rect()
        self.rect.center = [x, y]
        
        self.animation_time = .4
        self.current_time = 0
        
        self.animation_frames = 6
        self.current_frame = 0

    #def update(self):
        #  frame_based
    #    self.current_frame += 1
    #    if self.current_frame >= self.animation_frames and self.index < len(self.images) - 1:
    #        self.current_frame = 0
    #        self.index += 1
    #        self.image = self.images[self.index]
    #    if self.index >= len(self.images) - 1 and self.current_frame >= self.animation_frames:
    #        self.kill()
    
    def update(self, delta_time):
        # time_based
        self.current_time += delta_time
        if self.current_time >= self.animation_time and self.index < len(self.images)-1:
            self.current_time = 0
            self.index += 1
            self.image = self.images[self.index]
        if self.index >= len(self.images)-1 and self.current_time >= self.animation_time:
           self.kill()
        
    def draw(self):
        screen.blit(self.image, self.rect)
und:

Code: Alles auswählen

def check_laser_collision(self, enemies):
        for laser in self.lasers[:]:
            laser.update()
            for e in enemies:
                if laser.rect.colliderect(e.rect):
                    offset_x =  laser.rect.x - e.rect.x 
                    offset_y =  laser.rect.y - e.rect.y
                    if e.mask.overlap(laser.mask, (offset_x, offset_y)):
                        # alternativ-Code ohne Spritegruppe
                        # time_based
                        expl = Explosion(e.rect.centerx, e.rect.centery)
                        expl.update(delta_time)
                        expl.draw()
                        # frame_based
                        #expl = Explosion(e.rect.centerx, e.rect.centery)
                        #expl.update()
                        #expl.draw()
                        
                        #expl = Explosion(e.rect.centerx, e.rect.centery)
                        #explosion_group.add(expl)
                        
                        e.dead = True
                        self.lasers.remove(laser)
                        
Wie gesagt, beide Versionen laufen mit Spritegruppen, ohne , wie beschrieben.
Irgendwo hakt es bei mir, vielleicht möchtest du dir das noch mal ansehen?
Gruss und danke für deine Antwort.