It appears that in Python graphics.py, new objects are drawn behind existing objects. So if I draw a blue circle and THEN place a rectangular box over half the circle, you find the circle in on top of the rectangular box. The box, even though it was written last, is appearing behind the circle. Is there a way to control this behavior so the rectangle would appear on top of the circle and not behind it? I want to avoid having to undraw the circle, draw the rectangle, then redraw the circle, so that the rectangle is on top of the circle instead of behind it.
Certainly, this becomes more cumbersome as you have more and more overlapped objects.
Related
As I understood pygame drawing method, the 2nd argument of the blit function (screen.blit(surface, (0,0))) tells pygame where to draw the given surface (like an offset to start drawing from, or rather a placement of the surface on the screen). Although, from recent experimenting, it seems that pygame surfaces placements are fixed, and that (0,0) is used to crop the surface before pygame blit it to the screen, for efficiency purposes.
Are surfaces placement really fixed (is my latter observation correct)? and if so, is there another way to conveniently move an already drawn surface to another position on the screen? Or should i implement my own way of moving complex "drawable" objects?
Thanks!
A pygame.Surface object has no position, it has a size only. Note, the location of the Rect object which is returned by pygame.Surface.get_rect() is always (0, 0).
When you blit a Surface on another Surface, then each pixel is copied and placed at the corresponding position of the destination Surface. Thus always a position has to be specified, when a Surface is blit on a destination Surface.
See also Why is my collision test always returning 'true' and why is the position of the rectangle of the image always wrong (0, 0)?
[...] if so, is there another way to conveniently move an already drawn surface to another position [...]
You have a basic misunderstanding. A Surface cannot be "moved". A Surface is copied on the Surface object which is associated to the game window.
A Surface appears to be moving, because the entire scene is drawn in every frame. First the background is drawn, then the objects (Sprites, Surfaces) are drawn on top of the background and finally the display is updated (in every frame). If an object is placed at a slightly different position in every frame, then the object appears to be moving smoothly.
This question already has an answer here:
How do I focus light or how do I only draw certain circular parts of the window in pygame?
(1 answer)
Closed 2 years ago.
That question wasn't very clear.
Essentially, I am trying to make a multi-player Pac-Man game whereby the players (when playing as ghosts) can only see a certain radius around them. My best guess for going about this is to have a rectangle which covers the whole maze and then somehow cut out a circle which will be centred on the ghost's rect. However, I am not sure how to do this last part in pygame.
I'd just like to add if it's even possible in pygame, it would be ideal for the circle to be pixelated and not a smooth circle, but this is not essential.
Any suggestions? Cheers.
The best I can think of is kind of a hack. Build an image outside pygame that is mostly black with a circle of zero-alpha in the center, then blit that object on top of your ghost character to only see a circle around it. I hope there is a better way but I do not know what that is.
If you only want to show the scene inside a circular area, then you can do the following:
Clear the display.
Limit the drawing region to a square area around the circular area
Draw the scene
Draw a transparent circle surface on top of the square area
The circle surface can be created with ease on runtime. Define the radius of the circular area (areaRadius). Create a square pygame.Surface with the doubled radius of the circular area. Fill it with opaque black and draw a transparent circle in the middle:
circularArea = pygame.Surface((areaRadius*2, areaRadius*2), pygame.SRCALPHA)
circularArea.fill((0, 0, 0, 255))
pygame.draw.circle(circularArea, (0,0,0,0), (areaRadius, areaRadius), areaRadius)
The drawing region of a surface can be limited by .set_clip(). Calling the function with the parameter None removes the clipping area. In the following screen is the surface which represents the window and areaCenter is the center of the circular area on the screen:
while run:
# [...]
# remove clipping region and clear the entire screen
screen.set_clip(None)
screen.fill(0)
# set the clipping region to square around the circular area
areaTopleft = (areaCenter[0]-areaRadius, areaCenter[1]-areaRadius)
clipRect = pygame.Rect(areaTopleft, (areaRadius*2, areaRadius*2))
screen.set_clip(clipRect)
# draw the scene
# [...]
# draw the transparent circle on top of the rectangular clipping region
screen.blit(circularArea, areaTopleft)
# clear the dripping region and draw all the things which should be visible in any case
screen.set_clip(None)
# [...]
pygame.display.flip()
I have got some surfaces in Pygame with a transparent background. They're all the same size. But there's a different sized circle drawn on each of them, so the circle doesn't exactly fit the image.
Here are some example images (I took a screenshot in Photoshop so you can clearly see the transparency and the size of the images):
Now I want to remove the transparent border around the image so the circle exactly fits into the image. I don't want the surface to be circle shaped, I don't think that's possible, but I want that the surface doesn't have blank columns on the left and right and that it doesn't have any blank rows on the top and the bottom. The wanted results:
The circle on the surfaces changes size every frame so I have to recalculate the new surfaces every frame.
I already Googled it, but I haven't found anything for Pygame surfaces yet. I also tried making my own function but it looks ugly and much worse: the framerate drops from 50 (if I don't call the function) to 30 fps (if I do call the function). I tested it a little bit and I found out that smaller circles take longer to process than bigger circles. How can I do this, but faster. If you want I can show the function I made.
The surface object has a method called get_bounding_rect which is where we will start. The function returns the smallest rect possible which contains all of the non-transparent pixels on the surface.
pixel_rect = image.get_bounding_rect()
With the size of this rect, we can create a new surface:
trimmed_surface = pygame.Surface(pixel_rect.size)
Now blit the portion of image contained within pixel_rect onto trimmed_surface:
trimmed_surface.blit(image, (0,0), pixel_rect)
At this point, trimmed_surface should be a surface the same size as pixel_rect, with the unwanted transparent rows and columns "trimmed" off of the original surface.
Documentation for Surface.get_bounding_rect: http://www.pygame.org/docs/ref/surface.html#Surface.get_bounding_rect
I am working on a game that has destructible terrain (like in the game Worms, or Scorched Earth) and uses pixel perfect collision detection via masks.
The level is a single surface and how it works now is that I create a copy every frame, draw all sprites that need drawing on it, then blit the visible area to the display surface.
Is there any way to avoid copying the whole level surface every frame and still be able to use the pixel perfect collision tools found in pygame?
I tried blitting the level surface first, then blitting every sprite on the screen (with their blit coordinates adjusted by the camera, except for the player character whose coordinates are static), but in that case the collision detection system falls apart and I can't seem to be able to fix it.
UPDATE
I have managed to make it work the following way:
When drawing the sprites, I convert their game world coordinates (which are basically coordinates relative to the origin of the level bitmap) to screen coordinates (coordinates relative to the camera, which is the currently visible area of the level).
During the collision detection phase I use the coordinates and bounding boxes that are positioned relative to the level surface; so just like above. The thing is that the camera's position is bound to the player's position which is not and should not have been a static value (I am really not sure how I managed to not realize that for so long).
While this fixes my problem, the answer below is a much more comprehensive look on how to improve performance in a situation like this.
I am also open to suggestions to use other libraries that would make the ordeal easier, or faster. I have thought about pyglet and rabbyt, but it looks like the same problem exists there.
This is an issue that used to come up a lot in the days before graphics accelerators, when computers were slow. You basically want to minimize the work required to refresh the screen. You are on the right track, but I recommend the following:
Keep a copy of the background available offscreen, as you are doing
now.
Allocate a working bitmap that is the same size as the screen.
For each sprite, compute the bounding rectangle (bounding box) for
its new and old positions.
If the new and old bounding boxes overlap, combine them into one
larger box. If they do not overlap, treat them separately.
Group all the bounding boxes into sets that overlap. They might all
end up in one set (when the sprites are close to each other), or
each bounding box might be in a set by itself (when the sprites are
far apart).
Copy the background to regions of the working bitmap corresponding
to each bounding box set.
Copy the sprites for each set to the working bitmap in their new
positions (in the correct z-order, of course!).
Finally, copy the finished offscreen bitmap to the display surface,
set bounding box by set bounding box.
This approach minimizes the amount of copying that you have to do, both of background and sprite. If the sprites are small relative to the display area, the savings should be significant. The worst case is where the sprites are all arranged on a diagonal line, just barely overlapping each other. In this case, you might want to switch to a more generalized bounding shape than a box. Take a look at QuickDraw Regions for an example: Wikipedia Discussion Patent Source.
Now, you may be thinking that the work to group the bounding boxes into sets is a O(n^2) operation, and you would be right. But it grows only with the square of the number of sprites. 16 sprites implies 256 comparisons. That's probably less work than a single sprite blit.
I focused on minimizing the pixel copying work. I must admin I am not familiar with the particulars of your collision detection library, but I get the idea. Hopefully that is compatible with the algorithm I have proposed.
Good luck. If you finish the game and post it online, put a link to it in your question or a comment.
is there a way to fill everything outside of a closed path (polygon)?
Background: I'd like to render some maps with coastlines - so sometimes I need to fill the sea with blue color, so I thought it would be the easiest and in my situation the most efficient to fill everything outside of this coastline polygon with blue color.
Thanks in advance!
You can add a rectangle covering the whole drawing area to your coastline path and set the fill rule to cairo.FILL_RULE_EVEN_ODD. Calling fill() after this fills the area outside your original path. (If you choose the correct orientation for your rectangle you can skip setting the fill rule.)
Draw a big blue rectangle over the entire cairo surface and then draw your coastline on top of that?
While you could create a closed path the size of the surface and then fill it with a solidpattern (the fill rule won't matter for a simple rectangle), it would be easier to just use the context paint() method which will fill the current clip region (that is initially set to the entire surface). It's important to do this before drawing the map/coastline boundaries and filling them so they will be on top of the background.