I'm crowdfunding my newest game: reTux (new Mario-inspired platformer)


Wow, that seems to be REALLY unoptimized.


SuperTux even runs fullspeed on a GP2X, which is... don't know, 5 times slower (or more?) than the Pandora?


Additionally, Barbie Seahorse Adventure is also written in Python / PyGame and also runs fullspeed on the Pandora.


So something sounds really wrong here, have you ever tried profiling the code to find out what the issues are?


I guess so far the game doesn't look impressive as it uses the graphics and music from SuperTux but runs a lot slower.


I know you added a lot more features, but most people who only take a glance and know SuperTux will probably complain about it.


Then again, with IndieGoGo, you can keep every cent you got, even if you miss the goal... :)
He keeps talking about ethical stuff, but this guy is full of shit ;) Hmmm... what is the word I am looking for to describe this kind of people? Hypocrite? ;)
 
Why another Mario-style platformer for the Free World?

Platformers that exist that are fully or partly FOSS:

SuperTux (Mario-style)

Super Maryo Chronicles (Mario-style)

Mari0 (the original Super Mario Brothers but with Portals! -- legally dubious, but meh)

Frogatto & Friends (I suppose that this is a bit like a cutesy Metroid-clone?)

Abuse (Run, Jump & Gun shooter platformer, originally commercial but now public domain for the shareware version)

Commander Keen: Keen Dreams (source code released under GPLv2)

XEvil (Not sure what to compare it to, but it has been around for a long time)

What does seem to be missing, in terms of platformers, is games like any of the following: Sonic the Hedgehog/Jazz Jackrabbit, Prince of Persia/Aladdin. Perhaps you would benefit the Free World more by looking at some of those?

Other game types that are difficult to find as FOSS, and which require little in terms of run-time speed (since your engine seems to lack that):

First-persion perspective, turn-based dungeon-crawlers/RPGs like Might & Magic 3 to 5 (especially World of Xeen) or like Wizardry 1 to 5 or like Wizardry 6 and 7;

Top-down/isometric/axonometric third-person turn-based RPGs like Fallout;

Multiple-perspective turn-based RPGs like Pool of Radiance (and other Gold Box games), like the Realms of Arkania series etc.;

Point & Click Adventure games in general.
 
Last edited by a moderator:
The Pandora has 512MB RAM.
I speak of the classic Pandora, which has 256.

512 MB is normally sufficient, though only just barely. 750 MB is the maximum amount of RAM I've witnessed reTux using at one time. 256 MB is barely enough to even get the game to start up; I had to check the FPS from MiniMenu because there was not enough RAM available with Xfce running at the same time.


That is enough to store 349 full screens (800x480) worth of pixels stored in 32 bit RGBA. It should not be a limiting factor at all, unless you're doing something quite horribly wrong from efficiency point of view.
Keep in mind that Python uses garbage collection, not manual memory management, and garbage collection does not happen immediately. Additionally, the Pygame SGE caches a lot of things for a short time to avoid wasting CPU (for example, text which is projected onto the window is cached so it doesn't have to be generated again every frame). Basically, RAM usage is wildly imprecise and can vary due to all sorts of factors.

I don't know for sure whether RAM usage could be made more efficient without adding more load to the CPU. But I consider this to be of low importance right now. In the past, there was a serious problem with RAM usage because of caches not being emptied properly, but the current RAM usage stays at a level which most machines should be perfectly capable of handling.


Why and how does SGE prevent pygame using it's optimizations?
Because they require making assumptions the SGE can't make, or having fine-tuned control over low-level details the SGE doesn't provide an interface to.


And why is your collision check so complex? Do you do pixel based collision checks? Otherwise it should be quite fast - especially in a jump and run.
No, that's not the problem. The problem is loops. The loops that match up pairs of objects for collision detection are one example. If I execute code too much, making that code more efficient isn't going to solve it. I mentioned before that a simple property was once the driving force behind slowdown according to a profiler, which meant that the primary cause was simply Python's overhead for a function call. It turned out the way I had to fix that was to stop having several thousand individual objects that were just sitting there and being a static part of the background, because that was requiring the SGE to loop through them all and blit them onto the display surface one by one, every frame.


That's under a non-commercial only license. It's not libre.
 
I didn't not create a Vimeo account because of "hassle". It's entirely because of ethical grounds.
- trying to get $50.000 for a (technically horribly bad performing) average platformer using mostly (only?) stolen assets from other people

- talking about ethical grounds

This made my day.
 
Last edited by a moderator:
I'm not using the assets in violation of their license. I've even talked to the SuperTux developers about this, and they don't mind. They just requested that I link back to the SuperTux website or Git repository so that people coming across reTux become potential SuperTux contributors.
 
That memory usage is ridiculous.

Python uses reference counting for memory management. The garbage collector primarily runs cycle detection algorithms to determine if there are unreachable cyclic structures. If you avoid cyclic references as much as possible then memory is freed the very moment it goes out of scope.

Is each object in your scene storing all of its frames or are you sharing those frames across similar objects? It really sounds like you have some explosion from duplication going on.
 
I haven't investigated much, but I believe it has to do with the way resources are handled. They're all pre-loaded, and then they end up getting duplicated as a result of speed optimizations. (For instance, every level contains a couple tile layers, which become one giant image each.)
Uh, does this mean you store the entire level at once into the RAM instead of the usual "tiling" methods that classic 2D sidescroller do? I mean they are tiled for a reason to only load what's visible on screen. What format are the graphics? 8 or 16 or 32 Bit? Does the game makes heavy usage of alpha blending maybe?But like already mentioned, even in the worst case this should run somehow better on the Pandora. Well, except maybe from the alpha blending stuff, even on todays PC's this can drop the FPS noticable, I know more than one game where effects like rain etc. drop the FPS, sadly.
 
Last edited by a moderator:
Uh, does this mean you store the entire level at once into the RAM instead of the usual "tiling" methods that classic 2D sidescroller do?
The entire room, more accurately. Levels can be composed of multiple rooms (and some of them are).
What format are the graphics? 8 or 16 or 32 Bit?
They're all 32-bit (with alpha transparency).
Does the game makes heavy usage of alpha blending maybe?
Yes. All transparency is done with alpha blending. Actually, getting rid of this in the cases where this is possible is a good idea. I've done so for some background layers that didn't need transparency at all; this raises the frame rate by 3 FPS already.

EDIT: Did so, and the total gain is around 3-4 FPS. Not much, but not insignificant, either. (Of course, almost all of it was from getting rid of unnecessary transparency in the backgrounds.)
 
Last edited by a moderator:
50,000 is a shitload of money to ask for. If you ask for too much money people may cotton on to this and your campaign won't succeed, Imo you need to ask for the right amount.
 
Literally scale, or "scale" in some metaphorical way?


The SGE Game Engine handles (literal) scaling just fine. "Smooth" scaling is a bit slow, but not tremendously. With the window maximized, I get about 25 FPS instead of about 37. With simplistic scaling, it's less of a slowdown; when I enable an option I have in reTux called "--scale-basic", which disables smooth scaling, and maximize the window, I get about 31 FPS (again, compared to 37 FPS unscaled).


Also worth noting is whatever benefit there is to using fullscreen on the Pandora (note: reTux's resolution is 800x448, so fullscreen on the Pandora is unscaled), it doesn't noticeably affect frame rate.
 
Last edited by a moderator:
Crowdfunding aside, your engine seems to have a few conceptual problems here and there.
That RAM usage is horrendous for the on the screen for starters.
Are you saying that you render the whole room on a surface and render that every frame instead of rendering the tiles? Ouch. Not sure how smart SDL is there and it probably skips pixels out that won't land on the screen surface, but that's still at least tons of looping over transparent pixels.
Are your last fps numbers from the Pandora version or from your PC? If it's the latter, then that's not really good.

This is SDL1 software rendering, right? If so, alpha blending is no fun on the Pandora there. You might want to have OpenGL running there.
Oh and iirc the Pandora screen runs on 16bit by default, so there could be on the fly bit depth conversions going on every frame too.


50k is a lot for what you're showing so far to be honest. A playable demo wouldn't have hurt either.
 
Are you saying that you render the whole room on a surface and render that every frame instead of rendering the tiles? Ouch. Not sure how smart SDL is there and it probably skips pixels out that won't land on the screen surface, but that's still at least tons of looping over transparent pixels.
It's much faster to blit a giant room-sized image once than to loop through several thousand small images, check to see if they are within a view, and blit them individually.

This is the code that does that:

Code:
class RenderedTiles(sge.Object):

    """
    An object of this class takes all :class:`xsge_tmx.Decoration`
    objects which:

    * Have the same Z-axis value as this object;
    * Have a sprite with only one frame;
    * Are inactive;
    * Are not tangible;
    * Have no speed;
    * Have no acceleration;
    * Do not in any way reside outside of the room.

    It then draws all such objects' sprites onto a single room-sized
    sprite, which becomes this object's sprite, and destroys the
    objects.

    If such objects do not exist, this object is destroyed.

    Effectively, this eliminates slowdown caused by looping through
    hundreds or thousands of objects representing tiles.
    """

    def event_create(self):
        rw = sge.game.current_room.width
        rh = sge.game.current_room.height
        my_tiles = []
        for obj in sge.game.current_room.objects:
            if (isinstance(obj, Decoration) and
                    obj.z == self.z and obj.sprite is not None and
                    obj.sprite.frames == 1 and not obj.active and
                    not obj.tangible and not obj.speed and
                    not obj.xacceleration and not obj.yacceleration and
                    obj.x - obj.image_origin_x >= 0 and
                    obj.x - obj.image_origin_x + obj.sprite.width < rw and
                    obj.y - obj.image_origin_y >= 0 and
                    obj.y - obj.image_origin_y + obj.sprite.height < rh):
                my_tiles.append(obj)

        if len(my_tiles) > 1:
            self.tangible = False
            self.sprite = sge.Sprite(width=rw, height=rh)
            self.sprite.draw_lock()
            for obj in my_tiles:
                if obj.visible and obj.image_alpha:
                    spr = obj.sprite.copy()

                    if obj.image_blend is not None:
                        bspr = sge.Sprite(width=spr.width, height=spr.height)
                        bspr.draw_rectangle(0, 0, bspr.width, bspr.height,
                                            fill=obj.image_blend)
                        spr.draw_sprite(bspr, 0, 0, 0,
                                        blend_mode=sge.BLEND_RGB_MULTIPLY)

                    if obj.image_alpha < 255:
                        bspr = sge.Sprite(width=spr.width, height=spr.height)
                        bspr.draw_rectangle(0, 0, bspr.width, bspr.height,
                                            fill=sge.Color((255, 255, 255,
                                                            obj.image_alpha)))
                        spr.draw_sprite(bspr, 0, 0, 0,
                                        blend_mode=sge.BLEND_RGB_MULTIPLY)

                    if obj.image_xscale < 0:
                        spr.mirror()

                    if obj.image_yscale < 0:
                        spr.flip()

                    spr.width *= abs(obj.image_xscale)
                    spr.height *= abs(obj.image_yscale)

                    origin_x = spr.origin_x
                    origin_y = spr.origin_y

                    if obj.image_rotation % 360:
                        spr.rotate(obj.image_rotation)

                    if obj.image_origin_x is not None:
                        spr.origin_x = obj.image_origin_x
                    else:
                        spr.origin_x = origin_x

                    if obj.image_origin_y is not None:
                        spr.origin_y = obj.image_origin_y
                    else:
                        spr.origin_y = origin_y

                    self.sprite.draw_sprite(spr, 0, obj.x, obj.y)

                obj.destroy()

            self.sprite.draw_unlock()
        else:
            self.destroy()


Oh and iirc the Pandora screen runs on 16bit by default, so there could be on the fly bit depth conversions going on every frame too.
All images are converted to the format of the screen, if possible, when loaded into memory. That being said, the Pandora's native depth being 16 bits probably does result in more slowdown when alpha transparency is used.
This is SDL1 software rendering, right?
It's using hardware acceleration, but this doesn't make that substantial of a difference, usually, because most of the chokehold on speed is Python code.
 
Literally scale, or "scale" in some metaphorical way?
I mean to scale in the sense of algorithms - rendering more tiles for example.  The way you only recently made it so it wouldn't run collision detection checks between two tiles that weren't moving because they're background, for example.  Before that, your complexity for just that part of the code would have been an O(n squared) problem, I suspect, and if that makes up an at all significant of your runtime, that won't scale well as you increase the number of tiles on screen.  Theoretically, any algorithm that has a component that is O(n squared) in it will tend towards O(n squared) complexity overall (I think, or worse) as n tends to infinity, though in practice as long as you don't try to plot infinity tiles, you tend to be okay ;)
 
It's much faster to blit a giant room-sized image once than to loop through several thousand small images, check to see if they are within a view, and blit them individually.
You don't need to check any more tiles than are on screen, because they're on a grid and you can use some simple math to see which x and y ranges of tiles will be visible.

When I made my platformer though, I loaded each level chunk into one of LOVE2D's SpriteBatches. This way I can draw everything with one draw call, but I don't have to keep a giant image in memory. It just pulls from the tileset texture based on the texture coords of each tile. There is still a giant mesh, but it takes way less RAM than a giant image. An image of the level wouldn't even fit in GPU memory of the Pandora, I expect, and it wouldn't be a square POT texture, so blitting it would be incredibly slow.

And it seems to run fine on Pandora, especially once I remembered to turn off alpha blending for the background.

750 MB for a SuperTux level is ridiculous.
 
Last edited by a moderator:
It's much faster to blit a giant room-sized image once than to loop through several thousand small images, check to see if they are within a view, and blit them individually.
 You don't need to check any more tiles than are on screen, because they're on a grid and you can use some simple math to see which x and y ranges of tiles will be visible.
This. PandaBAS does this for tilemaps, including rotation and scaling of the map on-screen and maintains speed even on a CC Pandora, despite having been written in pure Pascal.

It really is trivial to do. If you need to be able to track sprites that are off-screen this can still be done on a sprite-by-sprite basis; the saving in CPU time from reducing map calculation overhead leaves more than enough for this and let's face it - there's far less sprites than map tiles in a level.

D.
 
If tile assembly is really that expensive (dunno how much calculation goes into it) you could always go for a striped approach, i.e. group tiles into larger surfaces that have level height, but are only a few tiles wide (16 or something). That way you can combine the benefits or prerendering and culling.

Edit: "benefist", while a funny word, is not what I wanted to write ^^
 
Last edited by a moderator:
Using the NEON blitters from notaz' omapfb SDL will also significantly speed up blitting speed.

But if the problem is not in the blitting, then where is it? In the game logic? If a "physics engine" for a 2D platformer needs that much RAM and CPU, then something is quite horribly wrong. Some kind of excessive copying/garbage production maybe, in a way that gives the garbage collector a lot of work. Or perhaps an algorithm with a highly nonoptimal time complexity.

Looping quadratically over a few thousand things is probably not something you should need (there should never be that many dynamic things on-screen, unless you're making something like nanoLemmings ;) ). But if you need something like that, then you better make sure that the innermost loop is executed efficiently, i.e. no copying of large objects, etc.

The whole point of a game engine is that it should be efficient. Even if the Pyra is able to just barely run it, at 100% cpu usage and highest clock speed, then that would still kind of suck, because it would mean the battery would get drained faster than necessary -- a 30fps 2D platformer should be a piece of cake for a Pyra.

So you really should consider to rewrite the bottleneck parts of your engine, in some lower level language that allows you more control over what is actually happening. Especially in terms of memory management. E.g. garbage collection kicking in at unexpected points in time will inevitably cause hiccups.
 
You don't need to check any more tiles than are on screen, because they're on a grid
No, they're not. The "tiles" are actually regular objects; they are given pixel coordinates, and multiples can exist on one location.

That being said, it might be a useful idea to explicitly split objects into regions so that they can be quickly found based on position. That would effectively generalize the current "collision areas" feature of the Pygame SGE and make it accessible to the user. I'll have to investigate this possibility.

It may also be that supporting true tiles would be a good idea, perhaps as an alternative background layer class.


The whole point of a game engine is that it should be efficient.
I don't agree that that's the whole point. Another point is that it should be easy to use.
 
Back
Top