Spriteanimation (Explosion)

Hier werden alle anderen GUI-Toolkits sowie Spezial-Toolkits wie Spiele-Engines behandelt.
Antworten
hell
User
Beiträge: 40
Registriert: Montag 1. Oktober 2018, 18:01

Mittwoch 24. November 2021, 21:21

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()
__deets__
User
Beiträge: 10711
Registriert: Mittwoch 14. Oktober 2015, 14:29

Mittwoch 24. November 2021, 22:22

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.
hell
User
Beiträge: 40
Registriert: Montag 1. Oktober 2018, 18:01

Donnerstag 25. November 2021, 15:56

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.
Antworten