Pygame: Save a list of objects/classes/surfaces - python

I am working on a game, in which you can create mazes. You place blocks on a 16x16 grid, while choosing from a variety of block to make the level with. Whenever you create a block, it adds this class:
class Block(object):
def __init__(self,x,y,spr):
self.x=x
self.y=y
self.sprite=spr
self.rect=self.sprite.get_rect(x=self.x,y=self.y)
to a list called instances.
I tried shelving it to a .bin file, but it returns some error dealing with surfaces. How can I go about saving and loading levels?
Any help is appreciated! :)
Here is the whole code for reference:
import pygame
from pygame.locals import *
#initstuff
pygame.init()
screen=pygame.display.set_mode((640,480))
pygame.display.set_caption('PiMaze')
instances=[]
#loadsprites
menuspr=pygame.image.load('images/menu.png').convert()
b1spr=pygame.image.load('images/b1.png').convert()
b2spr=pygame.image.load('images/b2.png').convert()
currentbspr=b1spr
curspr=pygame.image.load('images/curs.png').convert()
curspr.set_colorkey((0,255,0))
#menu
menuspr.set_alpha(185)
menurect=menuspr.get_rect(x=-260,y=4)
class MenuItem(object):
def __init__(self,pos,spr):
self.x=pos[0]
self.y=pos[1]
self.sprite=spr
self.pos=(self.x,self.y)
self.rect=self.sprite.get_rect(x=self.x,y=self.y)
class Block(object):
def __init__(self,x,y,spr):
self.x=x
self.y=y
self.sprite=spr
self.rect=self.sprite.get_rect(x=self.x,y=self.y)
while True:
#menu items
b1menu=b1spr.get_rect(x=menurect.left+32,y=48)
b2menu=b2spr.get_rect(x=menurect.left+64,y=48)
menuitems=[MenuItem(b1menu,b1spr),MenuItem(b2menu,b2spr)]
screen.fill((20,30,85))
mse=pygame.mouse.get_pos()
key=pygame.key.get_pressed()
placepos=((mse[0]/16)*16,(mse[1]/16)*16)
if key[K_q]:
if mse[0]<260:
if menurect.right<255:
menurect.right+=1
else:
if menurect.left>-260:
menurect.left-=1
else:
if menurect.left>-260:
menurect.left-=1
for e in pygame.event.get():
if e.type==QUIT:
exit()
if menurect.right<100:
if e.type==MOUSEBUTTONUP:
if e.button==1:
to_remove = [i for i in instances if i.rect.collidepoint(placepos)]
for i in to_remove:
instances.remove(i)
if not to_remove:
instances.append(Block(placepos[0],placepos[1],currentbspr))
for i in instances:
screen.blit(i.sprite,i.rect)
if not key[K_q]:
screen.blit(curspr,placepos)
screen.blit(menuspr,menurect)
for item in menuitems:
screen.blit(item.sprite,item.pos)
if item.rect.collidepoint(mse):
if pygame.mouse.get_pressed()==(1,0,0):
currentbspr=item.sprite
pygame.draw.rect(screen, ((255,0,0)), item, 1)
pygame.display.flip()

You can't serialize/pickle/shelve pygame's Surface objects (at least not without a lot of effort). So the answer to your question is: just don't try to serialize your surfaces (it will just waste disk space anyway).
You could for example create a simple dict to store your surfaces, and let your classes just store the key, for example:
menuspr=pygame.image.load('images/menu.png').convert()
b1spr=pygame.image.load('images/b1.png').convert()
b2spr=pygame.image.load('images/b2.png').convert()
currentbspr=b1spr
curspr=pygame.image.load('images/curs.png').convert()
curspr.set_colorkey((0,255,0))
# create a dict to store all surfaces
surf_dict = {'b1spr': b1spr,
'b2spr': b2spr,
'currentbspr': currentbspr,
'curspr': curspr}
...
class Block(object):
def __init__(self,x,y,spr):
self.x=x
self.y=y
self.sprite=spr
# self.sprite is no longer a Surface, but a str
self.rect=surf_dict[self.sprite].get_rect(x=self.x,y=self.y)
...
...
# don't pass the surface to the Block, just the key
instances.append(Block(placepos[0],placepos[1], 'currentbspr'))
...
for i in instances:
# get the Surface from the dict, not from the instance itself
screen.blit(surf_dict[i.sprite],i.rect)
Now you can savely try to pickle/shelve all Block-instances (I see you have asked a related question here).

I found a way around it myself. I used python's built in open(fname,mode) to create a level file.
Whenever a block is created, it takes the sprite-name and coordinates of the block, and adds it to the save file in a .bin format:
f.write('Block('+str(placepos[0])+','+str(placepos[1])+',b1spr).')
Then I created a function to read this, and create the level accordingly:
def CreateLevel(levelname):
f=open(levelname,'r')
obj=f.read()
f.close()
obj=obj.split('.')
for b in obj:
instances.append(eval(b))
And it worked with flying colors!
Here is the whole code, and thank you guys for the help.
import pygame
from pygame.locals import *
#initstuff
pygame.init()
screen=pygame.display.set_mode((640,480))
pygame.display.set_caption('PiMaze')
instances=[]
level='save.bin'
#loadsprites
menuspr=pygame.image.load('images/menu.png').convert()
b1spr=pygame.image.load('images/b1.png').convert()
b2spr=pygame.image.load('images/b2.png').convert()
b3spr=pygame.image.load('images/b3.png').convert()
currentbspr=b1spr
curspr=pygame.image.load('images/curs.png').convert()
curspr.set_colorkey((0,255,0))
#menu
menuspr.set_alpha(185)
menurect=menuspr.get_rect(x=-260,y=4)
class MenuItem(object):
def __init__(self,pos,spr):
self.x=pos[0]
self.y=pos[1]
self.sprite=spr
self.pos=(self.x,self.y)
self.rect=self.sprite.get_rect(x=self.x,y=self.y)
class Block(object):
def __init__(self,x,y,spr):
self.x=x
self.y=y
self.sprite=spr
self.rect=self.sprite.get_rect(x=self.x,y=self.y)
def CreateLevel(levelname):
f=open(levelname,'r')
obj=f.read()
f.close()
obj=obj.split('.')
for b in obj:
instances.append(eval(b))
try:
CreateLevel(level)
except:
pass
f=open(level,'a+')
while True:
#menu items
b1menu=b1spr.get_rect(x=menurect.left+32,y=48)
b2menu=b2spr.get_rect(x=menurect.left+64,y=48)
b3menu=b3spr.get_rect(x=menurect.left+96,y=48)
menuitems=[MenuItem(b1menu,b1spr),MenuItem(b2menu,b2spr),MenuItem(b3menu,b3spr)]
screen.fill((20,30,85))
mse=pygame.mouse.get_pos()
key=pygame.key.get_pressed()
placepos=((mse[0]/16)*16,(mse[1]/16)*16)
if key[K_q]:
if mse[0]<260:
if menurect.right<255:
menurect.right+=1
else:
if menurect.left>-260:
menurect.left-=1
else:
if menurect.left>-260:
menurect.left-=1
for e in pygame.event.get():
if e.type==QUIT:
f.close()
exit()
if menurect.right<100:
if key[K_LSHIFT]:
if pygame.mouse.get_pressed()==(1,0,0):
to_remove = [i for i in instances if i.rect.collidepoint(placepos)]
if not to_remove:
instances.append(Block(placepos[0],placepos[1],currentbspr))
f.write('Block('+str(placepos[0])+','+str(placepos[1])+',b1spr).')
to_remove = []
if not key[K_LSHIFT] or key[K_RSHIFT]:
if e.type==MOUSEBUTTONUP:
if e.button==1:
to_remove = [i for i in instances if i.rect.collidepoint(placepos)]
for i in to_remove:
instances.remove(i)
if not to_remove:
instances.append(Block(placepos[0],placepos[1],currentbspr))
f.write('Block('+str(placepos[0])+','+str(placepos[1])+',b1spr).')
if key[K_RSHIFT]:
if pygame.mouse.get_pressed()==(1,0,0):
to_remove = [i for i in instances if i.rect.collidepoint(placepos)]
for i in to_remove:
instances.remove(i)
to_remove=[]
for i in instances:
screen.blit(i.sprite,i.rect)
if not key[K_q]:
screen.blit(curspr,placepos)
screen.blit(menuspr,menurect)
for item in menuitems:
screen.blit(item.sprite,item.pos)
if item.rect.collidepoint(mse):
if pygame.mouse.get_pressed()==(1,0,0):
currentbspr=item.sprite
pygame.draw.rect(screen, ((255,0,0)), item, 1)
pygame.display.flip()

Related

Pygame segmentation fault with import depending on position in code?

I know these segmentation fault questions are common, but I couldn't find answer and I am pulling my hair out as the code works without issue in some parts of the code, but not others or in some cases it works the first time then, the next day, it falls over.
Basically, I am importing spritesheets for a simply pygame game. With help, I wrote a small class which allows spritesheets to be imported and, through a few methods turns them into a list using the json file. That program is as follows:
import pygame
import json
pygame.init()
class Spritesheet:
def __init__(self, filename):
self.filename = filename
self.sprite_sheet = pygame.image.load(filename).convert()
self.data_addr = self.filename.replace('png', 'json')
with open(self.data_addr, encoding = 'utf8') as f:
self.data = json.load(f)
f.close()
def check_file(self):
print(self.data)
def get_sprite(self, x, y, w, h):
sprite = pygame.Surface((w, h))
sprite.set_colorkey((0,0,0))
sprite.blit(self.sprite_sheet,(0, 0),(x, y, w, h))
return sprite
def parse(self, index):
sprite = self.data['frames'][index]['frame']
x, y, w, h = sprite["x"], sprite["y"], sprite["w"], sprite["h"]
image = self.get_sprite(x, y, w, h)
return image
def dict_len(self):
length = len(self.data['frames'])
return length
This has worked fine in the main code. I have a player class, an enemy class etc and, upon initiallising, this class is called to create a list of pngs for the graphics (that I can step through etc.) A pertinent cut of the player class is as follows:
class Player(pygame.sprite.Sprite):
def __init__(self, player_posx, health):
super().__init__()
#right facing sprites
self.spritesr = Spritesheet('../graphics/player/players.png')
self.player_spritesr = []
for value in range(self.spritesr.dict_len()):
self.player_spritesr.append(self.spritesr.parse(value))
# left facing sprites
self.spritesl = Spritesheet('../graphics/player/playersl.png')
self.player_spritesl = []
for value in range(self.spritesl.dict_len()):
self.player_spritesl.append(self.spritesl.parse(value))
#static sprites
self.sprites_static = Spritesheet('../graphics/player/player_static.png')
self.player_static = []
for value in range(self.sprites_static.dict_len()):
self.player_static.append(self.sprites_static.parse(value))
self.image_index = 0
self.image = self.player_spritesr[self.image_index]
self.rect = self.image.get_rect()
In this class it works fine with the Spritesheets class. However, when I decided to create new file to import all the graphics in a separate file, I get the segmentation error. The code is simply:
from spritesheets import Spritesheet
test = Spritesheet('../graphics/player/playersl.png')
Calling exactly the same class with the same argument, but I get an error saying:
Fatal Python error: (pygame parachute) Segmentation Fault
Python runtime state: initialized
I really don't understand at all, especially why it runs fine in one file but falls over everywhere else.
The likely problem is that you are doing your import before you have called pygame.init(), so pygame has not been initialized.
Consider doing this in your second file:
from spritesheets import Spritesheet
def get_sprites():
return [
Spritesheet('../graphics/player/players.png'),
Spritesheet('../graphics/player/playersl.png'),
Spritesheet('../graphics/player/players_static.png')
]
Now you can put your import at the top, and call
sprites = xxx.get_sprites()
after you have initialized pygame.

expected argument in a class

hello I created a class enemy
class Enemy(object):
def __init__(self):
self.hauteur = 249
self.largeur = 353
self.enemyx = 250
self.enemyy=250
self.dead=0
def montre(self,win):
if self.dead==1:
win.blit(enemydead, (self.enemyx,self.enemyy))
else:
win.blit(poulpe,(self.enemyx, self.enemyy))
pygame.draw.rect(win,(255,0,0),(self.enemyx,self.enemyy, self.largeur, self.hauteur),2)
but when I call it in my my loop that draw the images
def images():
win.blit(bg, (0, 0))
win.blit(capa, (854, 449))
character.walk(win)
Enemy.montre(win)
character.shoot(win)
I just put 'win' but I then get an error saying that I am missing the 'win' argument. This is the second class that I made and in the first one (character) it is working fine.
You have to construct an Instance Objects of the Enemy class:
my_enemy = Enemy()
Use the instance to invoke montre. Either
my_enemy.montre(win)
or
Enemy.montre(my_enemy, win)

Why "NameError: name 'draw_Objects' is not defined" occurs

I am making a small game using pygame.
I have defined a function called draw_Objects() but Name Error occurs as if I didn't define the function. What should I do?
The code is part of the module
I have tried to change the draw_Objects() function but I couldn't solve it.
import pygame
from const import *
class item(pygame.sprite.Sprite):
def __init__(self,game):
super().__init__()
self.game=game
#self.screen=screen
self.width=WIDTH
self.height=HEIGHT
#potion 사진 불러옴
self.image=pygame.image.load("tile/red-potion.png")
self.rect = self.image.get_rect()
self.red_potion=pygame.image.load("tile/red-potion.png")
self.blue_potion=pygame.image.load("tile/blue-potion.png")
self.mask=pygame.mask.from_surface(self.image)
self.rect = self.image.get_rect()
redItem=[50,530,300,50,850,100,600,280,800,400]
def __call__(self):
print (" ")
def item_display(self,screen):
#아이템 화면에 표시
screen.blit(self.red_potion,(50,530)) #red1
screen.blit(self.blue_potion,(30,530))
screen.blit(self.blue_potion,(30,460))
screen.blit(self.red_potion,(300,50)) #red2
screen.blit(self.blue_potion,(400,150))
screen.blit(self.image,(850,100)) #red3
screen.blit(self.red_potion,(600,280)) #red4
screen.blit(self.red_potion,(800,400)) #red5
screen.blit(self.blue_potion,(600,370))
def draw_Objects(myList=[]):
for i in myList:
x=myList[i]
y=myList[i+1]
screen.blit(self.red_potion,(x,y))
i+=1
def item_eat(self,screen):
item__=item(self)
#red2 item
self.rect.x=300
self.rect.y=50
#item과 player 충돌 검사
hits_item=pygame.sprite.spritecollide(self,self.game.player_group,False,pygame.sprite.collide_mask)
if hits_item :
screen.blit(self.red_potion,(50,530)) #red1
screen.blit(self.blue_potion,(30,530))
screen.blit(self.blue_potion,(30,460))
screen.blit(self.blue_potion,(400,150))
screen.blit(self.red_potion,(850,100)) #red3
screen.blit(self.red_potion,(600,280)) #red4
screen.blit(self.red_potion,(800,400)) #red5
screen.blit(self.blue_potion,(600,370))
else:
#item__.item_display(self.screen)
draw_Objects(redItem)
this line
draw_Objects(redItem)
is looking for a global function named draw_Objects which does not exist. you need to call
self.draw_Objects(redItem)
However, you would also need to include self as a parameter in the function definition like so
def draw_Objects(self, myList=[]):
draw_Objects is not a global function, it is an attribute of the class and can be called with self.
def item_eat(self,screen):
item__=item(self)
#red2 item
self.rect.x=300
self.rect.y=50
#item과 player 충돌 검사
hits_item=pygame.sprite.spritecollide(self,self.game.player_group,False,pygame.sprite.collide_mask)
if hits_item :
screen.blit(self.red_potion,(50,530)) #red1
screen.blit(self.blue_potion,(30,530))
screen.blit(self.blue_potion,(30,460))
screen.blit(self.blue_potion,(400,150))
screen.blit(self.red_potion,(850,100)) #red3
screen.blit(self.red_potion,(600,280)) #red4
screen.blit(self.red_potion,(800,400)) #red5
screen.blit(self.blue_potion,(600,370))
else:
#item__.item_display(self.screen)
self.draw_Objects(redItem) # Change this line to reference draw_Objects
It's rather simple.
def draw_Objects(self):
myList=[]
for i in myList:
x=myList[i]
y=myList[i+1]
screen.blit(self.red_potion,(x,y))
i+=1
Then call it like:
self.draw_Objects()

Updating object on Python < arcade library >

I'm currently coding in some assignment.
I create a class named Ship and created some methods to make the object class ship be able to move.
class Ship():
def __init__(self):
self.center.x = 400
self.center.y = 300
self.velocity.dx = .25
self.velocity.dy = .25
def advance_down(self):
self.center.y = self.center.y - self.velocity.dy
This is my code
def check_keys(self):
"""
This function checks for keys that are being held down.
You will need to put your own method calls in here.
"""
if arcade.key.DOWN in self.held_keys:
self.ship1.advance_down()
advance down just changes the position
def on_key_press(self, key: int, modifiers: int):
"""
Puts the current key in the set of keys that are being held.
You will need to add things here to handle firing the bullet.
"""
if key == arcade.key.DOWN:
self.held_keys.add(key)
This adds the current key being pressed to the set of self.held_keys.
I'm calling an update of the positon.
def update(self, delta_time):
"""
Update each object in the game.
:param delta_time: tells us how much time has actually elapsed
""
self.ship1.advance_down()
when I run the code I'm able to display everything just fine but it's not doing anything once I press my keys.
Any ideas why?

Object not behaving correctly

I'm using Livewires and pygame and one of my objects in the game that gives you extra lives is being mistaken as an asteroid object, and when the extra lives objects collides with the player it returns the 'Extra lives object has no attribute handle_caught' error message, so can I please have some help.
class Extralives(games.Sprite):
global lives
image = games.load_image('lives.png', transparent = True)
speed = 2
def __init__(self,x,y = 10):
""" Initialize a asteroid object. """
super(Extralives, self).__init__(image = Extralives.image,
x = x, y = y,
dy = Extralives.speed)
def update(self):
""" Check if bottom edge has reached screen bottom. """
if self.bottom>games.screen.height:
self.destroy()
self.add_extralives
def add_extralives(self):
lives+=1
The asteroid class:
class Asteroid(games.Sprite):
global lives
global score
"""
A asteroid which falls through space.
"""
image = games.load_image("asteroid_med.bmp")
speed = 1.7
def __init__(self, x,image, y = 10):
""" Initialize a asteroid object. """
super(Asteroid, self).__init__(image = image,
x = x, y = y,
dy = Asteroid.speed)
def update(self):
""" Check if bottom edge has reached screen bottom. """
if self.bottom>games.screen.height:
self.destroy()
score.value+=10
def handle_caught(self):
if lives.value>0:
lives.value-=1
self.destroy_asteroid()
if lives.value <= 0:
self.destroy_asteroid()
self.end_game()
def destroy_asteroid(self):
self.destroy()
part of the player class which handles the collisions:
def update(self):
""" uses A and D keys to move the ship """
if games.keyboard.is_pressed(games.K_a):
self.x-=4
if games.keyboard.is_pressed(games.K_d):
self.x+=4
if self.left < 0:
self.left = 0
if self.right > games.screen.width:
self.right = games.screen.width
self.check_collison()
def ship_destroy(self):
self.destroy()
def check_collison(self):
""" Check if catch pizzas. """
global lives
for asteroid in self.overlapping_sprites:
asteroid.handle_caught()
if lives.value <=0:
self.ship_destroy()
for extralives in self.overlapping_sprites:
extralives.add_extralives()
Here is your problem:
for asteroid in self.overlapping_sprites:
asteroid.handle_caught()
if lives.value <=0:
self.ship_destroy()
The fact that you call your loop variable asteroid does not mean that it's magically only going to ever be an asteroid. Not if you have other kinds of objects you can collide with! overlapping_sprites is all overlapping sprites, not just asteroids. At some point asteroid is an ExtraLives object. When you try to call handle_caught() on it, this obviously fails because ExtraLives doesn't have a handle_caught() method.
The simplest solution here is to rename add_extralives to handle_caught on your ExtraLives class. After all, you're doing the same thing: handling the situation where you collide with (or "catch") the object, it's just a different kind of object so the result needs to be different, which you specify by providing different code. Being able to implement entirely different kinds of behavior by calling the same methods (called "polymorphism") is kinda the whole point of object-oriented programming.
The following loop has a similar problem, in that you're calling add_extralives() on objects that might not be of type ExtraLives. Fortunately you can remove this code since you're already handling this situation by renaming add_extralives to handle_caught.
for extralives in self.overlapping_sprites:
extralives.add_extralives()

Categories