I'm trying to write a 2D game using python / pygame that blits several layers on top of one another every screen refresh. My basic setup is (from bottom to top):
Background: surface (non-transparent), scrolls at a different rate than rest
Midground: SRCALPHA transparent surface (static)
Player / Sprites / Enemies: sprite group
Forground: SRCALPHA transparent surface (static)
Right now, I'm blitting these four layers one on top of another every screen. The background scrolls at a different rate than the other three layers, which is why I have it separate from midground. As I have the game structured now, it runs on my fairly modest laptop at 60fps.
-BUT- I'm having trouble with the sprite group, which I'm blitting directly to the screen. Having to adjust the rect for every sprite according to my current viewport seems like an ugly way to program things, and I'd like a more elegant solution.
I'd love to blit the sprites to another transparent surface which I could manage, but therin lies my problem: I can't find a way of clearing a transparent layer that doesn't half my performance. Some of the setups I've tried:
I've tried filling the layer with a white surface with blend mode rgba_sub (surf.fill((255,255,255,255), area, BLEND_RGBA_SUB)) -- this is super, super slow
I've tried surface.copy() of a blank surface - this is faster, but still halves my fps
I've tried combining the sprites with the midground layer and using pygame.sprite.LayeredUpdates to update the sprites. This has no effect on performance, but does not work where the midground is transparent. I get trails of sprites over the background layer.
The best solution I've found so far is my current setup of drawing sprites directly to the screen. It looks great, runs fast, but is a pain to manage, as I have to make sure each sprites' rect is adjusted according to the viewport every frame. Its also making collision detection difficult.
Is there another quick way to clear a pygame transparent surface? Quick as in, can be done 60+ times a second? Alternately, is there a setup for my layers that would still accomplish the same effect?
I figured out a fast way of clearing a sprites only transparent layer by applying Peter's solution selectively to the layer:
for s in self.level.sprites:
spritelayer.fill((0), s.rect)
This seems to be working fine (erasing everything each frame) and still runs at 60fps.
The Surface.fill() will clear all R, G, B, and A values.
>>> img = pygame.image.load("hasalpha.png")
>>> print img.get_at((300, 300))
(238, 240, 239, 255)
>>> surface.fill(0)
>>> print img.get_at((300, 300))
(0, 0, 0, 0)
It sounds like this will do what you are describing. If you are trying to do something more specific with the alpha values the pygame.surfarray.pixel functions can give you directly editable values. That will be quick, but requires numpy as a dependency.
Related
Using pygame, would it be more efficient to:
Blit a single sprite from a portion of a spritesheet each frame
At startup, blit each sprite from a spritesheet to their own Surfaces, then blit those Surfaces
Is there any performance differences between the two? Would it take more draw calls to use the first method, versus individual Surfaces at startup (i.e. does doing this at startup store copies of those pixels in ram/vram)?
Thanks
It would be faster to blit the images into a surface, then blit those surfaces.
However it would consume more memory, since you'll need to keep those surfaces somewhere in memory.
Why?
When you go to blit the image from your spritesheet, you'll end up subsurfing/clipping the spritesheet surface, which will mean you'll need to generate another surface on the spot. However, this process won't take long.
The performance benefit most likely isn't worth it, so I'd recommend just go with whichever method you're most comfortable with. If you're concerned about performance, check out the builtin CProfile python module.
VRAM never comes into this equation. pygame.Surface is derived from SDL_Surface from the SDL library for the C programming language. SDL_Surface is primarily targeted toward software rendering, which means the surface pixels are stored in standard RAM.
I'm having some performance issues in pygame, so I'm trying to optimize the rendering.
Currently, I'm blitting the background image to the display buffer:
self.display.blit(self.bg, (0, 0))
Instead, I'm looking for a way to replace the buffer with a copy of the background surface, and draw over that. This way, I don't have to blit a large image every frame, saving me some time.
Is there any way to do so?
It doesn't matter that much how often you blit something to the screen surface, since the display does only get updated once you call pygame.display.update or pygame.display.flip.
If you're sure that blitting the whole background image to the screen surface is a bottle neck in your game, you can try the following things:
a) Instead of blitting the whole background every frame, use the clear() function to "erase" your sprites from the screen.
b) Instead of calling pygame.display.flip or pygame.display.update without an argument, call pygame.display.update with the list of the areas on the screen that have been changed, which is returned by the draw() function (maybe in combination with clear()).
c) Create your display surface with the FULLSCREEN, DOUBLEBUF and HWSURFACE flags.
But as I already said: make sure you know where your bottle neck is. Some common performance pitfalls are: loading images multiple times from disk, font rendering, using different pixel formats (e.g. not calling convert/convert_alpha on surfaces created from images) and generally the lack of caching.
(also note that python/pygame is generally not the first choice when creating graphically demanding games)
Without seeing your code it will be hard to really see where the bottleneck is. Sloth is correct on his points as a way to optimize. in my experience all images should be pre-processed outside of the main game loop by drawing them onto their own surfaces. Blitting surfaces to surfaces is much faster than blitting images to surfaces.
img_surface = pygame.Surface((img_rect.width, img_rect.height), pygame.SRCALPHA)
img_surface.fill((0, 0, 0, 0))
img_surface.blit(get_image("my_image.png"), img_rect)
This happens outside the game loop. In the game loop you blit the image surface to your surface. If you really want to evaluate you code, use cProfile. This will help you nail down exactly where the bottleneck is. Do as much pre-processing outside the main game loop as possible.
Python getting meaningful results from cProfile
This link really helped me understand cProfile. Sloth is also correct in that pygame is limited, so you are going to need to optimize everything as much as you can. That is where cProfile comes in.
...Also, make sure you are only drawing things that are visible to the user. That can really help improve performance.
So I am making a game with multiple parts using pygame and it has a "drunk" component to it. My goal is to make it so that the more "drunk" the player is, the screen will look more blurry accordingly.
Here is a hack provided for blurring a surface
But that does not work for the whole screen, which is what I need.
How could I go about blurring the entire screen?
How could I go about blurring the entire screen?
When you do pygame.display.set_mode() (or some other function to get the game screen*), what you get back is a Surface.
If you're following the tutorials, you've probably stored it in a variable named screen. Just use that as your surface.
Or you can always just draw to an off-screen surface, transform that, and blit to the screen from the transformed version.
Also, you might want to consider using surfarray; you can probably do a much better blur than just anti-aliased pixelization with about the same amount of code and less CPU work…
* The game screen may be the whole monitor screen, for a full-screen game, or the window, for a windowed game. Either way, if that's the thing you want to blur, that's the Surface you use. If you wanted to blur the entire monitor screen from a windowed game, that wouldn't be possible, because you don't have a handle to that… but from comments, that isn't what you want.
I made a 2D project with a lot of tile sprites, and one player sprite. I'm trying to get the camera to follow the player, and for the most part it's working. However, there's one problem:
If you go to the edge of the map, it scrolls normally, but instead of the black background, it displays copies of the sprites on the edge of the map instead of the background (black). It has the same problem if I leave some squares empty, when I move it displays a copy of the tile that was previously there.
The camera works like this:
Select sprites that should be visible
Do sprite.visible = 1 for them, and sprite.visible = 0 for all other sprites
Set the position sprite.rect of all sprites to coords - offset
Update the screen (I use flip(), because the camera moves every turn, so the whole screen has to be updated every turn)
All DirtySprites have dirty = 2.
Does anyone know why it's displaying copies of the sprites on the edge instead of the background?
Help would be appreciated!
Unless you manually clear your screen surface, flip will not change its content.
Thus, if you neglect to draw to a certain location, it will remain the same.
If you want to get rid of this effect, usually called "hall of mirrors", you will have to keep track of what portions of the screen have not been drawn to yet and draw over these yourself.
It may be easier to define background sprites around your map's contours and block your camera from going off too far.
Since you use a "dirty/clean" approach to only redrawing what's changed, you won't have the option to just fill the whole screen surface before you draw your frame, because that would draw over anything that's stayed the same since the last frame.
What I am trying to do is create a viewport to view a small portion of a background. (And later put sprites in).
However the problem I have noticed is there seems to be an issue of the background blurring when it starts moving. I was not sure if this is because blitting is slow or because of a problem in the code. I was looking for examples on how others blit or create scrolling backgrounds and found this article: Scrolling Games
I used their simple example and sure enough the background appears blurry as you scroll (aka blit the background with an offset). I also thought it might be the FPS dropping for whatever reason however it doesn't deviate at all. I can't recall an issue like this with other 2D games. I understand there may be some motion blur due to it constantly shifting. Just wondering if I can do anything to alleviate this. Can someone chime in on anything I may be missing? I would appreciate any feedback or help. Thank you
I couldn't know what caused the problem you faced, but I guess it is related to double buffering.
Did you use at least two surfaces?
# preparing two surfaces in __init__()
screen = pygame.display.set_mode((800,600))
background = pygame.Surface(screen.get_size())
background.fill((250, 250, 250))
# called at every step in main loop
# draw images on the background surface
background.blit(image, position)
....
# blit background to screen
screen.blit(background, (0, 0))
pygame.display.flip()
If images are drawn on the screen surface directly, flicking occurs.
By "blurry" do you mean that the background appears "doubled"? Do you get the same effect when moving a normal-sized (e.g., 64x64) sprite?
If you are seeing double, then it's probably a refresh rate problem. Turning on vsync may help.
What frame rate are you getting?
If you slow down the animation to around 10 FPS, do you have the same problem?