I am encountering a problem which says that the source object must be a surface. I asked this question before, and the answer worked, but now it's a different scenario. I looked at the previous question I had, and I couldn't figure out how to solve it.
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 131, in run
self.clouds.draw(self.display_surface, self.world_shift)
File "C:\Users\Daniel\Desktop\decoration.py", line 67, in draw
self.cloud_sprites.draw(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.image, spr.rect) for
spr in sprites)))
TypeError: Source objects must be a surface
Basically I am trying to blit things onto a surface, and the thing that confuses me the most is that it worked when I was trying to blit water onto the screen, it had the exact same code but instead of 'cloud' it had 'water', however the water blitting worked while the cloud did not.
Water Class:
class Water:
def __init__(self, top, level_width):
water_start = -width
water_tile_width = 192
tile_x_amount = int((level_width + width) / water_tile_width)
self.water_sprites = pygame.sprite.Group()
for tile in range(tile_x_amount):
x = tile * water_tile_width + water_start
y = top
sprite = AnimatedTile(192, x, y, (x, y),
'C:\\Desktop\\Game\\decoration\\water')
self.water_sprites.add(sprite)
def draw(self, surface, shift):
self.water_sprites.update(shift)
self.water_sprites.draw(surface)
Cloud Class:
class Clouds:
def __init__(self, horizon, level_width, cloud_number):
min_x = -width
max_x = level_width + width
min_y = 0
max_y = horizon
cloud_surface_list = 'C:\\Desktop\\Game\\decoration\\clouds'
self.cloud_sprites = pygame.sprite.Group()
for cloud in range(cloud_number):
cloud = choice(cloud_surface_list)
x = randint(min_x, max_x)
y = randint(min_y, max_y)
sprite = StaticTile(0, x, y, (x, y),
'C:\\Desktop\\Game\\decoration\\clouds')
self.cloud_sprites.add(sprite)
def draw(self, surface, shift):
self.cloud_sprites.update(shift)
self.cloud_sprites.draw(surface)
As you can see the draw method is the exact same code, pretty much. The only difference that I can spot that could be causing the problem would be the inheritance. Water inherits from a class called AnimatedTile while Clouds inherits from StaticTile. I will put the code for both of those here:
AnimatedTile:
class AnimatedTile(Tile):
def __init__(self, size, x, y, pos, path):
super().__init__(size, x, y, (x, y))
self.frames = import_folder(path)
self.frame_index = 0
self.image = self.frames[int(self.frame_index)]
def animate(self):
self.frame_index += 0.15
if self.frame_index >= 4:
self.frame_index = 0
self.image = self.frames[int(self.frame_index)]
def update(self, shift):
self.animate()
self.rect.x += shift
StaticTile:
class StaticTile(Tile):
def __init__(self, size, x, y, pos, surface):
super().__init__(size, x, y, (x,y))
self.image = surface
self.surface = surface
Drawing and Updating:
self.clouds.draw(self.display_surface,
self.world_shift)
*I know these are surfaces because I drew them the same exact way to blit the water onto the screen (self.water.draw(self.display_surface, self.world_shift) and it worked.
Other code:
class Level:
def __init__(self, level_data, surface):
self.display_surface = surface
self.world_shift = -8
Tile:
class Tile(pygame.sprite.Sprite):
def __init__(self, size, x, y, pos):
super().__init__()
self.image = pygame.Surface((size, size))
self.rect = self.image.get_rect(topleft = pos)
def update(self, shift):
self.rect.x += shift
world_shift is basically how fast the level is moving (negative means right, positive means left)
I'm sorry if this sounds stupid and the answer is obvious.
You're not correctly assigning to the image attribute of your StaticTile sprite in Clouds. I'm not sure if it's the caller (Clouds) or the tile class that is getting it wrong though, you'll have to decide what the API should be.
The mismatch is between your call, here:
sprite = StaticTile(0, x, y, (x, y),
'C:\\Desktop\\Game\\decoration\\clouds')
And the StaticTile.__init__ method, which is declared like this:
def __init__(self, size, x, y, pos, surface):
super().__init__(size, x, y, (x,y))
self.image = surface
self.surface = surface
Note that the calling code is passing a string as the surface argument. That's similar to how AnimatedTile is initialized (with a path), but the StaticTile class expects a surface object to already be loaded.
Related
This question already has answers here:
How do I rotate an image around its center using Pygame?
(6 answers)
Closed last year.
I have created a class for the zombie I blit into the screen, but the rotation does not work at all, it rotates a lot and moves the image around, is there some way to rotate the image around its center? how could I change the rotate def so that it works properly?
class zombieObj:
def __init__(self, x, y, vel, angle):
tempZombie = pygame.image.load('zombieSprite.png')
self.zombieSpriteSurface = pygame.transform.scale(tempZombie, (64, 64))
self.x = x
self.y = y
self.vel = 1
self.angle = angle
def rotate(self, image, angle):
self.zombieSpriteSurface = pygame.transform.rotate(image, angle)
and this is how I called it in the loop:
zombieSprite.angle = zombieSprite.angle + 5
zombieSprite.rotate(zombieSprite.zombieSpriteSurface, zombieSprite.angle)
See How do I rotate an image around its center using PyGame?. The trick is to get the center of the image before rotation and set it after rotation. Also, you need to rotate the original image to avoid distortion:
class ZombieObj:
def __init__(self, x, y, vel, angle):
tempZombie = pygame.image.load('zombieSprite.png')
self.originalImage = pygame.transform.scale(tempZombie, (64, 64))
self.vel = 1
self.angle = 0
self.rect = self.originalImage.get_rect(topleft = (x, y))
self.roatate(angle)
def rotate(self, angle):
self.angle = angle
self.zombieSpriteSurface = pygame.transform.rotate(self.originalImage, self.angle)
self.rect = self.zombieSpriteSurface.get_rect(center = self.rect.center)
If you want to increase the rotation by a certain angle, you need to add the additional angle to the current angle:
class ZombieObj:
# [...]
def rotate(self, deltaAngle):
self.angle += deltaAngle
self.zombieSpriteSurface = pygame.transform.rotate(self.originalImage, self.angle)
self.rect = self.zombieSpriteSurface.get_rect(center = self.rect.center)
I am using pytmx to load my map with pygame, but some items needs to be set as sprites and displayed above the surface, so the layering doesn't stay, I will show you 2 examples, one with the player displaying on top of decorations (where the point layer in under the decorations) the other one is the door displaying above the walls.
I tried to refresh my map with my player and door object by rewriting make_map() and render(surface) functions from Renderer, but instead of passing object I iterated threw them to blit the one it was, it has displaying, but still not layered, and also half FPS goes down.
Si if anyone knows how to be able to keep the layering even with player and other object used to render animated sprites I would be glad to get your help please.
This is the code I tried to use to refresh map every time with layering (it was displaying, but it has the exact same results as if I draw only the surface and then draw sprites above) :
def draw(self, display, camera = None):
self.surface = self.make_map()
self.rect = self.surface.get_rect()
if not camera:
display.blit(self.surface, self.rect)
else:
display.blit(self.surface, (self.rect.x - camera.offset.x, self.rect.y - camera.offset.y))
def render(self, surface):
tw = self.tmx_data.tilewidth
th = self.tmx_data.tileheight
if self.tmx_data.background_color:
surface.fill(self.tmx_data.background_color)
else:
surface.fill((0, 0, 0))
for layer in self.tmx_data.layers:
if isinstance(layer, pytmx.TiledTileLayer):
for x, y, image in layer.tiles():
if image:
surface.blit(image.convert_alpha() , (x * tw, y * th))
elif isinstance(layer, pytmx.TiledObjectGroup):
for tile_object in layer:
if tile_object.name == 'player':
surface.blit(self.level.game.player.image.convert_alpha(), (self.level.game.player.rect.x, self.level.game.player.rect.y))
if tile_object.name == 'door':
for door in self.level.game.doors:
if door.type == tile_object.type:
surface.blit(door.image.convert_alpha(), (door.rect.x, door.rect.y))
elif isinstance(layer, pytmx.TiledImageLayer):
if image:
surface.blit(image , (0, 0))
def make_map(self):
temp_surface = pygame.Surface(self.renderer.size)
self.render(temp_surface)
return temp_surface
So I have found a way to get what I wanted, I had used pyagme sprites groups layeredUpdates, I added to my original map only floor, I created layered sprites with my other layers (walls,decorations) and then displayed with the layer
The map rendering
def render(self, surface):
tw = self.tmx_data.tilewidth
th = self.tmx_data.tileheight
if self.tmx_data.background_color:
surface.fill(self.tmx_data.background_color)
else:
surface.fill((0, 0, 0))
for layer in self.tmx_data.layers:
if isinstance(layer, pytmx.TiledTileLayer):
for x, y, image in layer.tiles():
if image:
if layer.name == 'walls' or 'wall' in layer.name:
Wall(self.game, x * tw, y * th, image.convert_alpha())
elif 'decoration' in layer.name:
Decoration(self.game, x * tw, y * th, image.convert_alpha())
else:
surface.blit(image.convert_alpha() , (x * tw, y * th))
elif isinstance(layer, pytmx.TiledObjectGroup):
pass
elif isinstance(layer, pytmx.TiledImageLayer):
if image:
surface.blit(image , (0, 0))
def make_map(self):
temp_surface = pygame.Surface(self.size)
self.render(temp_surface)
return temp_surface
The objects :
class Decoration(pygame.sprite.Sprite):
def __init__(self, game, x, y, image, layer = DECORATION_TOP_LAYER):
self.groups = game.all_sprites, game.decorations
self._layer = layer
pygame.sprite.Sprite.__init__(self, self.groups)
class Wall(pygame.sprite.Sprite):
def __init__(self, game, x, y, image):
self.groups = game.all_sprites, game.walls
self._layer = WALL_LAYER
pygame.sprite.Sprite.__init__(self, self.groups)
class Door(pygame.sprite.Sprite):
def __init__(self, game, x, y, w, h, open_actions, animated_image, type):
self._layer = DOOR_LAYER
self.groups = game.all_sprites, game.doors
pygame.sprite.Sprite.__init__(self, self.groups)
class NPC(pygame.sprite.Sprite):
def __init__(self, game, x, y, w, h, animated_image):
self.groups = game.all_sprites, game.NPCs
self._layer = NPC_LAYER
pygame.sprite.Sprite.__init__(self, self.groups)
And the thing that makes all works together is simply :
self.all_sprites = pygame.sprite.LayeredUpdates()
I've made a Rectangle class:
class Rectangle(pygame.sprite.Sprite):
def __init__(self, len, x, y, color):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((len, len), flags=pygame.SRCALPHA)
# self.image.set_colorkey(Constants.WHITE)
self.image.fill(color)
self.rect = self.image.get_rect()
self.rect.center = (x + len / 2, y + len / 2)
self.is_blocked = False
self.x_pos = x
self.y_pos = y
self.x = math.floor((x - Constants.PADTOPBOTTOM) / Constants.CELL_SIZE)
self.y = math.floor((y - Constants.PADLEFTRIGHT) / Constants.CELL_SIZE)
def update(self):
if self.is_blocked:
print("is update working?") # it is working.
self.image.fill(Constants.GREEN)
And when update() is called I want to change the color of the rectangle to green but it doesn't work.
I'm new to pygame and I don't know what to do.
Your problem is that you keep creating new sprites every frame, so when you change the color of one Rectangle to green, it will be covered by a white one.
You do this by calling the fill function (which creates new Rectangle instances) inside the drawGrid function.
I'm making a very simple 2D game in pygame and I've made several files with classes in them that defines the characteristics of a few particles and how the terrain is going to look. The problem is that I can't get them to both show on the same surface. I've tried fiddling around with it but when I run it, the terrain ends up showing without the particles. The particles appears in a separate window only after I've exited the first window.
So my question is:
How do I show objects from different files on the same surface/screen with Pygame?
I'm using the pygame.draw function to draw the player and the particles.
Part of one of the classes:
class Volcano():
def __init__(self, x, y, size):
self.x = x
self.y = y
self.size = size
self.colour = (0, 0, 255)
self.thickness = 1
self.speed = 0
self.angle = 0
def display(self):
pygame.draw.circle(screen, self.colour, (int(self.x), int(self.y)), self.size, self.thickness)
The target Surface needs to be an argument of the draw method:
class Volcano():
# [...]
def display(self, surf):
pygame.draw.circle(surf, self.colour,
(int(self.x), int(self.y)), self.size, self.thickness)
Pass the screen Surface to the draw method when it is called:
volcano = Volcano(x, y, size)
volcano.draw(screen)
I'm writing a simple toy in Pygame. When you press a key on the home row, it does a little burst of particles.
class Particle():
x = 0
y = 0
size = 0
colour = (255, 255, 255)
rect = None
def __init__(self, x, y, size, colour):
self.x = x
self.y = y
self.size = size
self.colour = colour # Particle has its own colour
self.rect = pygame.Rect(self.x, self.y, self.size, self.size)
class Burst():
x = 0
y = 0
colour = (255, 255, 255)
count = 0
sound = None
particles = []
def __init__(self, x, y, colour, count, sound):
self.x = x
self.y = y
self.colour = colour # Burst has its own colour, too - all its particles should have the same colour as it
self.count = count
self.sound = sound
self.particles.append(Particle(self.x, self.y, 5, self.colour))
def update(self):
self.particles.append(Particle(random.randint(1, 30) + self.x, random.randint(1, 30) + self.y, 5, self.colour))
def draw(self):
global screen
for p in self.particles:
pygame.draw.rect(screen, p.colour, p.rect) # This draws the particles with the correct colours
#pygame.draw.rect(screen, self.colour, (60, 60, 120, 120), 4) # This draws the particles all the same colour
#screen.fill(p.colour, p.rect) # This draws the particles all the same colour
The line you're looking for is in Burst.draw. For some reason, only the uncommented one works correctly. The other two lines, which should be the same as far as I can tell, only draw the first burst's particles correctly. Any subsequent bursts change all particles onscreen to match their colour.
I can provide more code, but there's not much more to it. Basically keypresses add Bursts to an array, and every tick I step through that array calling update() and draw().
Does anyone know what I did wrong, and then accidentally fixed?
Because all particles in the screen belong to the same collection Burst.particles.
And every time you process a Burst you are processing all the particles, and all gets painted with the last colour.
Just move the initialization particles = [] to the init method.
def __init__(self, x, y, colour, count, sound):
...
self.particles = []
self.particles.append(Particle(self.x, self.y, 5, self.colour))
Update
You are using a Java/C# style of coding classes. You shouldn't put any of the initializations at the class level, unless they are constants or class attributes.
IE:
class Burst():
class_attribute = 0 # declaration of class (static) attribute
def __init__(self, ...):
self.attribute = 0 # declaration of object (regular) attribute
You shouldn't make class declarations of attribute you will use a object attributes.
Just remove all the declarations previous to the init method in both classes.