Description
Issue №1185 opened by mcpalmer1980 at 2019-07-21 14:24:30
I'm sure you guys must be getting sick of me by now. I have tons of request but haven't offered to help any. I would like to, but I haven't touched C since I was 15 and my harddrive crapped out while I was working on my first version of Flyboy. It ran in 16-bit real-mode DOS using VGA mode 13h and 600kb of memory. I know nothing about the Python-C boundry or where to start looking for help. I also don't know the time-table for the official renderer API or your expectations for it.
I tried porting pytmx and pyscroll to use the pygame renderer and got pytmx working easily. pyscroll will be a bit more trouble and I don't want to tackle it until the final API interface is decided upon. I will port my own simpler sprite, tilemap, and menu classes to _sdl.video soon though, and share Flyboy with one modification. Deaths will shoot 100s of gibs instead of just 10.
transform_and_draw() for image or sprite class
I ran some sample renderer code and it pumped about 1.2 million static images per second. That is quite an improvement over CPU blitting. I created a simple sprite class with a transform() method to move, scale, rotate, etc each frame and the CPU usage climbs to about 35% at 1000 sprites and 30fps. Still not bad.
That's plenty of game objects to play with. But for particle effects a transform_and_draw method would probably speed things up significantly. Also, for tilemaps and other static images that scroll, some sort of global transform vector would be nice. This would greatly reduce the number of python calls necessary for objects that don't need to think or interact.
What might this look like?
global_transform = vector2()
for i in range(1000):
s = my_sprite.copy()
sprite_group.add(s)
for sprite in sprite_group:
image.SetTransorm(
offset = global_transform, # for scrolling
rotation = 1.2,
velocity = vector2(random(-2, 2), 7),
acceleration = vector2(0, -4), # gravity
scale = .995,
transform_for = rand(2500, 3500), # how long should the object transform for?
finish_group = dead_sprites) # where should the object go when its done?
while Playing:
global_transform.x += 1
sprite_group.transform_and_draw()
In this example 1000 sprites will be created. They will all move up, rotate, and shrink by 0.5% each time they're drawn. Downward acceleration will eventually make them fall. After approximately 3 seconds they will be removed from the sprite_group and added to the dead_sprites group. Until then they'll move at their own static x velocity between -2 and 2. The entire scene will also scroll slowly to the left.
Without documentation I can only guess what kind of color effects could also be done. The example code I used to build my sprite prototype had 'img.color[0] = int(255.0 * (.5 + math.sin(.5 * t + 10.0) / 2.0))' in it and made the UFO flash red like the old pallete effects that VGA games used to use.
Referenced 'GLOBAL' Transform
One last thought about transforms. What if an image or sprite had an attribute such as global_transform that referenced a dictionary with settings for angle, rotation, scale, velocity, etc.? This would allow many object's attributes to be changeD by changing the value in one dictionary. These transforms could then be applied in addition to its own transform attributes.
Must rects alway use ints?
I suppose there are good reasons that rects are made up of int values. This unfortunately requires game objects to have float attributes for their x and y positions, otherwise small velocities and accelerations won't work. Perhaps an image rect's x and y value should be stored as floats so I don't need methods like this:
class Sprite:
def SetPos(x, y):
self.x = x; self.y = y
self.rect.x = x; self.rect.y = y
One More step to a complete sprite class
Although particle effects don't need it, a fully featured sprite class will also need to change frames after a set delay. Creating an animations for a fully featured sprite might look like this:
new_anim = Animation([
{ delay: 10, frame: 2, move=(-2, 0), rotate=1, scale=1.1 },
{ delay: 10, frame: 3, move=(-1, 0), rotate=2 },
{ delay: 15, frame: 5, rotate=3 } ] )
ship.SetAnimation(new_anim)
This can be done in python for a fairly large number of game objects, but eventually the ceiling will get hit(a good lesson I guess). I realize pygame was meant as an approachable, lower-level framework. But why should students be forced to switch away from pygame in the middle of their school project beause a fully featured sprite class wasn't included and their own python code can't quite fulfill their dreams? If low-level image transforms are added to make particle effects and scrolling HD maps possible, why not add the last few bits for a full featured sprite?
Of course a smart teacher would make their students create their own sprite class and hide my hypothetical built-in until graduation day.
Comments
# # robertpfeiffer commented at 2019-07-21 15:15:35
I looked at the SDL2 code just now (that is the actual code in SDL2, not the SDL2 stuff in pygame), and you'll be delighted to hear that SDL2 can easily scale and rotate textures.
As for the other ideas, this seems to be mixing up high-level game objects and particle systems.
# # illume commented at 2019-07-21 15:17:50
Must rects alway use ints?
Someone would need to implement this. There are a few tricky things to consider however.
# # illume commented at 2019-07-21 15:19:49
Animations in the sprite class... a good idea. But again a big topic to consider how it is done. There are number of pre-existing attempts to add animation to sprite, and various implementations in games too. Additionally, css/html has some very good animation ideas.
# # illume commented at 2019-07-21 15:21:05
Transform and draw for sprite needs to consider how it can be done in software. Which often means caching, or using a rotozoom/transform function for performance.
# # illume commented at 2019-07-21 15:43:00
It sounds like scrolling maps with pytmx is lots faster with hardware acceleration.
That's a pretty good result :)
It would be interesting to know why pyscroll will be a bit more trouble? Maybe we can change the API to make it an easier port.
# # mcpalmer1980 commented at 2019-07-24 14:33:13
"As for the other ideas, this seems to be mixing up high-level game objects and particle systems."
I am certainly mixing them. In a low level language anyone could throw together a particle system that runs fast enough, whereas high-level frameworks and prebuilt game engines include a working system. But python is a glue languages only as useful as its pieces. Video.image is a useful piece, allowing transformations by simply changing is angle, source rect, color, and whatever. But changing these attributes for thousands of images every frame is unnecessarily slow in pure python, without any way for the average pygame user to speed it up. Implementing my suggestions for transform_and_draw would at the very least avoid the inefficiency of iterating through a sprite or object list twice, once for update and again for draw.
By granting the bare necessities of a particle system to the image class many interesting effects move into the average user's reach. Of course more dynamic game objects(players, enemies) would require users to develop their own high level game object classes, and the quick and dirty sprite class that I derived from pygame.Sprite and _sdl2.video.image proves that at least 1000 such objects can be handled.
There are many things pygame could and should never be. Not a fully fledged framework ready for AAA games like LibGDX or monogame. Not a ready built engine like Unity or Godot. But for hobbyists like myself who love python and lack the budget or development team to use 'better' (more difficult) tools, pygame is the perfect tool for realizing their dreams in code. Having over 25 years of amateur video and board game design experience has taught me a few things, among which is how to expand my ideas into a game that squeezes the most out of my own limitations, and how to make the most out of the resources available to me.
After considering all of the desirable additions to pygame that I can't fudge with a few dozen line of python, I factored it down to the lowest common denominator: transforming images and a fast tile blitter.
Must rects alway use ints? There are a few tricky things to consider however.
I've no doubt about tricky considerations, not the least of which is tons of legacy code that expects integers. On some level decimal rects don't even make sense. But for many common uses of my suggested transform_and_draw(), integer values may not prove precise enough.
Transform and draw for sprite needs to consider how it can be done in software. Which often means caching, or using a rotozoom/transform function for performance.
I think transform_and_draw would best be implemented in the video.image class where angle, flip, and scaling attributes already exist. Only sprites that use video.image and a rendering context would support it, so I don't think software blitting needs consideration. Whether sdl2 uses opengl or vulcan or directx, it still provides some sort of high-level access to the common transformations, and on 99% of devices, they will be hardware accelerated.
Animations in the sprite class... a good idea. There are number of pre-existing attempts to add animation to sprite
I'm sure many sharper minds than mine have creating pygame classes that outperform my own while providing a richer feature set. Most people would find fault with my methods as sure as I would of theirs. But MANY MANY more minds have probably given up halfway through trying. The truth is that most experienced users will create their own animated sprite class for their game objects unless the default ones were REALLY good, but providing such high-level sprites don't fit into the pygame design philosophy.
But if something like transform_and_draw is implemented, why not allow it to change frames in some simplistic way? It would provide a decent starting point for cool particles, gunshots, and animated tilemaps that can be extended or bypassed for more dynamic game objects.
It sounds like scrolling maps with pytmx is lots faster with hardware acceleration. That's a pretty good result :)
I caused a bit of confusion here. The numbers I quoted were not from pytmx or pyscrolll, but from my quick and dirty sprite class that draws animated and rotating images using all the new effects provided by the image class. I basically just extended the example code from another contributer. He had dropped texture images into a pygame sprite group to test blitting speed with 100,000 images and it ran at 60fps(1.2million total). I wanted to see how fast pygame could transform and animate them.
As for pytmx, I wrote a image loader that creates video.images for each tile in a tileset that is stored as a texture. That's what pytmx does. It loads all the data from a Tiled tilemap, including all the images.
Pyscroll does that actual drawing. My first attempt to drop in the video.images resulted in unexplained segmentation faults, so I dug a little deeper and tried to wrap my head around pyscroll's methods. It uses a large surface as a buffer and only blits the cells that have changed due to scrolling. Porting it may be as simple as replacing the surface buffer with a texture that uses the target flag, but given the unfinished and undocumented renderer api, I decided it wasn't worth continuing that experiment at this time.
I will probably port my own, much simpler tilemap class to video.images as an experiment, but even that's not ideal. The renderer can pump out plenty of tiles to fill the screen, its just a matter of providing a global transform vector to handle scrolling, and an efficient way to iterate over only the visible tiles. I see no reason to use a buffer at all.
It would be interesting to know why pyscroll will be a bit more trouble? Maybe we can change the API to make it an easier port.
I noticed that _sdl2.Renderer has a blit method for compatabilty but Texture and Image don't. Most legacy code blits into some sort of buffer surface, or at least into the display surface. If textures had a compatibility blit method I could replace the surface with a 'target mode' texture and everything should work. Instead I will have to replace the blits with draw commands. I'm trying to finish and upload my latest game, Alien Park, so I haven't experimented with this yet.
I'd prefer go port my own simpler tilemap first because then my 3 pygames would be running on a renderer. Flyboy is and Alien Park will be a complete, in-depth, and well-polished game. I think seeing them rendered on pygame2._sdl2 will be more valuable to the developers than the sample code that comes with pyscroll.
# # mcpalmer1980 commented at 2019-07-27 12:34:38
complication with video.image and sprite classes
Currently the image class stores a texture region and various attributes such as angle, flip, etc. Dlon proved that images could be used in a sprite group for batched rendering and I extended his work with a real-world animated sprite class to toy with. It uses a list of rects to store frames from the spritesheet and sets the image's srcrect to the desired frame in its SetFrame and NextFrame methods(sorry about the camel case but I've been using it too long for anything else to look right. Sometimes I feel like refactoring the entire pygame api to use it).
I considered swapping out the rect list with a list of image instances for each frame, hoping that would solve my problem setting the image's origin where it needs to be so that the sprite rotates from its center. I expected 'self.image.origin = self.image.srcrect.center' to do the job but it doesn't.
Unfortunately creating a list of images for a sprite presents a new problem. Since the transform attributes exist inside the image, every time I change the sprite's frame I would have to set its angle, flip, and color attributes as well. A small issue, I suppose, but one that would effect the implementation of my desired transform_and_draw() method, as well as any sprite class that draws video.images
My sprite class works fine using rects except for the rotate origin issue mentioned above, but it uses almost as much CPU time to update and render 1000 sprites than dlon's example did to render 100,000. This is why I requested a transform_and_draw feature to be added.