A few months ago, I optimized blitting a lot. But wasn’t really satisfied with the results. I thought of giving up on pygame entirely. Yes, there is that experimental SDL2 wrapper to try but I was a bit disappointed with pygame’s rendering capabilities altogether so didn’t pin my hopes to it. I went looking for other options including looking at other engines. But before making any decisions, I decided to give pygame one last try.
Before diving into the SDL2 wrapper, I wanted to see if I could squeeze even more NPCs onto the map while maintaining a stable FPS using blitting alone (tried both blit() and pygame-ce’s fblits() which renders in bulk). The idea was simple: only render what’s in the viewport. There’s no need to calculate positions or blit thousands of NPCs that aren’t even visible on screen. Here’s a quick sketch of what I was aiming for.

The goal was to determine which grid coordinates were visible to the player and render only the NPCs in those areas. Sounds straightforward enough, right? Below is a short animation of my first attempt…
In the end, I managed to render 18,000 NPCs on the world map while keeping only 3,000 visible to the player, resulting in 70 FPS. Not bad, but not exactly a breakthrough either. Have a look at the final result below.
This technique will definitely be useful later, but blitting is still too slow. Recall that cProfiler already showed the biggest offender being Surface blitting. Even without any game logic or animation, I’m already eating into my FPS budget. Once I start adding actual gameplay mechanics—like pathfinding and animations—there won’t be nearly enough room left. Time to see if SDL2 can handle things better.