Real time NumPy (and ghosts!)
After playing with the boids example I posted some time ago (https://forum.omz-software.com/topic/2393/beta-boids-with-a-crash) I started to wonder how many sprites I could have moving around in Pythonista on my iPad. I thought it would be fun to try to use NumPy to achieve higher speed. Perhaps I will implement boids with ndarrays some day, but I started with something simple.
Here's my little script: https://gist.github.com/offe/7269e9e3cff6ac88b584
It is 350 semitransparent tinted ghosts moving around on the screen at 60 fps (on my 1st generation iPad mini). If you hold your finger somewhere on the screen they will move towards it.
It looks like this when it's run:
All of the calculations for the movement is done on NumPy ndarrays. The sprite postions end up in self.positions, an ndarray in which each line is the position for a sprite. Then the sprite node positions are updated like this:
def set_sprite_positions_from_ndarray(self): ps = self.positions for i, sprite in enumerate(self.sprites): sprite.position = ps[i]
Does anyone have any ideas how to increase the number of sprites (SpriteNodes, with same movement, at full frame rate)?
I was a bit surprised that when I tried doing nothing in the update method I could only get up to 600 sprites at full frame rate. 600 is a lot, but I was still expecting more since almost no python is being executed. Why is that? You can also see in the code that the entire position update calculations in NumPy is about as time consuming as the simple loop to update the nodes' positions. It illustrates the fantastic performance of NumPy with its ndarrays.
You can also see my failed attempt at profiling a Scene. I guess the work actual work is done after run() has returned, but I can't figure out how to do it instead. Does anyone have an idea how to profile a Scene to figure out where the time is being spent?
if you're using xCode you can see individual threads and %load in real time as your app runs - hope that's helpful.
But I don't understand the purpose of the first line in update_positions().
self.vels *= 0.8 ** self.dt
What is 0.8, and why is it raised to the power of time elapsed?
That's "friction". It slows down the ghosts, if there were no other forces they would loose 20% of their velocity every second. Try running the code without it!