[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

Re: [pygame] sprite engine



Kamilche schrieb:

Thanks for your fast response. It's ok to not show the code if it will be a commercial product. So lets talk a bit about theory.

I though that to be fast, the screen update area has to be as minimal as posible (so, essentially this means that do not update a pixel twice). So far this is still a problem to solve for me because I do not know how to do this in a fast way.

The second thing was that most information (especially not changing) has to be stored by the renderer to have fast acces ( dereferencing can slow things down).

And the other thing is to minimize the information needed. I think one need following thing to know about a sprite:

image
rect
oldrect
dirty
area (attention to when rect/image size changes!!)
layer
lostrects (only for renderer)
(perhaps in future: blendmode)

In my code I store oldrect, layer and lostrects in the renderer itself.


But I wanted also that one could use the pygame.sprite.groups (except draw() and clear() ), so that one could code the same way as till now. But perhaps this has to change altough so far it was not a big problem.



I finally came up with following way to implement my sprite engine (very general):


1. find screen update area (area on screen that has to be updated, as minimal as possible)
2. blit any sprite that has a part in a screen update area (in the right order of layers!)



The area in point 1. are all areas where sprites has to be removed (cleared) from screen and area that has to be redrawen (essentially oldrect and rect). Also the sprite that has been removed (lostsprites) generates a screen update area.


The 2. point is easier. Check every sprite on collision with a screen update area and if so blit it.

Perhaps it can be done in a better way. So what do you say to me algorithme?

~DR0ID





Yeah, that's a good start. You're right, you need to keep the picture as an object attribute somewhere, you can't be loading it from disk every time you need to draw it. I call this routine 'Paint' in my code; some might call it 'Refresh.' But it IS different from 'Draw', and should be called as little as possible.

Avoid keeping both oldrect and newrect. I did that in old versions of my sprite engine, and it was overkill, and led to difficult-to-detect dirty rectangle bugs. Instead, maintain a single rectangle. Whenever the sprite changes size or position, call a routine to handle most of the scut work for you. This routine shouuld first dirty the area of the screen of the EXISTING rectangle, update the rectangle based on the current size/position, then dirty the screen for the UPDATED rectangle. I haven't had a dirty rectangle bug since implementing this method.

When adding rectangles to the dirty rectangle list, do some optimization. Check to see if there's a rectangle in the list that the new rectangle collides with - if so, enlarge that rectangle to accommodate both. It saves lots of redrawing.

Finally, the 'Draw' routine should be based on the dirty rectangles. For each dirty rectangle, it goes through each sprite on the screen from lowest to highest zorder, and if it touches the dirty rectangle, redraws the sprit there.

I don't know what 'lostrects' would be for... or 'blendmode.' I use pixel-level alpha on all game sprites, so there's I don't use a single 'surface alpha'.

Finally - the use of the 'dirty' flag. I also use that, but only for 'paint.' It's a whopper of an enhancement! Instead of repainting the sprite any time the text changes, the color changes, the font changes, the picture changes, etc., I only 'mark it' as dirty - I don't repaint right then. Marking it as dirty, sets the dirty flag to 1, and dirties that area of the screen. In the main draw routine, IF the picture is 'dirty', I 'Paint' it (the expensive operation), reset the dirty flag to 0, then blit it. This allows 20 different routines to affect the sprite, yet only have a single expensive 'paint' routine called at the end. It makes the engine work well even on slower machines.

Though I don't generally pass out code, I sometimes pass on suggestions. I hope you find them helpful.

--Kamilche


Hello again

I keep the picture in my sprite object. If I understand you right, the 'Paint' routine does add a dirty area to the dirty rects list, but only if it is market 'dirty' ( no blitting or drawing). And the 'Draw' routine does the work of blitting all sprites that collides with a screen update area and update the screen ares. So far it seems very similar to mine, except that my sprite has no 'Paint' routine and I do this in the render() method.

Keeping oldrect and newrect is a bit of memory abuse. But I optimize there to keep the dirty screen update area minimal as follows (pseudocode):

#----------------------------------------------------
unionrect = oldrect.union(sprite.rect)
if unionrect.width*unionrect.height< 2*sprite.area: # assumes that oldrect and sprite.rect has same area,
# if not its only for one frame
# perhaps I should also save the oldrect area to do it right
screenupdates.append( unionrect )
else:
screenupdates.append( spr.rect )
screenupdates.append( oldrect )
#----------------------------------------------------


This way I never had problems with dirty-rectcangles bugs. But I have to think about your suggestion to keep only one rect (anyway my sprite does not know about the oldrect). If I could optimize the dirty screen update areas in a fast and efficient way, then I see that I do no need both, oldrect and rect.

Perhaps same/similar code could be used to optimize dirty screen update areas.

The lostrect is a rectangle list of areas that are dirty because a sprite is removed from screen. I just found a background blitting bug thanks to you. And as conclusion of bugfixing this I think I will not need anymore a lostrect list.

The blendmode is a new feature (should be in the CVS) and I think its important to support it ( and think now about it so no need to rewrite the engine).

Only the sprites with a dirty flag has a dirty screen area, so similar to your code, the sprite can change a number of times without beeing blitet directly, only at the end.

Changing the sprite.image in size or the image itself should not be a problem if the .rect attribute is changed also (I have not to do extra stuff because of that).

It seem that all things considered that I'm going in the right direction.

It's ok if you dont want to share code and I find your advice helpful. Thanks.

~DR0ID

There you will find a early version until then I have made improvements and changes. Its incomplete and could have some bugs, but if you want to take a look, go ahead. There is a working Demo included. I hope I can update it to a newer version in a few days.

http://www.mypage.bluewin.ch/DR0ID/index.html

You can find me also there:
IRC at freenode.net in #pygame as DR0ID_