0
\$\begingroup\$

Goal, Description & Scope (when encountered issue)

I was building a testing script to see how implementing different ideas (event handling, update handling, draw method) to loading sprite images, passing data between class objects/attributes, movement mechanics, and the like. When I noticed the self.all_sprites instance of pygame.sprite.Group.draw method wasn't rendering the player image to the display. I walked through it line by line, threw in some print functions to check the script a different points, spent some time looking through Pygame's docs (online & looking inside libs directory to see how Pygame built SpriteGroup objects), StackOverflow, & general to specific search queries of other sites... Still, I have no clue why the SpriteGroup draw method doesn't work, but Player's draw method does. SpriteGroup update works, and everything else seems to run fine, but not drawing to the main display through SpriteGroup's draw method.

Full code below:

import sys
from os import listdir
from os.path import join
import pygame as pg


# support function image loader
def img_loader(path=r'..\tmp'):
    if path:
        img_dir = {}
        ls_files = listdir(path)
        for file in ls_files:
            if '.py' not in file:
                key = file.split('.')[0]
                value = pg.image.load(join(path, file)).convert_alpha()
                img_dir[key] = value
        img_dir['left'] = pg.transform.flip(
            img_dir['right'], True, False)
        img_dir['topleft'] = pg.transform.flip(
            img_dir['topright'], True, False)
        img_dir['bottomleft'] = pg.transform.flip(
            img_dir['bottomright'], True, False)

        img_dir['left_sink0'] = pg.transform.flip(
            img_dir['right_sink0'], True, False)
        img_dir['topleft_sink0'] = pg.transform.flip(
            img_dir['topright_sink0'], True, False)
        img_dir['bottomleft_sink0'] = pg.transform.flip(
            img_dir['bottomright_sink0'], True, False)

        img_dir['left_sink1'] = pg.transform.flip(
            img_dir['right_sink1'], True, False)
        img_dir['topleft_sink1'] = pg.transform.flip(
            img_dir['topright_sink1'], True, False)
        img_dir['bottomleft_sink1'] = pg.transform.flip(
            img_dir['bottomright_sink1'], True, False)
        return img_dir
    else:
        tmp = {}
        return tmp


class Projectile(pg.sprite.Sprite):
    def __init__(self):
        pass

    
# Player Sprite
class Player(pg.sprite.Sprite):
    def __init__(self, groups):
        super().__init__(groups)
        self.ds = pg.display.get_surface()
        self.img_dir = img_loader()
        self.key_dir = 'bottom'
        self.image_reset = False
        self.pos = pg.Vector2(200, 200)
        self.image = self.img_dir[self.key_dir]#implement
        self.rect = self.image.get_rect(center=self.pos)
        self.direction = pg.Vector2()
        self.speed = 100
        self.projectiles = pg.sprite.Group()

    def key_w(self, delta): self.direction.y -= self.speed * delta

    def key_s(self, delta): self.direction.y += self.speed * delta

    def key_a(self, delta): self.direction.x -= self.speed * delta

    def key_d(self, delta): self.direction.x += self.speed * delta

    def fire(self, delta, pos):
        print('Cannon has fired')
        pass# make bullet & add to self.projectiles w/delayed timer

    def update(self, delta):
        # update bullets before player
        # make bullet class w/update method
        #self.projectiles.update(delta)
        if self.direction.x != 0.0 and self.direction.y != 0.0:
            if self.direction.y < 0.0 and self.direction.x < 0.0:
                self.key_dir = 'topleft'
            elif self.direction.y < 0.0 and self.direction.x > 0.0:
                self.key_dir = 'topright'
            elif self.direction.y > 0.0 and self.direction.x < 0.0:
                self.key_dir = 'bottomleft'
            else: self.key_dir = 'bottomright'
            self.direction *= 0.707107 # equalize diagonal movement
            self.image_reset = True

        else:
            if self.direction.y != 0.0:
                self.image_reset = True
                if self.direction.y < 0.0: self.key_dir = 'top'
                else: self.key_dir = 'bottom'
            elif self.direction.x != 0.0:
                self.image_reset = True
                if self.direction.x < 0.0: self.key_dir = 'left'
                else: self.key_dir = 'right'
            else: pass
        
        self.pos += self.direction
        self.direction.x, self.direction.y = 0.0, 0.0

    def draw(self, ds):
        # draw bullets before player
        #if self.projectiles.sprites(): self.projectiles.draw(ds)
        #else: pass
        if self.image_reset:
            self.image = self.img_dir[self.key_dir]
            self.rect = self.image.get_rect(center=self.pos)
            self.image_reset = False
            ds.blit(self.image, self.rect)
        else:
            ds.blit(self.image, self.rect)


# Main testing object
class Main:
    def __init__(self):
        pg.init()
        self.display = pg.display.set_mode((400, 400), 0, 32)
        pg.key.set_repeat(25, 25)
        self.time = pg.time.Clock()
        self.all_sprites = pg.sprite.Group()
        self.player = Player(self.all_sprites)

    def run(self):
        # preset vars
        done = False
        paused = False

        # set while loop
        while not done:
            # get framerate
            delta = self.time.tick() / 1000

            # handle Events
            if pg.event.get(pg.QUIT): done = True
            keys = pg.key.get_pressed()
            if keys[pg.K_ESCAPE]: done = True
            if keys[pg.K_SPACE]:
                mouse_pos = pg.mouse.get_pos()
                self.player.fire(delta, mouse_pos)
            if keys[pg.K_w]: self.player.key_w(delta)
            if keys[pg.K_s]: self.player.key_s(delta)
            if keys[pg.K_a]: self.player.key_a(delta)
            if keys[pg.K_d]: self.player.key_d(delta)
            pg.event.clear()

            # handle Updates
            self.player.update(delta)
##            self.all_sprites.update(delta) # Does handle correctly

            # handle Drawing
            _ = self.display.fill('black')
            self.player.draw(self.display) # WORKS, draws to self.display
##            self.all_sprites.draw(self.display) # Doesn't draw to self.display, gives a non-updated image


            # Update main display
            pg.display.update()


# autorun
if __name__ == '__main__':
    test = Main()
    test.run()
    pg.quit()
    sys.exit()

Image Illustration

Visual Aid of Code Bug

I need to get the self.all_sprites method draw fully working, for implementing & testing down the road, especially with multiple sprites (passive, enemies, etc ...)

Inclosing

Any & all insight would be greatly appreciated, and I would like to express my Thanks for taking the time to help me figure this out.

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Found bug (a user error)

I realized my self.player.draw method doesn't get called in the self.all_sprites.draw method.

To fix ...

I can either throw a wrapper class around SpriteGroup (see below)

class CustomSpriteGroup(pg.sprite.Group):
    def __init__(self):
        super().__init__()

    # override pygame's Sprite Group default of draw method
    def draw(self, ds):
        for spr in self.sprites():
            spr.draw(ds)

In which, creating self.all_sprites in Main.__init__ will need to be rewritten as,
self.all_sprites = CustomSpriteGroup()

Or

A more simple solution, by removing draw method from Player class and adding a few more lines to Player.update method (see below)

# Player Sprite
class Player(pg.sprite.Sprite):
    def __init__(self, groups):
##        super().__init__(groups)
        pg.sprite.Sprite.__init__(self, groups)
        self.ds = pg.display.get_surface()
        self.img_dir = img_loader()
        self.key_dir = 'bottom'
        self.image_reset = False
        self.pos = pg.Vector2(200, 200)
        self.image = self.img_dir[self.key_dir]#implement
        self.rect = self.image.get_rect(center=self.pos)
        self.direction = pg.Vector2()
        self.speed = 100
        self.projectiles = pg.sprite.Group()

    def key_w(self, delta): self.direction.y -= self.speed * delta

    def key_s(self, delta): self.direction.y += self.speed * delta

    def key_a(self, delta): self.direction.x -= self.speed * delta

    def key_d(self, delta): self.direction.x += self.speed * delta

    def fire(self, delta, pos):
        print('Cannon has fired')
        pass# make bullet & add to self.projectiles w/delayed timer

    def update(self, delta):
        # update bullets before player
        # make bullet class w/update method
        #self.projectiles.update(delta)
        if self.direction.x != 0.0 and self.direction.y != 0.0:
            if self.direction.y < 0.0 and self.direction.x < 0.0:
                self.key_dir = 'topleft'
            elif self.direction.y < 0.0 and self.direction.x > 0.0:
                self.key_dir = 'topright'
            elif self.direction.y > 0.0 and self.direction.x < 0.0:
                self.key_dir = 'bottomleft'
            else: self.key_dir = 'bottomright'
            self.direction *= 0.707107 # equalize diagonal movement
            self.image_reset = True

        else:
            if self.direction.y != 0.0:
                self.image_reset = True
                if self.direction.y < 0.0: self.key_dir = 'top'
                else: self.key_dir = 'bottom'
            elif self.direction.x != 0.0:
                self.image_reset = True
                if self.direction.x < 0.0: self.key_dir = 'left'
                else: self.key_dir = 'right'
            else: pass
        
        self.pos += self.direction
        self.direction.x, self.direction.y = 0.0, 0.0

## added from old draw method
        if self.image_reset:
            self.image = self.img_dir[self.key_dir]
            self.rect = self.image.get_rect(center=self.pos)
            self.image_reset = False

## REMOVE ALL BELOW
##    def draw(self, ds):
##        # draw bullets before player
##        #if self.projectiles.sprites(): self.projectiles.draw(ds)
##        #else: pass
##        if self.image_reset:
##            self.image = self.img_dir[self.key_dir]
##            self.rect = self.image.get_rect(center=self.pos)
##            self.image_reset = False
##            ds.blit(self.image, self.rect)
##        else:
##            ds.blit(self.image, self.rect)
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .