Pygame Sprite Drawing on Surface issue - python

The problem that I am encountering is that my source object is not a surface, and the problem is not in my code, but in the code that designed the sprites for pygame, and I don't know how to fix it. I'll put the code that I made in there anyway, in case it's the problem.
Error Message:
Traceback (most recent call last):
File "C:\Users\Daniel\Desktop\Thing.py", line 22, in <module>
level.run()
File "C:\Users\Daniel\Desktop\level2.py", line 63, in run
self.crate_sprites.draw(self.display_surface)
File "C:\Users\Daniel\AppData\Local\Programs\Python\Python310\lib\site-
packages\pygame\sprite.py", line 551, in draw
self.spritedict.update(zip(sprites, surface.blits((spr.surface, spr.rect)
for spr in sprites)))
TypeError: Source objects must be a surface
Code in sprites.py:
def draw(self, surface):
"""draw all sprites onto the surface
Group.draw(surface): return Rect_list
Draws all of the member sprites onto the given surface.
"""
sprites = self.sprites()
if hasattr(surface, "blits"):
self.spritedict.update(zip(sprites, surface.blits((spr.surface,
spr.rect) for spr in sprites)))
else:
for spr in sprites:
self.spritedict[spr] = surface.blit(spr.image, spr.rect)
self.lostsprites = []
dirty = self.lostsprites
Tiles:
class StaticTile(Tile):
def __init__(self, size, x, y, pos, surface):
super().__init__(size, x, y, (x,y))
self.image = surface
self.surface = surface
class Crate(StaticTile):
def __init__(self, size, x, y, pos, surface):
super().__init__(size, x, y,
pygame.image.load('C:\\desktop\\game\\crate.png').convert_alpha(), (x,
y))
Layout:
crate_layout = import_csv_layout(level_data['crates'])
self.crate_sprites = self.create_tile_group(crate_layout, 'crates')
Draw/Update:
self.crate_sprites.update(self.world_shift)
self.crate_sprites.draw(self.display_surface)

The signature of the constructor of StaticTile is:
def __init__(self, size, x, y, pos, surface):
So it needs to be called like this (in Crate. __init__)
super().__init__(size, x, y, pygame.image.load('C:\\desktop\\game\\crate.png').convert_alpha(), (x, y))
super().__init__(size, x, y, (x, y),
pygame.image.load('C:\\desktop\\game\\crate.png').convert_alpha())

Related

Pygame sprite group won't draw because of error: Source objects must be a suface

im having a problem with my 2D Platformer game and when trying to display sprite group, an error message appears saying: Pygame: TypeError: Source objects must be a surface, The problem seems to be around the coins sprite group when trying to draw it to the self.display_surface in my code here.
I would highly appreciate any help.
Here is my code for my level.py:
import pygame
from support import import_csv_layout, import_cut_graphics
from settings import tile_size
from tiles import Tile, StaticTile, Crate, AnimatedTile
class Level:
def __init__(self, level_data, surface):
# general setup
self.display_surface = surface
self.world_shift = -5
# 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
coins_layout = import_csv_layout(level_data['coins'])
self.coin_sprites = self.create_tile_group(coins_layout, 'coins')
def create_tile_group(self, layout, type):
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('../gfx/terrain/terrain_tiles.png')
tile_surface = terrain_tile_list[int(val)]
sprite = StaticTile(tile_size, x, y, tile_surface)
if type == 'grass':
grass_tile_list = import_cut_graphics('../gfx/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':
sprite = AnimatedTile(tile_size, x, y, '../gfx/coins/gold')
sprite_group.add(sprite)
return sprite_group
def run(self):
# run the entire game / level
# terrain
self.terrain_sprites.update(self.world_shift)
self.terrain_sprites.draw(self.display_surface)
# grass
self.grass_sprites.update(self.world_shift)
self.grass_sprites.draw(self.display_surface)
# crate
self.crate_sprites.update(self.world_shift)
self.crate_sprites.draw(self.display_surface)
# coins
self.coin_sprites.update(self.world_shift)
self.coin_sprites.draw(self.display_surface)
And Here is the tile.py file: AnimatedTile and SaticTile class:
class AnimatedTile(Tile):
def __init__(self, size, x, y, path):
super().__init__(size, x, y)
self.frames = import_folder(path)
self.frame_index = 0
self.image = self.frames[self.frame_index]
class StaticTile(Tile):
def __init__(self, size, x, y, surface):
super().__init__(size, x, y)
self.image = surface
Also import folder has a surface variable, so i can't tell if its in here:
surface_list = []
for _,__, image_files in walk(path):
for image in image_files:
full_path = path + '/' + image
image_surf = pygame.image.load(full_path).convert_alpha()
surface_list.append(image)
return surface_list
Crate:
class Crate(StaticTile):
def __init__(self, size, x, y):
super().__init__(size, x, y, pygame.image.load('../gfx/terrain/crate.png').convert_alpha())
offset_y = y + size
self.rect = self.image.get_rect(bottomleft = (x,offset_y))
The bug is in the code that creates the surface_list. You have to append image_surf to the surface_list, but not image:
surface_list.append(image)
surface_list.append(image_surf)

A variable which suppose to be pygame.Surface appears like a string

I just added a function to my code which should display an image from a directory. It requires an argument which specifies which window it displays it in. When I try to pass it in I receive the following error:
Traceback (most recent call last):
File "Pygame.py", line 122, in <module>
Player.load()
File "Pygame.py", line 74, in load
screen.blit(self.path, (self.x, self.y))
TypeError: argument 1 must be pygame.Surface, not str
My code:
import pygame
#init the pygame and create a screen
pygame.init()
screen = pygame.display.set_mode((1080,720))
done = False
#colours
blue = (0,0,255)
red = (255,0,0)
green = (0,255,0)
black = (0,0,0)
white = (255,255,255)
yellow = (255,255,0)
#path to the background
bg_path = "Racing.png"
#path to the car image
car_path = "car.png"
#starts the game clock
clock = pygame.time.Clock()
#opening bg image
background_image = pygame.image.load(bg_path).convert()
#class for all of the objects on the screen
class shape():
def __init__(self, place, x, y):
self.place = place
self.x = x
self.y = y
class rectangle(shape):
def __init__(self, place, colour, x, y, length, width):
super().__init__(place,x, y)
self.colour = colour
self.length = length
self.width = width
def draw(self):
pygame.draw.rect(screen, self.colour, pygame.Rect(self.x, self.y,
self.length, self.width))
def move_up(self):
self.y = self.y - 10
def move_down(self):
self.y = self.y + 10
def move_right(self):
self.x = self.x + 10
def move_left(self):
self.x = self.x - 10
class player(shape):
def __init__(self, place, x, y, length, width, path):
super().__init__(place,x, y)
self.length = length
self.width = width
self.path = path
def load(self):
screen.blit(self.path, (self.x, self.y))
Rectangle = rectangle(screen, yellow, 540, 660, 60, 60)
Player = player(screen, 540, 600, 60, 60, car_path)
Player.load()
This isn't all of the code but the rest isn't related to the problem (I think). Please tell me if more code is needed.
car_path is set as a string here
car_path = "car.png"
But blit() requires a pygame.Surface object for the first argument which pygame.image.load would give you, i.e.
car_path = pygame.image.load("car.png")
instead.

super() method initializes only some parameters

while programming a minigame, I stumbled across something I cant explain myself (fairly new to python).
This is my code:
class Block:
def __init__(self, x, y, hitpoints=1, color=(255, 0, 0), width=75, height=35):
self.color = color
self.x = x
self.y = y
self.height = height
self.width = width
self.hitpoints = hitpoints
class Ball(Block):
def __init__(self, x, y, size, color=(255, 255, 255), velocity=1):
super().__init__(self, x, y, color)
self.velocity = velocity
self.size = size
I initialize the object ball with
ball = Ball(x=200, y=200, size=30)
Problem arises when I call ball.x, as it returns
<Objects.Ball object at 0x00000249425A3508>.
If i call ball.y it works as intended and returns 200.
I can fix the whole problem by modifying the class Ball as follows:
class Ball(Block):
def __init__(self,x, y, size, color=(255, 255, 255), velocity=1):
super().__init__(self, y, color)
self.velocity = velocity
self.size = size
self.x = x
Can somebody explain to me why this happens?
Thanks alot!
You need to call super without self argument:
super().__init__(x, y, color=color)
This PEP explains how this works:
The new syntax:
super()
is equivalent to:
super(__class__, <firstarg>)
where __class__ is the class that the method was defined in, and
is the first parameter of the method (normally self for
instance methods, and cls for class methods).

Python: trouble inheriting from multiple parent classes

I have a subclass that needs to inherit from two parents differing only in methods and a single property. When instantiating this class, I'm getting an error stating I'm using to many parameters. When removing a param it says I'm not using enough.
Ball
class Ball:
"""
base class for bouncing objects
"""
def __init__(self, bounds, position, velocity, color, radius):
self.position = position
self.velocity = velocity
self.bounds = bounds
self.color = color
self.radius = radius
def update(self):
# bounce at edges. TODO: Fix sticky edges
if self.position.x < 0 + self.radius or self.position.x > self.bounds[0] - self.radius: # screen width
self.velocity.x *= -1
if self.position.y < 0 + self.radius or self.position.y > self.bounds[1] - self.radius: # screen height
self.velocity.y *= -1
self.position += self.velocity
def draw(self, screen, pygame):
# cast x and y to int for drawing
pygame.draw.circle(screen, self.color, [int(self.position.x), int(self.position.y)], self.radius)
BouncingBall
class BouncingBall(Ball):
"""
ball effected by gravity
"""
def __init__(self, bounds, position, velocity, color, radius, weight):
super().__init__(bounds, position, velocity, color, radius)
self.weight = weight
def update(self):
KineticBall
class KineticBall(Ball):
"""
A ball that collides with other collidable balls using simple elastic circle collision
"""
def __init__(self, bounds, position, velocity, color, radius, object_list):
super().__init__(bounds, position, velocity, color, radius)
self.object_list = object_list
KineticBouncing
class KineticBouncing(BouncingBall, KineticBall):
def __init__(self, bounds, position, velocity, color, radius, weight, object_list):
super().__init__(bounds, position, velocity, color, radius, weight, object_list)
ball => KineticBouncing
# super().__init__(bounds, position, velocity, color, radius, weight, object_list)
TypeError: __init__() takes 7 positional arguments but 8 were given
ball = KineticBouncing(SCREEN_SIZE, Vector2(50, 50), Vector2(3, 3), [255, 0, 255], 10, -1, object_list)
Lets try something else...
So this is confusing.. instead I find Python3 Multiple Inheritance which I'm sure will solve my problem. Just use the parents name + init instead of super() right?
KineticBouncing
class KineticBouncing(BouncingBall, KineticBall):
def __init__(self, bounds, position, velocity, color, radius, weight, object_list):
BouncingBall.__init__(self, bounds, position, velocity, color, radius, weight)
KineticBall.__init__(self, bounds, position, velocity, color, radius, object_list)
ball => KinetBouncing
#Traceback (most recent call last):
# File "draw.py", line 99, in <module>
# main()
# File "draw.py", line 61, in main
# debug_create_balls(object_list)
# File "draw.py", line 43, in debug_create_balls
# ball = KineticBouncing(SCREEN_SIZE, Vector2(50, 50), Vector2(3, 3), [255, 0, 255], 10, -1, object_list)
# File "/home/adam/Desktop/web_dev/lambda_school/python/Python-OOP-Toy/src/ball.py", line 115, in __init__
# BouncingBall.__init__(self, bounds, position, velocity, color, radius, weight)
# File "/home/adam/Desktop/web_dev/lambda_school/python/Python-OOP-Toy/src/ball.py", line 33, in __init__
# super().__init__(bounds, position, velocity, color, radius)
#TypeError: __init__() missing 1 required positional argument: 'object_list'
ball = KineticBouncing(SCREEN_SIZE, Vector2(50, 50), Vector2(3, 3), [255, 0, 255], 10, -1, object_list)
So how in the world am I supposed to inherit from these two parent classes?
As others have pointed out, you should redesign your use of classes a bit. The immediate problem is that super() resolves to the first parent class of the object in question (self), rather than the parent class of the __init__ method you're in at the time.
When you try to initialize your KineticBouncing object, you invoke KineticBouncing.super().__init__(). This explicitly calls the __init__ methods of its two parent classes. When it first calls the one in BouncingBall, the first active statement is
super().__init__(bounds, position, velocity, color, radius)
I believe that you expect this to call Ball.__init__; that's not how super works. Instead, it resolves super based on the object, which is of class KineticBouncing. So ... what is super to KineticBouncing? Note that between the two __init__ calls as you've written them, you have to be wrong at least once.
I'll leave you to read the links provided in the comments. These will help you think in terms of Python's inheritance structure. With what you've posted, I think you'll have little trouble handling the switch; you merely picked a hierarchy model from elsewhere.
Use *args in all your parent's init
class Ball(object):
def __init__(self, *args):
print ('IN Ball')
class BouncingBall(Ball):
"""
ball effected by gravity
"""
def __init__(self, bounds, position, velocity, color, radius, weight, *args):
print ('IN BouncingBall')
super().__init__(bounds, position, velocity, color, radius, *args)
self.weight = weight
class KineticBall(Ball):
"""
A ball that collides with other collidable balls using simple elastic circle collision
"""
def __init__(self, bounds, position, velocity, color, radius, object_list, *args):
print ('IN KineticBall')
super().__init__(bounds, position, velocity, color, radius, *args)
self.object_list = object_list
class KineticBouncing(BouncingBall, KineticBall):
def __init__(self, bounds, position, velocity, color, radius, weight, object_list):
print ('IN KineticBouncing')
super().__init__(bounds, position, velocity, color, radius, weight, object_list)
Now on creating a new kinetic bouncing ball
ball = KineticBouncing('SCREEN_SIZE', 'Vector2(50, 50)', 'Vector2(3, 3)', [255, 0, 255], 10, -1, 'object_list')
IN KineticBouncing
IN BouncingBall
IN KineticBall
IN Ball

AttributeError: object has no attribute rect

I get an error from my code and I don't know why, but syntactically, it's correct, I guess
import pygame
class BaseClass(pygame.sprite.Sprite):
allsprites = pygame.sprite.Group()
def __init__(self, x, y, width, height, image_string):
pygame.sprite.Sprite.__init__(self)
BaseClass.allsprites.add(self)
self.image = pygame.image.load("Images\lebron.png")
self.rectangle = self.image.get_rect()
self.rectangle.x = x
self.rectangle.y = y
self.width = width
self.height = height
class Lebron(BaseClass):
List = pygame.sprite.Group()
def __init__(self, x, y, width, height, image_string):
BaseClass.__init__(self, x, y, width, height, image_string)
Lebron.List.add(self)
self.velx = 0
def movement(self):
self.rectangle.x += self.velx
so if i compile this to my main file, code below
import pygame, sys
from classes import *
from process import process
pygame.init()
WIDTH, HEIGHT = 640, 360
screen = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()
FPS = 24
lebron = Lebron(0, 100, 104, 120, "Images\lebron.png")
while True:
process()
#LOGIC
lebron.movement()
#LOGIC
#DRAW
BaseClass.allsprites.draw(screen)
screen.fill((0,0,0))
pygame.display.flip()
#DRAW
clock.tick(FPS)
I get an error which is attribute error: Lebron object has no attribute rect. what's wrong? The traceback:
Traceback (most recent call last):
File "MAIN.py", line 24, in <module>
BaseClass.allsprites.draw(screen)
File "C:\Python27\lib\site-packages\pygame\sprite.py", line 409, in draw
self.spritedict[spr] = surface_blit(spr.image, spr.rect)
AttributeError: 'Lebron' object has no attribute 'rect'
It appears from the posted traceback (they look much better when you indent them by 4 spaces, and even better when you edit the question to include them) that the PyGame draw() method expects your attribute to provide a rect attribute. At a guess, if you replace every occurrence of rectangle with rect you will make at least some progress.
Basically, PyGame wants to draw your object but it can't find out how.

Categories