How to properly inherit the Surface class in Pygame - python

To put OOP in practice, I created my own Window class that inherits from pygame.Surface, and adds functionality. According to the following source, this is what I should do:
class MyWindow(pygame.Surface):
def __init__(self, w, h):
pygame.Surface.__init__(self, size=(w, h))
self.screens: list[Screen] = [] # For example
I tried the following code, and it seems to work fine, up until the point where display.update() is called. This throws a pygame.error.
myWin: MyWindow = MyWindow(200, 200)
print(myWin) # This correctly prints: <Surface(200x200x32 SW)>
pygame.draw.rect(myWin, (255, 255, 255), (90, 90, 20, 20))
pygame.display.update()
pygame.time.delay(1000)
That code doesn't even create an actual window. In contrast, the following code works just fine, instead relying on the display.set_mode() function to initialize the Surface.
pyWin: pygame.Surface = pygame.display.set_mode((200, 200))
pygame.draw.rect(pyWin, (255, 255, 255), (90, 90, 20, 20))
pygame.display.update() # Actually shows the square
pygame.time.delay(1000)
Clearly display.set_mode() does something that Surface.__init__() doesn't.
TL;DR: How can I initialize an object inheriting from Surface, in such a way that it actually shows and updates the window?
Thanks!

TL;DR: How can I initialize an object inheriting from Surface, in such a way that it actually shows and updates the window?
I'd say you can't do it in the way you think, and there is a good reason for not ever wanting to do it.
A couple of things to keep in mind:
A Surface is simply a 2D array of pixels, with convenient methods to read / edit / write the values of those pixels. Write where? To other surfaces. You can have as many surfaces you want in a game, each one storing the image of a different element of the game (your main character, an enemy, an element of the map, etc). You can write the content of a Surface on top of another Surface, to make complex images.
A Surface, by itself, it has no connection with the screen (i.e. with what is shown on the window you see on your PC monitor).
The display is a special Surface, instantiated by pygame.display.set_mode(). It is a Surface like all the other you can create using the Surface class, but that, and only that Surface, represents the display, i.e. what you see on your monitor.
This means that a pygame.Surface instance is never shown on the screen. To show stuffs on the screen, you need to blit the other Surfaces (i.e. copy their content) somewhere on the special display Surface.
Now let's go back to your question. With what I have just explained in mind, you could ask: Then can I write a Surface subclass such that each time the Surface value changes, it is automatically blit on the display and display.update() is called`?
Well, it will be complicated. If you use the function from pygame.draw the Surface is an argument, so you need some good trick to let an argument understanding when it is used.
However, trust me: you don't want to do it. As soon as your game becomes a little more complex, it will be highly inefficient. The reason is that blitting and drawing requires time, so you want to optimize these operations, performing them only when needed. If you have many surfaces in your game, you want to blit them on the display each iteration of your main loop (or each frame if you prefer) to "prepare" the new frame, and call display.update only once.

Related

Replacing a surface instead of blitting to it

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.

A bit confused with blitting (Pygame)

I've just started learning some pygame (quite new to programming overall), and I have some very basic questions about how it works.
I haven't found a place yet that explains when I need to blit or not to include a certain surface on the screen. For example, when drawing a circle:
circle = pygame.draw.circle(screen, (0, 0, 0), (100, 100), 15, 1)
I don't need to do screen.blit(circle), but when displaying text:
text = font.render("TEXT", 1, (10, 10, 10))
textpos = text.get_rect()
textpos.centerx = screen.get_rect().centerx
screen.blit(text, textpos)
If I don't blit, the text won't appear.
To be honest, I really don't know what blitting is supposed to do, apart from "pasting" the desired surface onto the screen. I hope I have been clear enough.
The short answer
I haven't found a place yet that explains when I need to blit or not to include a certain surface on the screen.
Each operation will behave differently, and you'll need to read the documentation for the function you're working with.
The long answer
What Is Blitting?
First, you need to realize what blitting is doing. Your screen is just a collection of pixels, and blitting is doing a complete copy of one set of pixels onto another. For example, you can have a surface with an image that you loaded from the hard drive, and can display it multiple times on the screen in different positions by blitting that surface on top of the screen surface multiple times.
So, you often have code like this...
my_image = load_my_image()
screen.blit(my_image, position)
screen.blit(my_image, another_position)
In two lines of code, we copied a ton of pixels from the source surface (my_image) onto the screen by "blitting".
How do the pygame.draw.* functions blit?
Technically, the pygame.draw.* methods could have been written to do something similar. So, instead of your example...
pygame.draw.circle(screen, COLOR, POS, RADIUS, WIDTH)
...they COULD have had you do this...
circle_surface = pygame.draw.circle(COLOR, RADIUS, WIDTH)
screen.blit(circle_surface, POS)
If this were the case, you would get the same result. Internally, though, the pygame.draw.circle() method directly manipulates the surface you pass to it rather than create a new surface. This might have been chosen as the way to do things because they could have it run faster or with less memory than creating a new surface.
So which do I do?
So, to your question of "when to blit" and "when not to", basically, you need to read the documentation to see what the function actually does.
Here is the pygame.draw.circle() docs:
pygame.draw.circle():
draw a circle around a point
circle(Surface, color, pos, radius, width=0) -> Rect
Draws a circular shape on the Surface. The pos argument is the center of the circle, and radius is the size. The width argument is the thickness to draw the outer edge. If width is zero then the circle will be filled.
Note that it says that "draws a shape on the surface", so it has already done the pixel changes for you. Also, it doesn't return a surface (it returns a Rect, but that just tells you where the pixel changes were done).
Now let's look at the pygame.font.Font.render() documentation:
draw text on a new Surface
render(text, antialias, color, background=None) -> Surface
This creates a new Surface with the specified text rendered on it. Pygame provides no way to directly draw text on an existing Surface: instead you must use Font.render() to create an image (Surface) of the text, then blit this image onto another Surface.
...
As you can see, it specifically says that the text is drawn on a NEW Surface, which is created and returned to you. This surface is NOT your screen's surface (it can't be, you didn't even tell the render() function what your screen's surface is). That's a pretty good indication that you will need to actually blit this surface to the screen.
Blit means 'BL'ock 'I'mage 'T'ranfser
When you are displaying things on the screen you will, in some way, use screen because that's where you are putting it.
When you do:
pygame.draw.circle(screen, (0, 0, 0), (100, 100), 15, 1)
you are still using screen but you are just not blitting because pygame is drawing it for you.
And when you use text, pygame renders it into an image then you have to blit it.
So basically you blit images, but you can also have pygame draw them for you. But remember when you blit an image, say over a background, you need to loop it back and fourth; so that it blits the background, then the image, then the background etc...
You dont need to know much more than that, but you can read all about it here Pygame Blit
I hope this helped. Good Luck!
Imagine that you are a painter:
You have a canvas, and a brush.
Let's say that your main screen surface will be your canvas, and all the other surfaces, are "in your head" - you know how to draw them already.
When you call blit, you paint on top of the surface, covering any pixels that were overlapped. That is why you need to repaint the whole screen black so that you won't have any smudges on the painting while moving an object.
As Mark already said, you can draw a circle with a function, or first blit it to a new surface, and blit that on the screen surface.
If you have a more complicated surface - curves, text etc. you wouldn't need to have a surface for that, so you don't have to do any expensive calculations, just drawing. The setback is that your program takes up more memory, so you have to choose between those 2.

How do I clear a pygame alpha layer efficiently?

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.

Pygame doesn't draw

I need help with a program I'm making. It's a version of Conway's Game of Life.
This game is right now made out of 3 files: main.py, cellBoard.py, cell.py
main.py takes care to instance cellboard and make it update its data, give it mouse input, and tell it to draw itself (an instance of the pygame surface is given to it, which handles it to the cells which are the actual ones that draw themselves)
cellboard.py creates a list of cells based off their size and the screen's size, to fill it properly. It's a 2D list. When it creates the cells it sets their state (alive currently) and handles them an instance of its instance of the original surface instance.
cell.py contains all the things a cell can do: die, live, be toggled, be drawn.
In fact, when I need to draw the whole board I just call cellBoard's own draw() and it should take care of calling each cell's draw. And it does.
The execution gets to the point where the cell should be drawn (checked with prints) and the pixel filling function is executed (using a for loop to cover an area). But nothing is actually drawn to the screen, or at least nothing is visible.
I have no idea what is causing this. I checked the code multiple times, I've even rewritten the whole program from scratch to make it more tidy (and I had the same problem as now)
What is causing this? My idea would be that somehow the instance of surface Cell gets is not good anymore to work because something happened to it (it goes through cellboard before getting to each cell, could that be the problem?)
Here's the source code (all 3 files, they are very short and barebones so they should be easy to read) http://dl.dropbox.com/u/2951174/src.zip
Thanks in advance to anyone who feels like helping. I need to complete this project very fast so your help would be greatly appreciated.
First of a quick suggestion:
People are much more likely to help you if they don't have to download a zip file, next time just post the code parts you suspect not to work.
Anyways, problem seems to be in your main loop:
#Keyboard events
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
running = 0
#Mouse events
#todo
#Grid update <------- here you update the grid and the cells are being drawn
cb.draw()
#Graphical output <------------ here you're filling the WHOLE screen with white
screen.fill(THECOLORS["white"])
pygame.display.flip()
You need to move your screen.fill call above cb.draw so you don't paint over the cells.
Also in cell.py your drawing code is A) Broken and B) bad.
Instead of setting every pixel on its own, which is slow and in it's current state doesn't draw the cells correctly, you can just as well draw rectangle:
pygame.draw.rect(self.surface, (100, 10, 10), (self.pos[0], self.pos[1], self.size, self.size))

Pygame: Blitting a moving background creates too much blur

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?

Categories