Cutting through all the unrelated parts this is how I implemented it:
done=False
while(done==False)
#here goes some code for listening to the events
if (some condition fulfilled) then
screen.blit(background, [0,0])
screen.blit(some_sprite,[x,y])
elif (more conditions)
do something else
else
do even more stuff
pygame.display.flip()
Without the background update within this conditional statement this sprite doesn't get deleted, of course, os I get multiple copies on the screen. I have a strong suspicion this is by far not the optimal way of handling the situation, because blitting the image that doesn't change every time I need to do something else seems like a waste of resources.
I would appreciate any suggestions
Here's what I would recommend, based on personal experience. I built a tile-based game and over-engineered it a bit. My final solution looked a bit like this:
class Graphic(object):
def __init__(*some_args):
self.owner = which_object_owns_this_graphic
self.priority = some_int
# if more than one graphic are stacked on each other, which one to display?
self.surface = surface_to_draw_on
self.graphic = sprite_to_draw
#property
def x(self): return self.owner.x
#property
def y(self): return self.owner.y
def draw(self):
self.surface.blit(self.graphic, (self.x, self.y))
class Tile(object):
def __init__(*some_args):
self.x = x
self.y = y
self.graphic = some_default_Graphic_with_priority_minusone
self.contains = [self]
# list of objects that live here right now
#property
def topmost(self):
"""returns the graphic of the object with highest priority that is contained here"""
global_update_list = []
# anytime a tile is moved into or out of, place it here
Then in my event loop:
for tile in global_update_list:
tile.topmost.draw()
global_update_list = []
That prevented me from having to redraw the screen every time something moved, and I could just redraw the tile it moved out of and the tile it moved into.
Related
tl;dr at the bottom. Most of this post is to give context to my problem. If that isn't needed, my main question is down below.
I am currently working on a pygame project (action adventure game) and I currently am working on stuff relating to object persistence and room transitioning. Basically, I want to create a system where I have a dictionary that contains all of the information about a particular room including the images it uses and the objects that are in that room.
My game has a dictionary that contains lists of the current objects that are updating and doing stuff in my game. Let's call this instances. Anytime I add an object to instances, it will appear in game. With that in mind, lets consider how loading the objects in a particular room actually works.
The way my system works is that I have a variable that is called room which contains a string that represents what room I am currently in. I have another dictionary that contains all of the objects within a room. Lets call this room_dict. room_dict would have the keys "objects":[obj1,obj2]. So based on the current value of room, it can access certain objects (i.e,room_dict[room]["objects"] would return a list of all of the objects in the current room).
Now that I've explained the basics of how it works, I have a method that actually knows when I have triggered a room transition (or rather, when the value of room is changed). When this happens, all of the objects existing in the room (that I was just in) are cleared from the instances dictionary. All of the objects from room_dict[room]["objects"] are added to instances so that they now appear in the game. Makes sense so far, right?
The main problem with this is that when objects in the instances dictionary (objects that are currently loaded) are updating, the objects that are in room_dict[room]["objects"] are also updated as well. This means that if I change the position of an enemy in one room and then leave the room and return, the object will be created at that position instead of the original position. So I tried doing instances[list_of_enemies].append(copy.copy(enemy_object)) to add a copy of the object as well. This still didn't work though, and when I tried doing a copy.deepcopy(), the interpreter said that it was unable to serialize the object because one of its attibutes was a pygame.Surface object.
So in other words my main issue is that I want to make a copy of an object that contains a pygame.Surface as its attribute that doesn't reference the original object at all. How would I go about making a deepcopy with an object that has a pygame.Surface type attribute?
tl;dr: I want to make a copy of an object that has an attribute that is a pygame.Surface object but copy.deepcopy() doesn't work. Is there any other way to copy without referencing the original object?
EDIT: Forgot to mention that the project is rather hefty, so it would be quite difficult to give code for context. I personally don't think it is needed, but I thought I'd put this out anyways. Thanks everyone!
One way to solve this is to create your objects from your json file everytime you need a new copy instead of creating the objects beforehand.
Or you could change your copy method: implement __deepcopy__ and/or __copy__ and copy the attributes of your objects without the image attribute, maybe just create a new instance.
Simple example:
import pygame
from copy import deepcopy
from itertools import cycle
# an implementation of something in our game
class Cloud(pygame.sprite.Sprite):
def __init__(self, pos, speed):
super().__init__()
self.pos = pos
self.speed = speed
self.image = pygame.Surface((50, 20))
self.image.set_colorkey((11, 12, 13))
self.image.fill((11, 12, 13))
pygame.draw.ellipse(self.image, 'white', self.image.get_rect())
self.rect = self.image.get_rect(topleft=self.pos)
def update(self):
super().update()
self.rect.move_ip(self.speed, 0)
if not pygame.display.get_surface().get_rect().colliderect(self.rect):
self.rect.right = 0
def __deepcopy__(self, memo):
# just create a new instance
return Cloud(self.pos, self.speed)
# the definition of our game world
game_data = {
'WORLD_A': {
'color': 'lightblue',
'objects': pygame.sprite.Group(Cloud((50, 50), 1))
},
'WORLD_B': {
'color': 'red',
'objects': pygame.sprite.Group(Cloud((100, 100), 2), Cloud((80, 30), 3))
},
}
screen = pygame.display.set_mode((300, 300))
clock = pygame.time.Clock()
keys = cycle(game_data.keys())
# happy deepcopying
current = deepcopy(game_data[next(keys)])
while True:
for e in pygame.event.get():
if e.type == pygame.QUIT:
quit()
if e.type == pygame.KEYDOWN:
# happy deepcopying
current = deepcopy(game_data[next(keys)])
screen.fill(current['color'])
current['objects'].update()
current['objects'].draw(screen)
pygame.display.flip()
clock.tick(30)
Another solution is to lazy load the images and look them up only when needed so you don't need to copy them. Here's a simple example:
... see example above ...
# load/create all images once and store them in a dict
def create_cloud_image():
image = pygame.Surface((50, 20))
image.set_colorkey((11, 12, 13))
image.fill((11, 12, 13))
pygame.draw.ellipse(image, 'white', image.get_rect())
return image
images = {
'cloud': create_cloud_image()
}
# a simple sprite that lazy loads its image
class CopyableActor(pygame.sprite.Sprite):
def __init__(self, image_key, pos):
super().__init__()
self.pos = pos
self.image_key = image_key
def init_image(self):
self.image = images['cloud']
self.rect = self.image.get_rect(topleft=self.pos)
def update(self):
if not hasattr(self, 'image'):
self.init_image()
# an implementation of something in our game
class Cloud(CopyableActor):
def __init__(self, pos, speed):
super().__init__('cloud', pos)
self.speed = speed
def update(self):
super().update()
self.rect.move_ip(self.speed, 0)
if not pygame.display.get_surface().get_rect().colliderect(self.rect):
self.rect.right = 0
... see example above ...
I want to build some visualizations for searching algorithms (BFS, A* etc.) within a grid.
My solution should show each step of the algorithm using CodeSkulptor simplegui (or the offline version using SimpleGUICS2Pygame.)
I've made a version which highlights all the cells visited by changing their color, but I've run into trouble trying to make the path display step-by-step with a time delay between each step.
I've extracted the essence of the problem and created a minimal example representing it in the code below, also run-able online here: http://www.codeskulptor.org/#user47_jB2CYfNrH2_2.py
What I want is during the change_colors() function, for there to be a delay between each iteration.
CodeSkulptor doesn't have time.sleep() available, and I don't think it would help anyway.
CodeSkulptor does have timers available, which might be one solution, although I can't see how to use one in this instance.
Code below:
import time
try:
import simplegui
except ImportError:
import SimpleGUICS2Pygame.simpleguics2pygame as simplegui
simplegui.Frame._hide_status = True
TITLE = "TEST"
FRAME_WIDTH = 400
FRAME_HEIGHT = 400
DELAY = 10
class Square:
"""This class represents a simple Square object."""
def __init__(self, size, pos, pen_size=2, pen_color="red", fill_color="blue"):
"""Constructor - create an instance of Square."""
self._size = size
self._pos = pos
self._pen_size = pen_size
self._pen_color = pen_color
self._fill_color = fill_color
def set_color(self, color):
self._fill_color = color
def get_color(self):
return self._fill_color
def is_in(self, pos):
"""
Determine whether coordinates are within the area of this Square.
"""
return self._pos[0] < pos[0] < self._pos[0] + self._size and self._pos[1] < pos[1] < self._pos[1] + self._size
def draw(self, canvas):
"""
calls canvas.draw_image() to display self on canvas.
"""
points = [(self._pos[0], self._pos[1]), (self._pos[0] + self._size, self._pos[1]),
(self._pos[0] + self._size, self._pos[1] + self._size), (self._pos[0], self._pos[1] + self._size)]
canvas.draw_polygon(points, self._pen_size, self._pen_color, self._fill_color)
def __str__(self):
return "Square: {}".format(self._pos)
def draw(canvas):
for square in squares:
square.draw(canvas)
def change_colors():
for square in squares:
# time.sleep(1) # Not implemented in CodeSkulptor and would'nt work anyway
square.set_color("green")
frame = simplegui.create_frame(TITLE, FRAME_WIDTH, FRAME_HEIGHT)
frame.set_draw_handler(draw)
width = 20
squares = []
for i in range(10):
squares.append(Square(width, (i * width, 0)))
change_colors()
frame.start()
Any help appreciated.
Yes, you need to use a timer. Something like this:
I = 0
def change_next_color():
if I < len(squares):
squares[I].set_color("green")
global I
I += 1
timer = simplegui.create_timer(1000, change_next_color)
timer.start()
http://www.codeskulptor.org/#user47_udyXzppCdw2OqdI.py
I also replaced
simplegui.Frame._hide_status = True
by simplegui.Frame._hide_controlpanel = True
https://simpleguics2pygame.readthedocs.io/en/latest/simpleguics2pygame/frame.html#SimpleGUICS2Pygame.simpleguics2pygame.frame.Frame._hide_controlpanel
See also _keep_timers option of SimpleGUICS2Pygame to help you:
https://simpleguics2pygame.readthedocs.io/en/latest/simpleguics2pygame/frame.html#SimpleGUICS2Pygame.simpleguics2pygame.frame.Frame._keep_timers
Possible improvements:
Find a better solution that don't use a global counter.
Stop timer when all work is finished.
as discussed in the title I am having issues with masking identical images.
#initalising the masks
Invader1= pygame.image.load('Space_invaders_character_1_1.png').convert_alpha()
Invader1= pygame.transform.scale(Invader11, (40,30))
Invader1_mask = pygame.mask.from_surface(Invader11)
Invader1_mask= Invader11_mask.scale((70,40))
Invader2= pygame.image.load('Space_invaders_character_2_1.png').convert_alpha()
Invader2= pygame.transform.scale(Invader21, (40,30))
Invader2_mask = pygame.mask.from_surface(Invader21)
Invader2_mask= Invader11_mask.scale((70,40))
Invader3= pygame.image.load('Space_invaders_character_3_1.png').convert_alpha()
Invader3= pygame.transform.scale(Invader31, (40,30))
Invader3_mask = pygame.mask.from_surface(Invader31)
Invader3_mask= Invader11_mask.scale((70,40))
#drawing characters
def drawEnemies (invX,invY):
for num in range (1,11):
invX = invX + 50
gameDisplay.blit(Invader32, (invX,invY))
gameDisplay.blit(Invader32, (invX,invY-50))
gameDisplay.blit(Invader22, (invX,invY-100))
gameDisplay.blit(Invader22, (invX,invY-150))
gameDisplay.blit(Invader12, (invX, invY -200))
while lives > 0:
offset = (bulletX -invX, bulletY - invY)
result = Invader11_mask.overlap(bullet_mask, offset)
Of course this isn't all my code, however, I hope you see what I am attempting to do. In essence I am attempting to loop to create a specific Invader (yes from space invaders), however, the masks are either not being created with the other invaders or aren't moving. Can someone please help me?
Thanks.
The meaningful answer to your problem is to stop what your doing right now and start using the Sprite and Group classes together with the collide_mask function.
You don't want to create several global variables for each thingy in your game. You want instances of classes (you usually use Sprite), and add them to a list (usually a Group).
So, create a class for your invaders that inherits from Sprite and give them a mask attribue, something like this:
class Invader(pygame.spriteSprite):
def __init__(self, image, pos):
super().__init__()
self.image = image
self.rect = image.get_rect(topleft=pos)
self.mask = pygame.mask.from_surface(image)
def update(self):
pass # handle movement
Create a Group for your bullets and one for your invaders, then you can check the collision with:
pygame.sprite.groupcollide(bullets, invaders, True, True, pygame.sprite.collide_mask)
I'm in the process of creating a basic game in pygame at the moment, and one part of that is the procedural generation of new areas as you go off the screen. As a test, I'm looking to generate an object once per area by defining its variables, and then save that area's object within the class for if you come back to it later. Here's what I have at the moment:
#area_gen is set to "true" if you move to a new area
#swidth and sheight are set to the size of the screen
#x_area and y_area are defined as you change areas, acting as sector coordinates
#Red is defined in globals
class areas:
def __init__(self, coords):
self.coordinates = coords
self.generated = False
def gen_objects(self):
if not self.generated:
self.objs = []
obj_type = "test object"
center_x = random.randrange(105, swidth-25)
center_y = random.randrange(25, swidth - 175)
self.objs.append([obj_type, center_x, center_y])
self.generated = True
#Within The Game Loop
if area_gen == "true":
coords = str(str(x_area) + " " + str(y_area))
area = areas(coords)
area.gen_objects()
for thing in area.objs:
if thing[0] == "test object":
pygame.draw.rect(screen, Red, (thing[1], thing[2], 250, 250))
#Bottom of the Game Loop
area_gen = "false"
What I thought the self.generated variable would do was stop the new object generation if one already existed, but that doesn't seem to be working. The square still generates at a new location even if the area has already been visited. My knowledge on classes is relatively limited, so I'm a bit stuck as to where to go from here.
Pass area_gen into the constructor for the areas class, and set self.generated = area_gen. Does this work? I can't see enough of your code to know, to be honest.
I've made a simple game using pygame and livewires, where a sprite has to avoid falling mushrooms. The number of mushrooms falling at a certain time is meant to increase as the score increases. Here is what I mean:
from livewires import games,color
import random
games.init(screen_width=633,screen_height=479,fps=50)
class Stick_Man(games.Sprite):
def update(self):
self.x=games.mouse.x
if self.left<0:
self.left=0
if self.right>games.screen.width:
self.right=games.screen.width
self.check_collision()
def check_collision(self):
if self.overlapping_sprites:
self.over_message()
def over_message(self):
b=games.Message(value="Game Over", size=100, color=color.red,x=games.screen.width/2,y=games.screen.height/2,lifetime=250,after_death=games.screen.quit)
games.screen.add(b)
class Mushroom(games.Sprite):
score=0
start=200
score_required=100
level=1
total_score=0
speed=1
mushroom=games.load_image("mushroom.jpg")
x_position=random.randrange(640)
#staticmethod
def next_level():
indicate='Level ', + Mushroom.level, ' cleared'
message=games.Message(value=indicate,size=50,color=color.red,x=games.screen.width/2,y=games.screen.height/2, lifetime=150)
games.screen.add(message)
Mushroom().score_required+=50
Mushroom().score-=Mushroom.score_required
Mushroom().start-=150
Mushroom().speed+=5
Mushroom().level+=1
if Mushroom().start==20:
Mushroom().start+=10
def __init__(self):
super(Mushroom,self).__init__(image=Mushroom.mushroom,x=games.mouse.x,y=0)
def update(self):
self.dy=Mushroom.speed
self.check()
self.check2()
def check(self):
if self.bottom==games.screen.height:
self.destroy()
Mushroom.score+=50
Mushroom.total_score+=Mushroom.score
if Mushroom().score==Mushroom.score_required:
self.next_level()
def check2(self):
if self.top==Mushroom.start:
self.duplicate()
def duplicate(self):
new_mush=Mushroom()
games.screen.add(new_mush)
background_image=games.load_image("background.jpg", transparent=False)
games.screen.background=background_image
stickman_image=games.load_image("stickman.png", transparent=True)
stickman=Stick_Man(image=stickman_image,left=1,bottom=480)
games.screen.add(stickman)
games.mouse.is_visible=False
b=Mushroom()
c=Mushroom()
a=Mushroom()
games.screen.add(b)
games.screen.add(a)
games.screen.add(c)
games.screen.event_brab=True
games.screen.mainloop()
The code is pretty self explanatory and whenever one of the mushrooms is equal to start, then a new object is created thus meaning a new mushroom comes in. However, what happens is that code doesn't function properly a second time and the mushrooms don't get faster spawn much faster either. Also, when the game first starts, the minute the first mushroom hits the bottom it says level one cleared, when it should be after two mushrooms. The sprite is just a red mushroom and also a stickman which can be found on g images if you want to simulate.
So my question is how do i make the object's STATS carry on from where it left off whenever another mushroom appears and also display the message at the right time
Your problem is in all of the lines that look like this:
Mushroom().score_required+=50
There are a number of problems here, which all together add up to make this have no useful effect:
Mushroom() creates a new Mushroom instance (which goes away as soon as this line is done).
Assigning (including update-assigning) to an attribute through an instance always creates or updates an instance attribute, even if there was a class attribute of the same name.
The += operator doesn't mutate immutable values like integers in-place (because that would be impossible); a += b is effectively the same as a = a + b.*
So, when you put that together, what you're doing is creating a new value equal to Mushroom.score_required + 50, then assigning that value to a new instance attribute of a temporary instance (which immediately goes away). This has no effect on the class attribute, or on any of the other instances.
You have a related, but different, problem in the lines like this:
x_position=random.randrange(640)
Unless you want all of the mushrooms to have the same x_position, this should not be a class attribute, but an instance attribute, and you're going to run into all kinds of strange problems.
Storing game stats as class attributes of a random class is a strange thing to do. There are ways you could make that work, but there's no good reason to even try. Class attributes are useful for constants that all instances of the class might as well share, but they're not useful as a substitute for global variables.
A better design would be something like this:
class Game(object):
def __init__(self):
self.score = 0
self.start = 200
self.score_required = 100
self.level = 1
self.total_score = 0
def next_level(self):
indicate = 'Level ', + Mushroom.level, ' cleared'
message = games.Message(value=indicate, size=50, color=color.red,
x=games.screen.width/2, y=games.screen.height/2,
lifetime=150)
games.screen.add(message)
self.score_required += 50
self.score -= self.score_required
self.start -= 150
self.speed += 5
self.level += 1
if self.start == 20:
self.start += 10
def update_score(self, n):
game.score += n
game.total_score += game.score
if self.score == self.score_required:
self.next_level()
class Mushroom(games.Sprite):
mushroom=games.load_image("mushroom.jpg")
def __init__(self, game):
self.x_position=random.randrange(640)
self.game = game
super(Mushroom,self).__init__(image=Mushroom.mushroom,x=games.mouse.x,y=0)
def update(self):
self.dy=Mushroom.speed
self.check()
self.check2()
def check(self):
if self.bottom == games.screen.height:
self.destroy()
game.update_score(50)
def check2(self):
if self.top == Mushroom.start:
self.duplicate()
def duplicate(self):
games.screen.add(Mushroom(self.game))
game = Game()
games.screen.add(Mushroom(game))
games.screen.add(Mushroom(game))
games.screen.add(Mushroom(game))
games.screen.event_brab=True
* That's not completely true. In fact, a = a + b is equivalent to a = a.__add__(b), while a += b is equivalent to a = a.__iadd__(b) if such a method exists, falling back to __add__ only if it doesn't. For mutable objects like lists, this makes a big difference, because __iadd__ can change self in-place and then return it, meaning you end up assigning the same object back to a that was already there. But for immutable objects, there's no difference.