I have a simple top down vertical shooter a la Galaga that I'm messing around with. However, after looking through the documentation I've become a bit confused on how to efficiently load images, and whether to blit them every frame or not. All of the images are loaded through a few classes which inherit the pygame sprite class. At the moment, I'm loading the image as a class level attribute as such:
class Laser(pygame.sprite.Sprite):
image = None
def __init__(self, start_x, start_y):
pygame.sprite.Sprite.__init__(self)
self.pos_x = start_x
self.pos_y = start_y
if Laser.image is None:
Laser.image = pygame.image.load('img/laser_single.png')
self.image = Laser.image
self.rect = self.image.get_rect()
self.rect.topleft = [self.pos_x, self.pos_y]
I'm hoping this prevents Python from loading a new instance of the image into memory every time I create a new Laser(). But will this work as I anticipate?
The second issue stems from blitting all of the active sprites onto the pygame surface. At the moment I loop through a list of Laser(), Enemy(), and whatnot objects and blit each one individually before calling pygame.display.update(). It seems redundant to have to blit each object individually, so I'm asking whether or not this is the most efficient method pygame implements. Or, is there a way to blit every object at once and see some sort of performance improvement?
I'm hoping this prevents Python from loading a new instance of the image into memory every time I create a new Laser(). But will this work as I anticipate?
Yes. Not only does this save memory, since every instance will just have a reference to the class variable, it will also increase performance because the image is only loaded once.
The second issue ssems from blitting all of the active sprites onto the pygame surface.
This depends. If your framerate is OK, then stick with it. If your framerate starts dropping, have a look at DirtySprite, and maybe this article.
Another python class that comes in handy is the LayeredDirty sprite group, which will automatically handle dirty sprites and updating only the relevant screen parts instead of the entire screen for you.
It seems redundant to have to blit each object individually
With sprite groups, as e.g. the LayeredDirty or the simple Group, you add your sprites to the group once and just call the draw method of the sprite group.
sprites = pygame.sprite.LayeredDirty((sprite1, sprite2, sprite3))
sprites.add(sprite4)
...
sprites.draw(pygame_screen)
Using sprite groups will also enable you to do simple collision detection between groups using pygame.sprite.groupcollide and pygame.sprite.spritecollideany
Related
I am creating a game with pygame and I ran into a problem. My game is a space game: there are meteors coming at you, you have to dodge them and you can also shoot missles at them. When the missles hit the meteors, I want to make the missle dissapear and the meteor to be reset back where it started falling. I saw a line of code that showed how to make an image transparent, by doing this:
transparant = (0,0,0,0)
#Code in between
missle.image = missleImg
#I wrote some code to see if the missle was touching a meteor
missle.image.fill(transparent)
However, when I try to shoot the missle again, it is still transparent. Does anybody have a better way to unblit and the reblit an image?
Use lists or groups to organize the objects. Create one list or groups to for the meteors and another list or group for the missiles. When a missile hits a meteor, remove the objects from the container. Create new objects when you need them:
If the meteor immediately appears in a different position, it is sufficient to change the meteor's position attribute rather than destroying an object and creating a new one.
e.g. pygame.sprite.Sprite and pygame.sprite.Group:
meteor_group = pygame.sprite.Group():
missile_group = pygame.sprite.Group():
pygame.sprite.groupcollide(meteor_group, missile_group, True, True)
e.g. lists:
meteor_list = []:
missile_list = []:
for meteor in meteor_list[:]:
for missile in missile_list[:]:
collide = # [...] check for collision
if collide:
meteor_list.remove(meteor)
missile_list.remove(missile)
I am using a for loop combined with .colliderect() for collision detection while attempting to make a game using pygame, the loop gets too slow with ~340 wall Rectangles, I was wondering if it could be faster somehow because it severely effects the game play loop?
I have tried using coordinate points on different places on every wall but only works if you're moving certain amounts of pixels at a time and every time you half the movement speed it quadruples the amount of coordinate points you save.
#disregard indent, this is all in an update function that is called every time that a player decides to move.
self._old_position = self.position
PlayerRectangle = pygame.Rect(self.position[0]+ x,self.position[1]+y,16,16)
cwd = os.getcwd()
tmxData = load_pygame(cwd+"\\Maps\\TestfileMap.tmx")
movement = True
for obj in self.walls:
if(pygame.Rect(obj[0],obj[1],16,16).colliderect(PlayerRectangle)):
movement = False
self.move_back()
else:
continue
if movement:
self.position[0] += x
self.position[1] += y
self.stats["position"]=self.position
self.rect.topleft = self.position
self.feet.midbottom = self.rect.midbottom
The provided code works, however it is too slow, I was wondering if there is a different method in collision detection or if there is a way to make what is shown faster, it bogs down things greatly. Thank you
EDIT:
So the solution was basically that I had load_pygame that ran literally every time it looped simply take out the line that does that and it clears things up a lot more, for further optimization change the line that makes a Rect for each object and just use a list of Rects that are already constructed, this limits function calls.
Without a minimal working example it is tough to give assertive advice.
As I stated in the comments, there is a spurious call to a "load_pygame" function that seems to read file data inside this code - that alone could be the cause of your slowdown. Moreover, the data read is not used in the collision detection.
The other advice I't have is to let your walls rectangles pre-calculated in a sprite group, instead of creating new rectangles for all walls in every check with:
pygame.Rect(obj[0],obj[1],16,16).colliderect(PlayerRectangle)). then you can use the sprite method "spritecollideany" - https://www.pygame.org/docs/ref/sprite.html#pygame.sprite.spritecollideany - that should optimize the rectangle-collision verification.
(This will also require your Player object to be a Sprite if it is not already).
I've been using pyglet for a while now and I really like it. I've got one thing I'd like to do but have been unable to do so far, however.
I'm working on a 2D roleplaying game and I'd like the characters to be able to look different - that is to say, I wouldn't like use completely prebuilt sprites, but instead I'd like there to be a range of, say, hairstyles and equipment, visible on characters in the game.
So to get this thing working, I thought the most sensible way to go on about it would be to create a texture with pyglet.image.Texture.create() and blit the correct sprite source images on that texture using Texture.blit_into. For example, I could blit a naked human image on the texture, then blit a hair texture on that, etc.
human_base = pyglet.image.load('x/human_base.png').get_image_data()
hair_style = pyglet.image.load('x/human_hair1.png').get_image_data()
texture = pyglet.image.Texture.create(width=human_base.width,height=human_base.height)
texture.blit_into(human_base, x=0, y=0, z=0)
texture.blit_into(hair_style, x=0, y=0, z=1)
sprite = pyglet.sprite.Sprite(img=texture, x=0, y=0, batch=my_sprite_batch)
The problem is that blitting the second image into the texture "overwrites" the texture already blitted in. Even though both of the images have an alpha channel, the image below (human_base) is not visible after hair_style is blit on top of it.
One reading this may be wondering why do it this way instead of, say, creating two different pyglet.sprite.Sprite objects, one for human_base and one for hair_style and just move them together. One thing is the draw ordering: the game is tile-based and isometric, so sorting a visible object consisting of multiple sprites with differing layers (or ordered groups, as pyglet calls them) would be a major pain.
So my question is, is there a way to retain alpha when using blit_into with pyglet. If there is no way to do it, please, any suggestions for alternative ways to go on about this would be very much appreciated!
setting the blend function correctly should fix this:
pyglet.gl.glBlendFunc(pyglet.gl.GL_SRC_ALPHA,pyglet.gl.GL_ONE_MINUS_SRC_ALPHA)
I ran into the very same problem and couldn't find a proper solution. Apparently blitting two RGBA images/textures overlapping together will remove the image beneath. Another approache I came up with was using every 'clothing image' on every character as an independent sprite attached to batches and groups, but that was far from the optimal and reduced the FPS dramatically.
I got my own solution by using PIL
import pyglet
from PIL import Image
class main(pyglet.window.Window):
def __init__ (self):
TILESIZE = 32
super(main, self).__init__(800, 600, fullscreen = False)
img1 = Image.open('under.png')
img2 = Image.open('over.png')
img1.paste(img2,(0,0),img2.convert('RGBA'))
img = img1.transpose(Image.FLIP_TOP_BOTTOM)
raw_image=img.tostring()
self.image=pyglet.image.ImageData(TILESIZE,TILESIZE,'RGBA',raw_image)
def run(self):
while not self.has_exit:
self.dispatch_events()
self.clear()
self.image.blit(0,0)
self.flip()
x = main()
x.run()
This may well not be the optimal solution, but if you do the loading in scene loading, then it won't matter, and with the result you can do almost almost anything you want to (as long as you don't blit it on another texture, heh). If you want to get just 1 tile (or a column or a row or a rectangular box) out of a tileset with PIL, you can use the crop function.
Yep, I'm asking another question about this program :D
Anyway, I currently a program that creates two lines on the screen with a gap in between them that can scroll. From here, I obviously need to see if the two objects are colliding. Since I only have one sprite and one rectangle I thought it was slightly pointless and overkill to make two classes for them. However, I can only find tutorials relating to classes which I obviously don't need. So, my question really is:
Is it possible to test for collision between a standard image and a Pygame rect? If it is not, how can I convert either the image, rectangle or both the sprites to do this. (All preferably without using classes.)
Note: the image and rectangle are created in the following ways (if it makes a difference)
bird = pygame.image.load("bird.png").convert_alpha()
pipeTop = pygame.draw.rect(screen, (0,200,30), Rect((scrollx,0),(30,height)))
pipeBottom = pygame.draw.rect(screen, (0,200,30), Rect((scrollx,900),(30,-bheight)))
An image by itself does not have a position. You cannot test collision between a rect and something that is not placed in the world. I would recommend to create a class Bird along with a class Pipe that will both subclass pygame.Sprite.
Pygame already has collision detection built in.
A short example
bird = Bird()
pipes = pygame.Group()
pipes.add(pipeTop)
pipes.add(pipeBottom)
while True:
if pygame.sprite.spritecollide(bird,pipes):
print "Game Over"
EDIT:
Don't be afraid of classes, you will have to use them anyways sooner or later.
If you really don't want to use sprites, you can use the birds rect and the pipe and call collide_rect to check if they overlap.
EDIT2:
an example Bird class modified from pygame docs
class Bird(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("bird.png").convert_alpha()
# Fetch the rectangle object that has the dimensions of the image
# Update the position of this object by setting the values of rect.x and rect.y
self.rect = self.image.get_rect()
You could then add methods such as move, which will move the bird down with the force of gravity.
The same would apply for the Pipe but instead of loading an image, you can create an empty Surface, and fill it with a color.
image = pygame.Surface(width,height)
image.fill((0,200,30)
You can just get the x and y values and compare them:
if pipe.x < bird.x < pipe.x+pipe.width:
#collision code
pass
I am writing a simple top down rpg in Pygame, and I have found that it is quite slow.... Although I am not expecting python or pygame to match the FPS of games made with compiled languages like C/C++ or event Byte Compiled ones like Java, But still the current FPS of pygame is like 15. I tried rendering 16-color Bitmaps instead of PNGs or 24 Bitmaps, which slightly boosted the speed, then in desperation , I switched everything to black and white monochrome bitmaps and that made the FPS go to 35. But not more. Now according to most Game Development books I have read, for a user to be completely satisfied with game graphics, the FPS of a 2d game should at least be 40, so is there ANY way of boosting the speed of pygame?
Use Psyco, for python2:
import psyco
psyco.full()
Also, enable doublebuffering. For example:
from pygame.locals import *
flags = FULLSCREEN | DOUBLEBUF
screen = pygame.display.set_mode(resolution, flags, bpp)
You could also turn off alpha if you don't need it:
screen.set_alpha(None)
Instead of flipping the entire screen every time, keep track of the changed areas and only update those. For example, something roughly like this (main loop):
events = pygame.events.get()
for event in events:
# deal with events
pygame.event.pump()
my_sprites.do_stuff_every_loop()
rects = my_sprites.draw()
activerects = rects + oldrects
activerects = filter(bool, activerects)
pygame.display.update(activerects)
oldrects = rects[:]
for rect in rects:
screen.blit(bgimg, rect, rect)
Most (all?) drawing functions return a rect.
You can also set only some allowed events, for more speedy event handling:
pygame.event.set_allowed([QUIT, KEYDOWN, KEYUP])
Also, I would not bother with creating a buffer manually and would not use the HWACCEL flag, as I've experienced problems with it on some setups.
Using this, I've achieved reasonably good FPS and smoothness for a small 2d-platformer.
All of these are great suggestions and work well, but you should also keep in mind two things:
1) Blitting surfaces onto surfaces is faster than drawing directly. So pre-drawing fixed images onto surfaces (outside the main game loop), then blitting the surface to the main screen will be more efficient. For exmample:
# pre-draw image outside of main game loop
image_rect = get_image("filename").get_rect()
image_surface = pygame.Surface((image_rect.width, image_rect.height))
image_surface.blit(get_image("filename"), image_rect)
......
# inside main game loop - blit surface to surface (the main screen)
screen.blit(image_surface, image_rect)
2) Make sure you aren't wasting resources by drawing stuff the user can't see. for example:
if point.x >= 0 and point.x <= SCREEN_WIDTH and point.y >= 0 and point.y <= SCREEN_HEIGHT:
# then draw your item
These are some general concepts that help me keep FPS high.
When using images it is important to convert them with the convert()-function of the image.
I have read that convert() disables alpha which is normally quite slow.
I also had speed problems until I used a colour depth of 16 bit and the convert function for my images. Now my FPS are around 150 even if I blit a big image to the screen.
image = image.convert()#video system has to be initialed
Also rotations and scaling takes a lot of time to calculate. A big, transformed image can be saved in another image if it is immutable.
So the idea is to calculate once and reuse the outcome multiple times.
When loading images, if you absolutely require transparency or other alpha values, use the Surface.convert_alpha() method. I have been using it for a game I've been programming, and it has been a huge increase in performance.
E.G: In your constructor, load your images using:
self.srcimage = pygame.image.load(imagepath).convert_alpha()
As far as I can tell, any transformations you do to the image retains the performance this method calls. E.G:
self.rotatedimage = pygame.transform.rotate(self.srcimage, angle).convert_alpha()
becomes redundant if you are using an image that has had convert_alpha() ran on it.
First, always use 'convert()' because it disables alpha which makes bliting faster.
Then only update the parts of the screen that need to be updated.
global rects
rects = []
rects.append(pygame.draw.line(screen, (0, 0, 0), (20, 20), (100, 400), 1))
pygame.display.update(rects) # pygame will only update those rects
Note:
When moving a sprite you have to include in the list the rect from their last position.
You could try using Psyco (http://psyco.sourceforge.net/introduction.html). It often makes quite a bit of difference.
There are a few things to consider for a well-performing Pygame application:
Ensure that the image Surface has the same format as the display Surface. Use convert() (or convert_alpha()) to create a Surface that has the same pixel format. This improves performance when the image is blit on the display, because the formats are compatible and blit does not need to perform an implicit transformation. e.g.:
surf = pygame.image.load('my_image.png').convert_alpha()
Do not load the images in the application loop. pygame.image.load is a very time-consuming operation because the image file must be loaded from the device and the image format must be decoded. Load the images once before the application loop, but use the images in the application loop.
If you have a static game map that consists of tiles, you can buy performance by paying with memory usage. Create a large Surface with the complete map. blit the area which is currently visible on the screen:
game_map = pygame.Surface((tile_size * columns, tile_size * rows))
for x in range(columns):
for y in range(rows):
tile_image = # image for this row and column
game_map.blit(tile_image , (x * tile_size, y * tile_size))
while game:
# [...]
map_sub_rect = screen.get_rect(topleft = (camera_x, camera_y))
screen.blit(game_map, (0, 0), map_sub_rect)
# [...]
If the text is static, the text does not need to be rendered in each frame. Create the text surface once at the beginning of the program or in the constructor of a class, and blit the text surface in each frame.
If the text is dynamic, it cannot even be pre-rendered. However, the most time-consuming is to create the pygame.font.Font/pygame.font.SysFont object. At the very least, you should avoid creating the font in every frame.
In a typical application you don't need all permutations of fonts and font sizes. You just need a couple of different font objects. Create a number of fonts at the beginning of the application and use them when rendering the text. For Instance. e.g.:
fontComic40 = pygame.font.SysFont("Comic Sans MS", 40)
fontComic180 = pygame.font.SysFont("Comic Sans MS", 180)