Skip to main content

Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines

How much did you struggle going from desktop to Web version? --> a real drag for me

A topic by jerkyhips created 18 days ago Views: 344 Replies: 14
Viewing posts 1 to 7

Hi everyone!

Recently I've come to understand that having to download a game to play / test it is actually a major friction for a LOT of people. It even seems that less and less people around me own a PC and those who do often only have a PC from their company, on which they can't or don't want to download or install anything.  Fair enough, it means it was time to get on with the "Web conversion" of my game. Now what I thought to be a walk in the park (given the relative simplicity of Bouncenoid) turned out to be much more annoying than anticipated - and for very different reasons I expected.

I guess it all starts with the dev framework you're using. In my case I started off with pygame and while this may be a good choice for simple games not necessarily meant to be shared/published widely, I'm starting to think that maybe right from the start I chose the wrong path... The good thing is that with pygame comes pygbag, which is a python package specially made to create a web version of pygame python games. And sure enough, after a few tweaks like adding "async def main()" to allow for asynchronous execution, Bouncenoid appeared in my browser... yet something seemed to be terribly bad:

  • The soundtrack was horribly crackling
  • Every screen felt sluggish like the browser was grinding almost to a halt, even for screens with hardly nothing going on.
  • The game was virtually not playable as everything happened in slow motion.

What the hell? I mean I've seen games out there on browsers like super busy shooters with dozens of moving pieces, many meters going on, etc. and I could play fine. So how was this possible ?

Well, I have spent hours investigating to understand where the time was spent in the code and why it was costing so much resources when it played just fine on the desktop version. And it's not pretty.

The thing is that there is so much operations that we just take for granted on a desktop, especially display routines that we just assume would be the same inside a browser, but that's not the case - at least for me. And I'm not even talking about bits of the code where operations were done 60 times per second just to display a static image with no cache (yes, I know, I got it eventually). I'm talking about just displaying an image with some alpha or transparency. That turned out to be a huge cost for the browser when using the pygame functions, even with some caching going on. A blit with alpha can cost by itself 15 to 25 miliseconds, for a total budget of 16 ms to reach the 60 fps target. One of the reason is that the rendering with pygbag/Emscripten is done via SDL2 software, with no hardware acceleration.

Another point is that each python function call has a significant cost with the python interpreter compiled in WebAssembly (pyodide) compared to the native CPython. Some many "small" calls, like checking on a property 60 times per second for each brick when there can be 100+ bricks will cause the browser to sweat but is unseen on desktop.

I was expecting at first that calculating the physics, the collisions, etc. would be what could be costly. Not just displaying simple graphics.

Anyway, long story short, I spent a very long time working to get the game back to a level of performance that feels normal compared to the desktop version. Squeezing performance out of every part, caching as much as possible, pre-calculating things, etc. And eventually got there, but what a drag it was for something so simple visually.

Is it just me? Is it pygame ? Or is it the normal thing you have to get through when migrating from desktop to Web version ? I'm curious, really! 

(+1)

I use Gamemaker and the HTML5 versions also suffer from bugs and crappy performance.

Good luck to you man! I've managed to sort out most issues and I'm about to release. Now that it's done I'm obviously quite happy, but I spent a lot of frustrating hours on that job.

(1 edit) (+1)

You’re using Python, an infamously slow language, inside of another emulated language, and the way you draw is slower than even just using canvas’s builtin drawing functions.

The physics engine is definitely the fastest here, considering it doesn’t have to loop anything per individual pixel.

Sorry, but there is nothing surprising here. At least one of the two things I mentioned have to go.

Thank you mid for explaining and confirming what is happening. I think I've managed to sort out most issues. But if I want to evolve the game much further, I'll have to reconsider the framework, which would mean a full re-writing of the code - at least to maintain a Web version... not sure I have enough motivation to do that just right now, but at least I'm warned ;-)

(+1)

I use HTML5, JS, and CSS to code. I got lucky, and only need to directly upload my files. Going from web version to downloadable is actually difficult for me.

Interesting to know that going the other way round can also be challenging. Good luck to you, then!

(+1)
Going the other way has one trap that costs an evening if you have not hit it before. An HTML5 game wrapped for desktop usually ends up loaded from a file:// URL rather than from a local http server, and fetch and XMLHttpRequest are both blocked on file:// origins. So if your game loads anything at runtime, an audio file, a texture atlas, a level JSON, those calls fail while the rest of the game runs fine. The window opens, the loop runs, and you get a silent game with missing art rather than an error pointing at the cause. Two fixes, either works. Serve the files over a local http server inside the wrapper, which is what Electron and Tauri will do if you point them at a server rather than at a path. Or inline the assets as data URIs at build time so nothing is fetched at all. We hit this on our own build and measured it both ways: identical bytes, correct over http, silent and textureless from file://. Worth knowing before you package rather than after.

Thanks for sharing that tip!

(+1)

I have several games created with Pygame and exported to the web.

As others have mentioned, Python is a high-level language, designed to be easy to use, but it's not hardware-optimized like some other languages, and Pygame, depending on your game's resolution, requires a lot of CPU cycles.

You can optimize by vectorizing, using parallel processing, or leveraging GPU acceleration, but all of these tend to cause problems with Pygbag. So, the truth is, not every game programmed in Pygame is playable on the web.

In my experience, the same game that runs at 165 FPS on the desktop version will drop to 30 or 20 FPS on the web version,  If you're going to use Pygbag, it's best if your game has a low resolution or is a slow-paced game, like an interactive fiction game.

Regardless, you should be aware that the performance you can achieve in web games will always be worse than what you can get natively. If your game is very demanding, you'll need to find a language that's well-optimized, and you'll likely have to make cuts or optimize your game.
(+1)

Thank you so much for sharing you experience, it's not just me then 😅 Anyway now that I'm done with the web version that I will release today, I'm not so sure about developing the game much more with the current framework. I mean small improvements/patches maybe, but now I'm aware about these performance issues, I'll think twice.

(+1)

Not just you 🎮 Though mid and hechelion are right that the interpreter is the biggest single term in your particular case. I came at the browser from the other side — Three.js, JS all the way down, realtime multiplayer — and I still hit a version of this, so it’s worth separating “Python is slow” from “the browser is a different machine”. The second one bites everybody, and it’s the part that will still be true if you ever do rewrite in something faster.

The bit of your first post that nobody picked up is the crackling soundtrack, and I think it was the most useful signal you had.

Browser audio crackle is almost never an audio problem. It’s a buffer underrun — the audio callback didn’t get its next chunk of samples in time, so the hardware plays whatever was in the buffer. And in an Emscripten/SDL build the mixer usually shares the main thread with your frame loop, unless it’s specifically been moved onto an AudioWorklet. So a frame that blows its budget starves the audio in the same instant.

Which means the crackle was never a second bug sitting next to the slowdown. It was the same bug, and it’s a better instrument than an FPS counter, because a frame counter averages and an audio buffer forgives nothing. If you ever go back into this: loop a constant tone and walk through every screen. Everywhere it buzzes is somewhere you’re over budget — including places where an averaged frame number will tell you everything is fine.

Two other things that are browser-specific rather than Python-specific, in case they’re useful when you pick the next framework:

Check what resolution your canvas is actually rendering at. If anything in your stack sizes the drawing buffer by devicePixelRatio, then on a HiDPI screen you’re pushing 4x the pixels (9x on some phones) for an identical-looking image. It’s worth confirming because it’s invisible in exactly the wrong way — if you develop on a standard-DPI monitor you will never see it, and your report will be from players on laptops saying it’s unplayable while it’s perfect on your desk. Clamping that multiplier is usually the cheapest large win available in a web build.

The download didn’t go away, it moved. You went to web to kill install friction, which was the right call. But the payload still has to arrive, and now it downloads while the player watches a blank canvas with nothing to look at and no commitment made yet. Install friction at least happens after someone has decided they want the thing. A slow first load happens before. So on the web, payload size is a conversion number, not a performance number — which is a strange thing to get used to, and it argues for a loading screen that shows something the moment it can rather than at the end.

One question, since you’ve just been through it and the answer is a line: did the crackling and the frame rate get better together as you optimised, or did you have to fix them separately? If they moved together that’s the main-thread story above, and it would be good to have it confirmed by someone who actually measured it rather than just me asserting it.

Wow, thanks a lot for taking the time to write a detailed reply, I really appreciate this.

And you are spot on with the crackling sound and performance going together. I first increased the buffer in pygame.mixer.pre_init to 2048 instead of 512. It did improve the sound but not completely. And then I spent a lot of time measuring the FPS and the time spent in almost each function to better understand where the "work" was being done... With regards to the sound, I think in there is still one particular screen where you can hear a subtle "hicup" the first time you display it, but the sound issue went away as I fixed the under-performances in most places. I know it's not perfect yet, but I think (I hope) it's good enough for now.

However, I had (and still have) plenty of ideas for the evolution of the game, and I have to say that this "webification job" dampened my enthusiasm... Now at least I'm really getting that a browser is not behaving like a desktop 😅

Thank you about the warning for the canvas resolution, I think it's really key.

I agree about the fact that the friction moved going from desktop to Web. And I wish that the loading was a little faster (though I also tried to squeeze this down to some extent). I'm using a template file to control at least the colors of the background and text displayed. I definitely could do better with the loading screen to make sure potential players don't go away before it's ready... but I think it's not so easy to control what is displayed very early on.

In any case, thank you again the explanation. It's a relief actually first to understand what happened and second to know that other dev experienced similar difficulty.

(+1)

I never managed to make web conversions of my games. For now they all are: download, unzip, play. No install ! To remove the game, just delete its folder.

Yeah, that's how Bouncenoid works as well for desktop, download, unzip and play. But several people told me they either don't want to download the game or they don't own a PC or their PC is a work PC and they're not allowed to download anything... that's why I got into the troubles of going web. Thanks for the reply, I'll check out your games if I can.