Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
Imported image as Scene background
-
O.
self.add_child(projectile) did the trick.
Thanks for the help @JonB
I will stress-test it tomorrow on my lunch.
Well, just found out that the number of fragments on-screen can easily be > 1000.
The performance seems much better already, I just need to make sure the fragments leave the scene’s view, otherwise they just sort of sit there for a while haha. -
Alright, I tried. It isn’t terrible until you start ramping things up.
Is there a way for me to copy/paste or upload my code (it’s > 32000 chars) so that it can be reviewed as a whole?I am still experiencing huge slow-down. Again, this might just be a limitation of Python and my iPhone pro max, but I am not sure. But I can upload my code if there’s somewhere I can do that.
-
@Robert_Tompkins, some git repository like Github is a popular choice. With a client app like Working Copy you can get nice version control flow for Pythonista.
-
From the wrench menu, share, share to Gist -- this will post a gist, then copies the link to the clipboard.
-
Incidentally, you might try using collections.deque instead of list -- it is designed for this sort of thing and is probably a little faster and more memory friendly (doesn't reallocate)
-
Great, thanks. I will try swapping out my lists later today or tomorrow.
I might also need to play around more with the whole ‘parent’ side of things, since I feel like that keeps hanging me up. I could benefit a ton by understanding it fully.
I’ll paste the instructions I typed out yesterday before I found out I couldn’t upload it here lol.If you have an iPhone 11 pro max, or Can resize the scene for your device easily.. feel free to try it out and experience it first hand.
If you tap Load Profile and enter ‘ADMIN’ or ‘SUDO’, you can specify a stage number and start with enough cash to upgrade either weapon plenty.I removed most of the sounds and most of the explosion textures, etc to narrow down the cause.
I also commented out all the unused functions/code to make it easier to navigate.
But it isn’t perfect haha. I typically clean my code up once I am happy with the functionality, which I haven’t achieved yet.Below you will find the program, as well as a modified game_menu.py that I modified to allow me to resize the menu by passing in additional arguments. I believe I only needed this functionality when I was recording and displaying a leaderboard. Anyway.
Make sure you name the game_menu as follows:
GameMenu.py
If you have issues running it, let me know. I think I added handling in there to take care of any custom-made items that others may not have on their device but could have missed something.
And no guarantees it will work/compile at all!
But it does work on my iPhone 11 Pro Max iOS 13.0 with Pythonista version 3.3 (latest version on Apple App Store).Here is the Main Code
https://gist.github.com/dad61ed3b8a5a83cd72f4acc5932db42Here is the modified menu code
https://gist.github.com/56ee84710967319b7bc4e8904d9391cf -
@Robert_Tompkins Im playing around with it but notice a few things:
It will probably be a little better to predefine your Texture's at the start, then pass the Textures to your SpriteNodes, rather than an image name. That saves a few millisec per call, on my old iPad anyway, but probably more important, I think it saves memory, though I can't prove it.
In other words, outside of init:
MeteorBrown=Texture('spc:MeteorBrown')
Then inside of MeteorBrown init
SpriteNode.init(MeteorBrown,...)I notice that you are still adding/destroying some meteor classes, and maybe doing add_child earlier than you intend.
I can't help but think that there might be a cleaner super class of your various Meteors, such that you don't need separate logic for each type, and can maybe just have a meteor factory that takes an integer/enumeration.
It would be useful to add some logging calls to see which methods called during update are taking the most time -- is it spawning? Collision detection? Something else? One could imagine that if you are doing collision detection on N projectiles against M meteors, that is going to add up quickly -- essentially O(N*M). So one key to speed is going to be to figure out a way
to get down to more linear performance -- for instance if rockets only travel along vertical paths, there may be a way to group projectiles based on x position when they are created based on the range of x that they will see over the screen... then that might be a faster downselect of which projectiles you need to consider for a given meteor .
(Or, some sort of kdtree type approach to finding nearest neighbor, and only checking the nearest neighbor projectiles to each meteor -- kdtrees can be a lot faster than computing all pairwise distances, and there are some pure python implementations, or perhaps aa numpy implementation). -
@JonB Awesome, thanks for taking the time to play around with it!
I will look into what you mentioned about the meteor init and try it out.I did notice an improvement when I added the fragments into a list.
But the moment I did the same for the meteors, I noticed that things start slowing down a ton, even without a bunch of fragments on screen.
For example, using the laser weapon, I still see the ‘slow-mo’ movement. I also disabled all sound except the weapon firing to help me listen for changes in performance. I can hear it bogging down for sure.I did add some logging to determine the time difference between creating a huge list and then popping everything from the list with the variable being using a ‘deque’ and using a standard list. Did the same with a dictionary. Funny enough, list seems faster. I tried to use it the way it was intended (fast access/modification from either end of list) but still saw the list out-performing.
I am in the process of simplifying my meteor creation and removing unnecessary calls, arguments, etc. so once I have it barebones, I will look at it from a ‘meteor generator’ perspective.
The other idea I had was similar to Chicken Invaders. Where instead of meteors, it was a set amount of enemies on screen, that just moved back and forth slightly, dropping danger until they are destroyed. Then a new stage is presented, etc.. This would limit the number of ‘items’ on screen, or at least define the exact number being presented. But this would require a lot more work, and I would want to clean everything up before I do that anyway.
Thanks for the ideas and feedback! I will add some more code to figure out what might be eating up resources. I did try multi-threading on each collision method, this may have improved performance slightly, but had some ‘pop from empty list’ errors, understandably.
-
This is NOT inside update(), it is in setup. Just for reference.
Time to add 5000 MiniRockets List: 0.2149660587310791These are all inside of update.
Max Item Collision execution time: 0.001150369644165039
Max Laser Collision execution time: 0.026622772216796875
Max Spawn Item execution time: 0.0004971027374267578
Max Should Fire execution time: 0.0007219314575195312The max value is a global variable and is only overwritten if the current call is > Max.
It looks like Laser Collisions are the worst, with Item Collisions coming in second.
Let me know if you have any recommendations on improving the laser collision checks. Aside from what you already recommended, of course.Here is how I’m recording it:
def update(self): global maxExecutionTimeItem global maxExecutionTimeLaserCollision global maxExecutionTimeSpawnItem global maxExecutionTimeShouldFire #if self.game_over: #return startTimeItem = time() self.check_item_collisions() stopTimeItem = time() executionTimeItem = stopTimeItem - startTimeItem if executionTimeItem > maxExecutionTimeItem: maxExecutionTimeItem = executionTimeItem #print(f'Item Collision execution time: {executionTimeItem}') startTimeLaser = time() self.check_laser_collisions() stopTimeLaser = time() executionTimeLaser = stopTimeLaser - startTimeLaser if executionTimeLaser > maxExecutionTimeLaserCollision: maxExecutionTimeLaserCollision = executionTimeLaser #print(f'Laser Collision execution time: {executionTimeLaser}') startTimeSpawnItem = time() if random.random() < 0.05 * self.stageNumber: if len(self.items) < 100: self.spawn_item() stopTimeSpawnItem = time() executionTimeSpawnItem = stopTimeSpawnItem - startTimeSpawnItem if executionTimeSpawnItem > maxExecutionTimeSpawnItem: maxExecutionTimeSpawnItem = executionTimeSpawnItem #print(f'Spawn Item execution time: {executionTimeSpawnItem}') if (len(self.touches)) >= 1: startTimeShouldFire = time() self.shouldFire() stopTimeShouldFire = time() executionTimeShouldFire = stopTimeShouldFire - startTimeShouldFire if executionTimeShouldFire > maxExecutionTimeShouldFire: maxExecutionTimeShouldFire = executionTimeShouldFire #print(f'Should Fire execution time: {executionTimeShouldFire}')
-
@Robert_Tompkins, I gave the game a try on an iPhone 11 Pro. It looked good, and I noticed no performance issues.
I did not get very far in the game, though, mainly due to some playability kinks:
- Ship jumping to finger location. instead of moving relative to finger movement, killed me several times.
- Game tended to go to the pause screen very easily, which broke the flow.
- Game balance seemed off, first a few little rocks, then a screenful.
-
@mikael said:
@Robert_Tompkins, I gave the game a try on an iPhone 11 Pro. It looked good, and I noticed no performance issues.
I did not get very far in the game, though, mainly due to some playability kinks:
- Ship jumping to finger location. instead of moving relative to finger movement, killed me several times.
- Game tended to go to the pause screen very easily, which broke the flow.
- Game balance seemed off, first a few little rocks, then a screenful.
Hey thanks for trying it out, I wonder why I keep running into performance issues then.
The jumping shouldn’t happen at all!
Pausing should only happen when your finger lifts off the screen.
I added that ‘feature’ because the upgrading required you to pause the game (top left corner) and by the time you came back out of it, the meteors would be on you before you had time to react lol.Yes, balance is way off in that version haha, that’s my ‘dev’ version. I tweaked it so that there was only 1 main currency, and it awarded a HUGE amount, and dropped often. This way I could upgrade a ton and stress it. But regardless, thanks for the feedback!
I also tweaked the stages from 30 seconds down to 10. So that may explain the none then flood.
-
Just an update, yes, I still play with this off/on. Minor tweaks, adding ideas, etc..
Anyway. I managed to improve the performance of the game by doing the following.I found that ‘generating’ lists of meteors(parent=self) as well as projectiles(parent=self) at the start of the game helps. For example, 500 basic meteors.. 2000 laserProjectiles.
Each time a new meteor is to be spawned, I pop it from the list and move on.
Each time a projectile is to be spawned, I pop it and move on as well.The Stage Timer is happy at 15 seconds (60 second Boss Round).
Each time the Stage Timer hits 0, the Stage Number increments. When this happens, if Stage Number % 10 != 0, I check the length of each list. If any list is less than a threshold, I enter a function that generates objects.
If any list count is VERY low, I append 1000/4000 meteors/projectiles to their respective list.
Otherwise, I generate less of each, just enough to double the threshold. This prevents frame drops due to excess generation mid-stage and small ‘loading’ times between stages is acceptable. Like 100ms loading times lol.I found that generating initial lists that are >10000 or totaling ~20k children, the frames drop even if none of those children are in-view. I assume memory issues. So I try to keep the lists just large enough to sustain them to the next Stage.
So to put it simply... Create lists at startup with items, pop from the lists as needed, and refill the lists as needed between stages. In emergencies mid-stage, I am able to call the generation function as well, to prevent popping from an empty list.
Anyway, thanks again for everybody’s help with this!
Oops, this appears to be what @JonB was recommending all that time ago..
Thanks Jon, now I get it ;) -
Another way to avoid the pain of meteor creation is to reuse the destroyed meteors (or meteors far outside of the game area)
Just like you pop a meteor when you want to use it, push it back (maybe better to use a queue -- pop_right and push_left). You can change the size when you push it back, and reinitialize the position, speed, etc. That way you only ever need queues as long as the number that can be onscreen at one time -- rather than creating 1000 at a time, I have to think that 1000 is more than enough if you reuse them.
Note you can also change the texture of a sprite (with a stored version of a Texture), and that's got to be faster than creating a whole new object.
-
@JonB said:
Another way to avoid the pain of meteor creation is to reuse the destroyed meteors (or meteors far outside of the game area)
Just like you pop a meteor when you want to use it, push it back (maybe better to use a queue -- pop_right and push_left). You can change the size when you push it back, and reinitialize the position, speed, etc. That way you only ever need queues as long as the number that can be onscreen at one time -- rather than creating 1000 at a time, I have to think that 1000 is more than enough if you reuse them.
So about that.. I do agree 100% and I think you recommended this as well. However, I attempted this and my implementation required a ton of additional handling for popping the specific object. (I did this for all objects, but all behaved similarly so we will focus on one meteor type!)
The way I set it up was using 2 lists: listOfInactiveMeteors, listOfActiveMeteors
When a meteor is spawned, I would pop from inactive, append to active, run actions.If an activeProjectile intersected with an activeMeteor, the ‘health’ of both objects (projectile/meteor) would decrement by 1.
If projectileHealth <= 0: pop projectile from active, append to inactive.
Found some code I never fully removed showing at least some of this implementation on a meteor:elif isinstance(meteor, Meteor2): if meteor in self.items: indexOfMeteor = self.items.index(meteor) if activeWeapon == 'laser': meteor.livesRemaining -= 9 meteor.livesRemaining -= 1 if meteor.livesRemaining <= 0: meteorPosition = meteor.position #indexOfMeteor = self.items.index(meteor) if meteor in self.activeMeteors: m = SpriteNode(explosionTexture, scale = 0.25, parent=self) m.position = meteorPosition m.run_action(A.sequence(A.fade_to(0, 0.3, TIMING_EASE_OUT), A.remove())) sound.play_effect('arcade:Explosion_3', 0.50) meteor.destroyed = True meteor.remove_from_parent() self.activeMeteors.remove(meteor) #self.listOfInactiveMeteor2.append(self.items.pop(indexOfMeteor)) #meteor.remove_from_parent() self.collect_item("money", (1E6 * (self.stageNumber**3))) ```
-
So with that implementation, I believe I had issues with popping that specific meteor from the list because by the time I executed the line that popped it from active and appended it to inactive, it no longer existed. Then I had additional trouble with the meteor no longer being a child when I would attempt to ‘reactivate’ a meteor I had Previously popped from active and appended to inactive. Is there a way to remove an object from view without the parent ‘disowning’ it? Because I just sort of disown all children to get them out of sight.
I believe the main issues I had were due to not fully understanding the relationship between parent and child, haha. With a 7 year old child of my own, I would expect to understand that by now!
So maybe I will give it another shot. It’s unfortunate that this topic is regarding imported images as backgrounds, rather than noob problems. But I’ll be sure to update here with the new issues I run into;) -
Alright, made some quick changes to setup what was mentioned.
I attempted this on the Laser objects..
I will have to dive in a little more to determine where this is happening, but here is what I noticed.Here is some misc. output I use for debugging that was produced when I noticed this occurring..
Generating Lasers with 992 left
timeToGen 2000 Lasers: 0.12253904342651367
generateMeteorLists: 0.006246089935302734
stage: 2
Generating Lasers with 2993 left
timeToGen 1000 Lasers: 0.07495284080505371
generateMeteorLists: 3.814697265625e-06
stage: 3
Generating Lasers with 3995 left
timeToGen 1000 Lasers: 0.08381533622741699
generateMeteorLists: 3.0994415283203125e-06
stage: 4
maxExecutionTimeUpdate: 0.1294388771057129
Max Star execution time: 0.0003571510314941406
Max Item Collision execution time: 0.0007109642028808594
Max Laser Collision execution time: 0.0007081031799316406
Max Spawn Item execution time: 0.00047898292541503906
Max Should Fire execution time: 0.002073049545288086
Active weapon: laser
Weapon Upgrades:
self.laserFireRateLevel: 50
self.laserNumberOfLasersLevel: 1
self.laserPowerLevel: 1
Dictionary of self.children and count of each:
<class '_scene2.SpriteNode'>: 230
<class 'scene.LabelNode'>: 5
<class 'main.MiniRocket'>: 1000
<class 'main.Laser'>: 4987
<class 'main.Meteor'>: 170
<class 'main.Star'>: 51
<class 'main.Meteor2'>: 3
len(listOfChildren): 6446
self.stageNumber: 4I noticed that when firing lasers, I should have been firing 10 projectiles each time, but some of the projectiles were not visible. So I set a breakpoint and found that some of the lasers in my list of projectiles (contains all projectiles that have been fired and should be visible) have no parent.
Unfortunately, the functions that may be involved in this are messy and I will need to dig around and clean up before I can efficiently get to root cause.
-
This post is deleted!