1

I'm following a tutorial for a Mario / Platformer type game in pygame and tiled, and currently I'm adding the 'player goal' part to display where the goal is and for some reason currently when I run the Mario / Platformer type game I get the error "AttributeError: 'int' object has no attribute 'sprites' " for self.goal.update(self.world_shift) in line 150

Here's the code:

import pygame
from support import import_csv_layout, import_cut_graphics
from settings import tile_size
from tiles import Tile, StaticTile, Crate, Coin, Palm
from enemy import Enemy

class Level:
    def __init__(self, level_data, surface):
        super().__init__()
        # general setup
        self.display_surface = surface
        self.world_shift = 0

        # player setup
        player_layout = import_csv_layout(level_data['player'])
        self.player = pygame.sprite.GroupSingle
        self.goal = pygame.sprite.GroupSingle
        self.player_setup(player_layout)

        # terrain setup
        terrain_layout = import_csv_layout(level_data['terrain'])
        self.terrain_sprites = self.create_tile_group(terrain_layout, 'terrain')

        # grass setup
        grass_layout = import_csv_layout(level_data['grass'])
        self.grass_sprites = self.create_tile_group(grass_layout, 'grass')

        # crates
        crate_layout = import_csv_layout(level_data['crates'])
        self.crate_sprites = self.create_tile_group(crate_layout, 'crates')

        # coins
        coin_layout = import_csv_layout(level_data['coins'])
        self.coin_sprites = self.create_tile_group(coin_layout, 'coins')

        # foreground palms
        fg_palm_layout = import_csv_layout(level_data['fg palms'])
        self.fg_palm_sprites = self.create_tile_group(fg_palm_layout, 'fg palms')

        # background palms
        bg_palm_layout = import_csv_layout(level_data['bg palms'])
        self.bg_palm_sprites = self.create_tile_group(bg_palm_layout, 'bg palms')

        # enemy
        enemy_layout = import_csv_layout(level_data['enemies'])
        self.enemy_sprites = self.create_tile_group(enemy_layout, 'enemies')

        # constraint
        contraint_layout = import_csv_layout(level_data['constraints'])
        self.contraint_sprites = self.create_tile_group(contraint_layout, 'constraints')

    def create_tile_group(self, layout, type):
        global sprite
        sprite_group = pygame.sprite.Group()

        for row_index, row in enumerate(layout):
            for col_index, val in enumerate(row):
                if val != '-1':
                    x = col_index * tile_size
                    y = row_index * tile_size

                    if type == 'terrain':
                        terrain_tile_list = import_cut_graphics('../graphics/terrain/terrain_tiles.png')
                        tile_surface = terrain_tile_list[int(val)]
                        sprite = StaticTile(tile_size, x, y, tile_surface)
                        sprite_group.add(sprite)

                    if type == 'grass':
                        grass_tile_list = import_cut_graphics('../graphics/decoration/grass/grass.png')
                        tile_surface = grass_tile_list[int(val)]
                        sprite = StaticTile(tile_size, x, y, tile_surface)

                    if type == 'crates':
                        sprite = Crate(tile_size, x, y)

                    if type == 'coins':
                        if val == '0':
                            sprite = Coin(tile_size, x, y, '../graphics/coins/gold')
                        elif val == '1':
                            sprite = Coin(tile_size, x, y, '../graphics/coins/silver')

                    if type == 'fg palms':
                        if val == '0':
                            sprite = Palm(tile_size, x, y, '../graphics/terrain/palm_small', 38)
                        elif val == '1':
                            sprite = Palm(tile_size, x, y, '../graphics/terrain/palm_large', 64)

                    if type == 'bg palms':
                        sprite = Palm(tile_size, x, y, '../graphics/terrain/palm_bg', 64)

                    if type == 'enemies':
                        sprite = Enemy(tile_size, x, y)

                    if type == 'constraints':
                        sprite = Tile(tile_size, x, y)

                    sprite_group.add(sprite)

        return sprite_group

    def player_setup(self, layout):
        for row_index, row in enumerate(layout):
            for col_index, val in enumerate(row):
                x = col_index * tile_size
                y = row_index * tile_size
                if val == '0':
                    pass
                if val == '1':
                    hat_surface = pygame.image.load('../graphics/character/hat.png').convert_alpha()
                    sprite = StaticTile(tile_size, x, y, hat_surface)
                    self.goal.add(sprite)

    def enemy_collision_reverse(self):
        for enemy in self.enemy_sprites.sprites():
            if pygame.sprite.spritecollide(enemy, self.contraint_sprites, False):
                enemy.reverse()

    def run(self):
        # background palms
        self.bg_palm_sprites.update(self.world_shift)
        self.bg_palm_sprites.draw(self.display_surface)

        # terrain
        self.terrain_sprites.update(self.world_shift)
        self.terrain_sprites.draw(self.display_surface)

        # enemy
        self.enemy_sprites.update(self.world_shift)
        self.contraint_sprites.update(self.world_shift)
        self.enemy_collision_reverse()
        self.enemy_sprites.draw(self.display_surface)

        # crates
        self.crate_sprites.update(self.world_shift)
        self.crate_sprites.draw(self.display_surface)

        # grass
        self.grass_sprites.update(self.world_shift)
        self.grass_sprites.draw(self.display_surface)

        # coins
        self.coin_sprites.update(self.world_shift)
        self.coin_sprites.draw(self.display_surface)

        # foreground palms
        self.fg_palm_sprites.update(self.world_shift)
        self.fg_palm_sprites.draw(self.display_surface)

        # player spites
        self.goal.update(self.world_shift)
        self.goal.draw(self.display_surface)
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
moron
  • 53
  • 5
  • Probably not related to your issue but why have you marked `sprite` as global in `create_tile_group()`? The only *possible* place it could be used in in `player_setup()` but, since it's modified there and *not* marked global, that is a local variable, unrelated to the global. Globals are usually a bad sign. – paxdiablo Aug 27 '22 at 01:03

1 Answers1

2
self.player = pygame.sprite.GroupSingle
self.goal = pygame.sprite.GroupSingle

These do not create a group object that you can add a sprite to, they instead just assign the GroupSingle class to your variables. To create groups, you would need:

self.player = pygame.sprite.GroupSingle()  # <- note parentheses.
self.goal = pygame.sprite.GroupSingle()

# Similar to "sprite_group = pygame.sprite.Group()" elsewhere
# in your code.

This creates an object of the class and calls the dunder-init function to initialise it, then returns that initialised object to you. Classes (as well as functions, modules, and packages) are first-class objects in Python, as demonstrated with a similar case:

>>> import random

>>> random
<module 'random' from '/usr/lib/python3.8/random.py'>

>>> random.random
<built-in method random of Random object at 0x1c87b80>

>>> random.random()
0.065572823274621

Here's a more germane example of omitting the parentheses:

>>> player = pygame.sprite.GroupSingle

>>> player
<class 'pygame.sprite.GroupSingle'>

>>> player.add(pygame.sprite.Sprite())

>>> for x in player.sprites():
...     print(type(x))
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: sprites() missing 1 required positional argument: 'self'

It's not exactly the same message that you have but that's probably because I'm not going through the full route that you do (i.e., through the sprite processing code).

The error shown is what you see when you call an object function through the class variable (no self is automatically added in that scenario).

I suspect that, based on the error you're seeing, the class object is being treated as an integer somewhere along that route, though I can't be certain of that without a deep dive into the pygame source code.

And, for completeness, this is an example with the parentheses, where you can see it working properly:

>>> player = pygame.sprite.GroupSingle()

>>> player.add(pygame.sprite.Sprite())

>>> for x in player.sprites():
...     print(type(x))
...
<class 'pygame.sprite.Sprite'>
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953